浏览代码

更新插件

hwh97 5 年之前
父节点
当前提交
6b3d333162

二进制
android/.gradle/5.4.1/executionHistory/executionHistory.bin


二进制
android/.gradle/5.4.1/executionHistory/executionHistory.lock


二进制
android/.gradle/5.4.1/fileChanges/last-build.bin


二进制
android/.gradle/5.4.1/fileHashes/fileHashes.bin


二进制
android/.gradle/5.4.1/fileHashes/fileHashes.lock


+ 0 - 0
android/.gradle/5.4.1/gc.properties


二进制
android/.gradle/buildOutputCleanup/buildOutputCleanup.lock


+ 2 - 0
android/.gradle/buildOutputCleanup/cache.properties

@@ -0,0 +1,2 @@
+#Mon Dec 09 14:58:01 CST 2019
+gradle.version=5.4.1

二进制
android/.gradle/buildOutputCleanup/outputFiles.bin


+ 0 - 0
android/.gradle/vcs-1/gc.properties


二进制
android/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Mon Dec 09 14:57:41 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

+ 172 - 0
android/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
android/gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 8 - 0
android/local.properties

@@ -0,0 +1,8 @@
+## This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Wed Oct 30 09:43:53 CST 2019
+sdk.dir=C\:\\androidsdk\\androidsdk\\android-sdk-windows

+ 0 - 1
android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt

@@ -314,5 +314,4 @@ class DubbingLibPlugin : MethodCallHandler {
         }
         }
         return null
         return null
     }
     }
-
 }
 }

+ 0 - 5
android/src/main/kotlin/cn/i2edu/dubbing_lib/bean/PaintedAudioData.kt

@@ -1,5 +0,0 @@
-package cn.i2edu.dubbing_lib.bean
-
-data class PaintedAudioData (val paintedId: String, val bgmUrl: String, val endTimeList: List<Long>, val durationList: List<Long>,
-                      val audioDecodePaths: List<String>, var bgmPath: String?=null, var decodeBgmPath: String?=null, var decodeAsyncBgmPath: String?=null,
-                      var encodeAudioWithBgmPath: String?=null, var mixVideoPath: String?=null)

+ 0 - 5
android/src/main/kotlin/cn/i2edu/dubbing_lib/bean/VideoData.kt

@@ -1,5 +0,0 @@
-package cn.i2edu.dubbing_lib.bean
-
-data class VideoData (val videoId: String, val bgmUrl: String, val endTimeList: List<Long>, val durationList: List<Long>, val videoPath: String,
-                      val audioDecodePaths: List<String>, var bgmPath: String?=null, var decodeBgmPath: String?=null, var decodeAsyncBgmPath: String?=null,
-                      var encodeAudioWithBgmPath: String?=null, var mixVideoPath: String?=null)

+ 24 - 0
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinHandler.kt

@@ -0,0 +1,24 @@
+package cn.i2edu.dubbing_lib.util
+
+import android.os.Handler
+import android.os.Message
+import cn.i2edu.dubbing_lib.callback.MixinHandlerCallback
+import java.lang.ref.WeakReference
+
+enum class HandlerMessage(var value: Int) {
+    START_MIX_IN(0X3999),
+    AUDIO_SYN_FINISHED(0X4001),
+    AUDIO_MIX_VIDIO_FINISHED(0X4002),
+    AUDIO_DECODE_FINISHED(0x4003),
+    AUDIO_ENCODE_FINISHED(0x4004),
+}
+
+class MixinHandler<T : MixinHandlerCallback>(util: T) : Handler() {
+    private val plugin: WeakReference<T> = WeakReference(util)
+
+    override fun handleMessage(msg: Message) {
+        if (plugin.get() != null) {
+            plugin.get()?.onHandleMessage(msg)
+        }
+    }
+}

+ 187 - 0
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinPaintedUtil.kt

@@ -0,0 +1,187 @@
+package cn.i2edu.dubbing_lib.util
+
+import android.content.Context
+import android.os.Message
+import android.text.TextUtils
+import cn.i2edu.dubbing_lib.audioUtils.AudioDecoder
+import cn.i2edu.dubbing_lib.audioUtils.AudioEncoder
+import cn.i2edu.dubbing_lib.audioUtils.compose.AudioComposer
+import cn.i2edu.dubbing_lib.callback.MixinHandlerCallback
+import java.io.File
+import java.util.*
+import kotlin.collections.ArrayList
+
+interface MixinPaintedCallBack {
+    fun onResult(resultPath: String)
+    fun onError(message: String)
+}
+
+class MixinPaintedUtil private constructor(private val context: Context) : MixinHandlerCallback {
+    private val mixinHandler: MixinHandler<MixinPaintedUtil> = MixinHandler(this)
+    // 合成需要的参数
+    private lateinit var audioPaths: List<String> // 未解码录音路径
+    private lateinit var bgmPath: String // 未解码背景音频路径
+    private lateinit var durationList: List<Long>
+    private lateinit var endTimeList: List<Long>
+    // result save
+    private lateinit var decodeAudioPaths: ArrayList<String> // 解码录音路径
+    private lateinit var decodeBgmPath: String // 解码背景音频路径
+    private lateinit var decodeAsyncBgmPath: String // 合成音频后的未解码路径
+    // dir
+    private lateinit var audioDecodePath: String
+    private lateinit var mixinFilePath: String
+    private lateinit var encodePath: String // 指定合成音频后的文件.mp3路径
+
+    private var mixinPaintedCallBack: MixinPaintedCallBack? = null
+    private val pausableThreadPool: PausableThreadPool = PausableThreadPool(1)
+    private val TAG: String = "MixinPaintedUtil"
+
+    companion object {
+        @Volatile
+        private var instance: MixinPaintedUtil? = null
+
+        fun getInstance(context: Context) = instance
+                ?: synchronized(this) {
+                    instance
+                            ?: MixinPaintedUtil(context)
+                                    .also { instance = it }
+                }
+    }
+
+    fun initParams(
+            audioPaths: List<String>, bgmPath: String, durationList: List<Long>, endTimeList: List<Long>, audioDecodePath: String,
+            mixinFilePath: String, encodePath: String
+    ): MixinPaintedUtil {
+        this.audioPaths = audioPaths
+        this.bgmPath = bgmPath
+        this.durationList = durationList
+        this.endTimeList = endTimeList
+        this.audioDecodePath = audioDecodePath
+        this.mixinFilePath = mixinFilePath
+        this.encodePath = encodePath
+        return instance!!
+    }
+
+    fun setComposeCallBack(callback: MixinPaintedCallBack): MixinPaintedUtil {
+        this.mixinPaintedCallBack = callback
+        return instance!!
+    }
+
+    fun startMixin() {
+        val message = Message.obtain()
+        message.what = HandlerMessage.START_MIX_IN.value
+        mixinHandler.sendMessage(message)
+    }
+
+    private fun decodeAudio() {
+        if (TextUtils.isEmpty(audioDecodePath)) return
+        decodeAudioPaths = arrayListOf()
+        pausableThreadPool.execute {
+            // 解码录音音频
+            for (path in audioPaths) {
+                val decodedPath = doDecode(UUID.randomUUID().toString(), path, audioDecodePath)
+                if (decodedPath == null) {
+                    mixinPaintedCallBack?.onError("decodeBgmAudio failed")
+                    return@execute
+                }
+                decodeAudioPaths.add(decodedPath)
+            }
+            // 解码背景音乐
+            val decodedPath = doDecode(UUID.randomUUID().toString(), bgmPath, audioDecodePath)
+            if (decodedPath == null) {
+                mixinPaintedCallBack?.onError("decodeBgmAudio failed")
+                return@execute
+            }
+            decodeBgmPath = decodedPath
+            // step3  背景音乐与录音合成
+            val message = Message.obtain()
+            message.what = HandlerMessage.AUDIO_DECODE_FINISHED.value
+            mixinHandler.sendMessage(message)
+        }
+    }
+
+    private fun syncAudios() {
+        // 合成背景音乐和录音 (从尾部开始)
+        val tempPath = arrayOf<String>(decodeBgmPath)
+        pausableThreadPool.execute {
+            AudioComposer.composeAudio(tempPath[0],
+                    decodeAudioPaths,
+                    mixinFilePath,
+                    false,
+                    endTimeList,
+                    durationList,
+                    object : AudioComposer.ComposeAudioInterface {
+                        override fun composeSuccess(resultPath: String?) {
+                            decodeAsyncBgmPath = resultPath!!
+                            val message = Message.obtain()
+                            message.what = HandlerMessage.AUDIO_SYN_FINISHED.value
+                            mixinHandler.sendMessage(message)
+                        }
+
+                        override fun composeFail() {
+                            mixinPaintedCallBack?.onError("async bgm and record failed")
+                        }
+                    })
+        }
+    }
+
+    private fun encodeAsynAudio() {
+        pausableThreadPool.execute {
+            val accEncoder = AudioEncoder
+                    .createAccEncoder(decodeAsyncBgmPath)
+            val isEncodeFinished = !TextUtils.isEmpty(accEncoder.encodeToFile(encodePath))
+            if (!isEncodeFinished) {
+                mixinPaintedCallBack?.onError("encode async audio failed")
+                return@execute
+            }
+            val msg = Message()
+            msg.what = HandlerMessage.AUDIO_MIX_VIDIO_FINISHED.value
+            mixinHandler.sendMessage(msg)
+        }
+    }
+
+    private fun audioMixPaintedFinish() {
+        mixinPaintedCallBack?.onResult(encodePath)
+    }
+
+    private fun doDecode(fileName: String, path: String, saveDirPath: String): String? {
+        try {
+            // 解码后的路径
+            val decodeFile = File(saveDirPath)
+            if (!decodeFile.exists()) {
+                decodeFile.mkdirs()
+            }
+            val finalFile = File(decodeFile.absolutePath + "/" + fileName)
+            if (!finalFile.exists()) {
+                finalFile.createNewFile()
+            }
+            val audioDec = AudioDecoder
+                    .createDefualtDecoder(path)
+            audioDec.decodeToFile(finalFile.absolutePath)
+            return finalFile.absolutePath
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return null
+    }
+
+    override fun onHandleMessage(msg: Message) {
+        when (msg.what) {
+            HandlerMessage.START_MIX_IN.value -> {
+                // step2 解码背景音乐及配音音频
+                decodeAudio()
+            }
+            HandlerMessage.AUDIO_DECODE_FINISHED.value -> {
+                // step3  背景音乐与录音合成
+                syncAudios()
+            }
+            HandlerMessage.AUDIO_SYN_FINISHED.value -> {
+                // step4 编码音频
+                encodeAsynAudio()
+            }
+            HandlerMessage.AUDIO_MIX_VIDIO_FINISHED.value -> {
+                audioMixPaintedFinish()
+            }
+        }
+    }
+}

+ 218 - 0
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinVideoUtil.kt

@@ -0,0 +1,218 @@
+package cn.i2edu.dubbing_lib.util
+
+import android.content.Context
+import android.os.Message
+import android.text.TextUtils
+import cn.i2edu.dubbing_lib.audioUtils.AudioDecoder
+import cn.i2edu.dubbing_lib.audioUtils.AudioEncoder
+import cn.i2edu.dubbing_lib.audioUtils.VideoAudioMixer
+import cn.i2edu.dubbing_lib.audioUtils.compose.AudioComposer
+import cn.i2edu.dubbing_lib.callback.MixinHandlerCallback
+import java.io.File
+
+interface MixinVideoCallBack {
+    fun onResult(resultPath: String)
+    fun onError(message: String)
+}
+
+class MixinVideoUtil private constructor(private val context: Context) : MixinHandlerCallback {
+    private val mixinHandler: MixinHandler<MixinVideoUtil> = MixinHandler(this)
+    // mixin params needs
+    private lateinit var videoId: String
+    private lateinit var bgmPath: String
+    private lateinit var videoPath: String
+    private lateinit var durationList: List<Long>
+    private lateinit var endTimeList: List<Long>
+    private lateinit var audioDecodePaths: List<String>
+    // dir
+    private lateinit var pathBgmDecodeDir: String
+    private lateinit var pathBgmRecordSyncDir: String
+    private lateinit var pathBgmRecordDecodeSyncDir: String
+    private lateinit var pathVideoMixinDir: String
+    // result save
+    private lateinit var decodeBgmPath: String
+    private lateinit var decodeAsyncBgmPath: String
+    private lateinit var encodeAudioWithBgmPath: String
+    private lateinit var mixVideoPath: String
+
+    private var mixinVideoCallBack: MixinVideoCallBack? = null
+    private val pausableThreadPool: PausableThreadPool = PausableThreadPool(1)
+    private val TAG: String = "MixinVideoUtil"
+
+    companion object {
+        @Volatile
+        private var instance: MixinVideoUtil? = null
+
+        fun getInstance(context: Context) = instance
+                ?: synchronized(this) {
+                    instance
+                            ?: MixinVideoUtil(context)
+                                    .also { instance = it }
+                }
+    }
+
+    fun initParams(
+            videoId: String, bgmPath: String, videoPath: String, durationList: List<Long>, endTimeList: List<Long>, audioDecodePaths: List<String>,
+            pathBgmDecodeDir: String, pathBgmRecordSyncDir: String, pathBgmRecordDecodeSyncDir: String, pathVideoMixinDir: String
+    ): MixinVideoUtil {
+        this.videoId = videoId
+        this.bgmPath = bgmPath
+        this.videoPath = videoPath
+        this.durationList = durationList
+        this.endTimeList = endTimeList
+        this.audioDecodePaths = audioDecodePaths
+        this.pathBgmDecodeDir = pathBgmDecodeDir
+        this.pathBgmRecordSyncDir = pathBgmRecordSyncDir
+        this.pathBgmRecordDecodeSyncDir = pathBgmRecordDecodeSyncDir
+        this.pathVideoMixinDir = pathVideoMixinDir
+        return instance!!
+    }
+
+    fun setComposeCallBack(callback: MixinVideoCallBack): MixinVideoUtil {
+        this.mixinVideoCallBack = callback
+        return instance!!
+    }
+
+    fun startMixin() {
+        val message = Message.obtain()
+        message.what = HandlerMessage.START_MIX_IN.value
+        mixinHandler.sendMessage(message)
+    }
+
+    private fun decodeBgmAudio(fileName: String, localPath: String) {
+        if (TextUtils.isEmpty(localPath)) return
+        pausableThreadPool.execute {
+            val decodedPath = doDecode(fileName, localPath, pathBgmDecodeDir)
+            if (decodedPath == null) {
+                this.mixinVideoCallBack?.onError("decodeBgmAudio failed")
+                return@execute
+            }
+            decodeBgmPath = decodedPath
+            // step3  背景音乐与录音合成
+            val message = Message.obtain()
+            message.what = HandlerMessage.AUDIO_DECODE_FINISHED.value
+            mixinHandler.sendMessage(message)
+        }
+    }
+
+    private fun syncAudios() {
+        // 合成背景音乐和录音 (从尾部开始)
+        val fileDIR = File(pathBgmRecordSyncDir)
+        if (!fileDIR.exists()) {
+            fileDIR.mkdirs()
+        }
+        val mixFilePath = File(fileDIR.absolutePath + "/" + "mixin.mp3")
+        // 合成操作
+        val tempPath = arrayOf<String>(decodeBgmPath)
+        pausableThreadPool.execute {
+            AudioComposer.composeAudio(tempPath[0],
+                    audioDecodePaths,
+                    mixFilePath.absolutePath,
+                    false,
+                    endTimeList,
+                    durationList,
+                    object : AudioComposer.ComposeAudioInterface {
+                        override fun composeSuccess(result: String?) {
+                            decodeAsyncBgmPath = result!!
+                            val message = Message.obtain()
+                            message.what = HandlerMessage.AUDIO_SYN_FINISHED.value
+                            mixinHandler.sendMessage(message)
+                        }
+
+                        override fun composeFail() {
+                            mixinVideoCallBack?.onError("composeAudio failed")
+                        }
+                    })
+        }
+    }
+
+    private fun encodeAsynAudio() {
+        pausableThreadPool.execute {
+            val accEncoder = AudioEncoder
+                    .createAccEncoder(decodeAsyncBgmPath)
+            val file = File(pathBgmRecordDecodeSyncDir)
+            if (!file.exists()) file.mkdirs()
+            val finalMixPath = pathBgmRecordDecodeSyncDir + "mixinDecode.aac"
+            val isEncodeFinished = !TextUtils.isEmpty(accEncoder.encodeToFile(finalMixPath))
+            if (!isEncodeFinished) {
+                mixinVideoCallBack?.onError("encodeToFile failed")
+                return@execute
+            }
+            encodeAudioWithBgmPath = finalMixPath
+            val message = Message.obtain()
+            message.what = HandlerMessage.AUDIO_ENCODE_FINISHED.value
+            mixinHandler.sendMessage(message)
+        }
+    }
+
+    private fun mixinAudioAndVideo() {
+        pausableThreadPool.execute {
+            val videoAudioMixer = VideoAudioMixer(context)
+            videoAudioMixer.setListener(object : VideoAudioMixer.VideoAudioMixListener {
+                override fun mixSuccess() {
+                    mixVideoPath = pathVideoMixinDir + "${videoId}_mix.mp4"
+                    val msg = Message()
+                    msg.what = HandlerMessage.AUDIO_MIX_VIDIO_FINISHED.value
+                    mixinHandler.sendMessage(msg)
+                }
+
+                override fun mixFail(reason: String?) {
+                    mixinVideoCallBack?.onError("mix video and audio failed")
+                }
+            })
+            videoAudioMixer.mux(encodeAudioWithBgmPath, videoPath,
+                    "${videoId}_mix.mp4", pathVideoMixinDir)
+        }
+    }
+
+    private fun audioMixVideoFinish() {
+        mixinVideoCallBack?.onResult(mixVideoPath)
+    }
+
+    private fun doDecode(fileName: String, path: String, saveDirPath: String): String? {
+        try {
+            // 解码后的路径
+            val decodeFile = File(saveDirPath)
+            if (!decodeFile.exists()) {
+                decodeFile.mkdirs()
+            }
+            val finalFile = File(decodeFile.absolutePath + "/" + fileName)
+            if (!finalFile.exists()) {
+                finalFile.createNewFile()
+            }
+            val audioDec = AudioDecoder
+                    .createDefualtDecoder(path)
+            audioDec.decodeToFile(finalFile.absolutePath)
+            return finalFile.absolutePath
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return null
+    }
+
+    override fun onHandleMessage(msg: Message) {
+        when (msg.what) {
+            HandlerMessage.START_MIX_IN.value -> {
+                // step2 解码背景音乐
+                val i = bgmPath.lastIndexOf('/')
+                val name = bgmPath.substring(i)
+                decodeBgmAudio(name, bgmPath)
+            }
+            HandlerMessage.AUDIO_DECODE_FINISHED.value -> {
+                // step3  背景音乐与录音合成
+                syncAudios()
+            }
+            HandlerMessage.AUDIO_SYN_FINISHED.value -> {
+                // step4 编码音频
+                encodeAsynAudio()
+            }
+            HandlerMessage.AUDIO_ENCODE_FINISHED.value -> {
+                // step5 音视频合并
+                mixinAudioAndVideo()
+            }
+            HandlerMessage.AUDIO_MIX_VIDIO_FINISHED.value -> {
+                audioMixVideoFinish()
+            }
+        }
+    }
+}