Files
featherVoice/TUIKit/TUIMultimediaPlugin/Edit/UI/VideoEditor/TUIMultimediaVideoEditorController.m
2025-08-08 10:49:36 +08:00

300 lines
12 KiB
Objective-C

// Copyright (c) 2024 Tencent. All rights reserved.
// Author: eddardliu
#import "TUIMultimediaVideoEditorController.h"
#import <Masonry/Masonry.h>
#import <ReactiveObjC/RACEXTScope.h>
#import <TUICore/TUIDefine.h>
#import <TXLiteAVSDK_Professional/TXVideoEditer.h>
#import <TXLiteAVSDK_Professional/TXVideoEditerTypeDef.h>
#import "TUIMultimediaPlugin/NSArray+Functional.h"
#import "TUIMultimediaPlugin/TUIMultimediaBGMEditController.h"
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
#import "TUIMultimediaPlugin/TUIMultimediaCommonEditorControlView.h"
#import "TUIMultimediaPlugin/TUIMultimediaImagePicker.h"
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectController.h"
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleEditController.h"
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
#import "TUIMultimediaPlugin/TUIMultimediaConstant.h"
#import "TUIMultimediaPlugin/TUIMultimediaAuthorizationPrompter.h"
@interface TUIMultimediaVideoEditorController () <TXVideoPreviewListener,
TXVideoGenerateListener,
TUIMultimediaPasterSelectControllerDelegate,
TUIMultimediaCommonEditorControlViewDelegate,
TUIMultimediaBGMEditControllerDelegate> {
TUIMultimediaPasterSelectController *_pasterSelectController;
TUIMultimediaCommonEditorControlView *_commonEditCtrlView;
TXVideoEditer *_editor;
NSString *_sourceVideoPath;
TXVideoInfo *_videoInfo;
TUIMultimediaSubtitleEditController *_subtitleEditController;
TUIMultimediaBGMEditController *_musicController;
TUIMultimediaEncodeConfig *_encodeConfig;
BOOL _originNavgationBarHidden;
float _lastGenerateProgress;
BOOL _hasAudioEdited;
}
@end
@implementation TUIMultimediaVideoEditorController
@dynamic sourceVideoPath;
- (instancetype)init {
self = [super init];
_encodeConfig = [[TUIMultimediaEncodeConfig alloc] initWithVideoQuality:[[TUIMultimediaConfig sharedInstance] getVideoQuality]];
_lastGenerateProgress = 0;
_sourceType = SOURCE_TYPE_RECORD;
_hasAudioEdited = NO;
return self;
}
- (void)viewDidLoad {
[self initUI];
_pasterSelectController = [[TUIMultimediaPasterSelectController alloc] init];
_pasterSelectController.delegate = self;
_pasterSelectController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
_subtitleEditController = [[TUIMultimediaSubtitleEditController alloc] init];
_subtitleEditController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
_musicController = [[TUIMultimediaBGMEditController alloc] init];
_musicController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
_musicController.delegate = self;
_musicController.clipDuration = _videoInfo.duration;
}
- (void)viewWillAppear:(BOOL)animated {
if (self.navigationController != nil) {
_originNavgationBarHidden = self.navigationController.navigationBarHidden;
self.navigationController.navigationBarHidden = YES;
}
}
- (void)viewWillDisappear:(BOOL)animated {
if (self.navigationController != nil) {
self.navigationController.navigationBarHidden = _originNavgationBarHidden;
}
}
- (NSString *)sourceVideoPath {
return _sourceVideoPath;
}
- (void)setSourceVideoPath:(NSString *)sourceVideoPath {
_sourceVideoPath = sourceVideoPath;
NSString *currentSourceVideoPath = sourceVideoPath;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (self->_sourceVideoPath != currentSourceVideoPath) return;
self->_videoInfo = [TXVideoInfoReader getVideoInfo:self->_sourceVideoPath];
NSLog(@"videoInfo angle = %d, width = %d, height = %d, duration = %f",
self->_videoInfo.angle, self->_videoInfo.width, self->_videoInfo.height, self->_videoInfo.duration);
if (self->_videoInfo.angle == 90 || self->_videoInfo.angle == 270) {
int temp = self->_videoInfo.width;
self->_videoInfo.width = self->_videoInfo.height;
self->_videoInfo.height = temp;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self tryReloadVideoAsset];
self->_musicController.clipDuration = self->_videoInfo.duration;
});
});
}
- (void)setSourceType:(int)sourceType {
NSLog(@"setSourceType sourceType = %d",sourceType);
_sourceType = sourceType;
if (_commonEditCtrlView != nil) {
_commonEditCtrlView.sourceType = sourceType;
}
}
- (void)tryReloadVideoAsset {
if (_editor == nil || _sourceVideoPath == nil) return;
int code = [_editor setVideoPath:_sourceVideoPath];
[_editor startPlayFromTime:0 toTime:_videoInfo.duration];
if (code != 0) {
NSString *title = [TUIMultimediaCommon localizedStringForKey:@"modify_load_assert_failed"];
[self showAlertWithTitle:title message:@"" action:@"OK"];
}
_commonEditCtrlView.previewSize = CGSizeMake(_videoInfo.width, _videoInfo.height);
}
#pragma mark - UI Init
- (void)initUI {
_commonEditCtrlView = [[TUIMultimediaCommonEditorControlView alloc] initWithConfig:TUIMultimediaCommonEditorConfig.configForVideoEditor];
[self.view addSubview:_commonEditCtrlView];
_commonEditCtrlView.backgroundColor = UIColor.blackColor;
_commonEditCtrlView.previewSize = CGSizeMake(_videoInfo.width, _videoInfo.height);
_commonEditCtrlView.sourceType = _sourceType;
_commonEditCtrlView.delegate = self;
TXPreviewParam *param = [[TXPreviewParam alloc] init];
param.videoView = _commonEditCtrlView.previewView;
param.renderMode = PREVIEW_RENDER_MODE_FILL_EDGE;
_editor = [[TXVideoEditer alloc] initWithPreview:param];
[self tryReloadVideoAsset];
_editor.previewDelegate = self;
_editor.generateDelegate = self;
[_commonEditCtrlView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message action:(NSString *)action {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:action style:UIAlertActionStyleDefault handler:nil];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - TXVideoPreviewListener protocol
- (void)onPreviewProgress:(CGFloat)time {
}
- (void)onPreviewFinished {
[_editor startPlayFromTime:0 toTime:_videoInfo.duration];
}
#pragma mark - TXVideoGenerateListener protocol
- (void)onGenerateProgress:(float)progress {
NSLog(@"TUIMultimediaVideoEditorController onGenerateProgress progress = %f",progress);
if (progress - _lastGenerateProgress > 0.01f || progress == 1.0f) {
_commonEditCtrlView.progressBarProgress = progress;
_lastGenerateProgress = progress;
}
}
- (void)onGenerateComplete:(TXGenerateResult *)result {
NSLog(@"TUIMultimediaVideoEditorController onGenerateComplete retCode = %ld",(long)result.retCode);
int resultCode = (result.retCode != 0) ? VIDEO_EDIT_RESULT_CODE_GENERATE_FAIL : VIDEO_EDIT_RESULT_CODE_GENERATE_SUCCESS;
_completeCallback(_resultVideoPath, resultCode);
}
#pragma mark - TUIMultimediaCommonEditorControlViewDelegate protocol
- (void)onCommonEditorControlViewComplete:(TUIMultimediaCommonEditorControlView *)view stickers:(NSArray<TUIMultimediaSticker *> *)stickers {
if (![TUIMultimediaAuthorizationPrompter verifyPermissionGranted:self]) {
return;
}
if (stickers.count == 0 && !_hasAudioEdited && _sourceType == SOURCE_TYPE_ALBUM) {
NSLog(@"Return directly without encoding the video");
_resultVideoPath = _sourceVideoPath;
_completeCallback(_resultVideoPath, VIDEO_EDIT_RESULT_CODE_NO_EDIT);
return;
}
NSArray<TXPaster *> *pasterList = [stickers tui_multimedia_map:^TXPaster *(TUIMultimediaSticker *s) {
TXPaster *p = [[TXPaster alloc] init];
p.pasterImage = s.image;
p.frame = s.frame;
p.startTime = 0.0;
p.endTime = self->_videoInfo.duration;
return p;
}];
[_editor setPasterList:pasterList];
_commonEditCtrlView.isGenerating = YES;
[self.view bringSubviewToFront:_commonEditCtrlView];
if (_resultVideoPath == nil || _resultVideoPath.length == 0) {
_resultVideoPath = [self getOutFilePath];
}
[_editor setVideoBitrate:_encodeConfig.bitrate];
[_editor generateVideo:[_encodeConfig getVideoEditCompressed] videoOutputPath:_resultVideoPath];
}
- (void)onCommonEditorControlViewCancel:(TUIMultimediaCommonEditorControlView *)view {
[_editor stopPlay];
_completeCallback(nil, VIDEO_EDIT_RESULT_CODE_CANCEL);
}
- (void)onCommonEditorControlViewNeedAddPaster:(TUIMultimediaCommonEditorControlView *)view {
NSLog(@"onCommonEditorControlViewNeedAddPaster");
_commonEditCtrlView.modifyButtonsHidden = YES;
[self presentViewController:_pasterSelectController animated:NO completion:nil];
}
- (void)onCommonEditorControlViewNeedModifySubtitle:(TUIMultimediaSubtitleInfo *)info callback:(void (^)(TUIMultimediaSubtitleInfo *info, BOOL isOk))callback {
_subtitleEditController.subtitleInfo = info;
_subtitleEditController.callback = ^(TUIMultimediaSubtitleEditController *c, BOOL isOk) {
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
if (callback != nil) {
callback(c.subtitleInfo, isOk);
}
};
[self presentViewController:_subtitleEditController animated:NO completion:nil];
}
- (void)onCommonEditorControlViewNeedEditMusic:(TUIMultimediaCommonEditorControlView *)view {
_commonEditCtrlView.modifyButtonsHidden = YES;
[self presentViewController:_musicController animated:NO completion:nil];
}
- (void)onCommonEditorControlViewCancelGenerate:(TUIMultimediaCommonEditorControlView *)view {
[_editor cancelGenerate];
TXPreviewParam *param = [[TXPreviewParam alloc] init];
param.videoView = _commonEditCtrlView.previewView;
param.renderMode = PREVIEW_RENDER_MODE_FILL_EDGE;
_editor = [[TXVideoEditer alloc] initWithPreview:param];
[self tryReloadVideoAsset];
_editor.previewDelegate = self;
_editor.generateDelegate = self;
}
-(NSString*) getOutFilePath{
NSDate* currentDate = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
NSString* currentDateString = [dateFormatter stringFromDate:currentDate];
NSString* outFileName = [NSString stringWithFormat:@"%@-%u-temp.mp4", currentDateString, arc4random()];
return [NSTemporaryDirectory() stringByAppendingPathComponent:outFileName];
}
#pragma mark - TUIMultimediaPasterSelectControllerDelegate protocol
- (void)pasterSelectController:(TUIMultimediaPasterSelectController *)c onPasterSelected:(UIImage *)image {
[_commonEditCtrlView addPaster:image];
_commonEditCtrlView.modifyButtonsHidden = NO;
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}
- (void)onPasterSelectControllerExit:(TUIMultimediaPasterSelectController *)c {
_commonEditCtrlView.modifyButtonsHidden = NO;
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}
#pragma mark - TUIMultimediaMusicControllerDelegate protocol
- (void)onBGMEditController:(TUIMultimediaBGMEditController *)bgmController bgmInfoChanged:(TUIMultimediaVideoBgmEditInfo *)bgmInfo {
if (![TUIMultimediaAuthorizationPrompter verifyPermissionGranted:bgmController]) {
return;
}
[_editor setBGMLoop:YES];
@weakify(self);
[_editor setBGMAsset:bgmInfo.bgm.asset
result:^(int result) {
@strongify(self);
if (result != 0) {
NSString *title = [TUIMultimediaCommon localizedStringForKey:@"modify_load_assert_failed"];
[self showAlertWithTitle:title message:@"" action:@"OK"];
}
}];
[_editor setBGMAtVideoTime:0];
[_editor setBGMStartTime:bgmInfo.bgm.startTime endTime:bgmInfo.bgm.endTime];
[_editor setBGMVolume:1];
if (bgmInfo.originAudio) {
[_editor setVideoVolume:1];
} else {
[_editor setVideoVolume:0];
}
_commonEditCtrlView.musicEdited = bgmInfo.bgm != nil;
_hasAudioEdited = (!bgmInfo.originAudio) || (bgmInfo.bgm != nil);
}
- (void)onBGMEditControllerExit:(nonnull TUIMultimediaBGMEditController *)c {
_commonEditCtrlView.modifyButtonsHidden = NO;
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}
@end