头文件 | 功能说明 |
---|---|
MultiVideoPickerViewController.h | 对视频列表进行选择,支持多选 |
[TuSDKTSAssetsManager testLibraryAuthor:^(NSError *error)
{
if (error) {
[TuSDKTSAssetsManager showAlertWithController:self loadFailure:error];
}else{
NSLog(@"已经获得了相册的权限");
}
}];
裁剪编辑器
// 进入视频时间裁剪
- (void)actionAfterPickVideos:(NSArray<AVURLAsset *> *)assets {
MovieCutViewController *cutter = [[MovieCutViewController alloc] initWithNibName:nil bundle:nil];
cutter.inputAssets = assets;
__weak typeof(self) weakSelf = self;
cutter.rightButtonActionHandler = ^(MovieCutViewController *cutter, UIButton *sender) {
[weakSelf actionAfterMovieCutWithURL:cutter.outputURL];
};
[self.navigationController pushViewController:cutter animated:YES];
}
用户可自定义资源数组直接进入视频裁剪界面
头文件 | 功能说明 |
---|---|
MovieCutViewController.h | 对输入的视频进行时间裁剪 |
TuSDKMediaMutableAssetMoviePlayer.h | 多视频播放器 |
TuSDKMediaMovieAssetTranscoder.h | 对视频进行导出操作,支持输出大小,输出质量,输出范围等设置 |
开启控制器需要传入一个视频资源数组
// 选取的视频
@property (nonatomic, strong) NSArray<AVAsset *> *inputAssets;
获取视频裁剪栏缩略图,用于裁剪栏的展示
// 时码线
TuSDKVideoImageExtractor *imageExtractor = [TuSDKVideoImageExtractor createExtractor];
imageExtractor.videoAssets = _inputAssets;
const NSInteger frameCount = 10;
imageExtractor.extractFrameCount = frameCount;
self.videoTrimmerView.thumbnailsView.thumbnailCount = frameCount;
__weak typeof(self) weakSelf = self;
// 渐进式配置缩略图
[imageExtractor asyncExtractImageWithHandler:^(UIImage * _Nonnull image, NSUInteger index) {
[weakSelf.videoTrimmerView.thumbnailsView setThumbnail:image atIndex:index];
}];
裁剪控制器会对视频本身进行裁剪处理,导出视频并保存在临时文件夹中,用户可自定义处理。
TuSDKVideoImageExtractor 可用于获取视频的缩略图,例如返回视频首帧画面作为展示封面。
视频裁剪是对输出视频时间范围的设定然后进行导出,示例如下:
_saver = [[TuSDKMediaMovieAssetTranscoder alloc] initWithInputAsset:_moviePlayer.asset exportOutputSettings:exportSettings];
_saver.delegate = self;
TuSDKMediaTimeRange *timeRange = [[TuSDKMediaTimeRange alloc] initWithStart:_timeRange.start duration:_timeRange.duration];
TuSDKMediaTimelineSlice *newSlice = [[TuSDKMediaTimelineSlice alloc] initWithTimeRange:timeRange];
[_saver appendSlice:newSlice];
[_saver startExport];
不同的功能模块,不要输出同样的文件名,现在相机 编辑 裁剪 最终输出文件的方法名均为 startRecording
头文件 | 功能说明 |
---|---|
TuSDKMovieEditor.h | 视频编辑类,支持视频播放,添加滤镜,MV,配音,场景特效等各种特效,并导出视频 |
TuSDKMediaFilterEffect.h | 滤镜特效对象 |
TuSDKMediaStickerAudioEffect.h | MV特效对象,含动态贴纸与音乐 |
TuSDKMediaAudioEffect.h | 音乐特效对象 |
TuSDKMediaTextEffect.h | 文字特效对象 |
TuSDKMediaSceneEffect.h | 场景特效对象 |
TuSDKMediaParticleEffect.h | 魔法特效对象 |
TuSDKMediaSpeedTimeEffect.h | 快慢速时间特效对象 |
TuSDKMediaReverseTimeEffect.h | 倒序时间特效对象 |
TuSDKMediaRepeatTimeEffect.h | 反复时间特效对象 |
TuSDKMediaAssetAudioRecorder.h | 录音接口, 支持变调录音,多段录音 |
TuSDKVideoTrackInfo.h | 视频轨道信息,含视频原始尺寸,展示尺寸等信息 |
视频裁剪完成后,获取到输出的视频地址后会开启 MovieEditViewController
开启后,会将视频的地址,传输到视频编辑的控制器
MovieEditViewController *vc = [MovieEditViewController new];
vc.inputURL = _inputURL;
[self pushViewController:vc animated:YES];
遵守代理 TuSDKMovieEditorDelegate
初始化视频编辑的参数设置
TuSDKMovieEditorOptions *options = [TuSDKMovieEditorOptions defaultOptions];
设置视频的 inputURL 地址
options.inputURL = self.inputURL;
设置视频截取范围
options.cutTimeRange = [TuSDKTimeRange makeTimeRangeWithStartSeconds:0 endSeconds:6];
是否按照正常速度播放
options.playAtActualSpeed = YES;
设置裁剪范围 注:该参数对应的值均为比例值,即:若视频展示 View 总高度800,此时截取时 y 从200开始,则cropRect的 originY = 偏移位置/总高度, 应为0.25, 其余三个值同理
options.cropRect = _cropRect;
设置编码视频的画质
options.encodeVideoQuality = [TuSDKVideoQuality makeQualityWith:TuSDKRecordVideoQuality_Medium2];
是否保留视频原音(置为 NO,视频中的原因就被去除)
options.enableVideoSound = YES;
保存到系统相册 默认为 YES
option.saveToAlbum = NO;
保存到指定相册(需要将 saveToAlbum 置为 YES 后生效)
option.saveToAlbumName = @"TuSDK";
设置录制文件格式(默认:lsqFileTypeQuickTimeMovie)
option.fileType = lsqFileTypeMPEG4;
设置水印,默认为空
option.waterMarkImage = [UIImage imageNamed:@"sample_watermark.png"];
设置水印图片的位置
option.waterMarkPosition = lsqWaterMarkTopRight;
设置画面特效输出时间轴
option.pictureEffectOptions.referTimelineType = TuSDKMediaEffectReferInputTimelineType;
初始化视频编辑器
_movieEditor = [[TuSDKMovieEditor alloc]initWithPreview:_previewView.videoView options:options];
设置代理
// 播放状态监听
_movieEditor.playerDelegate = self;
// 加载状态监听
_movieEditor.loadDelegate = self;
// 保存状态监听
_movieEditor.saveDelegate = self;
视频播放音量设置,0 ~ 1.0 仅在 enableVideoSound 为 YES 时有效
_movieEditor.videoSoundVolume = 0.5;
加载视频,显示第一帧
[_movieEditor loadVideo];
开始预览
[_movieEditor startPreview];
暂停预览
[_movieEditor pausePreView];
停止预览
[_movieEditor stopPreview];
开始导出
[_movieEditor startRecording];
取消导出
[_movieEditor cancelRecording];
添加特效,包括滤镜,场景特效,MV,文字效果等
[_movieEditor addMediaEffect:filterEffect];
移除特定类型的特效,包括滤镜,场景特效,MV,文字效果等
[_movieEditor removeMediaEffectsWithType:TuSDKMediaEffectDataTypeFilter];
移动到指定时间点展示对应视频帧
[_movieEditor seekToTime:kCMTimeZero];
添加了时间特效后,移动到指定时间点展示对应视频帧
[_movieEditor seekToInputTime:kCMTimeZero];
引入头文件 #import "FilterListView.h",滤镜栏继承自HorizontalListView
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
// 滤镜列表,获取滤镜前往 TuSDK.bundle/others/lsq_tusdk_configs.json
// TuSDK 滤镜信息介绍 @see-https://tusdk.com/docs/ios/self-customize-filter
_filterCodes = @[kVideoFilterCodes];
遵守代理 FilterListViewDelegate
,实现代理方法。
#pragma mark - 滤镜 View 代理方法 FilterListViewDelegate
// 滤镜码选中回调,在此应用或取消滤镜, 修改滤镜参数
- (void)filterList:(FilterListView *)filterList didSelectedCode:(NSString *)code tapCount:(NSInteger)tapCount {
TuSDKMediaFilterEffect *filterEffect = nil;
// 选中滤镜列表第一项,code 为空时,移除滤镜
if (!code) {
[self.movieEditor removeMediaEffectsWithType:TuSDKMediaEffectDataTypeFilter];
} else {
// 应用滤镜
filterEffect = [[TuSDKMediaFilterEffect alloc] initWithEffectCode:code];
[self.movieEditor addMediaEffect:filterEffect];
}
// 更新参数列表
// demo/文件
_paramtersView.hidden = tapCount <= 1;
if (!_paramtersView.hidden) [self updateParamtersViewWithFilterEffect:filterEffect];
}
引入头文件 #import "MVListView.h",MV 栏继承自HorizontalListView
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
MV特效数据源获取与线上打包下载后 TuSDK.bundle 中的 lsq_tusdk_configs.json 对应。用户想更换MV数据,可在应用管理后台自行添加删除MV资源后重新打包替换。
// 获取贴纸组数据源
NSMutableArray *mvEffectDatas = [NSMutableArray array];
NSArray<TuSDKPFStickerGroup *> *stickers = [[TuSDKPFStickerLocalPackage package] getSmartStickerGroupsWithFaceFeature:NO];
for (TuSDKPFStickerGroup *sticker in stickers) {
NSURL *audioURL = [self audioURLWithStickerIdt:sticker.idt];
TuSDKMediaStickerAudioEffect *mvData = [[TuSDKMediaStickerAudioEffect alloc] initWithAudioURL:audioURL stickerGroup:sticker];
[mvEffectDatas addObject:mvData];
}
self.mvEffectDatas = mvEffectDatas.copy;
遵守代理 MVListViewDelegate
,实现代理方法。
#pragma mark - MVListViewDelegate
// MV 特效选中回调
- (void)mvlist:(MVListView *)listView didSelectEffect:(TuSDKMediaStickerAudioEffect *)mvEffect tapCount:(NSInteger)tapCount {
// 点第2次MV项时显示参数面板
_paramtersView.hidden = tapCount <= 1;
self.currentMvEffect = mvEffect;
}
引入头文件 #import "MusicListView.h",配乐栏继承自HorizontalListView
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
NSMutableArray *musicURLs = [NSMutableArray array];
for (NSString *musicFileName in kMusicFileNameArray) {
NSURL *URL = [[NSBundle mainBundle] URLForResource:musicFileName withExtension:@"mp3"];
[musicURLs addObject:URL];
}
self.musicURLs = musicURLs;
NSArray *musicTitles = kMusicTitleArray;
NSArray *musicThubnails = kMusicThumbnailArray;
遵守代理 MusicListViewDelegate
,实现代理方法,配乐支持录音,并对配音做了处理。
/// 录音结果回调,应用录音为配乐
- (void)audioRecorder:(EditAudioRecordController *)audioRecorder didFinishRecordingWithURL:(NSURL *)recordURL {
TuSDKMediaAudioEffect *audioEffect = nil;
if (recordURL) {
audioEffect = [[TuSDKMediaAudioEffect alloc] initWithAudioURL:recordURL];
audioEffect.looping = NO;
dispatch_async(dispatch_get_main_queue(), ^{
self.musicListView.selectedIndex = 1;
});
}
self.recordURL = recordURL;
self.currentAudioEffect = audioEffect;
}
引入头文件 #import "TextEditAreaView.h",frame即为文字编辑区域
文字大小,描边颜色,文字颜色等设置均在 AttributedLabel
中实现
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
// 应用文字特效
NSArray *textItemInfos = _textEditAreaView.textItemInfos;
for (NSDictionary *info in textItemInfos) {
// 获取文字贴纸对象所需信息
UIImage *image = info[kTextItemInfoImageKey];
CGRect centerRect = [info[kTextItemInfoCenterRectKey] CGRectValue];
CGFloat degree = [info[kTextItemInfoDegreeKey] doubleValue];
CMTimeRange timeRange = [info[kTextItemInfoTimeRangeKey] CMTimeRangeValue];
// 获取视频原始大小
TuSDKVideoTrackInfo *trackInfo = self.movieEditor.inputAssetInfo.videoInfo.videoTrackInfoArray.firstObject;
CGSize videoSize = trackInfo.presentSize;
// 创建文字贴纸对象
TuSDKMediaTextEffect *textEffectData = [[TuSDKMediaTextEffect alloc] initWithStickerImage:image center:centerRect degree:degree designSize:videoSize];
textEffectData.atTimeRange = [TuSDKTimeRange makeTimeRangeWithStart:timeRange.start duration:timeRange.duration];
// 添加文字贴纸
[self.movieEditor addMediaEffect:textEffectData];
}
场景特效使用
引入头文件 #import "SceneEffectListView.h",场景特效栏继承自HorizontalListView
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
遵守代理 SceneEffectListViewDelegate
,实现代理方法。
// 场景特效列表项按下回调,开始应用场景特效
- (void)sceneEffectList:(SceneEffectListView *)listView didTouchDownWithCode:(NSString *)code color:(UIColor *)color {
// 跳过末尾
CMTime outputTime = self.movieEditor.outputTimeAtTimeline;
CMTime outputDuraiton = self.movieEditor.outputDuraiton;
CMTime minFrameDuration = self.movieEditor.inputAssetInfo.videoInfo.videoTrackInfoArray.firstObject.minFrameDuration;
if (CMTIME_COMPARE_INLINE(CMTimeAdd(outputTime, minFrameDuration), >=, outputDuraiton)) {
lsqLError(@"剩余时间太短,无法添加特效。");
[self.movieEditor stopPreview];
return;
}
// 同步更新 UI
[_trimmerView startMarkWithColor:color];
// 应用特效
TuSDKMediaSceneEffect *sceneEffect = [[TuSDKMediaSceneEffect alloc] initWithEffectsCode:code];
[self.movieEditor applyMediaEffect:sceneEffect];
_currentSceneEffect = sceneEffect;
[self.movieEditor startPreview];
}
应用和取消场景特效应成对调用
// 停止应用特效,并更新 UI
- (void)endUpdateCurrentSceneEffect {
// 取消应用特效
[self.movieEditor unApplyMediaEffect:_currentSceneEffect];
_currentSceneEffect = nil;
// 更新 UI
[_trimmerView endMark];
[self updateUndoButtonState];
}
魔法特效使用
引入头文件 #import "ParticleEffectEditAreaView.h",frame 即为魔法特效编辑区域
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
遵守代理 ParticleEffectEditAreaViewDelegate
,实现代理方法。
// 编辑区域触摸开始回调
- (void)particleEditAreaViewDidBeginEditing:(ParticleEffectEditAreaView *)particleEditAreaView {
// 跳过末尾
CMTime outputTime = self.movieEditor.outputTimeAtTimeline;
CMTime outputDuraiton = self.movieEditor.outputDuraiton;
CMTime minFrameDuration = self.movieEditor.inputAssetInfo.videoInfo.videoTrackInfoArray.firstObject.minFrameDuration;
if (CMTIME_COMPARE_INLINE(CMTimeAdd(outputTime, minFrameDuration), >=, outputDuraiton)) {
lsqLError(@"剩余时间太短,无法添加特效。");
[self.movieEditor stopPreview];
return;
}
// 懒加载生成特效
if (!self.currentParticleEffect) return;
// 应用当前特效,同时需要播放视频
[self.movieEditor applyMediaEffect:_currentParticleEffect];
[self.movieEditor startPreview];
// 更新 UI
_parametersPanelView.hidden = YES;
UIColor *markColor = _effectListView.particleEffectCodeColors[_currentParticleEffect.effectsCode];
[_trimmerView startMarkWithColor:markColor];
}
应用和取消魔法特效应成对调用
// 停止应用特效,并更新 UI
- (void)endUpdateCurrentParticleEffect {
// 取消应用特效
[self.movieEditor unApplyMediaEffect:_currentParticleEffect];
_currentParticleEffect = nil;
// 更新 UI
[_trimmerView endMark];
[self updateUndoButtonState];
}
时间特效使用
引入头文件 #import "TimeEffectListView.h",时间特效栏继承自HorizontalListView
Demo 提供使用范例,特效 UI 布局均加载在单独控制器内,用户可根据接口自定义修改相关使用。
添加倒序时间特效
// 获取时间特效触发时间范围
timeRange = CMTimeRangeMake(kCMTimeZero, self.movieEditor.inputDuration);
// 构建倒序特效对象
TuSDKMediaReverseTimeEffect *reverseTimeEffect = [[TuSDKMediaReverseTimeEffect alloc] initWithTimeRange:timeRange];
// 添加特效
[self.movieEditor addMediaTimeEffect:reverseTimeEffect];
添加反复时间特效,可设置反复次数
// 构建反复时间特效
TuSDKMediaRepeatTimeEffect *repeatTimeEffect = [[TuSDKMediaRepeatTimeEffect alloc] initWithTimeRange:timeRange];
// 设置反复次数
repeatTimeEffect.repeatCount = 2;
// 设置是否丢弃应用特效后累加的视频时长
repeatTimeEffect.dropOverTime = NO;
// 添加特效
[self.movieEditor addMediaTimeEffect:repeatTimeEffect];
添加慢动作时间特效
// 构建速率特效对象
TuSDKMediaSpeedTimeEffect *speedTimeEffect = [[TuSDKMediaSpeedTimeEffect alloc] initWithTimeRange:timeRange];
// 设置播放速率
speedTimeEffect.speedRate = 0.5f;
// 设置是否丢弃应用特效后累加的视频时长
speedTimeEffect.dropOverTime = NO;
// 添加特效
[self.movieEditor addMediaTimeEffect:speedTimeEffect];
调用保存视频接口,等待完成回调
- (void)mediaMovieEditor:(TuSDKMovieEditorBase *_Nonnull)editor saveResult:(TuSDKVideoResult *_Nullable)result error:(NSError *_Nullable)error {
if (error) {
NSLog(@"保存失败,error: %@", error);
[[TuSDK shared].messageHub showError:@"保存失败"];
return;
}
// 通过 result.videoPath 拿到视频的临时文件路径
if (result.videoPath) {
// 进行自定义操作,例如保存到相册
UISaveVideoAtPathToSavedPhotosAlbum(result.videoPath, nil, nil, nil);
[[TuSDK shared].messageHub showSuccess:@"保存成功"];
} else {
// _movieEditor.saveToAlbum = YES; (默认为 :YES)将自动保存到相册
[[TuSDK shared].messageHub showSuccess:@"保存成功"];
}
// 保存成功后取消提示框 同时返回到root
[self popToRootViewControllerAnimated:true];
}
typedef NS_ENUM(NSInteger, lsqMovieEditorStatus)
{
// 未知
lsqMovieEditorStatusUnknow,
// 加载失败
lsqMovieEditorStatusLoadFailed,
// 加载完成
lsqMovieEditorStatusLoaded,
// 正在播放
lsqMovieEditorStatusPreviewing,
// 正在保存
lsqMovieEditorStatusRecording,
// 保存完成
lsqMovieEditorStatusRecordingCompleted,
// 保存失败
lsqMovieEditorStatusRecordingFailed,
// 取消保存
lsqMovieEditorStatusRecordingCancelled,
// 预览完成
lsqMovieEditorStatusPreviewingCompleted,
// 暂停预览
lsqMovieEditorStatusPreviewingPause,
};
- (void)onMovieEditor:(TuSDKMovieEditor *)editor statusChanged:(lsqMovieEditorStatus)status
{
_movieEditorStatus = status;
if (status == lsqMovieEditorStatusPreviewingCompleted){
} else if (status == lsqMovieEditorStatusPreviewingPause){
}
}