Browse Source

添加ffmpeg modeule

Pq 2 years ago
parent
commit
b63b621549
20 changed files with 807 additions and 36 deletions
  1. 10 0
      BaseLibrary/src/main/java/com/cooleshow/base/callback/ResultCallback.java
  2. 23 0
      BaseLibrary/src/main/java/com/cooleshow/base/data/api/DownloadApi.java
  3. 9 2
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.java
  4. 9 2
      BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseFragment.java
  5. 1 1
      BaseLibrary/src/main/java/com/cooleshow/base/utils/FileUtils.java
  6. 19 2
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading.kt
  7. 73 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading2.java
  8. 19 8
      BaseLibrary/src/main/res/layout/progress_dialog.xml
  9. 1 0
      ffmpegCmd/build.gradle
  10. 1 1
      ffmpegCmd/src/main/java/com/cooleshow/ffmpegcmd/util/FFmpegUtil.java
  11. 1 0
      student/build.gradle
  12. 243 0
      student/src/main/java/com/cooleshow/student/helper/AccompanyHelper.java
  13. 54 14
      student/src/main/java/com/cooleshow/student/ui/web/AccompanyActivity.java
  14. 16 1
      student/src/main/java/com/cooleshow/student/ui/web/AccompanyFragment.java
  15. 11 0
      student/src/main/java/com/cooleshow/student/widgets/helper/JsInterfaceAccomPanyUtils.java
  16. 1 0
      teacher/build.gradle
  17. 238 0
      teacher/src/main/java/com/cooleshow/teacher/helper/AccompanyHelper.java
  18. 47 5
      teacher/src/main/java/com/cooleshow/teacher/ui/web/AccompanyActivity.java
  19. 13 0
      teacher/src/main/java/com/cooleshow/teacher/ui/web/AccompanyFragment.java
  20. 18 0
      teacher/src/main/java/com/cooleshow/teacher/widgets/helper/JsInterfaceAccomPanyUtils.java

+ 10 - 0
BaseLibrary/src/main/java/com/cooleshow/base/callback/ResultCallback.java

@@ -0,0 +1,10 @@
+package com.cooleshow.base.callback;
+
+/**
+ * Author by pq, Date on 2022/12/19.
+ */
+public interface ResultCallback<Result> {
+    void onSuccess(Result result);
+
+    void onFail(int errorCode, String errorStr);
+}

+ 23 - 0
BaseLibrary/src/main/java/com/cooleshow/base/data/api/DownloadApi.java

@@ -0,0 +1,23 @@
+package com.cooleshow.base.data.api;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.Streaming;
+import retrofit2.http.Url;
+
+/**
+ * Author by pq, Date on 2022/12/19.
+ */
+public interface DownloadApi {
+    /**
+     * 文件下载
+     * @param url
+     * @return
+     */
+    @Headers({"Content-Type:application/octet-stream"})
+    @Streaming  //文件特别大的时候可以防止内存溢出
+    @GET
+    Call<ResponseBody> download(@Url String url);
+}

+ 9 - 2
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.java

@@ -17,6 +17,7 @@ import com.cooleshow.base.presenter.view.BaseView;
 import com.cooleshow.base.utils.ErrorParse;
 import com.cooleshow.base.utils.helper.QMUIStatusBarHelper;
 import com.cooleshow.base.widgets.ProgressLoading;
+import com.cooleshow.base.widgets.ProgressLoading2;
 import com.trello.rxlifecycle4.components.support.RxAppCompatActivity;
 
 /**
@@ -24,7 +25,7 @@ import com.trello.rxlifecycle4.components.support.RxAppCompatActivity;
  */
 public abstract class BaseActivity<V extends ViewBinding> extends RxAppCompatActivity implements BaseView {
     protected V viewBinding = null;
-    protected ProgressLoading mLoading;
+    protected ProgressLoading2 mLoading;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -38,7 +39,7 @@ public abstract class BaseActivity<V extends ViewBinding> extends RxAppCompatAct
     }
 
     protected void initData() {
-        mLoading = ProgressLoading.Companion.create(this);
+        mLoading = new ProgressLoading2(this);
     }
 
     public void initMidTitleToolBar(Toolbar toolbar, String title) {
@@ -74,6 +75,12 @@ public abstract class BaseActivity<V extends ViewBinding> extends RxAppCompatAct
         }
     }
 
+    public void showLoading(String text){
+        if (mLoading != null && !mLoading.isShowing()) {
+            mLoading.showLoading(text);
+        }
+    }
+
     @Override
     public void hideLoading() {
         if (mLoading != null) {

+ 9 - 2
BaseLibrary/src/main/java/com/cooleshow/base/ui/fragment/BaseFragment.java

@@ -13,6 +13,7 @@ import com.cooleshow.base.R;
 import com.cooleshow.base.presenter.view.BaseView;
 import com.cooleshow.base.utils.ErrorParse;
 import com.cooleshow.base.widgets.ProgressLoading;
+import com.cooleshow.base.widgets.ProgressLoading2;
 
 import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
@@ -24,7 +25,7 @@ public abstract class BaseFragment<V extends ViewBinding> extends Fragment imple
     private FrameLayout mViewStubContent;
     private ViewStub mViewStubNoData;
     private ViewStub mViewStubError;
-    private ProgressLoading mLoading;
+    private ProgressLoading2 mLoading;
     protected V mViewBinding;
 
     @Override
@@ -60,7 +61,7 @@ public abstract class BaseFragment<V extends ViewBinding> extends Fragment imple
         mViewStubError = view.findViewById(R.id.view_stub_error);
         mViewBinding = getLayoutView();
         mViewStubContent.addView(mViewBinding.getRoot());
-        mLoading = ProgressLoading.Companion.create(requireContext());
+        mLoading = new ProgressLoading2(requireContext());
     }
 
     protected abstract V getLayoutView();
@@ -91,6 +92,12 @@ public abstract class BaseFragment<V extends ViewBinding> extends Fragment imple
         }
     }
 
+    public void showLoading(String text){
+        if (mLoading != null && !mLoading.isShowing()) {
+            mLoading.showLoading(text);
+        }
+    }
+
     public void hideLoading() {
         if (mLoading != null) {
             mLoading.hideLoading();

+ 1 - 1
BaseLibrary/src/main/java/com/cooleshow/base/utils/FileUtils.java

@@ -1842,7 +1842,7 @@ public final class FileUtils {
             OutputStream os = localContentResolver.openOutputStream(localUri);
             Files.copy(tempFile.toPath(), os);
             os.close();
-            tempFile.delete();
+//            tempFile.delete();
         }
     }
 

+ 19 - 2
BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading.kt

@@ -3,9 +3,14 @@ package com.cooleshow.base.widgets
 import android.app.Dialog
 import android.content.Context
 import android.graphics.drawable.AnimationDrawable
+import android.os.Bundle
 import android.view.Gravity
+import android.view.View
 import android.widget.ImageView
+import android.widget.TextView
 import com.cooleshow.base.R
+import com.cooleshow.base.ext.setVisible
+import com.cooleshow.base.utils.LogUtils
 
 /*
     加载对话框封装
@@ -14,6 +19,7 @@ class ProgressLoading private constructor(context: Context, theme: Int) : Dialog
 
     companion object {
         private lateinit var mDialog: ProgressLoading
+        private lateinit var tvLoadingText: TextView
         private var animDrawable: AnimationDrawable? = null
 
         /*
@@ -35,6 +41,7 @@ class ProgressLoading private constructor(context: Context, theme: Int) : Dialog
 
             //获取动画视图
             val loadingView = mDialog.findViewById<ImageView>(R.id.iv_loading)
+            tvLoadingText = mDialog.findViewById<TextView>(R.id.tv_loading_text);
             animDrawable = loadingView.background as AnimationDrawable
 
             return mDialog
@@ -49,15 +56,25 @@ class ProgressLoading private constructor(context: Context, theme: Int) : Dialog
         animDrawable?.start()
     }
 
+    fun showLoading(text:String){
+        super.show()
+        LogUtils.i("pq","showLoading animDrawable:"+animDrawable);
+        animDrawable?.start()
+        tvLoadingText.visibility=View.VISIBLE
+        tvLoadingText.text = text
+        LogUtils.i("pq","showLoading text:"+text);
+    }
+
     /*
         隐藏加载对话框,动画停止
      */
-    fun hideLoading(){
+    fun hideLoading() {
         super.dismiss()
         animDrawable?.stop()
+        tvLoadingText.setVisible(false)
     }
 
-    fun stopAnim(){
+    fun stopAnim() {
         animDrawable?.stop()
     }
 }

+ 73 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading2.java

@@ -0,0 +1,73 @@
+package com.cooleshow.base.widgets;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.cooleshow.base.R;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Author by pq, Date on 2022/12/19.
+ */
+public class ProgressLoading2 extends Dialog {
+
+    private ImageView mImageView;
+    private TextView mTvLoadingText;
+    private AnimationDrawable animDrawable;
+
+    public ProgressLoading2(@NonNull Context context) {
+        super(context, R.style.LightProgressDialog);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.progress_dialog);
+        setCancelable(true);
+        setCanceledOnTouchOutside(false);
+        WindowManager.LayoutParams attributes = getWindow().getAttributes();
+        attributes.gravity = Gravity.CENTER;
+        attributes.dimAmount = 0.2f;
+        getWindow().setAttributes(attributes);
+        mImageView = findViewById(R.id.iv_loading);
+        mTvLoadingText = findViewById(R.id.tv_loading_text);
+        animDrawable = (AnimationDrawable) mImageView.getBackground();
+    }
+
+    public void showLoading() {
+        super.show();
+        if (animDrawable != null) {
+            animDrawable.start();
+        }
+    }
+
+    public void showLoading(String text) {
+        super.show();
+        if (animDrawable != null) {
+            animDrawable.start();
+        }
+        if (mTvLoadingText != null) {
+            mTvLoadingText.setText(text);
+            mTvLoadingText.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public void hideLoading() {
+        super.dismiss();
+        stopAnim();
+    }
+
+    public void stopAnim() {
+        if (animDrawable != null) {
+            animDrawable.stop();
+        }
+    }
+}

+ 19 - 8
BaseLibrary/src/main/res/layout/progress_dialog.xml

@@ -1,13 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              style="@style/WrapWrap.Vertical"
-              android:background="@drawable/progress_dialog_bg"
-              android:gravity="center_horizontal"
-              android:padding="40dp">
+    style="@style/WrapWrap.Vertical"
+    android:padding="40dp"
+    android:background="@drawable/progress_dialog_bg"
+    android:gravity="center_horizontal">
+
     <ImageView
-            android:id="@+id/iv_loading"
-            style="@style/WrapWrap"
-            android:layout_gravity="center"
-            android:background="@drawable/progress_dialog_anim"/>
+        android:id="@+id/iv_loading"
+        style="@style/WrapWrap"
+        android:layout_gravity="center"
+        android:background="@drawable/progress_dialog_anim" />
 
+    <TextView
+        android:layout_marginTop="5dp"
+        android:layout_gravity="center_horizontal|bottom"
+        android:id="@+id/tv_loading_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="加载中..."
+        android:textColor="@color/white"
+        android:textSize="@dimen/sp_13"
+        android:visibility="gone" />
 </LinearLayout>

+ 1 - 0
ffmpegCmd/build.gradle

@@ -17,6 +17,7 @@ android {
         externalNativeBuild {
             cmake {
                 cppFlags ""
+                abiFilters 'armeabi-v7a', 'arm64-v8a'
             }
         }
         ndk {

+ 1 - 1
ffmpegCmd/src/main/java/com/cooleshow/ffmpegcmd/util/FFmpegUtil.java

@@ -129,7 +129,7 @@ public class FFmpegUtil {
         //weight: adjust weight(volume) of each audio stream
         //disableThumb:(-vn)if not disable, it may cause incorrect mixing
         int len = 8;
-        String amix = "amix=inputs=2:duration=longest";
+        String amix = "amix=inputs=2:duration=shortest";
         if (disableThumb) len += 1;
         String[] mixAudioCmd = new String[len];
         mixAudioCmd[0] = "ffmpeg";

+ 1 - 0
student/build.gradle

@@ -118,6 +118,7 @@ dependencies {
     implementation project(path: ':live_teaching')
     implementation project(path: ':metronome')
     implementation project(path: ':musictuner')
+    implementation project(path: ':ffmpegCmd')
     implementation "com.alibaba:arouter-api:$rootProject.ext.android.arouter_api_version"
     kapt "com.alibaba:arouter-compiler:$rootProject.ext.android.arouter_api_version"
 

+ 243 - 0
student/src/main/java/com/cooleshow/student/helper/AccompanyHelper.java

@@ -0,0 +1,243 @@
+package com.cooleshow.student.helper;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.cooleshow.base.callback.ResultCallback;
+import com.cooleshow.base.data.api.AppVersionApi;
+import com.cooleshow.base.data.api.DownloadApi;
+import com.cooleshow.base.data.net.RetrofitClientNoToken;
+import com.cooleshow.base.data.net.RetrofitFactory;
+import com.cooleshow.base.utils.FileUtils;
+import com.cooleshow.base.utils.Utils;
+import com.cooleshow.ffmpegcmd.FFmpegCmd;
+import com.cooleshow.ffmpegcmd.util.FFmpegUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableEmitter;
+import io.reactivex.rxjava3.core.ObservableOnSubscribe;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+/**
+ * Author by pq, Date on 2022/12/19.
+ */
+public class AccompanyHelper {
+    private static AccompanyHelper instance;
+    public static final String TAG = "AccompanyHelper";
+    public static final String BASE_PATH = FileUtils.getCacheDir(Utils.getApp(), "accompany");
+    public static final String accompanimentMp3Path = BASE_PATH + File.separator + "accompaniment.mp3";
+    public static final String videoMp3Path = BASE_PATH + File.separator + "videoBgm.mp3";
+    public static final String mergeMp3Path = BASE_PATH + File.separator + "merge.mp3";
+    public static final String onlyVideoPath = BASE_PATH + File.separator + "onlyVideoPath.mp4";
+    public static final String resultPathPath = BASE_PATH + File.separator + "result.mp4";
+
+    private AccompanyHelper() {
+
+    }
+
+    public static AccompanyHelper getInstance() {
+        if (instance == null) {
+            synchronized (AccompanyHelper.class) {
+                if (instance == null) {
+                    instance = new AccompanyHelper();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public void download(String url, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+            @Override
+            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                FileUtils.delete(accompanimentMp3Path);
+                FileUtils.createOrExistsFile(accompanimentMp3Path);
+                File file = new File(accompanimentMp3Path);
+                downloadAccompany(url, file);
+                if (emitter != null) {
+                    emitter.onNext(file.getAbsolutePath());
+                }
+            }
+        }).subscribeOn(Schedulers.newThread())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<String>() {
+                    @Override
+                    public void onSubscribe(@NonNull Disposable d) {
+
+                    }
+
+                    @Override
+                    public void onNext(@NonNull String s) {
+                        if (resultCallback != null) {
+                            resultCallback.onSuccess(s);
+                        }
+                    }
+
+                    @Override
+                    public void onError(@NonNull Throwable e) {
+                        e.printStackTrace();
+                        if (resultCallback != null) {
+                            resultCallback.onFail(-1, "download error");
+                        }
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    private void downloadAccompany(String url, File file) {
+        if (TextUtils.isEmpty(url)) {
+            return;
+        }
+        try {
+            Response<ResponseBody> response = RetrofitClientNoToken.getInstance().getRetrofit().create(DownloadApi.class)
+                    .download(url).execute();
+            if (response == null || response.body() == null) {
+                return;
+            }
+            long total = response.body().contentLength();//需要下载的总大小
+            long current = 0;
+            InputStream inputStream = response.body().byteStream();
+            FileOutputStream fileOutputStream = new FileOutputStream(file);
+            byte[] bytes = new byte[1024];
+            int len = 0;
+            while ((len = inputStream.read(bytes)) != -1) {
+                fileOutputStream.write(bytes, 0, len);
+                fileOutputStream.flush();
+                current = current + len;
+                Log.e(TAG, "已经下载=" + current + " 需要下载=" + total);
+            }
+            fileOutputStream.flush();
+            fileOutputStream.close();
+            inputStream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void startMix(String videoPath, String bgmPath, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+            @Override
+            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                deleteAllTempFile();
+                Log.i(TAG, "转换开始");
+                int getVideoMp3Result = getVideoMp3_2(videoPath, videoMp3Path);
+                Log.i(TAG, "getVideoMp3 complete:" + getVideoMp3Result);
+                //mix原音和视频bgm mp3
+                Log.i(TAG, "mixMp3 start");
+                int mixMp3Result = mixMp3(bgmPath, videoMp3Path, mergeMp3Path);
+                Log.i(TAG, "mixMp3 complete:" + mixMp3Result);
+                //获取纯视频
+                int getOnlyVideoResult = getOnlyVideo(videoPath, onlyVideoPath);
+                Log.i(TAG, "extractVideo complete:" + getOnlyVideoResult);
+                //merge音频和视频
+                int mergeResult = megreMp3AndMp4(onlyVideoPath, mergeMp3Path, resultPathPath);
+                Log.i(TAG, "mergeMp4 complete:" + mergeResult);
+                Log.i(TAG, "转换完成");
+                if (emitter != null) {
+                    boolean isSuccess = getVideoMp3Result == 0 && mixMp3Result == 0 && getOnlyVideoResult == 0 && mergeResult == 0;
+                    Log.i(TAG, "转换是否全部isSuccess:" + isSuccess);
+                    if (isSuccess) {
+                        emitter.onNext(resultPathPath);
+                    } else {
+                        emitter.onNext(videoPath);
+                    }
+                }
+            }
+        }).subscribeOn(Schedulers.newThread())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<String>() {
+                    @Override
+                    public void onSubscribe(@NonNull Disposable d) {
+
+                    }
+
+                    @Override
+                    public void onNext(@NonNull String s) {
+                        if (resultCallback != null) {
+                            resultCallback.onSuccess(s);
+                        }
+                    }
+
+                    @Override
+                    public void onError(@NonNull Throwable e) {
+                        e.printStackTrace();
+                        if (resultCallback != null) {
+                            resultCallback.onFail(-1, "mix error");
+                        }
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    private void deleteAllTempFile() {
+        FileUtils.delete(videoMp3Path);
+        FileUtils.delete(mergeMp3Path);
+        FileUtils.delete(onlyVideoPath);
+        FileUtils.delete(resultPathPath);
+    }
+
+    public int getVideoMp3_2(String videoPath, String outPath) {
+        //ffmpeg -i video.mp4 -vn audio.mp3
+        Log.i(TAG, "getVideoMp3 start videoPath:" + videoPath + "\noutPath:" + outPath);
+        String[] commands = new String[5];
+        commands[0] = "ffmpeg";
+        commands[1] = "-i";
+        commands[2] = videoPath;
+        commands[3] = "-vn";
+        commands[4] = outPath;
+        if (commands != null) {
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    private int mixMp3(String audioPath, String bgmPath, String outputPath) {
+        String[] commands = FFmpegUtil.mixAudio(audioPath, bgmPath, outputPath);
+        if (commands != null) {
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    private int getOnlyVideo(String videoPath, String out) {
+        Log.i(TAG, "extractVideo start");
+        String[] commands1 = FFmpegUtil.extractVideo(videoPath, out);
+        if (commands1 != null) {
+            return FFmpegCmd.executeSync(commands1);
+        }
+        return -1;
+    }
+
+
+    private int megreMp3AndMp4(String videoPath, String mp3Path, String outPath) {
+        Log.i(TAG, "mergeMp4 start");
+        String[] commands = FFmpegUtil.mediaMux(videoPath, mp3Path, true, outPath);
+        if (commands != null) {
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+}

+ 54 - 14
student/src/main/java/com/cooleshow/student/ui/web/AccompanyActivity.java

@@ -4,9 +4,7 @@ import static com.cooleshow.base.common.WebConstants.WEB_URL;
 
 import android.app.Activity;
 import android.content.Intent;
-import android.net.Uri;
 import android.os.Handler;
-import android.provider.MediaStore;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -24,17 +22,18 @@ import io.reactivex.rxjava3.functions.Consumer;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 import com.alibaba.android.arouter.facade.annotation.Route;
+import com.cooleshow.base.callback.ResultCallback;
 import com.cooleshow.base.router.RouterPath;
 import com.cooleshow.base.service.PlayMusicService;
 import com.cooleshow.base.ui.activity.BaseMVPActivity;
 import com.cooleshow.base.utils.FileUtils;
-import com.cooleshow.base.utils.LogUtils;
 import com.cooleshow.base.utils.MyFileUtils;
 import com.cooleshow.base.utils.ToastUtil;
 import com.cooleshow.base.utils.helper.WebParamsHelper;
 import com.cooleshow.base.utils.helper.upload.UploadHelper;
 import com.cooleshow.student.R;
 import com.cooleshow.student.databinding.ActivityAccompanyBinding;
+import com.cooleshow.student.helper.AccompanyHelper;
 import com.cooleshow.student.presenter.web.AccompanyPresenter;
 import com.cooleshow.usercenter.helper.UserHelper;
 import com.gyf.immersionbar.ImmersionBar;
@@ -64,6 +63,7 @@ import java.util.Locale;
  */
 @Route(path = RouterPath.WebCenter.ACTIVITY_ACCOMPANY_HTML)
 public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding, AccompanyPresenter> {
+    public static final String TAG = "AccompanyActivity";
     FrameLayout camera;
     FrameLayout fl_webview;
 
@@ -77,6 +77,7 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
     private String videoDerectoryName = "/cooleshowTaskVideo";
     private View cameraGroupView;
     private String recordVideFilePath; //评测视频
+    private String accompanimentUrl; //当前曲子伴奏mp3地址
     private AccompanyFragment accompanyFragment;
     private JSONObject baseJsonObject;
     private Intent intentOne;
@@ -164,12 +165,14 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
                             try {
                                 File file = cameraKitVideo.getVideoFile();
                                 filePath = file.getPath();
-                                saveVideoToGallery(filePath);
-                                hideLoading();
-                                ToastUtil.getInstance().showShort("保存成功");
+                                if (!TextUtils.isEmpty(accompanimentUrl)) {
+                                    mixMp3AndMp4(accompanimentUrl, filePath);
+                                } else {
+                                    handleFileResult(filePath);
+                                }
                             } catch (Exception e) {
+                                e.printStackTrace();
                             }
-
                         }
                     });
                 }
@@ -206,13 +209,12 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
             public void videoUpdate(JSONObject jsonObject) {
                 baseJsonObject = jsonObject;
                 File file = new File(recordVideFilePath);
-
                 if (!file.exists()) {
                     ToastUtil.getInstance().showShort("未找到该视频,请重试");
                     MyFileUtils.deleteFile(recordVideFilePath);
                 }
                 String bucket = WebParamsHelper.getParams(jsonObject, "bucket");
-                UploadHelper uploadHelper = new UploadHelper(AccompanyActivity.this,bucket);
+                UploadHelper uploadHelper = new UploadHelper(AccompanyActivity.this, bucket);
                 uploadHelper.uploadFile(file);
                 uploadHelper.setUpLoadCallBack(new UploadHelper.UpLoadCallBack() {
                     @Override
@@ -239,17 +241,22 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
 
             }
 
+            @Override
+            public void onDownloadAccompaniment(String accompanyUrl) {
+                AccompanyActivity.this.accompanimentUrl = accompanyUrl;
+            }
+
             /**
              * 停止录视频
              */
             @Override
             public void endCapture() {
+                showLoading("正在处理视频中");
                 if (cameraView != null && cameraView.getVisibility() == View.VISIBLE) {
                     new Handler().postDelayed(new Runnable() {
                         @Override
                         public void run() {
                             cameraView.stopVideo();//结束录像
-
                         }
                     }, 1000);
                 }
@@ -265,12 +272,45 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
         startService(intentOne);
     }
 
-    private void saveVideoToGallery(String filePath){
+    private void mixMp3AndMp4(String accompanimentUrl, String videoPath) {
+        AccompanyHelper.getInstance().download(accompanimentUrl, new ResultCallback<String>() {
+            @Override
+            public void onSuccess(String s) {
+                AccompanyHelper.getInstance().startMix(videoPath, s, new ResultCallback<String>() {
+                    @Override
+                    public void onSuccess(String s) {
+                        Log.i(TAG, "混音success:" + s);
+                        recordVideFilePath = s;
+                        handleFileResult(s);
+
+                    }
+
+                    @Override
+                    public void onFail(int errorCode, String errorStr) {
+                        handleFileResult(filePath);
+                    }
+                });
+            }
+
+            @Override
+            public void onFail(int errorCode, String errorStr) {
+                handleFileResult(filePath);
+            }
+        });
+    }
+
+    private void handleFileResult(String filePath) {
+        hideLoading();
+        saveVideoToGallery(filePath);
+        ToastUtil.getInstance().showShort("保存成功");
+    }
+
+    private void saveVideoToGallery(String filePath) {
         Observable.create(new ObservableOnSubscribe<Object>() {
             @Override
             public void subscribe(@NonNull ObservableEmitter<Object> emitter) throws Throwable {
-                Log.i("pq","saveVideoToGallery:"+Thread.currentThread().getName());
-                FileUtils.saveVideoToGallery(AccompanyActivity.this.getApplicationContext(),filePath);
+                Log.i("pq", "saveVideoToGallery:" + Thread.currentThread().getName());
+                FileUtils.saveVideoToGallery(AccompanyActivity.this.getApplicationContext(), filePath);
             }
         }).subscribeOn(Schedulers.newThread())
                 .observeOn(AndroidSchedulers.mainThread())
@@ -389,7 +429,7 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
             }
         } else if (requestCode == 1011) {
             String selectAddress = data.getStringExtra("selectAddress");
-            if (null!=accompanyFragment){
+            if (null != accompanyFragment) {
                 accompanyFragment.selectAddress(selectAddress);
             }
         }

+ 16 - 1
student/src/main/java/com/cooleshow/student/ui/web/AccompanyFragment.java

@@ -37,6 +37,7 @@ import android.widget.TextView;
 import com.alibaba.android.arouter.launcher.ARouter;
 import com.alipay.sdk.app.PayTask;
 import com.cooleshow.base.bean.WxPayResult;
+import com.cooleshow.base.callback.ResultCallback;
 import com.cooleshow.base.common.BaseApplication;
 import com.cooleshow.base.common.WebConstants;
 import com.cooleshow.base.constanst.Constants;
@@ -72,6 +73,7 @@ import com.cooleshow.student.bean.alipay.AuthResult;
 import com.cooleshow.student.bean.alipay.PayResult;
 import com.cooleshow.student.bean.weixinpay.WeixinPayInfo;
 import com.cooleshow.student.databinding.FragmentAccompanyBinding;
+import com.cooleshow.student.helper.AccompanyHelper;
 import com.cooleshow.student.helper.ShareHelper;
 import com.cooleshow.student.presenter.web.AccompanyPresenter;
 import com.cooleshow.student.widgets.LollipopFixedWebView;
@@ -437,7 +439,7 @@ public class AccompanyFragment extends BaseMVPFragment<FragmentAccompanyBinding,
             }
             onSendMessage(jsonObject.toString());
         } catch (Exception e) {
-            sendProgressMessage(MESSAGE_TYPE_HIDE_LOADING,0);
+            sendProgressMessage(MESSAGE_TYPE_HIDE_LOADING, 0);
             e.printStackTrace();
         }
     }
@@ -1439,6 +1441,12 @@ public class AccompanyFragment extends BaseMVPFragment<FragmentAccompanyBinding,
 
         void videoUpdate(JSONObject jsonObject);
 
+        /**
+         * mp3伴奏下载
+         *
+         * @param url
+         */
+        void onDownloadAccompaniment(String url);
     }
 
     public WebViewListener listener;
@@ -2036,6 +2044,13 @@ public class AccompanyFragment extends BaseMVPFragment<FragmentAccompanyBinding,
                 });
     }
 
+    @Override
+    public void saveAccompanimentMp3(String accompanyUrl) {
+        if (onAccompanyListener != null) {
+            onAccompanyListener.onDownloadAccompaniment(accompanyUrl);
+        }
+    }
+
     private void handleCloudFollow(String mode) {
         if (mMusicTunerHelper == null) {
             mMusicTunerHelper = new MusicTunerHelper(new MusicTunerHelper.OnEventListener() {

+ 11 - 0
student/src/main/java/com/cooleshow/student/widgets/helper/JsInterfaceAccomPanyUtils.java

@@ -321,6 +321,10 @@ public class JsInterfaceAccomPanyUtils extends Object {
                 if (TextUtils.equals("cloudAccompanyMessage", api)) {
                     JSONObject content = jsonObject.getJSONObject("content");
                     LogUtils.i("pq", "cloudAccompanyMessage:" + content.toString());
+                    if (onListener != null) {
+                        String accompanyUrl = content.optString("accompanyUrl");
+                        onListener.saveAccompanimentMp3(accompanyUrl);
+                    }
                     return;
                 }
 
@@ -639,6 +643,13 @@ public class JsInterfaceAccomPanyUtils extends Object {
          * 跟练模式
          */
         void cloudToggleFollow(String mode);
+
+        /**
+         * 下载伴奏
+         *
+         * @param url
+         */
+        void saveAccompanimentMp3(String url);
     }
 
 }

+ 1 - 0
teacher/build.gradle

@@ -125,6 +125,7 @@ dependencies {
     implementation project(path: ':live_teaching')
     implementation project(path: ':metronome')
     implementation project(path: ':musictuner')
+    implementation project(path: ':ffmpegCmd')
     implementation "com.alibaba:arouter-api:$rootProject.ext.android.arouter_api_version"
     kapt "com.alibaba:arouter-compiler:$rootProject.ext.android.arouter_api_version"
     //融云美颜库 //cn.rongcloud.sdk:rtc_lib:5.2.1

+ 238 - 0
teacher/src/main/java/com/cooleshow/teacher/helper/AccompanyHelper.java

@@ -0,0 +1,238 @@
+package com.cooleshow.teacher.helper;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.cooleshow.base.callback.ResultCallback;
+import com.cooleshow.base.data.api.DownloadApi;
+import com.cooleshow.base.data.net.RetrofitClientNoToken;
+import com.cooleshow.base.utils.FileUtils;
+import com.cooleshow.base.utils.Utils;
+import com.cooleshow.ffmpegcmd.FFmpegCmd;
+import com.cooleshow.ffmpegcmd.util.FFmpegUtil;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableEmitter;
+import io.reactivex.rxjava3.core.ObservableOnSubscribe;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import okhttp3.ResponseBody;
+import retrofit2.Response;
+
+/**
+ * Author by pq, Date on 2022/12/19.
+ */
+public class AccompanyHelper {
+    private static AccompanyHelper instance;
+    public static final String TAG = "AccompanyHelper";
+    public static final String BASE_PATH = FileUtils.getCacheDir(Utils.getApp(), "accompany");
+    public static final String accompanimentMp3Path = BASE_PATH + File.separator + "accompaniment.mp3";
+    public static final String videoMp3Path = BASE_PATH + File.separator + "videoBgm.mp3";
+    public static final String mergeMp3Path = BASE_PATH + File.separator + "merge.mp3";
+    public static final String onlyVideoPath = BASE_PATH + File.separator + "onlyVideoPath.mp4";
+    public static final String resultPathPath = BASE_PATH + File.separator + "result.mp4";
+
+    private AccompanyHelper() {
+
+    }
+
+    public static AccompanyHelper getInstance() {
+        if (instance == null) {
+            synchronized (AccompanyHelper.class) {
+                if (instance == null) {
+                    instance = new AccompanyHelper();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public void download(String url, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+            @Override
+            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                FileUtils.delete(accompanimentMp3Path);
+                FileUtils.createOrExistsFile(accompanimentMp3Path);
+                File file = new File(accompanimentMp3Path);
+                downloadAccompany(url, file);
+                if (emitter != null) {
+                    emitter.onNext(file.getAbsolutePath());
+                }
+            }
+        }).subscribeOn(Schedulers.newThread())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<String>() {
+                    @Override
+                    public void onSubscribe(@NonNull Disposable d) {
+
+                    }
+
+                    @Override
+                    public void onNext(@NonNull String s) {
+                        if (resultCallback != null) {
+                            resultCallback.onSuccess(s);
+                        }
+                    }
+
+                    @Override
+                    public void onError(@NonNull Throwable e) {
+                        e.printStackTrace();
+                        if (resultCallback != null) {
+                            resultCallback.onFail(-1, "download error");
+                        }
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    private void downloadAccompany(String url, File file) {
+        if (TextUtils.isEmpty(url)) {
+            return;
+        }
+        try {
+            Response<ResponseBody> response = RetrofitClientNoToken.getInstance().getRetrofit().create(DownloadApi.class)
+                    .download(url).execute();
+            if (response == null || response.body() == null) {
+                return;
+            }
+            long total = response.body().contentLength();//需要下载的总大小
+            long current = 0;
+            InputStream inputStream = response.body().byteStream();
+            FileOutputStream fileOutputStream = new FileOutputStream(file);
+            byte[] bytes = new byte[1024];
+            int len = 0;
+            while ((len = inputStream.read(bytes)) != -1) {
+                fileOutputStream.write(bytes, 0, len);
+                fileOutputStream.flush();
+                current = current + len;
+                Log.e(TAG, "已经下载=" + current + " 需要下载=" + total);
+            }
+            fileOutputStream.flush();
+            fileOutputStream.close();
+            inputStream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void startMix(String videoPath, String bgmPath, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+            @Override
+            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                deleteAllTempFile();
+                Log.i(TAG, "转换开始");
+                int getVideoMp3Result = getVideoMp3_2(videoPath, videoMp3Path);
+                Log.i(TAG, "getVideoMp3 complete:" + getVideoMp3Result);
+                //mix原音和视频bgm mp3
+                Log.i(TAG, "mixMp3 start");
+                int mixMp3Result = mixMp3(bgmPath, videoMp3Path, mergeMp3Path);
+                Log.i(TAG, "mixMp3 complete:" + mixMp3Result);
+                //获取纯视频
+                int getOnlyVideoResult = getOnlyVideo(videoPath, onlyVideoPath);
+                Log.i(TAG, "extractVideo complete:" + getOnlyVideoResult);
+                //merge音频和视频
+                int mergeResult = megreMp3AndMp4(onlyVideoPath, mergeMp3Path, resultPathPath);
+                Log.i(TAG, "mergeMp4 complete:" + mergeResult);
+                Log.i(TAG, "转换完成");
+                if (emitter != null) {
+                    boolean isSuccess = getVideoMp3Result == 0 && mixMp3Result == 0 && getOnlyVideoResult == 0 && mergeResult == 0;
+                    Log.i(TAG, "转换是否全部isSuccess:" + isSuccess);
+                    if (isSuccess) {
+                        emitter.onNext(resultPathPath);
+                    } else {
+                        emitter.onNext(videoPath);
+                    }
+                }
+            }
+        }).subscribeOn(Schedulers.newThread())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<String>() {
+                    @Override
+                    public void onSubscribe(@NonNull Disposable d) {
+
+                    }
+
+                    @Override
+                    public void onNext(@NonNull String s) {
+                        if (resultCallback != null) {
+                            resultCallback.onSuccess(s);
+                        }
+                    }
+
+                    @Override
+                    public void onError(@NonNull Throwable e) {
+                        e.printStackTrace();
+                        if (resultCallback != null) {
+                            resultCallback.onFail(-1, "mix error");
+                        }
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    private void deleteAllTempFile() {
+        FileUtils.delete(videoMp3Path);
+        FileUtils.delete(mergeMp3Path);
+        FileUtils.delete(onlyVideoPath);
+        FileUtils.delete(resultPathPath);
+    }
+
+    public int getVideoMp3_2(String videoPath, String outPath) {
+        //ffmpeg -i video.mp4 -vn audio.mp3
+        Log.i(TAG, "getVideoMp3 start videoPath:" + videoPath + "\noutPath:" + outPath);
+        String[] commands = new String[5];
+        commands[0] = "ffmpeg";
+        commands[1] = "-i";
+        commands[2] = videoPath;
+        commands[3] = "-vn";
+        commands[4] = outPath;
+        if (commands != null) {
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    private int mixMp3(String audioPath, String bgmPath, String outputPath) {
+        String[] commands = FFmpegUtil.mixAudio(audioPath, bgmPath, outputPath);
+        if (commands != null) {
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    private int getOnlyVideo(String videoPath, String out) {
+        Log.i(TAG, "extractVideo start");
+        String[] commands1 = FFmpegUtil.extractVideo(videoPath, out);
+        if (commands1 != null) {
+            return FFmpegCmd.executeSync(commands1);
+        }
+        return -1;
+    }
+
+
+    private int megreMp3AndMp4(String videoPath, String mp3Path, String outPath) {
+        Log.i(TAG, "mergeMp4 start");
+        String[] commands = FFmpegUtil.mediaMux(videoPath, mp3Path, true, outPath);
+        if (commands != null) {
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+}

+ 47 - 5
teacher/src/main/java/com/cooleshow/teacher/ui/web/AccompanyActivity.java

@@ -21,6 +21,7 @@ import io.reactivex.rxjava3.functions.Consumer;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 import com.alibaba.android.arouter.facade.annotation.Route;
+import com.cooleshow.base.callback.ResultCallback;
 import com.cooleshow.base.common.WebConstants;
 import com.cooleshow.base.router.RouterPath;
 import com.cooleshow.base.service.PlayMusicService;
@@ -31,6 +32,7 @@ import com.cooleshow.base.utils.helper.WebParamsHelper;
 import com.cooleshow.base.utils.helper.upload.UploadHelper;
 import com.cooleshow.teacher.R;
 import com.cooleshow.teacher.databinding.ActivityAccompanyBinding;
+import com.cooleshow.teacher.helper.AccompanyHelper;
 import com.cooleshow.teacher.presenter.web.AccompanyPresenter;
 import com.cooleshow.base.utils.MyFileUtils;
 import com.cooleshow.usercenter.helper.UserHelper;
@@ -61,6 +63,7 @@ import java.util.Locale;
  */
 @Route(path = RouterPath.WebCenter.ACTIVITY_ACCOMPANY_HTML)
 public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding, AccompanyPresenter> {
+    public static final String TAG = "AccompanyActivity";
     FrameLayout camera;
     FrameLayout fl_webview;
 
@@ -71,6 +74,7 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
 
     CameraView cameraView;
     private String filePath;
+    private String accompanimentUrl;
     private String videoDerectoryName = "/cooleshowTaskVideo";
     private View cameraGroupView;
     private String recordVideFilePath; //评测视频
@@ -162,13 +166,14 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
                             try {
                                 File file = cameraKitVideo.getVideoFile();
                                 filePath = file.getPath();
-                                saveVideoToGallery(filePath);
-                                hideLoading();
-                                ToastUtil.getInstance().showShort("保存成功");
+                                if (!TextUtils.isEmpty(accompanimentUrl)) {
+                                    mixMp3AndMp4(accompanimentUrl, filePath);
+                                } else {
+                                    handleFileResult(filePath);
+                                }
                             } catch (Exception e) {
                                 e.printStackTrace();
                             }
-
                         }
                     });
                 }
@@ -238,17 +243,22 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
 
             }
 
+            @Override
+            public void onDownloadAccompaniment(String accompanyUrl) {
+                AccompanyActivity.this.accompanimentUrl = accompanyUrl;
+            }
+
             /**
              * 停止录视频
              */
             @Override
             public void endCapture() {
                 if (cameraView != null && cameraView.getVisibility() == View.VISIBLE) {
+                    showLoading("正在处理视频中");
                     new Handler().postDelayed(new Runnable() {
                         @Override
                         public void run() {
                             cameraView.stopVideo();//结束录像
-
                         }
                     }, 1000);
                 }
@@ -264,6 +274,38 @@ public class AccompanyActivity extends BaseMVPActivity<ActivityAccompanyBinding,
         startService(intentOne);
     }
 
+    private void mixMp3AndMp4(String accompanimentUrl, String videoPath) {
+        AccompanyHelper.getInstance().download(accompanimentUrl, new ResultCallback<String>() {
+            @Override
+            public void onSuccess(String s) {
+                AccompanyHelper.getInstance().startMix(videoPath, s, new ResultCallback<String>() {
+                    @Override
+                    public void onSuccess(String s) {
+                        Log.i(TAG, "混音success:" + s);
+                        recordVideFilePath = s;
+                        handleFileResult(s);
+                    }
+
+                    @Override
+                    public void onFail(int errorCode, String errorStr) {
+                        handleFileResult(filePath);
+                    }
+                });
+            }
+
+            @Override
+            public void onFail(int errorCode, String errorStr) {
+                handleFileResult(filePath);
+            }
+        });
+    }
+
+    private void handleFileResult(String filePath) {
+        hideLoading();
+        saveVideoToGallery(filePath);
+        ToastUtil.getInstance().showShort("保存成功");
+    }
+
     private void saveVideoToGallery(String filePath) {
         Observable.create(new ObservableOnSubscribe<Object>() {
             @Override

+ 13 - 0
teacher/src/main/java/com/cooleshow/teacher/ui/web/AccompanyFragment.java

@@ -1441,6 +1441,12 @@ public class AccompanyFragment extends BaseMVPFragment<FragmentAccompanyBinding,
 
         void videoUpdate(JSONObject jsonObject);
 
+        /**
+         * mp3伴奏下载
+         *
+         * @param url
+         */
+        void onDownloadAccompaniment(String url);
     }
 
     public WebViewListener listener;
@@ -2028,6 +2034,13 @@ public class AccompanyFragment extends BaseMVPFragment<FragmentAccompanyBinding,
                 });
     }
 
+    @Override
+    public void saveAccompanimentMp3(String accompanyUrl) {
+        if (onAccompanyListener != null) {
+            onAccompanyListener.onDownloadAccompaniment(accompanyUrl);
+        }
+    }
+
     private void handleCloudFollow(String mode) {
         if (mMusicTunerHelper == null) {
             mMusicTunerHelper = new MusicTunerHelper(new MusicTunerHelper.OnEventListener() {

+ 18 - 0
teacher/src/main/java/com/cooleshow/teacher/widgets/helper/JsInterfaceAccomPanyUtils.java

@@ -12,6 +12,7 @@ import com.alibaba.android.arouter.launcher.ARouter;
 import com.cooleshow.base.common.WebApi;
 import com.cooleshow.base.common.WebConstants;
 import com.cooleshow.base.router.RouterPath;
+import com.cooleshow.base.utils.LogUtils;
 import com.cooleshow.teacher.helper.EventHelper;
 import com.cooleshow.usercenter.constants.UserConstants;
 import com.cooleshow.usercenter.helper.UserHelper;
@@ -313,6 +314,16 @@ public class JsInterfaceAccomPanyUtils extends Object {
                     }
                 }
 
+                if (TextUtils.equals("cloudAccompanyMessage", api)) {
+                    JSONObject content = jsonObject.getJSONObject("content");
+                    LogUtils.i("pq", "cloudAccompanyMessage:" + content.toString());
+                    if (onListener != null) {
+                        String accompanyUrl = content.optString("accompanyUrl");
+                        onListener.saveAccompanimentMp3(accompanyUrl);
+                    }
+                    return;
+                }
+
                 if (TextUtils.equals(WebApi.SET_EVENT_TRACKING, api)) {
                     //事件埋点
                     JSONObject content = jsonObject.getJSONObject("content");
@@ -625,6 +636,13 @@ public class JsInterfaceAccomPanyUtils extends Object {
          * 跟练模式
          */
         void cloudToggleFollow(String mode);
+
+        /**
+         * 下载伴奏
+         *
+         * @param url
+         */
+        void saveAccompanimentMp3(String url);
     }
 
 }