短视频SDK提供视频编辑功能,支持视频图片素材混合导入、滤镜、配音、时间特效、画中画等丰富的编辑效果。本文介绍iOS端短视频SDK视频编辑的流程及方法。
版本支持
版本 | 是否支持 |
专业版 | 支持所有功能。 |
标准版 | 部分支持,支持除字幕、动态贴纸、MV外的其他功能。 |
基础版 | 不支持。 |
相关类功能
操作 | 类名 | 功能 |
初始化 | 视频编辑核心类。 | |
开始编辑 | 视频源管理器。 | |
媒体片段。 | ||
工程配置里的主流轨道。 | ||
工程配置里的主流片段。 | ||
预览控制 | 播放协议。 | |
播放状态回调协议。 | ||
设置视频特效 | 滤镜效果model类。 | |
时间特效model类。 | ||
转场效果的基类。 | ||
MV效果model类。 | ||
滤镜管理器,可管理lut滤镜和静态滤镜。 | ||
lut滤镜控制器。 | ||
lut滤镜Model。 | ||
静态滤镜控制器。 | ||
静态滤镜Model。 | ||
设置音乐及音效 | 音乐。 | |
配音。 | ||
工程配置里的音轨。 | ||
工程配置里的音轨片段。 | ||
设置画中画 | 画中画管理类。 | |
画中画轨道控制器。 | ||
画中画片段控制器。 | ||
画中画数据模型。 | ||
工程配置中的画中画片段。 | ||
工程配置中的画中画轨道。 | ||
画面增强信息。 | ||
音频相关信息。 | ||
边框相关信息。 | ||
设置字幕及贴纸 | 贴纸及字幕管理器。 | |
字幕控制器。 | ||
动图控制器。 | ||
静图控制器。 | ||
字幕数据模型。 | ||
动图数据模型。 | ||
静图数据模型。 | ||
工程配置里的动图轨道。 | ||
工程配置里的静图轨道。 | ||
工程配置里的字幕轨道。 | ||
草稿箱 | 工程配置。 | |
草稿对象。 | ||
本地草稿管理器。 | ||
草稿资源加载任务。 | ||
草稿上传任务。 | ||
资源对象。 | ||
加载任务中更具体的资源模型。 | ||
其他设置 | 涂鸦画布视图。 | |
画笔。 |
编辑视频流程
阶段 | 流程 | 说明 | 示例代码 |
基础 | 1 | 创建并初始化编辑器。 | |
2 | 在编辑过程中动态裁剪视频、动态更换视频源、动态调整视频转场时间及转场效果。 | ||
3 | 设置编辑器的预览播放。 | ||
进阶 | 4 | 设置滤镜、转场及MV特效。 | |
5 | 设置背景音乐、配音及音效。 | ||
6 | 设置画中画。 | ||
7 | 设置字幕、花字、文字气泡及贴纸。 | ||
8 | 支持编辑草稿箱中的视频、或将编辑完成的视频保存至草稿箱。 | ||
9 | 设置涂鸦。 |
初始化
创建并初始化编辑器。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
// 1.任务路径:每一个媒体编辑状态会生成一个任务,编辑初始化时需要提供任务存储的路径,任务必须先初始化
NSString *taskPath = @"xxx"; // taskPath为导入视频的地址,通常为本地媒体资源导入或草稿箱导入
// 2.预览视图:设置预览视图后,编辑过程中,每一个操作都会实时地展示在一个预览视图上
UIView *preview = xxx;
// 3.实例化
AliyunEditor *editor = [[AliyunEditor alloc] initWithPath:taskPath preview:preview];
为了保证预览效果与输出效果保持一致,建议您的预览视图大小与最终输出视频的分辨率保持一样的宽高比。
视频管理
视频编辑器里的视频和图片最终会由视频源管理器AliyunIClipConstructor统一管理,通过AliyunIClipConstructor修改编辑器里的视频和图片后,需要下一次调用[editor startEdit]
编辑后才能生效。
代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
// 1. 获取视频源管理器
id<AliyunIClipConstructor> contructor = [editor getClipConstructor];
// 2. 添加片段
// 2.1 先停止编辑
[editor stopEdit];
// 2.2 创建视频
AliyunClip *clip = [[AliyunClip alloc] initWithVideoPath:videoPath startTime:2 duration:6 animDuration:0];
// 2.3.1 把视频追加到最后
[contructor addMediaClip:clip];
// 2.3.2 或者把视频加到指定位置
[contructor addMediaClip:clip atIndex:1];
// 2.4 开始编辑
[editor startEdit];
// 3. 更新片段(替换片段)
[contructor updateMediaClip:clip atIndex:1];
// 4. 删除片段
// 4.1 删除最后一个片段
[contructor deleteLastMediaClip];
// 4.2 删除指定的一个片段
[contructor deleteMediaClipAtIndex:1];
// 4.3 删除所有片段
[contructor deleteAllMediaClips];
// 5. 获取当前所有片段
NSArray<AliyunClip *> *mediaClips = contructor.mediaClips;
预览控制
在视频编辑过程中,提供一系列对当前视频的播放控制操作,如播放、暂停、获取当前时长等。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
播放控制
// 获取预览播放器
id<AliyunIPlayer> player = [editor getPlayer];
//开始播放
[player play];
//继续播放
[player resume];
// 暂停播放
[player pause];
// 跳转到指定位置
[player seek:timeInSecond];
// 静音
[editor setMute:YES];
// 设置音量
[editor setVolume:50];
//获取当前流的时间 - 不受时间特效影响
double currentTime = [player getCurrentStreamTime];
// 获取当前播放的时间 - 受时间特效影响
double currentTime = [player getCurrentTime];
// 获取流时长 - 不受时间特效影响
double duration = [player getStreamDuration];
// 获取播放总时长 - 受时间特效影响
double duration = [player getDuration];
播放回调
// 监听播放状态
editor.playerCallback = self;
// 协议
- (void)playerDidEnd {
// 播放结束回调
}
- (void)playProgress:(double)playSec streamProgress:(double)streamSec {
// 播放进度回调
}
- (void)playError:(int)errorCode {
// 播放错误回调
}
设置视频特效
支持设置的视频特效包括滤镜、转场、MV及时间特效。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
滤镜
滤镜分为lut滤镜、静态滤镜和动效滤镜。
lut滤镜:使用Lookup Table方式进行像素替换。
静态滤镜:通过编写着色语言方式进行像素计算,不支持带动画效果。
动效滤镜:通过编写着色语言方式进行像素计算,带动画效果。
滤镜支持自定义制作,制作方法请参见滤镜及转场。
lut滤镜
// 1 添加lut滤镜
AliyunLutFilterController *controller = [[editor getFilterManager] applyLutFilterWithPath:path intensity:intensity];
if (!controller) {
// 添加LutFilter失败
}
// 2 更新intensity
controller.model.intensity = 0.5;
// 3 删除lut滤镜
[[editor getFilterManager] removeFilter:controller];
静态滤镜
// 1 添加静态滤镜
AliyunShaderFilterController *controller = [[editor getFilterManager] applyShadeFilterWithPath:path];
if (!controller) {
// 添加LutFilter失败
}
// 2 删除静态滤镜
[[editor getFilterManager] removeFilter:controller];
动效滤镜
// 1 添加动效滤镜
AliyunEffectFilter *animationFilter = [[AliyunEffectFilter alloc] initWithFile:filterFolder];
animationFilter.startTime = 2;
animationFilter.endTime = 10;
// ... 其他属性请参考接口文档
[editor applyAnimationFilter:animationFilter];
// 2 修改动效滤镜
animationFilter.startTime = 4;
animationFilter.endTime = 12;
// ... 其他属性请参考接口文档
[editor updateAnimationFilter:animationFilter];
// 3 删除动效滤镜
[editor removeAnimationFilter:animationFilter];
转场
转场支持自定义制作,制作方法请参见滤镜及转场。
目前短视频SDK提供的转场效果包括:AliyunTransitionEffectTypeCircle(圆形打开)、AliyunTransitionEffectTypeFade(淡入淡出)、AliyunTransitionEffectTypePolygon(五角星)、AliyunTransitionEffectTypeShuffer(百叶窗)、AliyunTransitionEffectTypeTranslate(平移)。接口参数请参考AliyunTransitionEffectType。
转场位置
转场需要设置在两个片段中间,所以如果只有一个视频片段,不能添加转场,转场位置从0开始,位置含义如下:
[----A视频段----] [----B视频段----] [----C视频段----]...[----N段视频----]
^ ^ ^
位置: 0 1 N-1 -->
设置转场
//添加
//注意:使用此接口前,需要先调用[editor stopEdit],然后调用此接口,接着调用[editor startEdit]才会生效。
AliyunTransitionEffect *transition = [[AliyunTransitionEffect alloc] initWithPath:transitionFolder];
[editor applyTransition:transition atIndex:0];
//更新
//注意:转场的时长不能超过前后两段视频中最短的视频时长
transition.overlapDuration = 1;
// ... 其他更多属性请参考API文档
[editor updateTransition:transition atIndex:0];
//删除
[editor removeTransitionAtIndex:0];
MV
MV支持自定义制作,制作方法请参见MV。
// 添加MV
AliyunEffectMV *mv = [[AliyunEffectMV alloc] initWithFile:mvFolder];
[editor applyMV:mv];
// MV静音
[editor removeMVMusic];
// 删除MV
[editor removeMV];
时间特效
目前短视频SDK提供的时间特效包括:TimeFilterTypeSpeed(变速)、TimeFilterTypeRepeat(反复)、TimeFilterTypeInvert(倒放)。接口参数请参考TimeFilterType。
设置倒放时,如果视频的GOP过大 (35) (可通过AliyunNativeParser来获取GOP),则需要先对视频做一次转码后(可通过视频裁剪来转码),再使用倒放特效。
// 1. 添加时间特效
// 1.1 变速
AliyunEffectTimeFilter *timeFilter = [[AliyunEffectTimeFilter alloc] init];
timeFilter.type = TimeFilterTypeSpeed;
timeFilter.param = 0.67;
timeFilter.startTime = 2;
timeFilter.endTime = 10;
// ...其他属性请参考API文档
[editor applyTimeFilter:timeFilter];
// 1.2 反复
AliyunEffectTimeFilter *timeFilter = [[AliyunEffectTimeFilter alloc] init];
timeFilter.type = TimeFilterTypeRepeat;
timeFilter.param = 3; // 例如重复3次
timeFilter.startTime = 2;
timeFilter.endTime = 10;
// ...其他属性请参考API文档
[editor applyTimeFilter:timeFilter];
// 1.3 倒放
AliyunEffectTimeFilter *timeFilter = [[AliyunEffectTimeFilter alloc] init];
timeFilter.type = TimeFilterTypeInvert;
// ...其他属性请参考API文档
[editor applyTimeFilter:timeFilter];
// 2. 删除时间特效
[editor removeTimeFilter:timeFilter];
设置音乐及音效
支持添加音乐和配音,给音频添加各种音效(淡入淡出效果、变声)。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
音乐
音乐分为背景音乐和配音。背景音乐不受时间特效影响(变速、重复、倒放等),而配音会受到时间特效的影响。
背景音乐
// 1. 添加背景音乐
AliyunEffectMusic *music =[[AliyunEffectMusic alloc] initWithFile:musicFilePath];
music.duration = 3;
music.audioMixWeight = 50; // 音量大小(混音权重)
// ... 其他更多属性请参考API文档
[editor applyMusic:music];
// 2. 删除背景音乐
[editor removeMusic:music];
配音
// 1. 添加配音
AliyunEffectDub *dub =[[AliyunEffectDub alloc] initWithFile:dubFilePath];
dub.startTime = 2;
dub.audioMixWeight = 50; // 音量大小(混音权重)
dub.audioDenoiseWeight = 50; // 降噪程度
// ... 其他更多属性请参考API文档
[editor applyDub:dub];
// 2. 删除配音
[editor removeDub:dub];
音效
淡入淡出:支持AliyunAudioFadeShapeLinear(线性曲线)、AliyunAudioFadeShapeSin(正弦函数曲线),接口参数请参考AliyunAudioFadeShape。
变声效果:接口参数请参考AliyunAudioEffectType。
AliyunAudioEffectLolita(萝莉)
AliyunAudioEffectUncle(大叔)
AliyunAudioEffectReverb(混响)
AliyunAudioEffectEcho(回声)
AliyunAudioEffectRobot(机器人)
AliyunAudioEffectBigDevil(大魔王)
AliyunAudioEffectMinions(小黄人)
AliyunAudioEffectDialect(方言)
淡入淡出
// 1. 添加音频前指定淡入效果(淡出类似,属性为fadeOut)
AliyunAudioFade *fadeIn = [[AliyunAudioFade alloc] init];
fadeIn.shape = AliyunAudioFadeShapeLinear;
fadeIn.duration = 2;
music.fadeIn = fadeIn; // 配音也一样设置
// 2. 添加音频后修改淡入效果(淡出类似,函数为 setAudioFadeOutShape:duration:streamId:)
[editor setAudioFadeInShape:AliyunAudioFadeShapeLinear duration:2 streamId:music.effectVid];
// 3. 添加音频后删除淡入效果(淡出类似,函数为 removeAudioFadeOutWithStreamId:)
[editor removeAudioFadeInWithStreamId:music.effectVid]; // 配音也一样调用
变声
// 1. 添加音频前设置变声
AliyunAudioEffect *audioEffect = [[AliyunAudioEffect alloc] init];
audioEffect.type = AliyunAudioEffectLolita;
audioEffect.weight = 50;
[dub.audioEffects addObject:audioEffect]; // 注意:暂时只支持添加一个
// 2. 添加音频后设置变声
[editor setAudioEffect:AliyunAudioEffectLolita weight:50 streamId:dub.effectVid];
// 3. 添加音频后删除变声
[editor removeAudioEffect:AliyunAudioEffectLolita streamId:dub.effectVid];
设置画中画
画中画功能允许在现有主轨道的基础上,添加一个或者多个画中画。
主轨道:编辑页面默认轨道,有且仅有一个主轨道,一个主轨道可以有多个视频流。
画中画:允许添加多个画中画,画中画允许设置位置,缩放,旋转等。创建画中画默认创建一个画中画轨道。画中画可以在不同画中画轨道中移动。
代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
//获取画中画管理器,画中画管理器负责画中画的增删改查
AliyunPipManager * pipManager = [editor getPipManager];
//添加画中画
// 1. 在最上层添加一个画中画轨道,把画中画片段添加进该轨道
// 1.1 添加视频片段
NSError *error = nil;
AliyunPipClipController *pipClipController = [pipManager addClipWithType:AliyunPipClipTypeVideo path:xxVideoPath error:&error];
// 1.2 添加图片片段
NSError *error = nil;
AliyunPipClipController *pipClipController = [pipManager addClipWithType:AliyunPipClipTypeImage path:xxImagePath error:&error];
// 2. 把画中画插入到指定一个画中画轨道
// 2.1 创建画中画片段
AliyunPipClip *pipClip = [[AliyunPipClip alloc] initWithClipType:AliyunPipClipTypeVideo clipPath:xxxVideoPath];
// ... 更多画中画片段设置请参考文档:https://alivc-demo-cms.alicdn.com/versionProduct/doc/shortVideo/iOS_cn/Classes/AliyunPipClip.html
// 2.2 插入到指定的轨道中
NSError *error = nil;
AliyunPipClipController *pipClipController = [pipManager addClipWithModel:pipClip toTrack:pipManager.trackControllers.firstObject error:&error]; // 例如添加到第一条轨道里
//删除画中画
NSError *error = nil;
[pipManager removePipClipController:pipClipController error:&error];
//切换画中画轨道
[pipManager movePipClipController:pipController toTrack:pipManager.trackControllers.firstObject withStartTime:0]; // 例如移动到第一条轨道的开始位置
//修改画中画片段
// 例如直接修改画中画位置
pipController.clip.center = CGPointMake(100, 100);
// 例如批量修改位置、大小、旋转等
[pipController beginEdit];
pipController.clip.center = CGPointMake(100, 100);
pipController.clip.scale = 0.7;
pipController.clip.rotation = M_PI_2;
[pipController endEdit];
//点击测试
// 例如获取当前时间的某个触摸点最上层的画中画片段
double currentTime = [[editor getPlayer] getCurrentTime];
AliyunPipClipController *pipClipController = [pipManager hitTest:touchPoint withTime:currentTime];
设置字幕及贴纸
字幕及贴纸统一通过管理器 AliyunStickerManager进行管理,字幕及贴纸本身的状态分别通过字幕控制器AliyunCaptionStickerController和贴纸控制器AliyunStickerController进行管理。而字幕控制器和渲染控制器都继承于基础渲染控制器AliyunRenderBaseController,所以修改字幕或贴纸也采用修改基础元素属性同样的逻辑。
贴纸分为静态贴纸和动态贴纸,动态贴纸和气泡文字类似,只不过缺少了文字部分。动态贴纸支持自定义制作,制作规范及方法请参见动图。
代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
管理器
管理器负责字幕的及贴纸的增删改查,接口参数请参考AliyunStickerManager。
字幕和贴纸都继承于渲染模型AliyunRenderModel,有基础渲染元素的属性,您可以通过基于预览坐标系的点击位置来查找某一时刻在最上层的字幕或贴纸的控制器。
//获取管理器
AliyunStickerManager *stickerManager = [editor getStickerManager];
//添加
// 例如添加字幕
AliyunCaptionStickerController *captionController = [stickerManager addCaptionText:@"Hello" bubblePath:nil startTime:0 duration:5];
//删除
[stickerManager remove:gifController];
//查找
// 例如获取当前时间的某个触摸点最上层的字幕或贴纸
double currentTime = [[editor getPlayer] getCurrentTime];
AliyunRenderBaseController *controller = [stickerManager findControllerAtPoint:touchPoint atTime:currentTime];
字幕
//添加字幕
AliyunCaptionStickerController *captionController = [stickerManager addCaptionText:@"Hello" bubblePath:nil startTime:0 duration:5];
//修改字幕
[captionController beginEdit];
captionController.model.text = xxx;
captionController.model.outlineWidth = 3;
captionController.model.outlineColor = UIColor.redColor;
// ... 其他属性修改请参考AliyunCaptionSticker接口文档
[captionController endEdit];
花字
// 应用花字
captionController.model.fontEffectTemplatePath = fontEffectFolder; // 花字特效资源包文件夹目录
// 取消花字
captionController.model.fontEffectTemplatePath = nil;
文字气泡
// 应用气泡
captionController.model.resourePath = bubbleEffectFolder; // 文字气泡特效资源包文件夹目录
// 取消气泡
captionController.model.resourePath = nil;
动态贴纸
//添加动态贴纸
AliyunGifStickerController *gifController = [stickerManager addGif:gifFilePath startTime:0 duration:5];
//修改动态贴纸
[gifController beginEdit];
gifController.gif.center = xxx;
// ...其他属性修改请参考AliyunGifSticker接口文档
[gifController endEdit];
静态贴纸
//添加静态贴纸
AliyunImageStickerController *imageController = [stickerManager addImage:imageFilePath startTime:0 duration:5];
//修改静态贴纸
[imageController beginEdit];
imageController.image.center = xxx;
// ...其他属性修改请参考AliyunImageSticker接口文档
[imageController endEdit];
草稿箱
每次编辑会生成一个编辑任务,以工程配置的形式记录下来。如果您不想马上导出当前编辑结果,可以将编辑状态保存为草稿,下次通过草稿加载就能够恢复上次的编辑状态继续编辑。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
工程配置
编辑状态是以草稿对象的形式,以时间线的结构来描述的,每一个编辑动作最终会体现到这个工程配置上的状态改变。建议您将工程配置跟您的编辑界面相关联。草稿恢复时使用该工程配置恢复您的编辑状态对应的界面状态。
// 编辑中获取工程配置对象
AliyunEditorProject *project = [editor getEditorProject];
草稿对象
编辑状态可以保存为一个草稿对象AliyunDraft。您可以按以下方式保存当前的编辑状态。
// 0. 保存为草稿前,您需要定义草稿应该保存在哪里,我们提供了一个草稿管理对象作为草稿存储管理的容器
AliyunDraftManager *draftManager; // 草稿管理的介绍请参考下方文档说明
// 1. 指定草稿的标题保存,需要指定保存到哪里,返回草稿对象
AliyunDraft *draft = [editor saveToDraft:draftManager withTitle:@"your draft title"];
// 2.1 不指定草稿标题(使用上一次草稿标题)
AliyunDraft *draft = [editor saveToDraft:draftManager];
// 2.2 可以在获得草稿对象后修改标题
[draft renameTitle:@"your draft title"];
为了能更好地标识一个草稿,助力您开展本地或云端草稿业务,短视频SDK预留了一个ID用于标识一个草稿,您可以通过接口设置为您业务标识的ID。
[draft changeProjectId:@"your custom project id"];
草稿封面
编辑过程中,会自动生成一个合适的封面图(通常为视频的第一帧画面)。如果您觉得不合适,也可以通过接口替换为自定义的封面。
[editor updateCover:yourCoverImage];
// 也可以通过设置为空,让我们为您自动生成(默认)
[editor updateCover:nil];
在获得草稿对象后也支持更换封面图。
[draft updateCover:yourCoverImage];
如果需要自定义草稿封面,建议尽早设置,因为指定为自定义草稿封面后将不再自动去更新封面内容,因此,尽早设置后会减少不必要的更新动作,从而提高编辑性能。
草稿管理
由于草稿管理在初始化时会解析出所管理的所有草稿对象,所以有一定的性能损耗,建议作为一个全局的对象不要频繁地创建和销毁。
//初始化
//为更好地实现用户隔离,以ID作为草稿管理的标识,建议您使用用户的标识作为草稿管理的标识
NSString *draftManagerId;
//实例化
AliyunDraftManager *draftManager = [[AliyunDraftManager alloc] initWithId:draftManagerId];
//草稿列表
// 获取草稿列表
NSArray<AliyunDraft *> *draftList = draftManager.draftList;
// 也可以监听草稿列表的变化
draftManager.delegate = yourListener;
// 实现 AliyunDraftManagerDelegate协议 即可
// - (void) onAliyunDraftManager:(AliyunDraftManager *)mgr listDidChange:(NSArray<AliyunDraft *> *)list;
//删除草稿
[draftManager deleteDraft:targetDraft];
//复制草稿
[draftManager copyDraft:fromDraft toPath:newDraftTaskPath withTitle:@"your new draft title"];
草稿加载
由于编辑过程可能会用到多种资源,出于性能和存储空间的考虑,我们一般不会把用到的资源都拷贝到任务目录下,因此在从草稿恢复编辑状态前我们需要确保此次编辑中用到的资源都处于准备好的状态。所以,在草稿恢复到编辑状态前,您必须要处理一些资源的加载任务,以确保编辑用到的资源都已经准备好了。以下两类资源建议您特别关注:
相册中的媒体资源:需要确保开始编辑之前拥有读取的权限。
动态自定义的字体资源:需要确保开始编辑之前对应的字体已经注册到系统里,至少注册到当前app会话中。
[draft load:^(NSArray<AliyunDraftLoadTask *> *tasks) {
for (AliyunDraftLoadTask *task in tasks) {
// 资源加载任务处理
// 1. 获取当前任务的资源模型
AEPResourceModel *resource = task.resource;
// 2. 处理...
// 更多资源类型的处理可以参考官方Demo里的 AliyunDraftLoader.m
// 3. 标记处理结果
// 3.1.1 成功:忽略结果
[task onIgnore];
// 3.1.2 成功:需要修改资源模型的属性
AEPSource *resultSoruce = [resource.source createWithPath:@"result path"]; // 例如加载后资源路径发生了改变
[task onSuccess:resultSoruce];
// 3.2.1 失败:把对应的节点删除掉继续加载
[task onFailToRemove]; // 例如某个字幕的字体加载不了,选择把该字幕删除掉继续加载打开
// 3.2.2 失败:标记整体加载失败,把下面的error作为整体的加载失败原因
NSError *error = xxxx; // 需要您给出具体加载失败的原因
[task onFailToStopWithError:error]; // 发生这种失败时,建议您主动停止其他的加载任务
}
} completion:^(NSString *taskPath, AliyunEditorBaseProject *project, NSError *error) {
if (!taskPath || !project || error) {
// 加载失败处理...
// 一般情况下,error.localizedDescription中会有详细的失败原因
return;
}
// 加载成功
// 可以使用 taskPath创建Editor以获得恢复后的编辑状态,请参考编辑视频的初始化文档
}];
草稿上传
编辑状态主要由工程配置和资源组成。所以只要把这两部分同步到云端即可实现云草稿。接口参数请参考AliyunDraftProjectUploadTask。
为了性能和存储空间,默认不会拷贝编辑用到的所有资源,但草稿会记录所有用到资源的描述。为了更好地索引资源,提供了AEPSource.path(本地路径)、AEPSource.sourceId(资源ID)等多种方式来描述一个资源,详细可参考 AEPSource。在草稿资源的加载、上传、下载等过程中,除了会提供资源的描述,还会提供当前加载的该资源所在的节点对象、时间线所属模块等信息,详细可参考AEPResourceModel。
草稿上传分为如下两个过程:
上传编辑中所有用到的资源,这个过程中会修改工程配置中资源的描述。
上传资源描述修改后的工程配置文件。
[draft uploadWithResourceUploader:^(NSArray<AliyunDraftLoadTask *> *tasks) {
for (AliyunDraftLoadTask *task in tasks) {
// 上传任务处理
// 1. 获取当前任务的资源模型
AEPResourceModel *resource = task.resource;
// 2. 处理...
// 更多资源类型的处理可以参考官方Demo里的 AliyunDraftLoader.m
// 3. 标记处理结果
// 3.1.1 成功:忽略结果
[task onIgnore]; // 例如业务内置资源不需要上传处理
// 3.1.2 成功:需要修改资源模型的属性
AEPSource *resultSoruce = [resource.source createWithURL:@"resource URL"]; // 例如上传资源后获得网络链接
[task onSuccess:resultSoruce];
// 3.2.1 失败:把对应的节点删除掉继续上传
[task onFailToRemove]; // 例如某个字幕的字体上传失败了,选择把该字幕删除掉继续上传其他的
// 3.2.2 失败:标记整体上传失败,把下面的error作为最后的整体上传失败原因
NSError *error = xxx; // 需要您给出具体上传失败的原因
[task onFailToStopWithError:error]; // 发生这种失败时,建议您主动停止其他的上传任务
}
} projectUploader:^(AliyunDraftProjectUploadTask *projTask) {
// 添加云草稿处理
// 1.1 获取当前草稿的工程配置文件路径
NSString *projectFilePath = projTask.projectFilePath;
// 1.2 处理工程配置文件
NSString *projectUrl = [yourUploader upload:projectFilePath]; // 例如上传配置文件,返回网络路径
// 2.1 同步云草稿
NSString *projectId = [yourApi addCloudDraft:projectUrl]; // 例如调用您的业务服务返回一个标识ID
// 2.2 更新草稿的工程ID
[projTask.draft changeProjectId:projectId];
// 3. 标记处理结果
// 3.1 成功
[projTask onSuccess];
// 3.2 失败
NSError *error = xxx; // 需要您给出添加失败的原因
[projTask onFailWithError:error];
} completion:^(NSError *error) {
if (error) {
// 上传失败处理...
return;
}
// 上传成功处理...
}];
草稿下载
因为草稿的工程配置记录着所有用到的资源描述,所以只要提供工程配置文件就能枚举出所有的资源,把资源同步到本地就能完成草稿的下载。
跟保存草稿一样,需要确定草稿保存到哪里,所以下载接口定义在了本地草稿管理器AliyunDraftManager。
// 0. 通过您的业务服务获取云端草稿对应的工程配置文件和工程ID
NSString *projectFilePath = xxx;
NSString *projectId = xxx;
// 1. 下载草稿
[draftManager downloadDraftWithProjectFile:projectFilePath resourceDownloader:^(NSArray<AliyunDraftLoadTask *> *tasks) {
for (AliyunDraftLoadTask *task in tasks) {
// 下载任务处理
// 1. 获取资源
AEPResourceModel *resource = task.resource;
// 2. 处理...
NSString *localPath = [yourDownloader download:resource.source.URL]; // 例如网络下载
// 3. 标记处理结果
AEPSource *localSource = [resource.source createWithPath:localPath]; // 例如下载到本地了
[task onSuccess:localSource];
// 更多结果标记参考 加载任务
}
} completion:^(AliyunDraft *draft, NSError *error) {
if (error || !draft) {
// 下载失败处理...
return;
}
// 下载成功处理...
[draft changeProjectId:projectId]; // 建议同步一下云端的ID
// 其他更多处理...
}];
其他设置
涂鸦
短视频SDK封装了一套涂鸦接口,包含画板、画笔等,整个涂鸦操作由涂鸦画布视图(AliyunICanvasView)完成。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能。
// 1. 创建画笔
AliyunIPaint *paint = [[AliyunIPaint alloc]initWithLineWidth:5.0 lineColor:UIColor.whiteColor];
// 2. 添加涂鸦视图
AliyunICanvasView *paintView = [[AliyunICanvasView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) paint:paint];
[yourView addSubview:paintView];
// 3. 涂鸦
// 触摸视图即能进行涂鸦
// 4. 其他操作
// 4.1 撤销一步
[paintView undo];
// 4.2 反撤销一步
[paintView redo];
// 4.3 撤销本次涂鸦所有的操作
[paintView undoAllChanges];
// 4.4 清空所有线条(不可恢复)
[paintView remove];
// 5. 完成涂鸦
UIImage *image = [paintView complete];
NSString *paintPath = xxx;
[UIImagePNGRepresentation(image) writeToFile:paintPath atomically:YES];
// 6. 添加到编辑
AliyunEffectImage *paintImage = [[AliyunEffectImage alloc] initWithFile:paintPath];
[editor applyPaint:paintImage linesData:paintView.lines];
// 7. 删除涂鸦
[editor removePaint:paintImage];