功能模块 | 作用 | 方法 | 注意事项 | 必须调用 |
---|---|---|---|---|
初始化 | 初始化EOExportManager |
| activity: 上下文,需要使用ComponentActivity | 是 |
暂停视频 | fun pause() | 否 | ||
设置视频到指定时间 |
| position:单位毫秒 | 否 | |
获取封面图 | fun getVideoCoverImage( videoSize: EOVideoSize, completionBlock: (draftImg: String, framePosition: Long, isAlbumCover: Boolean) -> Unit ) | videoSize:获取视频帧作为封图大小 | 否 | |
视频分辨率 | fun getVideoSize(): EOVideoSize? | 注意可能为null,未初始化成功 | 否 | |
视频时长 |
| 单位:us | 否 | |
获取来源 | fun getEOExportModel(): EOExportModel? | 获取sdk来源数据模型 | 否 | |
封面 | 视频抽帧 |
| timestamps:单位:ms 抽帧位置集合List,支持单帧,多帧 completionBlock:耗时操作 videoFrame: 抽帧的bitmap,可能多次调用,依赖timestamps | 否 |
设置封面(从视频帧) |
| videoSize*:保存视频帧封图大小(宽,高)* savedBlock*:耗时操作* String?:保存视频帧路径String Long: 视频帧位置,单位ms | 否 | |
设置封面(从相册) |
| 从sdk内置相册选择封面图功能,只支持静图 completionBlock: 参数1 String:选择并裁剪成功后的图片路径 | 否 | |
导出 | 是否添加水印 |
| shouldAddWaterMark:默认添加 | 否 |
设置水印图片 | fun setWaterMarkPath(waterMarkPath: String?) | waterMarkPath: 水印路径,只支持sdcard | 否 | |
设置水印图片大小 |
| videoSize:水印图片大小,单位dp | 否 | |
导出视频 |
| outputPath:导出视频保存路径 | 是 | |
取消导出 | fun cancelExport() | cancel回调 | 否 |
fun init( activity: ComponentActivity, surfaceView: SurfaceView? = null, previewSurfaceImage: Bitmap? = null, eoExportInitConfig: EOExportInitConfig = EOExportInitConfig(), callback: (suc: Boolean) -> Unit, ): EOExportManager
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
activity | ComponentActivity | 无 | 是 | pickAlbum接口需要依赖,从相册选择封图需要使用,用于关注宿主生命状态,做一些资源释放操作 |
surfaceView | SurfaceView | null | 否 | 用于在编辑过程中预览视频的SurfaceView,在某些不需要显示预览的场景下,可以不传 |
previewSurfaceImage | Bitmap | null | 否 | 设置预览帧,主要用于首帧优化 |
eoExportInitConfig | EOExportInitConfig | EOExportInitConfig() | 否 |
|
返回值类型 | 说明 |
---|---|
EOExportManager | 编辑实例,所有编辑相关的接口都在这个Manager里面 |
private lateinit var eoExportManager:EOExportManager override fun onViewCreated( view: View, savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) //>>>> step1 初始化EOExportManager eoExportManager.init(requireActivity(), view.findViewById<SurfaceView>(R.id.export_cover_preview)) { suc -> if (!suc) { requireActivity().finish() } } }
fun getVideoCoverImage( videoSize: EOVideoSize, completionBlock: (draftImg: String, framePosition: Long, isAlbumCover: Boolean) -> Unit )
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
videoSize | EOVideoSize | 无 | 是 | 抽取视频帧的大小封图(宽,高) |
completionBlock | (draftImg: String, framePosition: Long, isAlbumCover: Boolean) -> Unit | 无 | 是 | 设置预览帧,主要用于首帧优化 |
返回值类型 | 说明 |
---|---|
(draftImg: String, framePosition: Long, isAlbumCover: Boolean) | 获取草稿封图和视频帧图 |
eoExportManager.getVideoCoverImage { draftImg, framePosition, isAlbumCover -> if (isAlbumCover) { //updateCoverAlbum(draftImg) } else { //updateCoverFrame(draftImg, framePosition) } }
fun pause()
无
无
eoExportManager.pause()
fun seekTo(position: Long, isSmooth: Boolean = false)
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
position | Long | 无 | 是 | 设置视频的位置,单位ms |
isSmooth | Boolean | false | 否 | 设置视频过程中是否跟手滑动 |
无
eoExportManager.seekTo(position, isSmooth)
fun getVideoSize(): EOVideoSize?
无
返回值类型 | 说明 |
---|---|
EOVideoSize | 获取视频分辨率(宽和高) |
val eoVidoeSize = eoExportManager.getVideoSize() eoVidoeSize?.let{ Log.d("Tag"," width = ${eoVidoeSize.width}, height = ${eoVidoeSize.height}") }
fun geTotalVideoDuration(): Long?
无
返回值类型 | 说明 |
---|---|
Long | 获取视频时长 |
val videoDuration = eoExportManager.geTotalVideoDuration() eoVidoeSize?.let{ Log.d("Tag"," videoDuration = $videoDuration") }
fun getEOExportModel(): EOExportModel?
无
返回值类型 | 说明 |
---|---|
EOExportModel | 获取EffectOne上游数据模型,主要用户业务方使用,
|
val exportViewModel = eoExportManager.getEOExportModel() ?: return val totalDuration = eoExportManager.geTotalVideoDuration() ?: return exportViewModel.takif(it.isAlbumCover.not)?.let{ //业务保存视频帧做封面后,退出页面,再次打开,重新选择视频帧时候,希望seek到上次保存视频帧的位置 eoExportManager.seekTo(it.framePosition,false) }
fun getVideoFrames( timestamps: VecLongLong, eoVideoSize: EOVideoSize, completionBlock: ((videoFrame: Bitmap?) -> Unit)?=null )
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
timestamps | VecLongLong | 无 | 是 | 抽帧视频位置序列集合List,支持单帧,多帧 |
eoVideoSize | EOVideoSize | 无 | 是 | 抽取视频的大小,(宽,高) |
completionBlock | ((videoFrame: Bitmap?) -> Unit) | null | 否 | 耗时操作 videoFrame: 成功抽帧返回bitmap,可能多次调用,依赖timestamps |
返回值类型 | 说明 |
---|---|
Bitmap | 成功抽帧返回bitmap,可能多次调用,回调次序按照timestamps添加顺序 |
eoExportManager.getVideoFrames( VecLongLong(listOf(0)), EOVideoSize(720, 1080) ) { bmp -> runOnUiThread { bmp?.let { // } } }
fun saveVideoFrame( videoSize: EOVideoSize, saveToDraft: Boolean = true, savedBlock: (( String?, Long) -> Unit)? = null )
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
videoSize | EOVideoSize | 无 | 是 | 抽取视频的大小,(宽,高) |
saveToDraft | Boolean | true | 否 | 保存视频帧到草稿箱封面,用户草稿页展示 |
savedBlock | ((String?, Long) -> Unit) | null | 否 | 保存当前视频帧 String?:保存视频帧路径String Long: 视频帧位置,单位ms |
返回值类型 | 说明 |
---|---|
(( String?, Long) -> Unit) | String?:保存视频帧路径String Long: 视频帧位置,单位ms |
eoExportManager.saveVideoFrame(EOVideoSize(720, 1080)){ loading, bitmapPath, position ->{ } }
fun pickAlbum(saveToDraft: Boolean = true, completionBlock: ((String?, String?, Boolean) -> Unit))
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
saveToDraft | Boolean | true | 否 | 保存相册图片到草稿箱封面,用户草稿页展示 |
completionBlock | ((String?, String?, Boolean) -> Unit) | 无 | 是 | 从sdk内置相册选择封面图功能,只支持静图 String: String: Boolean*:* |
返回值类型 | 说明 |
---|---|
((Boolean, String?, Long) -> Unit) | 参数1 String:选择并裁剪成功后的图片路径 |
eoExportManager.pickAlbum { bitmapPath: String?, errorMsg: String?, cancel: Boolean -> bitmapPath?.let { Log.d("Tag","bitmapPath -> $it ") } }
概述:SDK只接受本地路径的输入,不接受res资源的输入
因此 需要提前将水印图片下载到本地SD卡上或者内置到app中 再拷贝数据到SD卡上
位置:水印默认显示视频总时长,前半视频左上,后半视频右下
fun shouldAddWaterMark(shouldAddWaterMark: Boolean = true)
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
shouldAddWaterMark | Boolean | true | 否 | 导出视频是否添加水印 |
无
eoExportManager.shouldAddWaterMark(true)
fun setWaterMarkPath(waterMarkPath: String?)
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
waterMarkPath | String | 无 | 是 | 设置导出视频水印图片路径 |
无
eoExportManager.setWaterMarkPath("/sdcard/Download/waterMark/eo-waterMark.jpg")
fun setWaterMarkSize(videoSize: EOVideoSize)
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
videoSize | EOVideoSize | EEOVideoSize(134,32) | 是 | 设置导出视频水印图片大小: 单位:dp,根据UI设计师给定大小 |
无
eoExportManager.setWaterMarkSize(EOVideoSize(width = 29, height = 31))
fun exportVideo( outputPath: String, outputSetting: [EoOutputVideoSettings](https://bytedance.larkoffice.com/docx/A3ogdjxYkoREIdx6S8ScZc3wnTb#part-XhisdcJWYo2J3Oxpuh1c7XH0nyb)= EOOutputVideoSettings(), exportListener: [IEoExportListener](https://bytedance.larkoffice.com/docx/A3ogdjxYkoREIdx6S8ScZc3wnTb#part-LxcPdwDDMoknedxBax6cFEWGnnh)? = null )
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
outputPath | String | 无 | 是 | 导出视频的路径,比如相册等 |
outputSetting | EoOutputVideoSettings | EOOutputVideoSettings() | 否 | 导出参数设置
|
exportListener | IEoExportListener | null | 否 | 导出视频回调
|
val saveDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath val dateFormat = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()) val exportFileName = "eo-${dateFormat.format(Date())}.mp4" val exportFilePath = "$saveDir${File.separator}$exportFileName" eoExportManager.exportVideo( outputPath = exportFilePath, outputSetting = EOOutputVideoSettings(), exportListener = iEOExportListenerImpl, ) private val iEOExportListenerImpl by lazy { object : EOExportListenerAdapter() { override fun onDone() { Log.d(TAG, "onCompileDone()") } override fun onError(error: Int, msg: String?) { Log.d(TAG,"onCompileError() error = $error, msg = $msg") } override fun onProgress(progress: Float) { Log.d( TAG,"onCompileProgress() progress =${progress.times(100).toInt()}" ) } } }
导出图片功能是在导出视频功能 fun exportVideo() 的基础上实现的,在完成视频导出之后,通过导出视频帧的方式完成图片的导出
同上(导出视频)
同上(导出视频)
// 创建临时视频文件和导出图片文件 val dateFormat = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()) val exportTempFileName = "eo-${dateFormat.format(Date())}.mp4" val tempFileDir = EOUtils.pathUtil.externalDir("temp_export").absolutePath val tempPath = File(tempFileDir, exportTempFileName) val exportFileName = "eo-${dateFormat.format(Date())}.jpg" val photoFileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)?.toString() val photoPath = File(photoFileDir, exportFileName) // 执行导出视频 eoExportViewModel.eoExportManager.exportVideo( outputPath = tempPath.absolutePath, outputSetting = EOOutputVideoSettings().apply { eoVideoEncodeViewModel.resolutionFps.value?.let { outputRes = it.first outputFps = it.second } useSoftwareDecode = true useHWEncoder = true }, exportListener = EOExportImageListener(activity, photoPath) ) // 处理导出视频文件的listener class EOExportImageListener( private val activity: Activity, private val photoPath: File, ) : EOExportDefaultListener() { override fun onDone(outputPath: String) { saveFrameAsPhoto(outputPath, photoPath) // 删除临时视频文件 val tempVideoFile = File(outputPath) if (tempVideoFile.exists()) { tempVideoFile.delete() MediaScannerConnection.scanFile(activity, arrayOf(tempVideoFile.absolutePath), null ) {_, _ -> LogKit.d(TAG, "temp video file delete succeed: ${tempVideoFile.absolutePath}") } } } override fun onError(error: Int, msg: String?, ) { LogKit.d(TAG, "onCompileError() called with: error = $error, msg = $msg") } override fun onProgress(progress: Float) { LogKit.d(TAG, "onCompileProgress() : progress = ${progress.times(100).roundToInt()}") } private fun saveFrameAsPhoto(tempPath: String?, photoPath: File){ if (tempPath == null) return val retriever = MediaMetadataRetriever() try { retriever.setDataSource(tempPath) val frameBitmap = retriever.getFrameAtTime(0) val outStream = FileOutputStream(photoPath) frameBitmap?.compress(Bitmap.CompressFormat.JPEG, 100, outStream) MediaScannerConnection.scanFile(activity, arrayOf(photoPath.absolutePath), null) { _, _ -> // 导出图片成功 LogKit.d(TAG, "onCompileDone() outputPath $photoPath") } outStream.close() } catch (e: Exception) { // 导出图片失败 LogKit.e(TAG, "Get first frame from video failed.", e) } finally { retriever.release() } }
fun cancelExport()
无
无
eoExportManager.cancelExport()
导出页简介,可以参考EffectOne导出页简介
const val ACTION_EXPORT_ACTIVITY = "com.volcengine.effectone.Launch.EOExport" //AndroidManifest.xml <activity android:name=".export.EffectOneExportActivity" android:exported="true" android:screenOrientation="portrait" android:theme="@style/Theme.EffectOne"> <intent-filter> <action android:name="com.volcengine.effectone.Launch.EOExport" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
fun setMetadata(metadata: Map<String, String>)
参数名称 | 参数类型 | 默认值 | 是否必须 | 说明 |
---|---|---|---|---|
metadata | Map<String, String> | 无 | 是 | 以<key, value>的形式传入需要自定义的metadata数据 |
val customMetadata = mutableMapOf<String, String>() customMetadata["metadata_key_1"] = "metadata_value_1" customMetadata["metadata_key_2"] = "metadata_value_2" customMetadata["metadata_key_3"] = "metadata_value_3" eoExportManager.setMetadata(customMetadata) // 需在导出视频之前调用 // ... eoExportManager.exportVideo()
概述:SDK只接受本地路径的输入,不接受res资源的输入
因此 需要提前将水印图片下载到本地SD卡上或者内置到app中 再拷贝数据到SD卡上
//假设业务水印资源放到assets目录下 // 业务方自己实现,具体可见sample示例代码 EffectOneExportActivity.copyWaterMark(this)
private lateinit var eoExportManager:EOExportManager override fun onViewCreated( view: View, savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) //>>>> step1 初始化EOExportManager eoExportManager.init(requireActivity(), view.findViewById<SurfaceView>(R.id.export_cover_preview)) { suc -> if (!suc) { requireActivity().finish() } } }
val saveDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath val dateFormat = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()) val exportFileName = "eo-${dateFormat.format(Date())}.mp4" val exportFilePath = "$saveDir${File.separator}$exportFileName" eoExportManager.exportVideo( outputPath = exportFilePath, outputSetting = EOOutputVideoSettings(), exportListener = iEOExportListenerImpl, ) private val iEOExportListenerImpl by lazy { object : EOExportDefaultListener() { override fun onDone() { Log.d(TAG, "onCompileDone()") } override fun onError(error: Int, msg: String?) { Log.d(TAG,"onCompileError() error = $error, msg = $msg") } override fun onProgress(progress: Float) { Log.d( TAG,"onCompileProgress() progress =${progress.times(100).toInt()}" ) } } }
/** * 导出初始化配置, * @property previewCompileFinishPage 预览和导出功能,一次导出完成后就finish页面, SurfaceView不能为空 * @property autoReleaseCompileDone 导出完成后是否自动释放内存 */ data class EOExportInitConfig( var previewCompileFinishPage :Boolean = false, var autoReleaseCompileDone :Boolean = true )
@Parcelize data class EOExportModel( var draftData: String = "", var draftImg: String,//草稿页封图地址,需要业务关闭时候透传回来 var draftId: String, var isAlbumCover: Boolean = false,//标记封图来源,是相册还是视频帧(true相册,false:视频帧),需要业务关闭时候透传回来 var framePosition: Long = 0//视频帧封图位置,需要业务关闭时候透传回来 ) : Parcelable
data class EoVideoSize( @androidx.annotation.IntRange(from = 1) val width: Int = 720, @androidx.annotation.IntRange(from = 1) val height: Int = 1280 )
data class EOOutputVideoSettings( var outputFps: [EOFps](https://bytedance.larkoffice.com/docx/A3ogdjxYkoREIdx6S8ScZc3wnTb#part-AIpmdd0a7oiGG4xLMEncOBdvnOg) = EOFps.FPS_30, var outputRes: [EOResolution](https://bytedance.larkoffice.com/docx/A3ogdjxYkoREIdx6S8ScZc3wnTb#part-WIqRd5OLhoYZkCxOMaJczyGpnTb) = EoResolution.RES_720P, // 是否使用软解码,低端机或者或硬解硬编不足,根据业务方自行调整,默认开启软解 var useSoftwareDecode: Boolean = true, // 是否使用硬编码 默认开启 var useHWEncoder: Boolean = true, )
enum class EOResolution( val resValue: String, ) { RES_540P("540p"), RES_720P("720p"), RES_1080P("1080p"), RES_4K("4k"),; }
enum class EOFps(val fpsValue: String, val value: Int) { FPS_25("25 FPS", 25), FPS_30("30 FPS", 30), FPS_50("50 FPS", 50), FPS_60("60 FPS", 60), }
interface IEOExportListener { /** * {zh}导出出错 * @param error 错误码 * @param msg 错误消息 */ fun onError(error: Int, msg: String?) /** * {zh}导出成功 */ fun onDone() /** * {zh}导出出错 * @param progress 进度 0~1 */ fun onProgress(progress: Float) }