Demo中视频编辑的Activity
流程为
MovieAlbumActivity
视频选择页面MovieEditorCutActivity
视频编辑-裁剪页面MovieEditorActivity
视频编辑页面类名 | 功能说明 |
---|---|
MovieAlbumActivity | 对视频列表进行选择 |
其中需要说明这几个参数
/* 最小视频时长(单位:ms) */
private static int MIN_VIDEO_DURATION = 3000;
/* 最大视频时长(单位:ms) */
private static int MAX_VIDEO_DURATION = 60000;
/** 最大边长限制 **/
private static final int MAX_SIZE = 3840;
最大边长限制,我们限制在了4K以下,因为不同的厂商的手机对4K的支持不同,我们统一限制视频为4K以下的视频可以编辑
类名 | 功能说明 |
---|---|
MovieEditorCutActivity | 编辑-裁剪页面 |
TuSdkMediaMutableFilePlayer | 视频播放器(支持多文件) |
TuSdkMediaFilesCuterImpl | 裁剪器(支持多文件) |
TuSdkVideoImageExtractor | 封面抽取(支持多文件) |
进入MovieEditorCutActivity
需要在打开这个activity
的时候通过Bundle
传入视频路径,SerializableExtr
的name
为videoPaths
/** 加载视频缩略图 */
public void loadVideoThumbList() {
List<TuSdkMediaDataSource> sourceList = new ArrayList<>();
for (MovieInfo movieInfo : mVideoPaths)
sourceList.add(TuSdkMediaDataSource.create(movieInfo.getPath()).get(0));
/** 准备视频缩略图抽取器 */
final TuSdkVideoImageExtractor imageThumbExtractor = new TuSdkVideoImageExtractor(sourceList);
imageThumbExtractor
//.setOutputImageSize(TuSdkSize.create(50,50)) // 设置抽取的缩略图大小
.setExtractFrameCount(20) // 设置抽取的图片数量
.setImageListener(new TuSdkVideoImageExtractorListener() {
/**
* 输出一帧略图信息
*
* @param videoImage 视频图片
* @since v3.2.1
*/
public void onOutputFrameImage(final TuSdkVideoImageExtractor.VideoImage videoImage) {
ThreadHelper.post(new Runnable() {
@Override
public void run() {
mEditorCutView.addBitmap(videoImage.bitmap);
if(!isSetDuration) {
float duration = mVideoPlayer.durationUs() / 1000000.0f;
mEditorCutView.setRangTime(duration);
mEditorCutView.setTotalTime(mVideoPlayer.durationUs());
if(duration >0)
isSetDuration = true;
}
mEditorCutView.setMinCutTimeUs(mMinCutTimeUs/(float)mDurationTimeUs);
}
});
}
/**
* 抽取器抽取完成
*
* @since v3.2.1
*/
@Override
public void onImageExtractorCompleted(List<TuSdkVideoImageExtractor.VideoImage> videoImagesList) {
/** 注意: videoImagesList 需要开发者自己释放 bitmap */
imageThumbExtractor.release();
}
})
.extractImages(); // 抽取图片
}
/**
* 开始合成视频
*/
private void startCompound(){
if (cuter != null) {
return;
}
isCutting = true;
List<TuSdkMediaDataSource> sourceList = new ArrayList<>();
// 遍历视频源
for (MovieInfo movieInfo : mVideoPaths) {
sourceList.add(TuSdkMediaDataSource.create(movieInfo.getPath()).get(0));
}
// 准备切片时间
TuSdkMediaTimeSlice tuSdkMediaTimeSlice = new TuSdkMediaTimeSlice(mLeftTimeRangUs,mRightTimeRangUs);
tuSdkMediaTimeSlice.speed = mVideoPlayer.speed();
// 准备裁剪对象
cuter = new TuSdkMediaFilesCuterImpl();
// 设置裁剪切片时间
cuter.setTimeSlice(tuSdkMediaTimeSlice);
// 设置数据源
cuter.setMediaDataSources(sourceList);
// 设置文件输出路径
cuter.setOutputFilePath(getOutputTempFilePath().getPath());
// 准备视频格式
MediaFormat videoFormat = TuSdkMediaFormat.buildSafeVideoEncodecFormat( cuter.preferredOutputSize().width, cuter.preferredOutputSize().height,
30, TuSdkVideoQuality.RECORD_MEDIUM2.getBitrate(), MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, 0, 0);
// 设置视频输出格式
cuter.setOutputVideoFormat(videoFormat);
// 设置音频输出格式
cuter.setOutputAudioFormat(TuSdkMediaFormat.buildSafeAudioEncodecFormat());
// 开始裁剪
cuter.run(new TuSdkMediaProgress() {
/**
* 裁剪进度回调
* @param progress 进度百分比 0-1
* @param mediaDataSource 当前处理的视频媒体源
* @param index 当前处理的视频索引
* @param total 总共需要处理的文件数
*/
@Override
public void onProgress(final float progress, TuSdkMediaDataSource mediaDataSource, int index, int total) {
ThreadHelper.post(new Runnable() {
@Override
public void run() {
mLoadContent.setVisibility(View.VISIBLE);
mLoadProgress.setValue(progress * 100);
}
});
}
/**
* 裁剪结束回调
* @param e 如果成功则为Null
* @param outputFile 输出文件路径
* @param total 处理文件总数
*/
@Override
public void onCompleted(Exception e, TuSdkMediaDataSource outputFile, int total) {
isCutting = false;
ThreadHelper.post(new Runnable() {
@Override
public void run() {
setEnable(true);
mLoadContent.setVisibility(View.GONE);
mLoadProgress.setValue(0);
mPlayBtn.setVisibility(mVideoPlayer.isPause()?View.VISIBLE:View.GONE);
}
});
Intent intent = new Intent(MovieEditorCutActivity.this,MovieEditorActivity.class);
intent.putExtra("videoPath", outputFile.getPath());
startActivity(intent);
cuter = null;
}
});
}
// 设置裁剪切片
public void setTimeSlice(TuSdkMediaTimeSlice slice)
// 设置数据源
public final void setMediaDataSources(List<TuSdkMediaDataSource> mediaDataSources)
// 设置输出文件路径
public void setOutputFilePath(String filePath)
// 设置输出视频格式
public int setOutputVideoFormat(MediaFormat videoFormat)
// 设置输出音频格式
public int setOutputAudioFormat(MediaFormat audioFormat)
// 进行裁剪
public boolean run(TuSdkMediaProgress progress)
裁剪的回调为
/** 媒体处理进度接口 */
public interface TuSdkMediaProgress {
/**
* 执行进度 [主线程]
*
* @param progress 进度百分比 0-1
* @param mediaDataSource 当前处理的视频媒体源
* @param index 当前处理的视频索引
* @param total 总共需要处理的文件数
*/
void onProgress(float progress, TuSdkMediaDataSource mediaDataSource, int index, int total);
/***
* 完成转码 [主线程]
* @param e 如果成功则为Null
* @param outputFile 输出文件路径
* @param total 处理文件总数
*/
void onCompleted(Exception e, TuSdkMediaDataSource outputFile, int total);
}
由MovieEditorCutActivity
转码后,进入了视频编辑MovieEditorActivity
页面,
类名 | 功能描述 |
---|---|
MovieEditorActivity | 视频编辑页面 |
MovieEditorController | 视频编辑控制器 |
TuSdkMovieEditor | 视频编辑器 |
视频编辑的功能是由TuSdkMovieEditor
提供,Demo界面以及相关操作,都在MovieEditorController
里以操作组件的方式封装
类名 | 功能描述 |
---|---|
EditorHomeComponent | 视频编辑主页面组件 |
EditorFilterComponent | 滤镜效果组件 |
EditorMVComponent | MV效果组件 |
EditorMusicComponent | 配音效果组件 |
EditorTextComponent | 文字效果组件 |
EditorEffectComponent | 特效组件(包括场景特效、时间特效、魔法特效) |
EditorEffectTransitionsComponent | 转场特效组件 |
EditorStickerComponent | 图片贴纸组件 |
EditorTrimComponent | 视频裁剪组件 |
在MovieEditorController
的构造方法中需要初始化视频编辑器,以下是一个最简单的视频加载逻辑。
/**
* context 当前context
* holderView 视频播放器的父容器
* options 视频配置项
**/
TuSdkMovieEditor mMovieEditor = new TuSdkMovieEditorImpl(context, holderView, options);
//之前经历过MovieEditorCutActivity裁剪加载 则不用开启转码
mMovieEditor.setEnableTranscode(false);
//加载视频
mMovieEditor.loadVideo();
接口描述:
/**
* 是否开启转码
*
* @param isEnableTranscode true 开启 false 不开启 默认开启
*/
public void setEnableTranscode(boolean isEnableTranscode);
/**
* 加载视频
*
* @since 3.0
*/
void loadVideo();
/**
* 保存视频
*
* @since 3.0
*/
void saveVideo();
简单使用方式如下:
TuSdkMovieEditor.TuSdkMovieEditorOptions defaultOptions = TuSdkMovieEditor.TuSdkMovieEditorOptions.defaultOptions();
defaultOptions
// 设置视频数据源
.setVideoDataSource(new TuSdkMediaDataSource(mVideoPath))
// 设置是否保存或者播放原音
.setIncludeAudioInVideo(true)
// 设置MovieEditor销毁时是否自动清除缓存音频解码信息
.setClearAudioDecodeCacheInfoOnDestory(false)
// 设置时间线模式
.setPictureEffectReferTimelineType(TuSdkMediaEffectReferInputTimelineType)
// 设置水印
.setWaterImage(BitmapHelper.getBitmapFormRaw(this, R.raw.sample_watermark), TuSdkWaterMarkOption.WaterMarkPosition.TopRight, true);
接口描述:
public TuSdkMovieEditorOptions setVideoDataSource(TuSdkMediaDataSource videoDataSource);
public TuSdkMovieEditorOptions setMovieOutputFilePath(File movieOutputFilePath);
public TuSdkMovieEditorOptions setCutTimeRange(TuSDKTimeRange cutTimeRange);
public TuSdkMovieEditorOptions setCanvasRectF(RectF canvasRect);
public TuSdkMovieEditorOptions setIncludeAudioInVideo(boolean includeAudioInVideo);
public TuSdkMovieEditorOptions setPictureEffectReferTimelineType(TuSdkMediaPictureEffectReferTimelineType timelineType);
public TuSdkMovieEditorOptions setOutputSize(TuSdkSize outputSize);
public TuSdkMovieEditorOptions setSaveToAlbum(Boolean saveToAlbum);
public TuSdkMovieEditorOptions setSaveToAlbumName(String saveToAlbumName);
public TuSdkMovieEditorOptions setClearAudioDecodeCacheInfoOnDestory(boolean clearAudioDecodeCacheInfoOnDestory);
/**
* waterImage 水印图片 (Bitmap)
* watermarkPosition 水印的位置
* isRecycleWaterImage 是否回收水印图片(Bitmap)
**/
public TuSdkMovieEditorOptions setWaterImage(Bitmap waterImage, TuSdkWaterMarkOption.WaterMarkPosition watermarkPosition, boolean isRecycleWaterImage)
视频编辑TuSdkMovieEditor
中由一下几个组件组成,调用的时候通过一下不同的功能组件调用不同的API
TuSdkEditorTranscoder
转码器 如果没有预转码或者开启了转码,则由此转码器进行视频的裁剪与处理TuSdkEditorPlayer
播放器 编辑内的播放器,负责控制时间特效,以及相关播放的APITuSdkEditorEffector
特效器 负责特效 添加 删除相关的APITuSdkEditorAudioMixer
混音器 混音相关的APITuSdkEditorSaver
保存器 最后保存视频的相关API相关特效分为普通特效和时间特效两种 普通特效:
特效类名 | 功能描述 |
---|---|
TuSDKMediaTextEffectData | 文字贴纸特效 |
TuSDKMediaParticleEffectData | 魔法特效 |
TuSDKMediaStickerAudioEffectData | MV特效 |
TuSDKMediaFilterEffectData | 滤镜效果 |
TuSDKMediaStickerEffectData | 贴纸特效 |
TuSDKMediaAudioEffectData | 配音特效 |
TuSDKMediaSceneEffectData | 场景特效 |
TuSDKMediaComicEffectData | 卡通特效 |
TuSdkMediaTransitionEffectData | 转场特效 |
TuSdkMediaStickerImageEffectData | 图片贴纸特效 |
上述特效由TuSdkEditorEffector
特效器进行添加 、删除的操作,下列三种是时间特效,由TuSdkEditorPlayer
管理
特效类名 | 功能描述 |
---|---|
TuSDKMediaReversalTimeEffect | 倒序时间特效 |
TuSDKMediaRepeatTimeEffect | 反复时间特效 |
TuSDKMediaSlowTimeEffect | 慢动作时间特效 |
在EditorFilterComponent
中mFilterRecyclerView
的Item
点击回调内,回去到当前点击的滤镜的code
(通过mFilterRecyclerAdapter.setFilterList(filterList)
设置)
滤镜列表,获取滤镜前往 TuSDK.bundle/others/lsq_tusdk_configs.json
TuSDK 滤镜信息介绍 @see-https://tusdk.com/docs/image-android/customize-filter
TuSDKMediaFilterEffectData mediaFilterEffectData = new TuSDKMediaFilterEffectData(code);
getMovieEditor().getEditorEffector().addMediaEffectData(filterEffectData);
滤镜的改变回调可以通过TuSdkEditorEffector
来设置
//设置滤镜改变的回调
getEditorEffector().setFilterChangeListener(mFilterChangeListener);
在EditorMVComponent
中的mMvRecyclerView
的Item
点击回调内
/********************* 添加 *************/
if (mMusicMap != null && mMusicMap.containsKey(groupId)) {
//带音效的MV
Uri uri = Uri.parse("android.resource://" + getEditorController().getActivity().getPackageName() + "/" + mMusicMap.get(groupId));
//创建MV数据类
TuSDKMediaStickerAudioEffectData stickerAudioEffectDat = new TuSDKMediaStickerAudioEffectData(new TuSdkMediaDataSource(context, uri), itemData);
//设置时间范围
stickerAudioEffectDat.setAtTimeRange(TuSDKTimeRange.makeRange(0, Float.MAX_VALUE));
//设置MV的背景音效是否循环播放
stickerAudioEffectDat.getMediaAudioEffectData().getAudioEntry().setLooping(true);
//添加MV效果
getEditorEffector().addMediaEffectData(stickerAudioEffectDat);
} else {
//纯贴纸的MV
TuSDKMediaStickerEffectData stickerEffectData = new TuSDKMediaStickerEffectData(itemData);
//设置时间范围
stickerEffectData.setAtTimeRange(TuSDKTimeRange.makeRange(0, Float.MAX_VALUE));
//添加MV效果
getMovieEditor().getEditorEffector().addMediaEffectData(stickerEffectData);
}
在EditorMusicComponent
中的mMusicRecycle
的Item
点击中
//创建音频特效对象
TuSDKMediaAudioEffectData audioEffectData = new TuSDKMediaAudioEffectData(new TuSdkMediaDataSource(context, audioPathUri));
//设置时间
audioEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(0, getEditorPlayer().getOutputTotalTimeUS()));
加载音频回调是在混音器中加入
getEditorMixer().addTaskStateListener(mAudioDecoderTask);
回调的状态有
/**
* 当前执行类状态
*/
public enum State {
/** 空闲状态 **/
Idle,
/** 正在解码 **/
Decoding,
/** 解码完成 **/
Complete,
/** 已取消 **/
Cancelled
}
音频录音回调
/**
* 录音裁剪进度监听
*/
public interface OnAudioRecordCuterListener {
/**
* 当前执行的进度
*
* @param percent 当前进度的百分比 (0 ~ 1)
* @param currentTimeUS 当前执行的时间(微秒)
* @param totalTimeUS 总时长 (微秒)
*/
void onProgressChanged(float percent, long currentTimeUS, long totalTimeUS);
/**
* 输出完毕
*
* @param outputFile 输出完成的文件
*/
void onComplete(File outputFile);
}
文字功能在EditorTextComponent
中,添加一个文字特效
/**
* 将数据转成公用的 TuSDKMediaEffectData
*
* @param sticker 贴纸数据
* @param bitmap 文字生成的图片
* @param offsetX 相对视频左上角X轴的位置
* @param offsetY 相对视频左上角Y轴的位置
* @param rotation 旋转的角度
* @param startTimeUs 文字特效开始的时间
* @param stopTimeUs 文字特效结束的时间
* @param stickerSize 当前StickerView的宽高(计算比例用)
*/
//创建一个文字贴纸包装类
TuSDKTextStickerImage stickerImage = new TuSDKTextStickerImage();
//创建文字贴纸数据类
TextStickerData stickerData = new TextStickerData(bitmap, bitmap.getWidth(), bitmap.getHeight(), 0, offsetX, offsetY, rotation);
stickerImage.setCurrentSticker(stickerData);
//设置设计画布的宽高
stickerImage.setDesignScreenSize(stickerSize);
//创建文字特效类
TuSDKMediaTextEffectData mediaTextEffectData = new TuSDKMediaTextEffectData(stickerImage);
//设置当前文字特效的时间
mediaTextEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(startTimeUs, stopTimeUs
//添加特效
getEditorEffector().addMediaEffectData(textMediaEffectData)
特效里分为场景特效 、 时间特效、魔法特效 三种,在EditorEffectComponent
中,分为三个Fragment
ScreenEffectFragment
场景特效FragmentTimeEffectFragment
时间特效FragmentMagicEffectFragment
魔法特效Fragment添加一个场景特效
//创建一个场景特效数据类
TuSDKMediaSceneEffectData mediaSceneEffectData = new TuSDKMediaSceneEffectData(mScreenCode);
//设置场景特效的时间
mediaSceneEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(starTimeUs, endTimeUs));
//添加当前场景特效
getEditorEffector().addMediaEffectData(mediaSceneEffectData);
删除一个场景特效
getEditorEffector().removeMediaEffectData(mediaEffectData);
//实例化反复特效数据类
TuSDKMediaRepeatTimeEffect repeatTimeEffect = new TuSDKMediaRepeatTimeEffect();
//设置开始与结束的时间范围
repeatTimeEffect.setTimeRange(startTimeUS, endTimeUS);
//设置反复的次数
repeatTimeEffect.setRepeatCount(2);
//是否裁剪多余的时间
repeatTimeEffect.setDropOverTime(false);
//应用时间特效
getEditorPlayer().setTimeEffect(repeatTimeEffect);
//实例化慢动作特效数据
TuSDKMediaSlowTimeEffect slowTimeEffect = new TuSDKMediaSlowTimeEffect();
//设置慢动作的时间范围
slowTimeEffect.setTimeRange(startTimeUS, endTimeUS);
//设置慢动作的速率
slowTimeEffect.setSpeed(0.6f);
//应用时间特效
getEditorPlayer().setTimeEffect(slowTimeEffect);
//实例化时光倒流特效数据
TuSDKMediaReversalTimeEffect reversalTimeEffect = new TuSDKMediaReversalTimeEffect();
//应用时间特效
getEditorPlayer().setTimeEffect(reversalTimeEffect);
getEditorPlayer().clearTimeEffect();
//实例化魔法效果
TuSDKMediaParticleEffectData effectModel = new TuSDKMediaParticleEffectData(mCurrentMagicCode);
//设置粒子大小
effectModel.setSize(mMagicConfig.getSize());
//设置粒子颜色
effectModel.setColor(mMagicConfig.getColor());
//设置粒子位置(持续的移动不断的put 参考Demo中的MagicEffectFragment)
effectModel.putPoint(getEditorPlayer().getCurrentTimeUs(), pointF);
//预览魔法特效
getEditorEffector().addMediaEffectData(effectModel);
转场特效功能在EditorEffectTransitionsComponent
中,添加一个转场特效
/** 转场特效类型枚举 */
public enum TuSDKMediaTransitionType{
/** 转场 - 淡入 @since v3.4.1 */
TuSDKMediaTransitionTypeFadeIn,
/** 转场 - 飞入 @since v3.4.1 */
TuSDKMediaTransitionTypeFlyIn,
/** 转场 - 拉入--右侧进入 @since v3.4.1 */
TuSDKMediaTransitionTypePullInRight,
/** 转场 - 拉入--左侧进入 @since v3.4.1 */
TuSDKMediaTransitionTypePullInLeft,
/** 转场 - 拉入--顶部进入 @since v3.4.1 */
TuSDKMediaTransitionTypePullInTop,
/** 转场 - 拉入--底部进入 @since v3.4.1 */
TuSDKMediaTransitionTypePullInBottom,
/** 转场 - 散步进入 @since v3.4.1 */
TuSDKMediaTransitionTypeSpreadIn,
/** 转场 - 闪光灯 @since v3.4.1 */
TuSDKMediaTransitionTypeFlashLight,
/** 转场 - 翻页 @since v3.4.1 */
TuSDKMediaTransitionTypeFlip,
/** 转场 - 聚焦-小到大 @since v3.4.1 */
TuSDKMediaTransitionTypeFocusOut,
/** 转场 - 聚焦-大到小 @since v3.4.1 */
TuSDKMediaTransitionTypeFocusIn,
/** 转场 - 叠起 @since v3.4.1 */
TuSDKMediaTransitionTypeStackUp,
/** 转场 - 缩放 @since v3.4.1 */
TuSDKMediaTransitionTypeZoom
}
//创建一个转场特效数据类,接收一个TuSDKMediaTransitionType类型的枚举
TuSdkMediaTransitionEffectData mediaTransitionEffectData = new TuSdkMediaTransitionEffectData(TuSDKMediaTransitionType);
//设置转场特效开始与结束时间
mediaTransitionEffectData.setAtTimeRange(TuSDKTimeRange.makeTimeUsRange(starTimeUs, endTimeUs));
//设置转场特效持续时间
mediaTransitionEffectData.getFilterArg("duration").setValue(durationUs);
mediaTransitionEffectData.submitParameters();
//预览转场特效
getEditorEffector().addMediaEffectData(mediaTransitionEffectData);
图片贴纸特效功能在EditorStickerComponent
中, 添加图片贴纸
//创建一个贴纸数据对象
StickerImageData imageData = new StickerImageData();
//设置要显示的图片Bitmap
imageData.setImage(bitmap);
//设置图片的高度
imageData.height = TuSdkContext.px2dip(bitmap.getHeight());
//设置图片的宽度
imageData.width = TuSdkContext.px2dip(bitmap.getWidth());
//设置图片开始显示时间
imageData.starTimeUs = 0;
//设置图片结束显示时间
imageData.stopTimeUs = 2 * 1000000;
//添加贴纸数据对象
getEditorController().getActivity().getImageStickerView().appendSticker(imageData);
/**
* 将数据转成公用的 TuSdkMediaEffectData
*
* @param bitmap 图片
* @param displaySize 图片显示的大小
* @param offsetX 相对视频左上角X轴的位置
* @param offsetY 相对视频左上角Y轴的位置
* @param rotation 旋转的角度
* @param startTimeUs 特效开始的时间
* @param stopTimeUs 特效结束的时间
* @param stickerSize 当前StickerView的宽高(计算比例用)
* @return
*/
protected TuSdkMediaStickerImageEffectData createTileEffectData(Bitmap bitmap, TuSdkSize displaySize, float offsetX, float offsetY, float rotation, long startTimeUs, long stopTimeUs ,TuSdkSize stickerSize) {
TuSdkMediaStickerImageEffectData mediaTextEffectData = new TuSdkMediaStickerImageEffectData(bitmap,offsetX,offsetY,rotation,displaySize,stickerSize);
mediaTextEffectData.setAtTimeRange(TuSdkTimeRange.makeTimeUsRange(startTimeUs, stopTimeUs));
return mediaTextEffectData;
}
//添加贴纸特效对象
getEditorEffector().addMediaEffectData(createTileEffectData());
视频裁剪功能在EditorTrimComponent
中
//设置视频裁剪输出比例,ratio 为 float类型的输出比例,enableClip 为 是否裁剪画面 true 超出比例外的部分会被裁剪掉 false 整个画面缩到预定的比例大小里
getEditorPlayer().setOutputRatio(float ratio, boolean enableClip);
保存视频调用TuSdkMovieEditor.saveVideo()
,保存的回调可以在saveVieo()
之前向保存器内添加回调
getEditorSaver().addSaverProgressListener(mSaveProgressListener);
该回调为
//保存进度监听
interface TuSdkSaverProgressListener {
/**
* 当前进度
*
* @param progress
* @since v3.0
*/
void onProgress(float progress);
/**
* 保存完成
*
* @param outputFile
* @since v3.0
*/
void onCompleted(TuSdkMediaDataSource outputFile);
/**
* 保存错误
*
* @param e
* @since v3.0
*/
void onError(Exception e);
}