| 头文件 | 功能说明 | 
|---|---|
| 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){
    }
}