Browse Source

添加音频合成模块

Pq 1 year ago
parent
commit
c0bc2581cf
89 changed files with 4963 additions and 0 deletions
  1. 1 0
      BaseLibrary/src/main/java/com/cooleshow/base/router/RouterPath.kt
  2. 11 0
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.java
  3. 43 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/MyFileUtils.java
  4. 13 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/NumberUtils.java
  5. 7 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading2.java
  6. 123 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/CommonConfirmDialog2.java
  7. 7 0
      BaseLibrary/src/main/res/drawable/shape_2dc7aa_to_2dc7aa_22dp.xml
  8. 5 0
      BaseLibrary/src/main/res/drawable/shape_border_dbdbdb_1dp_22dp.xml
  9. 80 0
      BaseLibrary/src/main/res/layout/dialog_common_confirm_layout2.xml
  10. 1 0
      musicMerge/.gitignore
  11. 64 0
      musicMerge/build.gradle
  12. 0 0
      musicMerge/consumer-rules.pro
  13. 21 0
      musicMerge/proguard-rules.pro
  14. 26 0
      musicMerge/src/androidTest/java/com/cooleshow/musicmerge/ExampleInstrumentedTest.java
  15. 11 0
      musicMerge/src/main/AndroidManifest.xml
  16. 28 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/api/Api.java
  17. 35 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/api/DownloadApi.java
  18. 270 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicDataBean.java
  19. 43 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicInfoBean.java
  20. 12 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/callback/ResultCallback.java
  21. 10 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/constants/MusicMergeConfig.java
  22. 18 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/contract/MusicFileHandleContract.java
  23. 11 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/contract/VideoFileHandleContract.java
  24. 518 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/helper/MixHelper.java
  25. 307 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/player/CustomPlayer.java
  26. 106 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/presenter/MusicFileHandlePresenter.java
  27. 91 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/presenter/VideoFileHandlePresenter.java
  28. 1131 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleActivity.java
  29. 480 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleSettingFragment.java
  30. 33 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/SelectVideoFrameActivity.java
  31. 60 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/viewmodel/MusicMergeViewModel.java
  32. 212 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/MusicFrequencyView.java
  33. 177 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/UploadCoverTipDialog.java
  34. BIN
      musicMerge/src/main/res/drawable-xhdpi/bg_music_merge_gramophone.png
  35. BIN
      musicMerge/src/main/res/drawable-xhdpi/bg_offset_progress.png
  36. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_ablum_tag.png
  37. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_album.png
  38. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_offset_add.png
  39. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_offset_reduce.png
  40. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_pause.png
  41. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_pause_white.png
  42. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_play.png
  43. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_play_white.png
  44. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_pointer.png
  45. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_shadow.png
  46. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_rerecord.png
  47. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_save.png
  48. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_seekbar_value_bg.png
  49. BIN
      musicMerge/src/main/res/drawable-xhdpi/icon_shrink_setting_arrow.png
  50. BIN
      musicMerge/src/main/res/drawable-xhdpi/tc_icon_left_arrow.png
  51. BIN
      musicMerge/src/main/res/drawable-xxhdpi/bg_music_merge_gramophone.png
  52. BIN
      musicMerge/src/main/res/drawable-xxhdpi/bg_musicmerge.png
  53. BIN
      musicMerge/src/main/res/drawable-xxhdpi/bg_offset_progress.png
  54. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_default_music_song_cover.png
  55. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_ablum_tag.png
  56. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_album.png
  57. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_offset_add.png
  58. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_offset_reduce.png
  59. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_pause.png
  60. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_pause_white.png
  61. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_play.png
  62. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_play_white.png
  63. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_pointer.png
  64. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_shadow.png
  65. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_offset_seekbar_thumb.png
  66. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_rerecord.png
  67. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_save.png
  68. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_seekbar_value_bg.png
  69. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_shrink_setting_arrow.png
  70. BIN
      musicMerge/src/main/res/drawable-xxhdpi/tc_icon_left_arrow.png
  71. 7 0
      musicMerge/src/main/res/drawable/shape_000000_to_70000000.xml
  72. 5 0
      musicMerge/src/main/res/drawable/shape_269efe_18dp.xml
  73. 7 0
      musicMerge/src/main/res/drawable/shape_70000000_to_tran.xml
  74. 5 0
      musicMerge/src/main/res/drawable/shape_dfe7f2_2dp.xml
  75. 6 0
      musicMerge/src/main/res/drawable/shape_left_16dp_white.xml
  76. 7 0
      musicMerge/src/main/res/drawable/shape_offset_seekbar_thumb.xml
  77. 27 0
      musicMerge/src/main/res/drawable/shape_play_progress_seekbar_bg.xml
  78. 27 0
      musicMerge/src/main/res/drawable/shape_play_progress_seekbar_bg2.xml
  79. 8 0
      musicMerge/src/main/res/drawable/shape_volume_progreesbar_thumb.xml
  80. 8 0
      musicMerge/src/main/res/drawable/shape_volume_progreesbar_thumb2.xml
  81. 27 0
      musicMerge/src/main/res/drawable/shape_volume_seekbar_bg.xml
  82. 337 0
      musicMerge/src/main/res/layout/ac_music_handle_layout.xml
  83. 6 0
      musicMerge/src/main/res/layout/ac_select_video_frame_layout.xml
  84. 187 0
      musicMerge/src/main/res/layout/dialog_upload_cover_tip_layout.xml
  85. 302 0
      musicMerge/src/main/res/layout/fg_music_handle_setting_layout.xml
  86. 16 0
      musicMerge/src/main/res/values/attrs.xml
  87. 8 0
      musicMerge/src/main/res/values/colors.xml
  88. 17 0
      musicMerge/src/test/java/com/cooleshow/musicmerge/ExampleUnitTest.java
  89. 1 0
      settings.gradle

+ 1 - 0
BaseLibrary/src/main/java/com/cooleshow/base/router/RouterPath.kt

@@ -211,6 +211,7 @@ object RouterPath {
     class MusicTuner {
         companion object {
             const val MUSIC_TUNER_PAGE = "/musictuner/musictuner/MusicTunerActivity"
+            const val MUSIC_MERGE_PAGE = "/musicmerge/musicmerge/MusicHandleActivity"
         }
     }
 

+ 11 - 0
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.java

@@ -81,6 +81,17 @@ public abstract class BaseActivity<V extends ViewBinding> extends RxAppCompatAct
         }
     }
 
+    public void updateLoadingText(String text) {
+        if (mLoading != null) {
+            mLoading.updateLoadingText(text);
+        }
+    }
+
+    public void setLoadingCancelable(boolean cancelable){
+        mLoading.setCancelable(cancelable);
+        mLoading.setCanceledOnTouchOutside(cancelable);
+    }
+
     @Override
     public void hideLoading() {
         if (mLoading != null) {

+ 43 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/MyFileUtils.java

@@ -57,6 +57,10 @@ public class MyFileUtils {
     private static final String filesDirectory = getCacheDir(Utils.getApp())+ File.separator+"cooleshow";
     public static final String examDownloadDirectory = "examDownload";
 
+    public static final String MP3_FILE_SUFFIX = ".mp3";
+    public static final String MP4_FILE_SUFFIX = ".mp4";
+    public static final String WAV_FILE_SUFFIX = ".wav";
+
     public static String getCacheDir(Context context) {
         String cacheDir = context.getExternalCacheDir().getAbsolutePath();
         return cacheDir;
@@ -1651,4 +1655,43 @@ public class MyFileUtils {
         byte[] bytes = Base64.decode(base64Data, Base64.NO_WRAP);
         return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
     }
+
+    public static boolean isVideo(String filePath) {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            return false;
+        }
+        boolean b = filePath.endsWith(".mp4");
+        return b;
+    }
+
+    public static boolean isVideoFromUrl(String url) {
+        boolean b = url.endsWith(".mp4");
+        return b;
+    }
+
+    public static String getMp3OrMp4FileSuffix(String url) {
+        boolean isVideo = MyFileUtils.isVideoFromUrl(url);
+        String fileEndSuffix = isVideo ? MP4_FILE_SUFFIX : MP3_FILE_SUFFIX;
+        return fileEndSuffix;
+    }
+
+    public static String getWAVOrMp4FileSuffix(String url) {
+        boolean isVideo = MyFileUtils.isVideoFromUrl(url);
+        String fileEndSuffix = isVideo ? MP4_FILE_SUFFIX : WAV_FILE_SUFFIX;
+        return fileEndSuffix;
+    }
+
+
+    public static String getDownloadSavePath(String url) {
+        int lastDot = url.lastIndexOf(".");
+        if (lastDot < 0) {
+            return "";
+        }
+        String fileType = url.substring(lastDot).toLowerCase();
+        String fileName = EncryptUtils.encryptMD5ToString(url);
+        String path = filesDirectory + File.separator + fileName + fileType;
+        LOG.i(TAG, "getDownloadSavePath:" + path);
+        return path;
+    }
 }

+ 13 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/NumberUtils.java

@@ -195,4 +195,17 @@ public final class NumberUtils {
     public static double float2Double(float value) {
         return new BigDecimal(String.valueOf(value)).doubleValue();
     }
+
+    public static boolean compareResult(float value, float value2) {
+        return getTwoDecimalValue(value) == getTwoDecimalValue(value2);
+    }
+
+    public static double getTwoDecimalValue(double num) {
+        try {
+            BigDecimal bd = new BigDecimal(num);
+            return bd.setScale(2, RoundingMode.HALF_UP).doubleValue();
+        } catch (Exception e) {
+            return 0;
+        }
+    }
 }

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

@@ -60,6 +60,13 @@ public class ProgressLoading2 extends Dialog {
         }
     }
 
+    public void updateLoadingText(String text) {
+        if (mTvLoadingText != null) {
+            mTvLoadingText.setText(text);
+            mTvLoadingText.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void hideLoading() {
         super.dismiss();
         stopAnim();

+ 123 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/CommonConfirmDialog2.java

@@ -0,0 +1,123 @@
+package com.cooleshow.base.widgets.dialog;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.cooleshow.base.R;
+import com.cooleshow.base.widgets.BaseDialog;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Author by pq, Date on 2022/6/17.
+ */
+public class CommonConfirmDialog2 extends BaseFullDialog {
+
+    private TextView mTvContent;
+    private TextView mTvCancel;
+    private TextView mTvConfirm;
+    private TextView mTvTitle;
+
+    private int width;
+    private int height;
+
+    public CommonConfirmDialog2(@NonNull Context context) {
+        super(context, R.style.BaseDialog);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.dialog_common_confirm_layout2);
+        Window window = getWindow();
+        WindowManager.LayoutParams lp = window.getAttributes();
+        lp.gravity = Gravity.CENTER;
+        lp.width = width != 0 ? width : WindowManager.LayoutParams.MATCH_PARENT;
+        lp.height = height != 0 ? height : WindowManager.LayoutParams.WRAP_CONTENT;
+        getWindow().setAttributes(lp);
+
+
+        mTvContent = findViewById(R.id.tv_content);
+        mTvCancel = findViewById(R.id.tv_cancel);
+        mTvConfirm = findViewById(R.id.tv_confirm);
+        mTvTitle = findViewById(R.id.tv_title);
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    /**
+     * 设置主文本
+     *
+     * @param text
+     */
+    public void setContent(String text) {
+        if (mTvContent != null) {
+            mTvContent.setText(text);
+        }
+    }
+
+    public void setTitle(String title) {
+        if (mTvTitle != null) {
+            mTvTitle.setText(title);
+        }
+    }
+
+    /**
+     * 设置取消按钮text
+     *
+     * @param text
+     */
+    public void setCancelText(String text) {
+        if (mTvCancel != null) {
+            mTvCancel.setText(text);
+        }
+    }
+
+    /**
+     * 设置确认按钮text
+     *
+     * @param text
+     */
+    public void setConfirmText(String text) {
+        if (mTvConfirm != null) {
+            mTvConfirm.setText(text);
+        }
+    }
+
+    public void setConfirmBackground(int background, int textColor) {
+        if (mTvConfirm != null) {
+            mTvConfirm.setBackgroundResource(background);
+            mTvConfirm.setTextColor(getContext().getResources().getColor(textColor));
+        }
+    }
+
+    public void setCancelBackground(int background, int textColor) {
+        if (mTvCancel != null) {
+            mTvCancel.setBackgroundResource(background);
+            mTvCancel.setTextColor(getContext().getResources().getColor(textColor));
+        }
+    }
+
+    public void setOnConfirmClickListener(View.OnClickListener listener) {
+        if (mTvConfirm != null) {
+            mTvConfirm.setOnClickListener(listener);
+        }
+    }
+
+    public void setOnCancelClickListener(View.OnClickListener listener) {
+        if (mTvCancel != null) {
+            mTvCancel.setOnClickListener(listener);
+        }
+    }
+}

+ 7 - 0
BaseLibrary/src/main/res/drawable/shape_2dc7aa_to_2dc7aa_22dp.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+<!--    <gradient android:startColor="@color/colorPrimary"-->
+<!--        android:endColor="@color/colorPrimary"/>-->
+    <solid android:color="@color/colorPrimary"/>
+    <corners android:radius="22dp"/>
+</shape>

+ 5 - 0
BaseLibrary/src/main/res/drawable/shape_border_dbdbdb_1dp_22dp.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <stroke android:color="#dbdbdb" android:width="1dp"/>
+    <corners android:radius="22dp"/>
+</shape>

+ 80 - 0
BaseLibrary/src/main/res/layout/dialog_common_confirm_layout2.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="30dp"
+    android:layout_marginEnd="30dp"
+    android:orientation="vertical">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/shape_10dp_white"
+        android:paddingBottom="20dp">
+
+
+        <TextView
+            android:id="@+id/tv_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:paddingTop="20dp"
+            android:textColor="@color/color_333333"
+            android:textSize="@dimen/sp_18"
+            android:textStyle="bold"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="我是标题" />
+
+        <TextView
+            android:id="@+id/tv_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="14dp"
+            android:layout_marginEnd="14dp"
+            android:gravity="center"
+            android:paddingTop="20dp"
+            android:paddingBottom="25dp"
+            android:textColor="@color/color_666666"
+            android:textSize="@dimen/sp_16"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/tv_title"
+            tools:text="结束后直播间关闭本场直播结束,不可再次开启" />
+
+
+        <TextView
+            android:id="@+id/tv_cancel"
+            android:layout_width="0dp"
+            android:layout_height="40dp"
+            android:layout_marginStart="15dp"
+            android:layout_marginEnd="8dp"
+            android:background="@drawable/shape_border_dbdbdb_1dp_22dp"
+            android:gravity="center"
+            android:text="取消"
+            android:textColor="@color/color_666666"
+            android:textSize="@dimen/sp_16"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toLeftOf="@+id/tv_confirm"
+            app:layout_constraintTop_toBottomOf="@+id/tv_content" />
+
+        <TextView
+            android:id="@+id/tv_confirm"
+            android:layout_width="0dp"
+            android:layout_height="40dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="15dp"
+            android:background="@drawable/shape_2dc7aa_to_2dc7aa_22dp"
+            android:gravity="center"
+            android:text="确认"
+            android:textColor="@color/white"
+            android:textSize="@dimen/sp_16"
+            app:layout_constraintLeft_toRightOf="@+id/tv_cancel"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/tv_cancel" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</LinearLayout>

+ 1 - 0
musicMerge/.gitignore

@@ -0,0 +1 @@
+/build

+ 64 - 0
musicMerge/build.gradle

@@ -0,0 +1,64 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+//    id 'kotlin-parcelize'
+    id 'kotlin-android'
+//    id 'kotlin-android-extensions'
+}
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-android-extensions'
+
+kapt {
+    arguments {
+        arg("AROUTER_MODULE_NAME", project.getName())
+    }
+}
+
+android {
+    namespace 'com.cooleshow.musicmerge'
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+
+    defaultConfig {
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+
+    buildFeatures {
+        viewBinding = true
+    }
+}
+
+dependencies {
+    implementation project(':BaseLibrary')
+    implementation project(':usercenter')
+    api project(path: ':ffmpegCmd')
+    implementation 'androidx.appcompat:appcompat:1.3.0'
+    implementation 'com.google.android.material:material:1.4.0'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    //ARouter
+    annotationProcessor("com.alibaba:arouter-compiler:$rootProject.ext.android.arouter_api_version")
+    implementation 'com.alibaba:arouter-api:1.5.2'
+    kapt 'com.alibaba:arouter-compiler:1.5.2'
+}

+ 0 - 0
musicMerge/consumer-rules.pro


+ 21 - 0
musicMerge/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
musicMerge/src/androidTest/java/com/cooleshow/musicmerge/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.cooleshow.musicmerge;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.cooleshow.musicmerge.test", appContext.getPackageName());
+    }
+}

+ 11 - 0
musicMerge/src/main/AndroidManifest.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+        <activity
+            android:name=".ui.MusicHandleActivity"
+            android:configChanges="orientation|screenSize|keyboardHidden|fontScale|smallestScreenSize|screenLayout"
+            android:screenOrientation="landscape"
+            android:exported="false">
+        </activity>
+    </application>
+</manifest>

+ 28 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/api/Api.java

@@ -0,0 +1,28 @@
+package com.cooleshow.musicmerge.api;
+
+import com.cooleshow.base.data.net.BaseResponse;
+import com.cooleshow.musicmerge.bean.MusicDataBean;
+
+import io.reactivex.rxjava3.core.Observable;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Streaming;
+import retrofit2.http.Url;
+
+/**
+ * Author by pq, Date on 2022/12/19.
+ */
+public interface Api {
+    @POST("{group_name}" + "/userMusic/save")
+    Observable<BaseResponse<Object>> save(@Body RequestBody body, @Path("group_name") String group_name);
+
+
+    @GET("{group_name}" + "/userMusic/detail/{id}")
+    Observable<BaseResponse<MusicDataBean>> getDetail(@Path("group_name") String group_name, @Path("id") String id);
+}

+ 35 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/api/DownloadApi.java

@@ -0,0 +1,35 @@
+package com.cooleshow.musicmerge.api;
+
+import io.reactivex.rxjava3.core.Observable;
+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);
+
+
+    /**
+     * 文件下载
+     *
+     * @return
+     */
+    @Streaming
+    @GET
+    @Headers("User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4)")
+    Observable<ResponseBody> downloadFileWithFixedUrl(@Url String url);
+}

+ 270 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicDataBean.java

@@ -0,0 +1,270 @@
+package com.cooleshow.musicmerge.bean;
+
+/**
+ * Author by pq, Date on 2023/11/1.
+ */
+public class MusicDataBean {
+
+    /**
+     * id : 1719544310004731906
+     * userId : 16
+     * clientType : STUDENT
+     * username : 测试账号
+     * avatar : https://daya-online-1303457149.cos.ap-nanjing.myqcloud.com/ktyq/user/android/2023-09/1694178261938.jpeg
+     * subjectId : 3
+     * subjectName : 葫芦丝
+     * currentGradeNum : 4
+     * currentClass : 4
+     * vipFlag : true
+     * musicPracticeRecordId : 1719544207055540226
+     * musicSheetId : 1692832673882292251
+     * musicSheetName : 小星星-葫芦丝
+     * musicSheetSubjectId : 3
+     * musicSheetSubjectName : 葫芦丝
+     * type : DRAFT
+     * img : https://oss.dayaedu.com/gyt/07/1690789356356.png
+     * videoUrl : https://oss.dayaedu.com/ktyq/user/android/2023-11/16_1698806281057.mp4
+     * recordFilePath : https://oss.dayaedu.com/cloud-coach/202311/16_2311011037599.wav
+     * videoFilePath : null
+     * jsonConfig : {"offset":170,"originalVolume":100,"accompanyVolume":100}
+     * likeNum : 0
+     * desc : null
+     * submitTime : null
+     * createTime : 2023-11-01 10:38:05
+     */
+
+    private String id;
+    private String userId;
+    private String clientType;
+    private String username;
+    private String avatar;
+    private String subjectId;
+    private String subjectName;
+    private int currentGradeNum;
+    private int currentClass;
+    private boolean vipFlag;
+    private String musicPracticeRecordId;
+    private String musicSheetId;
+    private String musicSheetName;
+    private String musicSheetSubjectId;
+    private String musicSheetSubjectName;
+    private String type;
+    private String img;
+    private String videoUrl;
+    private String recordFilePath;
+    private String videoFilePath;
+    private String jsonConfig;
+    private int likeNum;
+    private String desc;
+    private String submitTime;
+    private String createTime;
+    private String accompanyUrl;
+
+    public String getAccompanyUrl() {
+        return accompanyUrl;
+    }
+
+    public void setAccompanyUrl(String accompanyUrl) {
+        this.accompanyUrl = accompanyUrl;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getClientType() {
+        return clientType;
+    }
+
+    public void setClientType(String clientType) {
+        this.clientType = clientType;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(String subjectId) {
+        this.subjectId = subjectId;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public int getCurrentGradeNum() {
+        return currentGradeNum;
+    }
+
+    public void setCurrentGradeNum(int currentGradeNum) {
+        this.currentGradeNum = currentGradeNum;
+    }
+
+    public int getCurrentClass() {
+        return currentClass;
+    }
+
+    public void setCurrentClass(int currentClass) {
+        this.currentClass = currentClass;
+    }
+
+    public boolean isVipFlag() {
+        return vipFlag;
+    }
+
+    public void setVipFlag(boolean vipFlag) {
+        this.vipFlag = vipFlag;
+    }
+
+    public String getMusicPracticeRecordId() {
+        return musicPracticeRecordId;
+    }
+
+    public void setMusicPracticeRecordId(String musicPracticeRecordId) {
+        this.musicPracticeRecordId = musicPracticeRecordId;
+    }
+
+    public String getMusicSheetId() {
+        return musicSheetId;
+    }
+
+    public void setMusicSheetId(String musicSheetId) {
+        this.musicSheetId = musicSheetId;
+    }
+
+    public String getMusicSheetName() {
+        return musicSheetName;
+    }
+
+    public void setMusicSheetName(String musicSheetName) {
+        this.musicSheetName = musicSheetName;
+    }
+
+    public String getMusicSheetSubjectId() {
+        return musicSheetSubjectId;
+    }
+
+    public void setMusicSheetSubjectId(String musicSheetSubjectId) {
+        this.musicSheetSubjectId = musicSheetSubjectId;
+    }
+
+    public String getMusicSheetSubjectName() {
+        return musicSheetSubjectName;
+    }
+
+    public void setMusicSheetSubjectName(String musicSheetSubjectName) {
+        this.musicSheetSubjectName = musicSheetSubjectName;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getImg() {
+        return img;
+    }
+
+    public void setImg(String img) {
+        this.img = img;
+    }
+
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
+    }
+
+    public String getRecordFilePath() {
+        return recordFilePath;
+    }
+
+    public void setRecordFilePath(String recordFilePath) {
+        this.recordFilePath = recordFilePath;
+    }
+
+    public Object getVideoFilePath() {
+        return videoFilePath;
+    }
+
+    public void setVideoFilePath(String videoFilePath) {
+        this.videoFilePath = videoFilePath;
+    }
+
+    public String getJsonConfig() {
+        return jsonConfig;
+    }
+
+    public void setJsonConfig(String jsonConfig) {
+        this.jsonConfig = jsonConfig;
+    }
+
+    public int getLikeNum() {
+        return likeNum;
+    }
+
+    public void setLikeNum(int likeNum) {
+        this.likeNum = likeNum;
+    }
+
+    public Object getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+
+    public Object getSubmitTime() {
+        return submitTime;
+    }
+
+    public void setSubmitTime(String submitTime) {
+        this.submitTime = submitTime;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+}

+ 43 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicInfoBean.java

@@ -0,0 +1,43 @@
+package com.cooleshow.musicmerge.bean;
+
+/**
+ * Author by pq, Date on 2023/11/2.
+ */
+public class MusicInfoBean {
+    private String cover;
+    private String preCover;
+    private String musicTitle;
+    private String des;
+
+    public String getPreCover() {
+        return preCover;
+    }
+
+    public void setPreCover(String preCover) {
+        this.preCover = preCover;
+    }
+
+    public String getDes() {
+        return des;
+    }
+
+    public void setDes(String des) {
+        this.des = des;
+    }
+
+    public String getCover() {
+        return cover;
+    }
+
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+
+    public String getMusicTitle() {
+        return musicTitle;
+    }
+
+    public void setMusicTitle(String musicTitle) {
+        this.musicTitle = musicTitle;
+    }
+}

+ 12 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/callback/ResultCallback.java

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

+ 10 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/constants/MusicMergeConfig.java

@@ -0,0 +1,10 @@
+package com.cooleshow.musicmerge.constants;
+
+/**
+ * Author by pq, Date on 2023/11/1.
+ */
+public class MusicMergeConfig {
+    public static final String OFFSET_KEY="offset";
+    public static final String ORIGINALVOLUME_KEY="originalVolume";
+    public static final String ACCOMPANYVOLUME_KEY="accompanyVolume";
+}

+ 18 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/contract/MusicFileHandleContract.java

@@ -0,0 +1,18 @@
+package com.cooleshow.musicmerge.contract;
+
+import com.cooleshow.base.presenter.view.BaseView;
+import com.cooleshow.musicmerge.bean.MusicDataBean;
+
+/**
+ * Author by pq, Date on 2023/9/7.
+ */
+public interface MusicFileHandleContract extends BaseView {
+    void saveWorksSuccess();
+    void getDetailSuccess(MusicDataBean data);
+    void saveWorksDraftSuccess();
+
+    void upLoadImageSuccess(String url);
+
+    void upLoadImageFailure();
+
+}

+ 11 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/contract/VideoFileHandleContract.java

@@ -0,0 +1,11 @@
+package com.cooleshow.musicmerge.contract;
+
+import com.cooleshow.base.presenter.view.BaseView;
+import com.cooleshow.musicmerge.bean.MusicDataBean;
+
+/**
+ * Author by pq, Date on 2023/9/7.
+ */
+public interface VideoFileHandleContract extends BaseView {
+
+}

+ 518 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/helper/MixHelper.java

@@ -0,0 +1,518 @@
+package com.cooleshow.musicmerge.helper;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.cooleshow.base.data.net.RetrofitClientNoToken;
+import com.cooleshow.base.utils.ConvertUtils;
+import com.cooleshow.base.utils.EncodeUtils;
+import com.cooleshow.base.utils.EncryptUtils;
+import com.cooleshow.base.utils.FileUtils;
+import com.cooleshow.base.utils.LOG;
+import com.cooleshow.base.utils.LogUtils;
+import com.cooleshow.base.utils.Utils;
+import com.cooleshow.ffmpegcmd.FFmpegCmd;
+import com.cooleshow.ffmpegcmd.listener.OnHandleListener;
+import com.cooleshow.ffmpegcmd.util.FFmpegUtil;
+import com.cooleshow.musicmerge.api.DownloadApi;
+import com.cooleshow.musicmerge.callback.ResultCallback;
+import com.ksyun.ks3.util.Md5Utils;
+
+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 2023/8/28.
+ */
+public class MixHelper {
+    public static final int max_offset_value = 300;
+    public static final int min_offset_value = -300;
+    private int currentOffsetValue = 0;
+    public static final String TAG = "MixHelper";
+    public static final String BASE_PATH = FileUtils.getCacheDir(Utils.getApp(), "musicmerge");
+
+    public static final String videoMp3Path = BASE_PATH + File.separator + "videoBgm.wav";
+    public static final String accompanimentMp3Path = BASE_PATH + File.separator + "accompaniment_bgm.mp3";
+    public static final String accompanimentWAVPath = BASE_PATH + File.separator + "accompaniment_bgm.wav";
+    public static final String wavToMpePath = BASE_PATH + File.separator + "accompaniment_bgm_result.mp3";
+    public static final String mergeMp3Path = BASE_PATH + File.separator + "merge.mp3";
+    public static final String mergeMp3PathForMP4 = BASE_PATH + File.separator + "merge.aac";//目前只能aac格式,mp3格式合成视频后,IOS原生播放器播放没有声音
+    public static final String cutPath = BASE_PATH + File.separator + "cut.mp3";
+    public static final String cutPathForMP4 = BASE_PATH + File.separator + "cut.wav";
+    public static final String recordFileMp3Path = BASE_PATH + File.separator + "record_file.mp3";
+
+    public static final String onlyVideoPath = BASE_PATH + File.separator + "onlyVideoPath.mp4";
+
+    public static String getVoicePath() {
+        return FileUtils.getCacheDir(Utils.getApp()) + File.separator + "wav-accompany" + ".wav";
+    }
+
+    private MixHelper() {
+    }
+
+    public static MixHelper getInstance() {
+        return MixHelperHolder.sHelper;
+    }
+
+    private static class MixHelperHolder {
+        private static MixHelper sHelper = new MixHelper();
+    }
+
+    private int max_task_num = 0;
+    private int completed_num = 0;
+
+    private ResultCallback<String> tempResultCallback;
+
+    private void resetCount() {
+        max_task_num = 0;
+        completed_num = 0;
+        tempResultCallback = null;
+        FFmpegCmd.setOnProgressCallback(null);
+    }
+
+    private OnEventListener mEventListener = new OnEventListener() {
+        @Override
+        public void onBegin() {
+
+        }
+
+        @Override
+        public void onMsg(@androidx.annotation.NonNull String msg) {
+
+        }
+
+        @Override
+        public void onProgress(int progress, int duration) {
+            if (max_task_num != 0) {
+                LOG.i(TAG,"onProgress:"+progress+"--duration:"+duration);
+                float percent = progress * 1.0f /100;
+                LOG.i(TAG,"percent:"+percent);
+                float average = 100f / max_task_num;
+                LOG.i(TAG,"average:"+average);
+                int cProgress = (int) (average * completed_num + average * percent);
+                LOG.i(TAG,"cProgress:"+cProgress);
+                if (tempResultCallback != null) {
+                    tempResultCallback.onProgress(cProgress);
+                }
+            }
+        }
+
+        @Override
+        public void onEnd(int resultCode, @androidx.annotation.NonNull String resultMsg) {
+
+        }
+    };
+
+    private interface OnEventListener extends OnHandleListener {
+
+    }
+
+    public String getDownloadSavePath(String url) {
+        String fileName = EncryptUtils.encryptMD5ToString(url);
+        String path = BASE_PATH + File.separator + fileName + ".mp3";
+        LOG.i(TAG, "getDownloadSavePath:" + path);
+        return path;
+    }
+
+    public String getDownloadSavePath(String url,String fileEndSuffix) {
+        String fileName = EncryptUtils.encryptMD5ToString(url);
+        String path = BASE_PATH + File.separator + fileName + fileEndSuffix;
+        LOG.i(TAG, "getDownloadSavePath:" + path);
+        return path;
+    }
+
+    public String getDownloadSavePathForMp4(String url) {
+        String fileName = EncryptUtils.encryptMD5ToString(url);
+        String path = BASE_PATH + File.separator + fileName + ".mp4";
+        LOG.i(TAG, "getDownloadSavePath:" + path);
+        return path;
+    }
+
+    public String getDownloadSaveTempPath(String url,String fileEndSuffix) {
+        String fileName = EncryptUtils.encryptMD5ToString(url);
+        return BASE_PATH + File.separator + fileName + "_temp" + fileEndSuffix;
+    }
+
+    public void download(String url,String fileEndSuffix, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+                    @Override
+                    public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                        deleteAllTempFile();
+                        String downloadSaveTempPath = getDownloadSaveTempPath(url,fileEndSuffix);
+                        FileUtils.createOrExistsFile(downloadSaveTempPath);
+                        File file = new File(downloadSaveTempPath);
+                        downloadAccompany(url, file, resultCallback);
+                        if (emitter != null) {
+                            emitter.onNext(getDownloadSavePath(url));
+                        }
+                    }
+                }).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, ResultCallback<String> resultCallback) {
+        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);
+                if (resultCallback != null) {
+                    int progressPercent = (int) ((current * 1.0f / total) * 100);
+                    resultCallback.onProgress(progressPercent);
+                }
+            }
+            replaceName(file);
+            fileOutputStream.flush();
+            fileOutputStream.close();
+            inputStream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void replaceName(File file) {
+        String name = file.getName();
+        LOG.i("下载完成file:" + name);
+        String temp = name.replace("_temp", "");
+        LOG.i("替换之后file:" + temp);
+        FileUtils.rename(file, temp);
+    }
+
+    private void deleteAllTempFile() {
+        FileUtils.delete(mergeMp3Path);
+        FileUtils.delete(mergeMp3PathForMP4);
+        FileUtils.delete(cutPath);
+        FileUtils.delete(cutPathForMP4);
+        FileUtils.delete(recordFileMp3Path);
+        FileUtils.delete(videoMp3Path);
+        FileUtils.delete(onlyVideoPath);
+        FileUtils.delete(accompanimentWAVPath);
+//        boolean b = FileUtils.deleteAllInDir(BASE_PATH);
+//        LogUtils.i(TAG, "deleteAllTempFile:" + b);
+    }
+
+    public int mp3ToWav(String input,String outPut) {
+        String[] commands = FFmpegUtil.transformAudio(input, outPut);
+        if (commands != null) {
+            println(commands);
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    public int getMp3FromWav(String wavFilePath, String outPath) {
+        //ffmpeg -i video.mp4 -vn audio.mp3
+        Log.i(TAG, "getMp3FromWav start wavFilePath:" + wavFilePath + "\noutPath:" + outPath);
+//        String[] commands = new String[5];
+//        commands[0] = "ffmpeg";
+//        commands[1] = "-i";
+//        commands[2] = wavFilePath;
+//        commands[3] = "-f";
+//        commands[4] = outPath;
+        String[] commands = FFmpegUtil.transformAudio(wavFilePath, outPath);
+        if (commands != null) {
+            println(commands);
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    public int codecs() {
+        String[] commands = new String[5];
+        commands[0] = "ffmpeg";
+        commands[1] = "-codecs";
+        if (commands != null) {
+            println(commands);
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    private void println(String[] commands) {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0; i < commands.length; i++) {
+            String command = commands[i];
+            stringBuilder.append(command).append(" ");
+        }
+        Log.i(TAG, "println:" + stringBuilder.toString());
+    }
+
+
+    private int mixMp3(String audioPath, String bgmPath, String outputPath, int delay, int delay1, float volume1, float volume2) {
+        String[] commands = FFmpegUtil.mixAudio(audioPath, bgmPath, 0, 0, volume1, volume2, false, outputPath, delay, delay1);
+//        String[] commands = FFmpegUtil.mixAudio(audioPath, bgmPath,outputPath);
+        if (commands != null) {
+            println(commands);
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    private int cutAudio(String audioPath, float startTime, float duration, String outPath) {
+        String[] commands = FFmpegUtil.cutAudio(audioPath, startTime, duration, outPath);
+        if (commands != null) {
+            println(commands);
+            return FFmpegCmd.executeSync(commands);
+        }
+        return -1;
+    }
+
+    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 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;
+    }
+
+    private void execute(String[] commands) {
+        FFmpegCmd.execute(commands, mEventListener);
+    }
+
+    private String getLastResultPath() {
+        String timeStamp = String.valueOf(System.currentTimeMillis());
+        String lastPath = BASE_PATH + File.separator + "VIDEO_RESULT_" + timeStamp + ".mp4";
+        LogUtils.i(TAG, "lastPath:" + lastPath);
+        return lastPath;
+    }
+
+    public void startMixForMp4(String accompanimentMp3Path, String recordFilePath, int offsetValue, float volume1, float volume2, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+                    @Override
+                    public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                        deleteAllTempFile();
+                        resetCondition(resultCallback,offsetValue < 0 ? 5 : 4);
+
+                        Log.i(TAG, "转换开始");
+                        int getVideoMp3Result = getVideoMp3_2(recordFilePath, videoMp3Path);
+                        completed_num++;
+                        Log.i(TAG, "getVideoMp3 complete:" + getVideoMp3Result);
+                        //mix原音和视频bgm mp3
+//                        String accompanyPath = accompanimentMp3Path;
+                        int i = mp3ToWav(accompanimentMp3Path, accompanimentWAVPath);
+                        Log.i(TAG, "mp3ToWav complete:" + i);
+                        String accompanyPath = accompanimentWAVPath;
+                        int recordFileOffset = 0;
+                        int accompanimentFileOffset = 0;
+                        if (offsetValue >= 0) {
+                            accompanimentFileOffset = offsetValue;
+                        } else {
+                            float cutM = Math.abs(offsetValue) * 1.0f / 1000;
+                            cutAudio(accompanyPath, cutM, 1000000, cutPathForMP4);
+                            completed_num++;
+                            accompanyPath = cutPathForMP4;
+                        }
+                        Log.i(TAG, "mixMp3 start");
+                        int mixMp3Result = mixMp3(videoMp3Path, accompanyPath, mergeMp3PathForMP4, recordFileOffset, accompanimentFileOffset, volume1, volume2);
+                        completed_num++;
+                        Log.i(TAG, "mixMp3 complete:" + mixMp3Result);
+                        //获取纯视频
+                        Log.i(TAG, "getOnlyVideo start");
+                        int getOnlyVideoResult = getOnlyVideo(recordFilePath, onlyVideoPath);
+                        completed_num++;
+                        Log.i(TAG, "getOnlyVideo complete:" + getOnlyVideoResult);
+                        String lastResultPath = getLastResultPath();
+                        int mergeResult = megreMp3AndMp4(onlyVideoPath, mergeMp3PathForMP4, lastResultPath);
+                        Log.i(TAG, "mergeMp4 complete:" + mergeResult);
+                        Log.i(TAG, "转换完成");
+                        resetCount();
+
+                        if (emitter != null) {
+                            boolean isSuccess = getVideoMp3Result == 0 && mixMp3Result == 0 && getOnlyVideoResult == 0 && mergeResult == 0;
+                            Log.i(TAG, "转换是否全部isSuccess:" + isSuccess);
+                            if (isSuccess) {
+                                emitter.onNext(lastResultPath);
+                            } else {
+                                emitter.onNext("");
+                            }
+                        }
+                    }
+                }).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 resetCondition(ResultCallback<String> resultCallback,int num){
+        resetCount();
+        tempResultCallback = resultCallback;
+        max_task_num = num;
+        FFmpegCmd.setOnProgressCallback(mEventListener);
+    }
+
+    public void startMix(String accompanimentMp3Path, String recordFilePath, int offsetValue, float volume1, float volume2, ResultCallback<String> resultCallback) {
+        Observable.create(new ObservableOnSubscribe<String>() {
+                    @Override
+                    public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Throwable {
+                        Log.i(TAG, "转换开始");
+//                        int getVideoMp3Result = getVideoMp3_2(videoPath, videoMp3Path);
+//                        Log.i(TAG, "getVideoMp3 complete:" + getVideoMp3Result);
+//                        Log.i(TAG, "getMp3FromWav start");
+                        deleteAllTempFile();
+                        resetCondition(resultCallback,offsetValue < 0 ? 3 : 2);
+                        int mp3FromWav = getMp3FromWav(recordFilePath, recordFileMp3Path);
+                        completed_num++;
+                        Log.i(TAG, "getMp3FromWav complete:" + mp3FromWav);
+                        String accompanyPath = accompanimentMp3Path;
+                        int recordFileOffset = 0;
+                        int accompanimentFileOffset = 0;
+                        if (offsetValue >= 0) {
+                            accompanimentFileOffset = offsetValue;
+                        } else {
+                            float cutM = Math.abs(offsetValue) * 1.0f / 1000;
+                            Log.i(TAG, "cutAudio start");
+                            int cutAudioResult = cutAudio(accompanyPath, cutM, 1000000, cutPath);
+                            completed_num++;
+                            Log.i(TAG, "cutAudio end:"+cutAudioResult);
+                            accompanyPath = cutPath;
+                        }
+                        //mix原音和视频bgm mp3
+                        Log.i(TAG, "mixMp3 start");
+                        int mixMp3Result = mixMp3(recordFileMp3Path, accompanyPath, mergeMp3Path, recordFileOffset, accompanimentFileOffset, volume1, volume2);
+                        Log.i(TAG, "mixMp3 complete:" + mixMp3Result);
+                        resetCount();
+
+                        if (emitter != null) {
+                            boolean isSuccess = mp3FromWav == 0 && mixMp3Result == 0;
+                            Log.i(TAG, "转换是否全部isSuccess:" + isSuccess);
+                            if (isSuccess) {
+                                emitter.onNext(mergeMp3Path);
+                            } else {
+                                emitter.onNext("");
+                            }
+                        }
+                    }
+                }).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() {
+
+                    }
+                });
+    }
+}

+ 307 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/player/CustomPlayer.java

@@ -0,0 +1,307 @@
+package com.cooleshow.musicmerge.player;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import com.cooleshow.base.utils.LOG;
+
+import java.io.IOException;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Author by pq, Date on 2023/5/11.
+ */
+public class CustomPlayer implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnVideoSizeChangedListener {
+    public String TAG = "";
+    public static final int RESUME_PLAY_MSG_WHAT = 10001;
+    private Context mContext;
+    MediaPlayer mPlayer;
+    private boolean hasPrepared;
+    private boolean isStoped = false;
+    private AudioManager mAudioManager;
+    private OnEventListener onEventListener;
+
+    public CustomPlayer(String tag) {
+        TAG = tag;
+    }
+
+    private Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            int what = msg.what;
+            if (what == RESUME_PLAY_MSG_WHAT) {
+                expectPosition = 0;
+                resume();
+                return;
+            }
+        }
+    };
+
+    private Runnable progressRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mPlayer != null) {
+                int currentPosition = mPlayer.getCurrentPosition();
+                if (onEventListener != null) {
+                    onEventListener.onProgress(currentPosition);
+                }
+                sendProgressMsg();
+            }
+        }
+    };
+
+    public CustomPlayer() {
+        initPlayer();
+    }
+
+    private void initPlayer() {
+        if (null == mPlayer) {
+            mPlayer = new MediaPlayer();
+            mPlayer.setOnErrorListener(this);
+            mPlayer.setOnCompletionListener(this);
+            mPlayer.setOnPreparedListener(this);
+            mPlayer.setOnVideoSizeChangedListener(this);
+        }
+    }
+
+    public MediaPlayer getPlayer(){
+        return mPlayer;
+    }
+
+    public void setVolume(float volume) {
+        if (mPlayer != null) {
+            mPlayer.setVolume(volume, volume);
+        }
+    }
+
+    public void play(Context context, Uri dataSource) {
+        hasPrepared = false; // 开始播放前讲Flag置为不可操作
+        isStoped = false;
+        initPlayer(); // 如果是第一次播放/player已经释放了,就会重新创建、初始化
+        try {
+            removeAllMsg();
+            mPlayer.reset();
+            mPlayer.setDataSource(context, dataSource); // 设置曲目资源
+            mPlayer.prepareAsync(); // 异步的准备方法
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void play(String path) {
+        hasPrepared = false; // 开始播放前讲Flag置为不可操作
+        isStoped = false; //
+        initPlayer(); // 如果是第一次播放/player已经释放了,就会重新创建、初始化
+        try {
+            removeAllMsg();
+            mPlayer.reset();
+            mPlayer.setDataSource(path); // 设置曲目资源
+            mPlayer.prepareAsync(); // 异步的准备方法
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public int getCu() {
+        if (mPlayer != null) {
+            int currentPosition = mPlayer.getCurrentPosition();
+            return currentPosition;
+        }
+        return -1;
+    }
+
+
+    public int getT() {
+        if (mPlayer != null) {
+            int duration = mPlayer.getDuration();
+            return duration;
+        }
+        return -1;
+    }
+
+    public void start() {
+        if (isStoped) {
+            return;
+        }
+        // release()会释放player、将player置空,所以这里需要判断一下
+        if (null != mPlayer && hasPrepared) {
+            mPlayer.start();
+        }
+    }
+
+    public void pause() {
+        if (null != mPlayer && hasPrepared) {
+            mPlayer.pause();
+        }
+    }
+
+    public void resume() {
+        if (null != mPlayer && hasPrepared) {
+            LOG.i(TAG, "expectPosition:" + expectPosition);
+            if (expectPosition < 0) {
+                sendDelayPlayMsg(Math.abs(expectPosition));
+                return;
+            }
+            mPlayer.start();
+        }
+    }
+
+    private int expectPosition = 0;
+
+    public int getExpectPosition() {
+        return expectPosition;
+    }
+
+    public void setExpectPosition(int expectPosition) {
+        this.expectPosition = expectPosition;
+    }
+
+    public void seekTo(int position) {
+        if (mPlayer == null) {
+            return;
+        }
+        LOG.i(TAG, "seekTo getTotal:" + getTotal());
+        this.expectPosition = position;
+        if (position > getTotal()) {
+            position = getTotal();
+        }
+        LOG.i(TAG, "isPlaying:" + isPlaying());
+        if (position < 0) {
+            mPlayer.seekTo(0);
+            if (isPlaying()) {
+                pause();
+                sendDelayPlayMsg(Math.abs(position));
+            }
+            return;
+        }
+        LOG.i(TAG, "seekTo:" + position + "--hasPrepared:" + hasPrepared);
+        if (null != mPlayer && hasPrepared) {
+            mPlayer.seekTo(position);
+        }
+    }
+
+    private void sendDelayPlayMsg(int delay) {
+        Message obtain = Message.obtain();
+        obtain.what = RESUME_PLAY_MSG_WHAT;
+        mHandler.removeMessages(RESUME_PLAY_MSG_WHAT);
+        mHandler.sendMessageDelayed(obtain, delay);
+    }
+
+    // 对于播放视频来说,通过设置SurfaceHolder来设置显示Surface。这个方法不需要判断状态、也不会改变player状态
+    public void setDisplay(SurfaceHolder holder) {
+        if (null != mPlayer) {
+            mPlayer.setDisplay(holder);
+        }
+    }
+
+    public boolean isPlaying() {
+        if (null != mPlayer) {
+            return mPlayer.isPlaying();
+        }
+        return false;
+    }
+
+    public void release() {
+        removeAllMsg();
+        hasPrepared = false;
+        isStoped = true;
+        if (mPlayer != null) {
+            mPlayer.stop();
+            mPlayer.release();
+            mPlayer = null;
+        }
+    }
+
+    public void stop() {
+        if (mPlayer == null) {
+            return;
+        }
+        removeAllMsg();
+        isStoped = true;
+        hasPrepared = false;
+        Log.i(TAG, "player to stop:" + hasPrepared + "-thread:" + Thread.currentThread().getName());
+        mPlayer.stop();
+    }
+
+    @Override
+    public void onPrepared(MediaPlayer mp) {
+        hasPrepared = true; // 准备完成后回调到这里
+        sendProgressMsg();
+        start();
+        if (onEventListener != null) {
+            onEventListener.onPrepared(getTotal());
+        }
+    }
+
+    public int getTotal() {
+        if (mPlayer != null) {
+            return mPlayer.getDuration();
+        }
+        return 0;
+    }
+
+    private void sendProgressMsg() {
+        mHandler.postDelayed(progressRunnable, 100);
+    }
+
+    private void removeAllMsg() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    @Override
+    public void onCompletion(MediaPlayer mp) {
+//        hasPrepared = false;
+        if (onEventListener != null) {
+            onEventListener.onCompleted();
+        }
+        // 通知调用处,调用play()方法进行下一个曲目的播放
+    }
+
+    @Override
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        hasPrepared = false;
+        if (onEventListener != null) {
+            onEventListener.onError();
+        }
+        return true;
+    }
+
+    public void setOnEventListener(OnEventListener onEventListener) {
+        this.onEventListener = onEventListener;
+    }
+
+    @Override
+    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+        if (onEventListener != null) {
+            onEventListener.onVideoSizeChanged(mp,width,height);
+        }
+    }
+
+    public interface OnEventListener {
+        void onProgress(int progress);
+
+        void onPrepared(int duration);
+
+        void onCompleted();
+
+        void onError();
+
+        void onVideoSizeChanged(MediaPlayer mp, int width, int height);
+    }
+
+
+    public void setSurface(SurfaceHolder sh){
+        if (mPlayer != null) {
+            mPlayer.setDisplay(sh);
+        }
+    }
+}

+ 106 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/presenter/MusicFileHandlePresenter.java

@@ -0,0 +1,106 @@
+package com.cooleshow.musicmerge.presenter;
+
+import android.app.Activity;
+
+import com.cooleshow.base.common.BaseConstant;
+import com.cooleshow.base.constanst.UploadConstants;
+import com.cooleshow.base.presenter.BasePresenter;
+import com.cooleshow.base.rx.BaseObserver;
+import com.cooleshow.base.utils.RequestBodyUtil;
+import com.cooleshow.base.utils.helper.upload.UploadHelper;
+import com.cooleshow.musicmerge.api.Api;
+import com.cooleshow.musicmerge.bean.MusicDataBean;
+import com.cooleshow.musicmerge.contract.MusicFileHandleContract;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+
+/**
+ * Author by pq, Date on 2023/9/7.
+ */
+public class MusicFileHandlePresenter extends BasePresenter<MusicFileHandleContract> {
+
+    public void save(String recordId,String url,String imgCover,String des,String configJson) {
+        if (getView() != null) {
+            getView().showLoading();
+        }
+        JSONObject jsonObject =new JSONObject();
+        try {
+            jsonObject.put("musicPracticeRecordId",recordId);
+            jsonObject.put("img",imgCover);
+            jsonObject.put("type","FORMAL");//FORMAL作品 草稿DRAFT
+            jsonObject.put("videoUrl",url);
+            jsonObject.put("jsonConfig",configJson);
+            jsonObject.put("desc",des);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        addSubscribe(create(Api.class).save(RequestBodyUtil.convertToRequestBodyJson(jsonObject.toString()),BaseConstant.CLIENT_API_GROUP_NAME), new BaseObserver<Object>(getView()) {
+            @Override
+            protected void onSuccess(Object data) {
+                if (getView() != null) {
+                    getView().saveWorksSuccess();
+                }
+            }
+        });
+    }
+
+    public void saveDraft(String recordId,String url,String accompanyUrl,String imgCover,String configJson) {
+        if (getView() != null) {
+            getView().showLoading();
+        }
+        JSONObject jsonObject =new JSONObject();
+        try {
+            jsonObject.put("musicPracticeRecordId",recordId);
+            jsonObject.put("img",imgCover);
+            jsonObject.put("type","DRAFT");//FORMAL作品 草稿DRAFT
+            jsonObject.put("videoUrl",url);
+            jsonObject.put("accompanyUrl",accompanyUrl);
+            jsonObject.put("jsonConfig",configJson);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        addSubscribe(create(Api.class).save(RequestBodyUtil.convertToRequestBodyJson(jsonObject.toString()),BaseConstant.CLIENT_API_GROUP_NAME), new BaseObserver<Object>(getView()) {
+            @Override
+            protected void onSuccess(Object data) {
+                if (getView() != null) {
+                    getView().saveWorksDraftSuccess();
+                }
+            }
+        });
+    }
+
+    public void getDetail(String recordId) {
+        getView().showLoading();
+        addSubscribe(create(Api.class).getDetail(BaseConstant.CLIENT_API_GROUP_NAME,recordId), new BaseObserver<MusicDataBean>(getView()) {
+            @Override
+            protected void onSuccess(MusicDataBean data) {
+                if (getView() != null) {
+                    getView().getDetailSuccess(data);
+                }
+            }
+        });
+    }
+
+    public void upLoadImage(Activity activity, String filePath) {
+        UploadHelper uploadHelper = new UploadHelper(activity, UploadConstants.UPLOAD_TYPE_OTHER);
+        uploadHelper.uploadFile(new File(filePath));
+        uploadHelper.setUpLoadCallBack(new UploadHelper.UpLoadCallBack() {
+            @Override
+            public void onSuccess(String url) {
+                if (getView() != null) {
+                    getView().upLoadImageSuccess(url);
+                }
+            }
+
+            @Override
+            protected void onFailure() {
+                if (getView() != null) {
+                    getView().upLoadImageFailure();
+                }
+            }
+        });
+    }
+}

+ 91 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/presenter/VideoFileHandlePresenter.java

@@ -0,0 +1,91 @@
+package com.cooleshow.musicmerge.presenter;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+
+import com.cooleshow.base.common.BaseConstant;
+import com.cooleshow.base.constanst.UploadConstants;
+import com.cooleshow.base.presenter.BasePresenter;
+import com.cooleshow.base.rx.BaseObserver;
+import com.cooleshow.base.utils.RequestBodyUtil;
+import com.cooleshow.base.utils.helper.upload.UploadHelper;
+import com.cooleshow.musicmerge.api.Api;
+import com.cooleshow.musicmerge.bean.MusicDataBean;
+import com.cooleshow.musicmerge.contract.MusicFileHandleContract;
+import com.cooleshow.musicmerge.contract.VideoFileHandleContract;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+
+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.core.Scheduler;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+/**
+ * Author by pq, Date on 2023/9/7.
+ */
+public class VideoFileHandlePresenter extends BasePresenter<VideoFileHandleContract> {
+
+    public void parseVideoFile(String videoFilePath) {
+        Observable.create(new ObservableOnSubscribe<Bitmap[]>() {
+                    @Override
+                    public void subscribe(@NonNull ObservableEmitter<Bitmap[]> emitter) throws Throwable {
+                        Bitmap[] videoAllFrame = getVideoAllFrame(videoFilePath);
+                        if (videoAllFrame != null) {
+                            emitter.onNext(videoAllFrame);
+                        }
+                    }
+                }).observeOn(Schedulers.io())
+                .subscribeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<Bitmap[]>() {
+                    @Override
+                    public void onSubscribe(@NonNull Disposable d) {
+
+                    }
+
+                    @Override
+                    public void onNext(@NonNull Bitmap[] o) {
+
+                    }
+
+                    @Override
+                    public void onError(@NonNull Throwable e) {
+
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    private Bitmap[] getVideoAllFrame(String videoFilePath) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(videoFilePath);
+
+        String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+        long videoDuration = Long.parseLong(duration) * 1000; // in microseconds
+
+        int frameRate = 24; // 设置帧率
+        long interval = 1000000 / frameRate; // 每帧间隔时间,单位为微秒
+
+        int numFrames = (int) (videoDuration / interval);
+        Bitmap[] bitmaps = new Bitmap[numFrames];
+
+        for (int i = 0; i < numFrames; i++) {
+            bitmaps[i] = retriever.getFrameAtTime(i * interval, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
+        }
+        retriever.release();
+        return bitmaps;
+    }
+}

+ 1131 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleActivity.java

@@ -0,0 +1,1131 @@
+package com.cooleshow.musicmerge.ui;
+
+import android.animation.ObjectAnimator;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+import android.widget.SeekBar;
+
+import com.alibaba.android.arouter.facade.annotation.Route;
+import com.cooleshow.base.constanst.UploadConstants;
+import com.cooleshow.base.router.RouterPath;
+import com.cooleshow.base.ui.activity.BaseMVPActivity;
+import com.cooleshow.base.utils.FileUtils;
+import com.cooleshow.base.utils.GlideUtils;
+import com.cooleshow.base.utils.LOG;
+import com.cooleshow.base.utils.MyFileUtils;
+import com.cooleshow.base.utils.NumberUtils;
+import com.cooleshow.base.utils.SizeUtils;
+import com.cooleshow.base.utils.TimeUtils;
+import com.cooleshow.base.utils.ToastUtil;
+import com.cooleshow.base.utils.UiUtils;
+import com.cooleshow.base.utils.helper.QMUIStatusBarHelper;
+import com.cooleshow.base.utils.helper.upload.UploadHelper;
+import com.cooleshow.base.widgets.dialog.CommonConfirmDialog;
+import com.cooleshow.base.widgets.dialog.CommonConfirmDialog2;
+import com.cooleshow.musicmerge.R;
+import com.cooleshow.musicmerge.bean.MusicDataBean;
+import com.cooleshow.musicmerge.bean.MusicInfoBean;
+import com.cooleshow.musicmerge.callback.ResultCallback;
+import com.cooleshow.musicmerge.contract.MusicFileHandleContract;
+import com.cooleshow.musicmerge.databinding.AcMusicHandleLayoutBinding;
+import com.cooleshow.musicmerge.helper.MixHelper;
+import com.cooleshow.musicmerge.player.CustomPlayer;
+import com.cooleshow.musicmerge.presenter.MusicFileHandlePresenter;
+import com.cooleshow.musicmerge.viewmodel.MusicMergeViewModel;
+import com.luck.picture.lib.PictureSelector;
+import com.luck.picture.lib.entity.LocalMedia;
+
+import java.io.File;
+import java.util.List;
+import java.util.Locale;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * Author by pq, Date on 2023/8/28.
+ */
+@Route(path = RouterPath.MusicTuner.MUSIC_MERGE_PAGE)
+public class MusicHandleActivity extends BaseMVPActivity<AcMusicHandleLayoutBinding, MusicFileHandlePresenter> implements View.OnClickListener, SurfaceHolder.Callback, MusicFileHandleContract {
+    public static final int REQUEST_CODE_LOCAL = 0x19;
+    private CustomPlayer player1;
+    private CustomPlayer player2;
+    private String accompanyUrl;
+    private String recordFilePath;
+    private MusicHandleSettingFragment mSettingFragment;
+    private boolean isVideo;
+    private SurfaceView mSurfaceView;
+    private int videoWidth;
+    private int videoHeight;
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private String mRecordId;
+    private String worksId;
+    private String imgCover;
+    private String des;
+    private ObjectAnimator mRotateAnimation;
+    private ObjectAnimator mAlbumRotationAnimator;
+    private String originalFileUrl = "";
+    private String mTitle;
+    private MusicMergeViewModel mViewModel;
+
+    private boolean isNeedFinishPage = false;
+
+    private boolean isNeedResetScreenOrientation = true;
+
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        QMUIStatusBarHelper.hideStatusBar(this);
+    }
+
+    @Override
+    protected void initView() {
+        mTitle = getIntent().getStringExtra("title");
+        viewBinding.tvTitle.setText(mTitle);
+        int c_orientation = getIntent().getIntExtra("c_orientation", ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        isNeedResetScreenOrientation = c_orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+
+    }
+
+    private void initFragment() {
+        mSettingFragment = new MusicHandleSettingFragment();
+        Bundle bundle = new Bundle();
+        bundle.putString("accompanyUrl", accompanyUrl);
+        mSettingFragment.setArguments(bundle);
+        getSupportFragmentManager().beginTransaction().replace(R.id.fl_setting, mSettingFragment).commitAllowingStateLoss();
+    }
+
+    private void initSurfaceView() {
+        initVideoUIStyle();
+        viewBinding.groupAudioView.setVisibility(View.GONE);
+        viewBinding.flSurface.setVisibility(View.VISIBLE);
+        viewBinding.viewVideoTopBg.setVisibility(View.VISIBLE);
+        viewBinding.viewVideoBottomBg.setVisibility(View.VISIBLE);
+        viewBinding.viewVideoBg.setVisibility(View.VISIBLE);
+        mSurfaceView = new SurfaceView(MusicHandleActivity.this);
+        SurfaceHolder holder = mSurfaceView.getHolder();
+        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
+        layoutParams.gravity = Gravity.CENTER;
+        viewBinding.flSurface.addView(mSurfaceView, layoutParams);
+        holder.addCallback(MusicHandleActivity.this);
+        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+    }
+
+    private void initVideoUIStyle() {
+        viewBinding.tvTitle.setTextColor(Color.WHITE);
+        viewBinding.ivBack.setImageResource(com.cooleshow.base.R.drawable.ic_back_white);
+        viewBinding.ivPlay.setImageResource(R.drawable.icon_music_merge_play_white);
+        viewBinding.tvCurrentProgress.setTextColor(Color.WHITE);
+        viewBinding.tvTotalProgress.setTextColor(Color.WHITE);
+        viewBinding.seekPlay.setThumb(getResources().getDrawable(R.drawable.shape_volume_progreesbar_thumb2));
+        setSeekBarProgressDrawable(viewBinding.seekPlay, getResources().getDrawable(R.drawable.shape_play_progress_seekbar_bg2));
+
+        viewBinding.tvTotalProgress2.setTextColor(Color.WHITE);
+        viewBinding.seekPlay2.setThumb(getResources().getDrawable(R.drawable.shape_volume_progreesbar_thumb2));
+        viewBinding.tvCurrentProgress2.setTextColor(Color.WHITE);
+        setSeekBarProgressDrawable(viewBinding.seekPlay2, getResources().getDrawable(R.drawable.shape_play_progress_seekbar_bg2));
+    }
+
+    private void setSeekBarProgressDrawable(SeekBar seekBarProgress, Drawable drawable) {
+        Rect bounds = seekBarProgress.getProgressDrawable().getBounds();
+        seekBarProgress.setProgressDrawable(drawable);
+        seekBarProgress.getProgressDrawable().setBounds(bounds);
+    }
+
+    @Override
+    public void initData() {
+        super.initData();
+        mRecordId = getIntent().getStringExtra("recordId");
+        worksId = getIntent().getStringExtra("worksId");
+        imgCover = getIntent().getStringExtra("coverImg");
+
+        loadCover();
+        if (TextUtils.isEmpty(mRecordId)) {
+            ToastUtil.getInstance().showShort("作品生成失败");
+            finish();
+            return;
+        }
+        accompanyUrl = getIntent().getStringExtra("accompanyUrl");
+        recordFilePath = getIntent().getStringExtra("recordFilePath");
+
+        initViewModel();
+        initFragment();
+        initListener();
+        initPlayer();
+        LOG.i("recordFilePath:" + recordFilePath);
+        LOG.i("accompanyUrl:" + accompanyUrl);
+        if (!checkRecordFile()) {
+            if (!TextUtils.isEmpty(worksId)) {
+                presenter.getDetail(worksId);
+            } else {
+                ToastUtil.getInstance().showShort("作品生成失败");
+                finish();
+            }
+        } else {
+            preLoad();
+        }
+    }
+
+    private void initViewModel() {
+        ViewModelProvider.AndroidViewModelFactory instance =
+                ViewModelProvider.AndroidViewModelFactory
+                        .getInstance(getApplication());
+        mViewModel = new ViewModelProvider(this, instance)
+                .get(MusicMergeViewModel.class);
+        mViewModel.getWorksId().setValue(worksId);
+        refreshMusicInfo(imgCover);
+    }
+
+    private void preLoad() {
+        isVideo = MyFileUtils.isVideo(recordFilePath);
+        LOG.i("isVideo:" + isVideo);
+        boolean b = checkAccompanimentMp3File();
+        if (b) {
+            preparePlay();
+        }
+    }
+
+    private void preparePlay() {
+        if (isVideo) {
+            initSurfaceView();
+        } else {
+            toPlay(getAccompanyPath());
+        }
+    }
+
+    private void loadCover() {
+        GlideUtils.INSTANCE.loadImage(this, imgCover, viewBinding.ivCover, R.drawable.icon_default_music_song_cover);
+    }
+
+    @Override
+    protected MusicFileHandlePresenter createPresenter() {
+        return new MusicFileHandlePresenter();
+    }
+
+    private void initListener() {
+        viewBinding.ivBack.setOnClickListener(this);
+        viewBinding.ivPlay.setOnClickListener(this);
+        viewBinding.ivUnfoldSentting.setOnClickListener(this);
+
+        mSettingFragment.setEventListener(new MusicHandleSettingFragment.OnEventListener() {
+            @Override
+            public void onVolumeChange(int type, float value) {
+                if (type == MusicHandleSettingFragment.RECORD_TYPE) {
+                    player1.setVolume(value);
+                } else {
+                    player2.setVolume(value);
+                }
+            }
+
+            @Override
+            public void onOffsetValueChange(int value) {
+                aligningAccompany(value);
+            }
+
+            @Override
+            public void toMix(int offsetValue, float volume1, float volume2, boolean isNeedNotify) {
+                if (isVideo) {
+                    startMixForMp4(getAccompanyPath(), recordFilePath, offsetValue, volume1, volume2, isNeedNotify);
+                } else {
+                    startMix(getAccompanyPath(), recordFilePath, offsetValue, volume1, volume2, isNeedNotify);
+                }
+            }
+
+            @Override
+            public void hideSetting() {
+                handleSettingVisibility();
+            }
+
+            @Override
+            public void saveDraft() {
+                //上传原始视频 以及 相关调节参数
+                toSaveDraft();
+            }
+        });
+        viewBinding.seekPlay.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (fromUser) {
+                    handleSeekEvent();
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+
+            }
+        });
+    }
+
+    private void toSaveDraft() {
+        if (!TextUtils.isEmpty(recordFilePath)) {
+            if (!TextUtils.isEmpty(originalFileUrl)) {
+                toNotifyDraft(originalFileUrl);
+            } else {
+                uploadDraft(recordFilePath);
+            }
+        }
+    }
+
+    private void uploadDraft(String filePath) {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            return;
+        }
+        setLoadingCancelable(false);
+        showLoading("正在上传草稿0%");
+        UploadHelper uploadHelper = new UploadHelper(null, UploadConstants.UPLOAD_TYPE_HOMEWORK);
+        uploadHelper.setUpLoadCallBack(new UploadHelper.UpLoadCallBack() {
+            @Override
+            protected void onSuccess(String url) {
+                updateLoadingText("正在上传草稿100%");
+                toNotifyDraft(url);
+            }
+
+            @Override
+            protected void onFailure() {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        hideLoading();
+                        ToastUtil.getInstance().showShort("草稿上传失败,请重试");
+                    }
+                });
+            }
+
+            @Override
+            public void onUploadProgress(double v) {
+                Log.i("pq","onUploadProgress"+v);
+                updateLoadingText("正在上传草稿"+UiUtils.convertDouble(v)+"%");
+            }
+        });
+        uploadHelper.setLoadingTip("正在上传草稿");
+
+        uploadHelper.uploadFile(file);
+    }
+
+    private void upload(String filePath) {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            return;
+        }
+        updateLoadingText("正在上传作品0%");
+        UploadHelper uploadHelper = new UploadHelper(null, UploadConstants.UPLOAD_TYPE_HOMEWORK);
+        uploadHelper.setUpLoadCallBack(new UploadHelper.UpLoadCallBack() {
+            @Override
+            protected void onSuccess(String url) {
+                updateLoadingText("正在上传作品100%");
+                toNotify(url);
+            }
+
+            @Override
+            protected void onFailure() {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        hideLoading();
+                        ToastUtil.getInstance().showShort("作品上传失败,请重试");
+                    }
+                });
+            }
+
+            @Override
+            public void onUploadProgress(double v) {
+                updateLoadingText("正在上传作品"+ UiUtils.convertDouble(v)+"%");
+            }
+        });
+        uploadHelper.setLoadingTip("正在上传作品");
+
+        uploadHelper.uploadFile(file);
+    }
+
+    private void toNotify(String url) {
+        String configJson = mSettingFragment.getConfigJson();
+        MusicInfoBean value = mViewModel.getMusicInfoLiveData().getValue();
+        String cover = imgCover;
+        if (value != null) {
+            String preCover = value.getPreCover();
+            if (!TextUtils.isEmpty(preCover)) {
+                cover = preCover;
+            }
+            des = value.getDes();
+        }
+        presenter.save(mRecordId, url, cover, des, configJson);
+    }
+
+    private void toNotifyDraft(String url) {
+        if (mSettingFragment != null) {
+            String configJson = mSettingFragment.getConfigJson();
+            presenter.saveDraft(mRecordId, url, accompanyUrl, imgCover, configJson);
+        }
+    }
+
+    public void startMixForMp4(String accompanimentMp3Path, String recordFilePath, int offsetValue, float recordFileVolume, float accompanyFileVolume, boolean isNeedNotify) {
+        setLoadingCancelable(false);
+        showLoading(String.format(Locale.getDefault(), "视频合成中 %d%%", 0));
+        MixHelper.getInstance().startMixForMp4(accompanimentMp3Path, recordFilePath, offsetValue, recordFileVolume, accompanyFileVolume, new ResultCallback<String>() {
+            @Override
+            public void onSuccess(String s) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        updateLoadingText(String.format(Locale.getDefault(), "视频合成中 %d%%", 100));
+                        if (!TextUtils.isEmpty(s)) {
+//                            ToastUtil.getInstance().showShort("mix completed:" + s);
+                            FileUtils.notifySystemToScan(s);
+                            if (isNeedNotify) {
+                                upload(s);
+                            } else {
+                                ToastUtil.getInstance().showShort("保存成功");
+                            }
+                        } else {
+                            ToastUtil.getInstance().showShort("mix onFail");
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void onProgress(int progressPercent) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        LOG.i("progressPercent:" + progressPercent);
+                        updateLoadingText(String.format(Locale.getDefault(), "视频合成中 %d%%", progressPercent));
+                    }
+                });
+            }
+
+            @Override
+            public void onFail(int errorCode, String errorStr) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        hideLoading();
+                        ToastUtil.getInstance().showShort("mix onFail:" + errorCode + "--reason:" + errorStr);
+                    }
+                });
+            }
+        });
+    }
+
+    private void startMix(String accompanimentMp3Path, String recordFilePath, int offsetValue, float recordFileVolume, float accompanyFileVolume, boolean isNeedNotify) {
+        setLoadingCancelable(false);
+        showLoading(String.format(Locale.getDefault(), "音频合成中 %d%%", 0));
+        MixHelper.getInstance().startMix(accompanimentMp3Path, recordFilePath, offsetValue, recordFileVolume, accompanyFileVolume, new ResultCallback<String>() {
+            @Override
+            public void onSuccess(String s) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        updateLoadingText(String.format(Locale.getDefault(), "音频合成中 %d%%", 100));
+                        hideLoading();
+//                        ToastUtil.getInstance().showShort("mix completed:" + s);
+                        if (!TextUtils.isEmpty(s)) {
+                            FileUtils.notifySystemToScan(s);
+                            if (isNeedNotify) {
+                                upload(s);
+                            } else {
+                                ToastUtil.getInstance().showShort("保存成功");
+                            }
+                        } else {
+                            ToastUtil.getInstance().showShort("mix onFail");
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void onProgress(int progressPercent) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        LOG.i("progressPercent:" + progressPercent);
+                        updateLoadingText(String.format(Locale.getDefault(), "音频合成中 %d%%", progressPercent));
+                    }
+                });
+            }
+
+            @Override
+            public void onFail(int errorCode, String errorStr) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        hideLoading();
+                        ToastUtil.getInstance().showShort("mix onFail:" + errorCode + "--reason:" + errorStr);
+                    }
+                });
+            }
+        });
+    }
+
+    private void aligningAccompany(int value) {
+        int cu = player1.getCu();
+        LOG.i("pq", "cu:" + cu);
+        LOG.i("pq", "currentOffsetValue:" + value);
+        int seekResult2 = cu - value;
+        LOG.i("pq", "seekResult2:" + seekResult2);
+        player2.seekTo(seekResult2);
+    }
+
+    private void handleSeekEvent() {
+        int progress = viewBinding.seekPlay.getProgress();
+        float percent = progress * 1.0f / viewBinding.seekPlay.getMax();
+        int seekResult = (int) (player1.getTotal() * percent);
+        LOG.i("pq", "seekResult:" + seekResult);
+        int currentOffsetValue = mSettingFragment.getCurrentOffsetValue();
+        LOG.i("pq", "currentOffsetValue:" + currentOffsetValue);
+        int seekResult2 = seekResult - currentOffsetValue;
+        LOG.i("pq", "seekResult2:" + seekResult2);
+        player1.seekTo(seekResult);
+        player2.seekTo(seekResult2);
+    }
+
+    private void initPlayer() {
+        player1 = new CustomPlayer("luzhi");
+        player2 = new CustomPlayer("伴奏");
+        player1.setOnEventListener(new CustomPlayer.OnEventListener() {
+            @Override
+            public void onProgress(int progress) {
+                if (!checkActivityExist()) {
+                    return;
+                }
+                String s = TimeUtils.msToTime(progress);
+                viewBinding.tvCurrentProgress.setText(s);
+                int maxProgress = viewBinding.seekPlay.getMax();
+                int percent = (int) ((progress * 1.0f / player1.getTotal()) * maxProgress);
+                viewBinding.seekPlay.setProgress(percent);
+            }
+
+            @Override
+            public void onPrepared(int duration) {
+                if (!checkActivityExist()) {
+                    return;
+                }
+                String s = TimeUtils.msToTime(duration);
+                viewBinding.tvTotalProgress.setText(s);
+                setVolume();
+                updatePlayStatus();
+            }
+
+            @Override
+            public void onCompleted() {
+                if (!checkActivityExist()) {
+                    return;
+                }
+                viewBinding.seekPlay.setProgress(0);
+                viewBinding.seekPlay2.setProgress(0);
+                player2.pause();
+                handleSeekEvent();
+                updatePlayStatus();
+            }
+
+            @Override
+            public void onError() {
+
+            }
+
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+                MusicHandleActivity.this.videoWidth = width;
+                MusicHandleActivity.this.videoHeight = height;
+                resetVideoSize(width, height);
+            }
+        });
+
+        player2.setOnEventListener(new CustomPlayer.OnEventListener() {
+            @Override
+            public void onProgress(int progress) {
+                String s = TimeUtils.msToTime(progress);
+                viewBinding.tvCurrentProgress2.setText(s);
+                int maxProgress = viewBinding.seekPlay2.getMax();
+                int percent = (int) ((progress * 1.0f / player2.getTotal()) * maxProgress);
+                viewBinding.seekPlay2.setProgress(percent);
+            }
+
+            @Override
+            public void onPrepared(int duration) {
+                String s = TimeUtils.msToTime(duration);
+                viewBinding.tvTotalProgress2.setText(s);
+            }
+
+            @Override
+            public void onCompleted() {
+
+            }
+
+            @Override
+            public void onError() {
+
+            }
+
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+
+            }
+        });
+    }
+
+    private void setVolume() {
+        if (mSettingFragment != null) {
+            int originalVolume = mSettingFragment.getOriginalVolume();
+            int accompanyVolume = mSettingFragment.getAccompanyVolume();
+            player1.setVolume(originalVolume);
+            player2.setVolume(accompanyVolume);
+        }
+    }
+
+    private void resetVideoSize(int width, int height) {
+        int surfaceWidth = viewBinding.flSurface.getWidth();
+        int surfaceHeight = viewBinding.flSurface.getHeight();
+        LOG.i("pq", "surfaceWidth:" + surfaceWidth);
+        LOG.i("pq", "surfaceHeight:" + surfaceHeight);
+        float percent = surfaceWidth * 1.0f / surfaceHeight;
+        float percent2 = width * 1.0f / height;
+        LOG.i("pq", "percent:" + percent);
+        LOG.i("pq", "percent2:" + percent2);
+        boolean b = NumberUtils.compareResult(percent, percent2);
+        LOG.i("pq", "compareResult:" + b);
+        if (b) {
+            return;
+        }
+        LOG.i("pq", "videoWidth:" + width);
+        LOG.i("pq", "videoHeight:" + height);
+        int w = surfaceWidth;
+        int h = (int) ((surfaceWidth * 1.0f / width) * height);
+        LOG.i("pq", "w:" + w);
+        LOG.i("pq", "h:" + h);
+        if (w > surfaceWidth) {
+            w = surfaceWidth;
+        }
+        if (h > surfaceHeight) {
+            h = surfaceHeight;
+        }
+        LOG.i("pq", "w:" + w);
+        LOG.i("pq", "h:" + h);
+        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mSurfaceView.getLayoutParams();
+        layoutParams.width = w;
+        layoutParams.height = h;
+        mSurfaceView.setLayoutParams(layoutParams);
+        mSurfaceView.getHolder().setFixedSize(w, h);
+    }
+
+    private boolean checkRecordFile() {
+        if (TextUtils.isEmpty(recordFilePath)) {
+            return false;
+        }
+        File file = new File(recordFilePath);
+        if (!file.exists()) {
+            return false;
+        }
+        return true;
+    }
+
+    private void toPlay(String accompanyPath) {
+        player1.play(recordFilePath);
+        player2.play(accompanyPath);
+    }
+
+    private boolean checkRecordFile(String recordUrl) {
+        String fileEndSuffix = MyFileUtils.getWAVOrMp4FileSuffix(recordUrl);
+        String recordFileDownloadPath = getRecordFileDownloadPath(recordUrl, fileEndSuffix);
+        recordFilePath = recordFileDownloadPath;
+        Log.i("pq", "checkRecordFile:" + recordFilePath);
+        File file = new File(recordFileDownloadPath);
+        if (file.exists()) {
+            return true;
+        }
+        Log.i("pq", "下载草稿");
+        setLoadingCancelable(false);
+        showLoading("草稿下载中");
+        MixHelper.getInstance().download(recordUrl, fileEndSuffix, new ResultCallback<String>() {
+            @Override
+            public void onSuccess(String s) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        hideLoading();
+                        preLoad();
+                    }
+                });
+            }
+
+            @Override
+            public void onProgress(int progressPercent) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        updateLoadingText("草稿下载中" + progressPercent + "%");
+                    }
+                });
+            }
+
+            @Override
+            public void onFail(int errorCode, String errorStr) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        ToastUtil.getInstance().showShort("草稿下载失败,请重试");
+                        hideLoading();
+                    }
+                });
+            }
+        });
+        return false;
+    }
+
+    private boolean checkAccompanimentMp3File() {
+        if (TextUtils.isEmpty(accompanyUrl)) {
+            ToastUtil.getInstance().showShort("未找到伴奏文件,请退出重试");
+            return false;
+        }
+        String accompanyPath = getAccompanyPath();
+        File file = new File(accompanyPath);
+        if (file.exists()) {
+            return true;
+        }
+        setLoadingCancelable(false);
+        showLoading("伴奏下载中");
+        MixHelper.getInstance().download(accompanyUrl, MyFileUtils.MP3_FILE_SUFFIX, new ResultCallback<String>() {
+            @Override
+            public void onSuccess(String s) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        preparePlay();
+                        hideLoading();
+                    }
+                });
+            }
+
+            @Override
+            public void onProgress(int progressPercent) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        updateLoadingText("伴奏下载中" + progressPercent + "%");
+                    }
+                });
+            }
+
+            @Override
+            public void onFail(int errorCode, String errorStr) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        ToastUtil.getInstance().showShort("伴奏下载失败,请重试");
+                        hideLoading();
+                    }
+                });
+            }
+        });
+        return false;
+    }
+
+
+    @Override
+    public void hideLoading() {
+        setLoadingCancelable(true);
+        super.hideLoading();
+    }
+
+    private String getAccompanyPath() {
+        String accompanyPath = MixHelper.getInstance().getDownloadSavePath(accompanyUrl);
+        return accompanyPath;
+    }
+
+    private String getRecordFileDownloadPath(String recordFileUrl, String fileEndSuffix) {
+        String recordFilePath;
+        if (MyFileUtils.isVideoFromUrl(recordFileUrl)) {
+            recordFilePath = MixHelper.getInstance().getDownloadSavePathForMp4(recordFileUrl);
+        } else {
+            recordFilePath = MixHelper.getInstance().getDownloadSavePath(recordFileUrl, fileEndSuffix);
+        }
+        return recordFilePath;
+    }
+
+    @Override
+    protected AcMusicHandleLayoutBinding getLayoutView() {
+        return AcMusicHandleLayoutBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    public void onClick(View v) {
+        int id = v.getId();
+        if (id == R.id.iv_play) {
+            if (player1.isPlaying()) {
+                player1.pause();
+                player2.pause();
+            } else {
+                player1.resume();
+                player2.resume();
+            }
+            updatePlayStatus();
+            return;
+        }
+        if (id == R.id.iv_unfold_sentting) {
+            handleSettingVisibility();
+            return;
+        }
+        if (id == R.id.iv_back) {
+            checkTipToFinish();
+            return;
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        pausePlay();
+    }
+
+    private void pausePlay() {
+        if (player1.isPlaying()) {
+            player1.pause();
+            player2.pause();
+        }
+        updatePlayStatus();
+    }
+
+    private void checkTipToFinish() {
+        boolean hasUpdate = mViewModel.isHasUpdate();
+        if (hasUpdate) {
+            showSaveTipDialog();
+        } else {
+            finish();
+        }
+    }
+
+    private void showSaveTipDialog() {
+        CommonConfirmDialog2 commonConfirmDialog = new CommonConfirmDialog2(this);
+        commonConfirmDialog.setWidth(SizeUtils.dp2px(387));
+        commonConfirmDialog.show();
+        commonConfirmDialog.setTitle("提示");
+        commonConfirmDialog.setContent("是否将本次录制的作品保存为草稿?");
+        commonConfirmDialog.setOnConfirmClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                commonConfirmDialog.dismiss();
+                isNeedFinishPage = true;
+                toSaveDraft();
+            }
+        });
+        commonConfirmDialog.setOnCancelClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                commonConfirmDialog.dismiss();
+                finish();
+            }
+        });
+    }
+
+    /**
+     * 监听返回键
+     *
+     * @param keyCode
+     * @param event
+     * @return
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            checkTipToFinish();
+            return true;
+
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void handleSettingVisibility() {
+        int visibility = viewBinding.flSetting.getVisibility();
+        if (visibility == View.VISIBLE) {
+            viewBinding.groupSetting.setVisibility(View.GONE);
+            viewBinding.ivUnfoldSentting.setVisibility(View.VISIBLE);
+        } else {
+            viewBinding.groupSetting.setVisibility(View.VISIBLE);
+            viewBinding.ivUnfoldSentting.setVisibility(View.GONE);
+        }
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                resetVideoSize(videoWidth, videoHeight);
+            }
+        }, 50);
+    }
+
+    private void updatePlayStatus() {
+        if (player1.isPlaying()) {
+            viewBinding.ivPlayPointer.setRotation(0);
+            handleAnim(true);
+            viewBinding.ivPlay.setImageResource(isVideo ? R.drawable.icon_music_merge_pause_white : R.drawable.icon_music_merge_pause);
+        } else {
+            handleAnim(false);
+            viewBinding.ivPlayPointer.setRotation(92);
+            viewBinding.ivPlay.setImageResource(isVideo ? R.drawable.icon_music_merge_play_white : R.drawable.icon_music_merge_play);
+        }
+    }
+
+    private void handleAnim(boolean isPlay) {
+        if (isVideo) {
+            return;
+        }
+        if (player1 != null && player1.getPlayer() != null && isPlay) {
+            viewBinding.musicFrequencyView.setMediaPlayer(player1.getPlayer());
+        }
+        rotation(isPlay);
+        rotationAlbum(isPlay);
+    }
+
+    private void rotation(boolean isPlay) {
+        float from = isPlay ? 92 : 0;
+        float to = isPlay ? 0 : 92;
+        viewBinding.ivPlayPointer.clearAnimation();
+        float rotation = viewBinding.ivPlayPointer.getRotation();
+        LOG.i("rotation:" + rotation);
+        if (mRotateAnimation == null) {
+            mRotateAnimation = ObjectAnimator.ofFloat(viewBinding.ivPlayPointer, "rotation", from, to);
+            mRotateAnimation.setInterpolator(new LinearInterpolator());//不停顿
+            mRotateAnimation.setDuration(300);
+            mRotateAnimation.setRepeatCount(0);
+        }
+        mRotateAnimation.setFloatValues(from, to);
+        mRotateAnimation.start();
+    }
+
+    private void rotationAlbum(boolean isPlay) {
+        float rotation = viewBinding.flAblum.getRotation();
+        if (mAlbumRotationAnimator == null) {
+            mAlbumRotationAnimator = ObjectAnimator.ofFloat(viewBinding.flAblum, "rotation", 0f, 360f);
+            mAlbumRotationAnimator.setDuration(5000); // 设置动画持续时间为1秒
+            mAlbumRotationAnimator.setRepeatCount(ObjectAnimator.INFINITE); // 设置无限循环
+            mAlbumRotationAnimator.setInterpolator(new LinearInterpolator()); // 设置动画插值器,这里使用线性插值器
+        }
+        if (isPlay) {
+            if (mAlbumRotationAnimator.isPaused()) {
+                mAlbumRotationAnimator.resume();
+            } else {
+                mAlbumRotationAnimator.start(); // 启动动画
+            }
+        } else {
+            mAlbumRotationAnimator.pause();//
+        }
+    }
+
+
+    @Override
+    public void saveWorksSuccess() {
+        if (!checkActivityExist()) {
+            return;
+        }
+        showToastViewAndFinish("发布成功");
+    }
+
+    private void toFinish() {
+        Intent intent = new Intent();
+        intent.putExtra("saveWorksStatus", 1);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    @Override
+    public void getDetailSuccess(MusicDataBean data) {
+        if (!checkActivityExist()) {
+            return;
+        }
+        if (data != null) {
+            this.originalFileUrl = data.getVideoUrl();
+            String jsonConfig = data.getJsonConfig();
+            accompanyUrl = data.getAccompanyUrl();
+            if (mSettingFragment != null) {
+                if (!TextUtils.isEmpty(jsonConfig)) {
+                    mSettingFragment.applyConfig(jsonConfig);
+                }
+                mSettingFragment.setAccompanyUrl(accompanyUrl);
+            }
+            //这里为了兼容IOS录制的wav音频文件格式不正确 导致合成失败的问题Failed to read frame size: Could not seek to 1026.
+            //取服务端存储的文件
+            boolean isVideo = MyFileUtils.isVideoFromUrl(data.getVideoUrl());
+            String fileUrl = isVideo ? data.getVideoUrl() : data.getRecordFilePath();
+            boolean b = checkRecordFile(fileUrl);
+            if (b) {
+                preLoad();
+            }
+
+        }
+    }
+
+    @Override
+    public void saveWorksDraftSuccess() {
+        if (!checkActivityExist()) {
+            return;
+        }
+        mViewModel.getUpdateEvent().setValue(false);
+        if (isNeedFinishPage) {
+            showToastViewAndFinish("保存成功");
+        } else {
+            ToastUtil.getInstance().showShort("保存成功");
+        }
+    }
+
+    private void showToastViewAndFinish(String tip) {
+        //测试lyr提出提示要在当前面提示,所以给出延迟finish
+        viewBinding.tvToastView.setText(tip);
+        viewBinding.tvToastView.setVisibility(View.VISIBLE);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                toFinish();
+            }
+        }, 1500);
+    }
+
+    @Override
+    public void upLoadImageSuccess(String url) {
+        if (!checkActivityExist()) {
+            return;
+        }
+        refreshMusicInfoPreCover(url);
+    }
+
+    private void refreshMusicInfoPreCover(String imgCover) {
+        mViewModel.refreshMusicPreCover(imgCover);
+    }
+
+    private void refreshMusicInfo(String imgCover) {
+        MusicInfoBean bean = new MusicInfoBean();
+        bean.setCover(imgCover);
+        bean.setMusicTitle(mTitle);
+        mViewModel.getMusicInfoLiveData().setValue(bean);
+    }
+
+    @Override
+    public void upLoadImageFailure() {
+        ToastUtil.getInstance().showShort("上传失败,请重试");
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (resultCode == RESULT_OK) {
+            if (requestCode == REQUEST_CODE_LOCAL) {
+                if (data != null) {
+                    // 图片、视频、音频选择结果回调
+                    List<LocalMedia> selectList = PictureSelector.obtainMultipleResult(data);
+                    String v_path = null;
+                    if (selectList != null && selectList.size() > 0) {
+                        v_path = selectList.get(0).getCompressPath();
+                    }
+                    if (!TextUtils.isEmpty(v_path)) {
+                        boolean isImg = MyFileUtils.isImg(v_path);
+                        if (isImg) {
+                            presenter.upLoadImage(MusicHandleActivity.this, v_path);
+                        } else {
+                            ToastUtil.getInstance().showShort("请选择图片类型文件");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        releaseAnim();
+        super.onDestroy();
+        if (mHandler != null) {
+            mHandler.removeCallbacksAndMessages(null);
+        }
+
+        if (player1 != null) {
+            player1.release();
+        }
+        if (player2 != null) {
+            player2.release();
+        }
+    }
+
+    private void releaseAnim() {
+        if (viewBinding != null && viewBinding.musicFrequencyView != null) {
+            viewBinding.musicFrequencyView.release();
+        }
+        if (mRotateAnimation != null) {
+            mRotateAnimation.cancel();
+            mRotateAnimation = null;
+        }
+        if (mAlbumRotationAnimator != null) {
+            mAlbumRotationAnimator.cancel();
+            mAlbumRotationAnimator = null;
+        }
+    }
+
+    @Override
+    public void surfaceCreated(@NonNull SurfaceHolder holder) {
+        LOG.i("surfaceCreated");
+        player1.setSurface(holder);
+        toPlay(getAccompanyPath());
+    }
+
+    @Override
+    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
+        LOG.i("surfaceChanged");
+        player1.setSurface(holder);
+    }
+
+    @Override
+    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+        LOG.i("surfaceDestroyed");
+        if (player1 != null && player1.isPlaying()) {
+            pausePlay();
+        }
+    }
+
+
+    @Override
+    public void finish() {
+        //为了适配华为mata40曲面屏,此页面是横屏,前一页面是竖屏,返回回去的时候会有UI闪动,所以这样处理
+        //观察发现横屏模式时候没有达到曲面最大效果(竖屏可以达到),猜测横屏模式就不是曲面模式了,这个时候回到前面(竖屏)页面,屏幕的最外层的容器可能会变化导致UI闪动
+        checkScreenOrientation();
+        super.finish();
+    }
+
+    @Override
+    public void onBackPressed() {
+        //为了适配华为mata40曲面屏,此页面是横屏,前一页面是竖屏,返回回去的时候会有UI闪动,所以这样处理
+        //观察发现横屏模式时候没有达到曲面最大效果(竖屏可以达到),猜测横屏模式就不是曲面模式了,这个时候回到前面(竖屏)页面,屏幕的最外层的容器可能会变化导致UI闪动
+        checkScreenOrientation();
+        super.onBackPressed();
+    }
+
+    private void checkScreenOrientation() {
+        LOG.i("isNeedResetScreenOrientation:" + isNeedResetScreenOrientation);
+        if (isNeedResetScreenOrientation) {
+            if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+            }
+        }
+    }
+}

+ 480 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleSettingFragment.java

@@ -0,0 +1,480 @@
+package com.cooleshow.musicmerge.ui;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.cooleshow.base.ui.fragment.BaseFragment;
+import com.cooleshow.base.utils.FileUtils;
+import com.cooleshow.base.utils.PermissionUtils;
+import com.cooleshow.base.utils.ToastUtil;
+import com.cooleshow.base.utils.helper.GlideEngine;
+import com.cooleshow.base.widgets.DialogUtil;
+import com.cooleshow.musicmerge.R;
+import com.cooleshow.musicmerge.constants.MusicMergeConfig;
+import com.cooleshow.musicmerge.databinding.FgMusicHandleSettingLayoutBinding;
+import com.cooleshow.musicmerge.helper.MixHelper;
+import com.cooleshow.musicmerge.viewmodel.MusicMergeViewModel;
+import com.cooleshow.musicmerge.widget.UploadCoverTipDialog;
+import com.luck.picture.lib.PictureSelector;
+import com.luck.picture.lib.config.PictureConfig;
+import com.luck.picture.lib.config.PictureMimeType;
+import com.tbruyelle.rxpermissions3.RxPermissions;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * Author by pq, Date on 2023/8/28.
+ */
+public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettingLayoutBinding> implements View.OnClickListener {
+    public static final int MAX_OFFSET_SECTION = 600;
+    public static final int ACCOMPANY_TYPE = 1;
+    public static final int RECORD_TYPE = 2;
+    private String accompanyUrl;
+    private String worksId;
+
+    private OnEventListener mEventListener;
+    private MusicMergeViewModel mViewModel;
+
+    private Runnable mRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mViewBinding != null) {
+                mViewBinding.tvRecordVolumeValue.setVisibility(View.GONE);
+            }
+        }
+    };
+
+    private Runnable mRunnable2 = new Runnable() {
+        @Override
+        public void run() {
+            if (mViewBinding != null) {
+                mViewBinding.tvAccompanyVolumeValue.setVisibility(View.GONE);
+            }
+        }
+    };
+
+    @Override
+    protected FgMusicHandleSettingLayoutBinding getLayoutView() {
+        return FgMusicHandleSettingLayoutBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    protected void initView(View rootView) {
+        mViewBinding.seekBarOffset.setMax(MAX_OFFSET_SECTION);
+        mViewBinding.seekBarOffset.setProgress(MAX_OFFSET_SECTION / 2);
+        formatProgress();
+    }
+
+    @Override
+    protected void initData() {
+        accompanyUrl = getArguments().getString("accompanyUrl");
+        initViewModel();
+        initListener();
+    }
+
+    private void initViewModel() {
+        ViewModelProvider.AndroidViewModelFactory instance =
+                ViewModelProvider.AndroidViewModelFactory
+                        .getInstance(getActivity().getApplication());
+        mViewModel = new ViewModelProvider(getActivity(), instance)
+                .get(MusicMergeViewModel.class);
+        worksId = mViewModel.getWorksId().getValue();
+    }
+
+    private void initListener() {
+        mViewBinding.ivAdd.setOnClickListener(this);
+        mViewBinding.ivReduce.setOnClickListener(this);
+        mViewBinding.tvSaveWorks.setOnClickListener(this);
+        mViewBinding.tvRecord.setOnClickListener(this);
+        mViewBinding.tvSave.setOnClickListener(this);
+        mViewBinding.ivShrinkArrow.setOnClickListener(this);
+        mViewBinding.seekVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (mEventListener != null) {
+                    float value = mViewBinding.seekVolume.getProgress() * 1.0f / mViewBinding.seekVolume.getMax();
+                    mEventListener.onVolumeChange(RECORD_TYPE, value);
+
+                    if (fromUser) {
+                        setUpdateStatus();
+                        if (mViewBinding.tvRecordVolumeValue.getVisibility() == View.GONE) {
+                            mViewBinding.tvRecordVolumeValue.setVisibility(View.VISIBLE);
+                        }
+                        ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) mViewBinding.viewLine.getLayoutParams();
+                        layoutParams.horizontalBias = value;
+                        mViewBinding.viewLine.setLayoutParams(layoutParams);
+                        mViewBinding.tvRecordVolumeValue.setText(String.valueOf(mViewBinding.seekVolume.getProgress()));
+                    }
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                mViewBinding.tvRecordVolumeValue.postDelayed(mRunnable, 200);
+            }
+        });
+
+        mViewBinding.seekVolume2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (mEventListener != null) {
+                    float value = mViewBinding.seekVolume2.getProgress() * 1.0f / mViewBinding.seekVolume2.getMax();
+                    mEventListener.onVolumeChange(ACCOMPANY_TYPE, value);
+
+                    if (fromUser) {
+                        setUpdateStatus();
+                        if (mViewBinding.tvAccompanyVolumeValue.getVisibility() == View.GONE) {
+                            mViewBinding.tvAccompanyVolumeValue.setVisibility(View.VISIBLE);
+                        }
+                        ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) mViewBinding.viewLine2.getLayoutParams();
+                        layoutParams.horizontalBias = value;
+                        mViewBinding.viewLine2.setLayoutParams(layoutParams);
+                        mViewBinding.tvAccompanyVolumeValue.setText(String.valueOf(mViewBinding.seekVolume2.getProgress()));
+                    }
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                mViewBinding.tvAccompanyVolumeValue.postDelayed(mRunnable2, 200);
+            }
+        });
+        mViewBinding.seekBarOffset.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (roundOffProgress(progress)) {
+                    formatProgress();
+                }
+                if (fromUser) {
+                    setUpdateStatus();
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+
+            }
+        });
+    }
+
+    private void setUpdateStatus() {
+        mViewModel.getUpdateEvent().postValue(true);
+    }
+
+    @Override
+    public void onClick(View view) {
+        int id = view.getId();
+        if (id == R.id.tv_save_works) {
+            showUploadCoverImgDialog();
+//            if (checkUploadCover()) {
+//                showUploadCoverImgDialog();
+//            } else {
+//                toCheckDownload(true);
+//            }
+            return;
+        }
+        if (id == R.id.iv_reduce) {
+            changeOffsetValue(false);
+            return;
+        }
+        if (id == R.id.iv_add) {
+            changeOffsetValue(true);
+            return;
+        }
+
+        if (id == R.id.tv_record) {
+            if (getActivity() != null) {
+                getActivity().finish();
+            }
+            return;
+        }
+
+        if (id == R.id.tv_save) {
+            if (mEventListener != null) {
+                mEventListener.saveDraft();
+            }
+//            toCheckDownload(false);
+            return;
+        }
+
+        if (id == R.id.iv_shrink_arrow) {
+            if (mEventListener != null) {
+                mEventListener.hideSetting();
+            }
+            return;
+        }
+    }
+
+
+    private void changeOffsetValue(boolean isAdd) {
+        int value = isAdd ? MAX_OFFSET_SECTION / 60 : -MAX_OFFSET_SECTION / 60;
+        int currentValue = mViewBinding.seekBarOffset.getProgress();
+        currentValue += value;
+        if (currentValue < 0) {
+            currentValue = 0;
+        }
+        if (currentValue > MAX_OFFSET_SECTION) {
+            currentValue = MAX_OFFSET_SECTION;
+        }
+        mViewBinding.seekBarOffset.setProgress(currentValue);
+    }
+
+    @SuppressLint("CheckResult")
+    private void toCheckDownload(boolean isNeedNotify) {
+        new RxPermissions(MusicHandleSettingFragment.this)
+                .request(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                .subscribe(permission -> {
+                    if (permission) {
+                        checkDownload(isNeedNotify);
+                    } else {
+                        DialogUtil.showInCenter(MusicHandleSettingFragment.this.getChildFragmentManager(), com.cooleshow.base.R.layout.accompany_permissions_popu, (holder, dialog) -> {
+                            TextView tvTitle = holder.getView(com.cooleshow.base.R.id.tv_title);
+                            TextView tvContent = holder.getView(com.cooleshow.base.R.id.tv_content);
+                            ImageView btncancel = holder.getView(com.cooleshow.base.R.id.btn_cancel);
+                            ImageView btnCommit = holder.getView(com.cooleshow.base.R.id.btn_commit);
+                            tvTitle.setText("提示");
+                            tvContent.setText("请开储存访问权限");
+                            btncancel.setOnClickListener(view1 -> {
+                                dialog.dismiss();
+
+                            });
+                            btnCommit.setOnClickListener(view1 -> {
+                                PermissionUtils.toSelfSetting(getContext());
+                                dialog.dismiss();
+                            });
+                        });
+                    }
+                });
+    }
+
+    private boolean checkUploadCover() {
+        return TextUtils.isEmpty(worksId);
+    }
+
+    private void showUploadCoverImgDialog() {
+        UploadCoverTipDialog coverTipDialog = new UploadCoverTipDialog();
+        coverTipDialog.setGravity(Gravity.CENTER);
+        coverTipDialog.setHeight(-1);
+        coverTipDialog.setWidth(316);
+        coverTipDialog.setEventListener(new UploadCoverTipDialog.OnEventListener() {
+            @Override
+            public void onUploadCover() {
+                toAlbum();
+            }
+
+            @Override
+            public void onPublish(String des) {
+                mViewModel.refreshMusicDes(des);
+                toCheckDownload(true);
+            }
+        });
+        coverTipDialog.show(getChildFragmentManager(), "");
+    }
+
+    private void toAlbum() {
+        new RxPermissions(this)
+                .request(Manifest.permission.CAMERA,
+                        Manifest.permission.READ_EXTERNAL_STORAGE,
+                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                .subscribe(granted -> {
+                    if (granted) {
+                        goAlbum();
+                    } else {
+                        ToastUtil.getInstance().show(getContext(), "请打开存储和相机权限!");
+                    }
+                });
+    }
+
+    private void goAlbum() {
+        PictureSelector.create(getActivity())
+                .openGallery(PictureMimeType.ofImage())//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio()
+                .loadImageEngine(GlideEngine.createGlideEngine())
+                .theme(com.cooleshow.base.R.style.picture_daya_style)// 主题样式设置 具体参考 values/styles   用法:R .style.picture.white.style
+                .selectionMode(PictureConfig.SINGLE)// 多选 or 单选 PictureConfig.MULTIPLE or PictureConfig.SINGLE
+                .enableCrop(true)// 是否裁剪 true or false
+                .withAspectRatio(1, 1)
+                .cutOutQuality(100)
+                .showCropGrid(false)// 是否显示裁剪矩形网格 圆形裁剪时建议设为false    true or false
+                .compress(true)// 是否压缩 true or false
+                .circleDimmedLayer(false)// 是否圆形裁剪 true or false
+                .forResult(MusicHandleActivity.REQUEST_CODE_LOCAL);
+    }
+
+    private void checkDownload(boolean isNeedNotify) {
+        String downloadSavePath = MixHelper.getInstance().getDownloadSavePath(accompanyUrl);
+        boolean fileExists = FileUtils.isFileExists(downloadSavePath);
+        if (!fileExists) {
+            return;
+        }
+        toMix(isNeedNotify);
+    }
+
+    private float getSeekVolume() {
+        return mViewBinding.seekVolume.getProgress() * 1.0f / mViewBinding.seekVolume.getMax();
+    }
+
+    private float getAccompanySeekVolume() {
+        return mViewBinding.seekVolume2.getProgress() * 1.0f / mViewBinding.seekVolume2.getMax();
+    }
+
+    private void toMix(boolean isNeedNotify) {
+        int i = getOffsetValue();
+        float recordFileVolume = getSeekVolume();
+        float accompanyFileVolume = getAccompanySeekVolume();
+        if (mEventListener != null) {
+            mEventListener.toMix(i, recordFileVolume, accompanyFileVolume, isNeedNotify);
+        }
+    }
+
+    private int formatProgress() {
+        int lastValue = getOffsetValue();
+        if (lastValue > 0) {
+            mViewBinding.tvOffsetResult.setText("已为您对齐演奏伴奏,演奏提前" + lastValue + "毫秒");
+        } else if (lastValue < 0) {
+            mViewBinding.tvOffsetResult.setText("已为您对齐演奏伴奏,演奏延后" + Math.abs(lastValue) + "毫秒");
+        } else {
+            mViewBinding.tvOffsetResult.setText("演奏伴奏没有对齐?试试调整这里");
+        }
+        if (mEventListener != null) {
+            mEventListener.onOffsetValueChange(lastValue);
+        }
+        return lastValue;
+    }
+
+    private int getOffsetValue() {
+        if (mViewBinding != null) {
+            int progress = mViewBinding.seekBarOffset.getProgress();
+            int lastValue = progress - MAX_OFFSET_SECTION / 2;
+            return lastValue;
+        }
+        return 0;
+    }
+
+    private int getProgressFromOffsetValue(int offset) {
+        int progress = offset + MAX_OFFSET_SECTION / 2;
+        return progress;
+    }
+
+    private boolean roundOffProgress(int progress) {
+        int minSection = MAX_OFFSET_SECTION / 60;
+        int result = progress % minSection;
+        if (result == 0) {
+            return true;
+        }
+        mViewBinding.seekBarOffset.setProgress(progress - result);
+        return false;
+    }
+
+    public int getCurrentOffsetValue() {
+        return getOffsetValue();
+    }
+
+    public int getOriginalVolume() {
+        int originalVolume = mViewBinding.seekVolume.getProgress();
+        return originalVolume;
+    }
+
+    public int getAccompanyVolume() {
+        int accompanyVolume = mViewBinding.seekVolume2.getProgress();
+        return accompanyVolume;
+    }
+
+    public String getConfigJson() {
+        if (isDetached()) {
+            return "";
+        }
+        if (!isAdded()) {
+            return "";
+        }
+        int currentOffsetValue = getCurrentOffsetValue();
+        int originalVolume = mViewBinding.seekVolume.getProgress();
+        int accompanyVolume = mViewBinding.seekVolume2.getProgress();
+        JSONObject jsonObject = new JSONObject();
+        try {
+            jsonObject.put(MusicMergeConfig.OFFSET_KEY, currentOffsetValue);
+            jsonObject.put(MusicMergeConfig.ORIGINALVOLUME_KEY, originalVolume);
+            jsonObject.put(MusicMergeConfig.ACCOMPANYVOLUME_KEY, accompanyVolume);
+            return jsonObject.toString();
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    public void setEventListener(OnEventListener eventListener) {
+        mEventListener = eventListener;
+    }
+
+    public void applyConfig(String jsonConfig) {
+        try {
+            JSONObject jsonObject = new JSONObject(jsonConfig);
+            int offset = jsonObject.optInt(MusicMergeConfig.OFFSET_KEY, 0);
+            int originalVolume = jsonObject.optInt(MusicMergeConfig.ORIGINALVOLUME_KEY, 100);
+            int accompanyVolume = jsonObject.optInt(MusicMergeConfig.ACCOMPANYVOLUME_KEY, 100);
+            int progressFromOffsetValue = getProgressFromOffsetValue(offset);
+            mViewBinding.seekBarOffset.setProgress(progressFromOffsetValue);
+            mViewBinding.seekVolume.setProgress(originalVolume);
+            mViewBinding.seekVolume2.setProgress(accompanyVolume);
+
+            mViewBinding.tvRecord.setVisibility(View.GONE);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void setAccompanyUrl(String accompanyUrl) {
+        this.accompanyUrl = accompanyUrl;
+    }
+
+    public interface OnEventListener {
+        void onVolumeChange(int type, float value);
+
+        void onOffsetValueChange(int value);
+
+        void toMix(int offsetValue, float volume1, float volume2, boolean isNeedNotify);
+
+        void hideSetting();
+
+        void saveDraft();
+    }
+
+    @Override
+    public void onDestroy() {
+        removeCallBack(mViewBinding.tvRecordVolumeValue, mRunnable);
+        removeCallBack(mViewBinding.tvAccompanyVolumeValue, mRunnable2);
+        super.onDestroy();
+    }
+
+    private void removeCallBack(View view, Runnable runnable) {
+        Handler handler = view.getHandler();
+        if (handler != null) {
+            handler.removeCallbacks(runnable);
+        }
+    }
+}

+ 33 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/SelectVideoFrameActivity.java

@@ -0,0 +1,33 @@
+package com.cooleshow.musicmerge.ui;
+
+import com.cooleshow.base.ui.activity.BaseActivity;
+import com.cooleshow.base.ui.activity.BaseMVPActivity;
+import com.cooleshow.musicmerge.databinding.AcSelectVideoFrameLayoutBinding;
+import com.cooleshow.musicmerge.presenter.VideoFileHandlePresenter;
+
+/**
+ * Author by pq, Date on 2023/11/8.
+ */
+public class SelectVideoFrameActivity extends BaseMVPActivity<AcSelectVideoFrameLayoutBinding, VideoFileHandlePresenter> {
+    @Override
+    protected void initView() {
+
+    }
+
+    @Override
+    public void initData() {
+        super.initData();
+    }
+
+
+
+    @Override
+    protected AcSelectVideoFrameLayoutBinding getLayoutView() {
+        return AcSelectVideoFrameLayoutBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    protected VideoFileHandlePresenter createPresenter() {
+        return new VideoFileHandlePresenter();
+    }
+}

+ 60 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/viewmodel/MusicMergeViewModel.java

@@ -0,0 +1,60 @@
+package com.cooleshow.musicmerge.viewmodel;
+
+import com.cooleshow.musicmerge.bean.MusicInfoBean;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+/**
+ * Author by pq, Date on 2023/11/2.
+ */
+public class MusicMergeViewModel extends ViewModel {
+    private MutableLiveData<MusicInfoBean> musicInfoLiveData = new MutableLiveData<>();   //
+    private MutableLiveData<String> worksId = new MutableLiveData<>();   //
+    private MutableLiveData<Boolean> updateStatus = new MutableLiveData<>();   //
+
+    public MutableLiveData<MusicInfoBean> getMusicInfoLiveData() {
+        return musicInfoLiveData;
+    }
+
+    public MutableLiveData<String> getWorksId() {
+        return worksId;
+    }
+
+    public MutableLiveData<Boolean> getUpdateEvent() {
+        return updateStatus;
+    }
+
+    public void refreshMusicDesAndPreCover(String des, String url) {
+        MusicInfoBean value = getMusicInfoLiveData().getValue();
+        if (value != null) {
+            value.setDes(des);
+            value.setPreCover(url);
+            getMusicInfoLiveData().setValue(value);
+        }
+    }
+
+    public void refreshMusicDes(String des) {
+        MusicInfoBean value = getMusicInfoLiveData().getValue();
+        if (value != null) {
+            value.setDes(des);
+            getMusicInfoLiveData().setValue(value);
+        }
+    }
+
+    public void refreshMusicPreCover(String preCover) {
+        MusicInfoBean value = getMusicInfoLiveData().getValue();
+        if (value != null) {
+            value.setPreCover(preCover);
+            getMusicInfoLiveData().setValue(value);
+        }
+    }
+
+    public boolean isHasUpdate() {
+        Boolean value = getUpdateEvent().getValue();
+        if (value != null && value) {
+            return true;
+        }
+        return false;
+    }
+}

+ 212 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/widget/MusicFrequencyView.java

@@ -0,0 +1,212 @@
+package com.cooleshow.musicmerge.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.media.MediaPlayer;
+import android.media.audiofx.Visualizer;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.cooleshow.base.utils.LOG;
+import com.cooleshow.base.utils.SizeUtils;
+import com.cooleshow.musicmerge.R;
+
+import androidx.annotation.Nullable;
+
+/**
+ * author by LiuGuo
+ * on 2021/4/13
+ * 自定义组件:音乐频谱显示组件
+ * API-> setMediaPlayer()  外层设置路径 播放之后 自动显示频谱效果
+ */
+public class MusicFrequencyView extends View {
+    private static final String TAG = "MusicFrequencyView";
+    private int LUMP_COUNT = 60;
+    private int LUMP_WIDTH = SizeUtils.dp2px(5);
+    private int LUMP_SPACE = SizeUtils.dp2px(3);
+    private int LUMP_SIZE = LUMP_WIDTH + LUMP_SPACE;
+    private int LUMP_MAX_HEIGHT = SizeUtils.dp2px(80);
+    private int LUMP_MIN_HEIGHT = SizeUtils.dp2px(5);
+    private float SCALE = 1.0f;
+    private int widthsize;
+    private int heightsize;
+    private Paint paint;
+    private Visualizer visualizer;
+    private int itemColor = Color.WHITE;
+    private Paint paint1;
+    private float mi;
+
+    public MusicFrequencyView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.musicView);
+//        itemColor = ta.getColor(R.styleable.musicView_itemColor, Color.parseColor("#269EFE"));
+        itemColor = ta.getColor(R.styleable.musicView_itemColor, Color.WHITE);
+    }
+
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        widthsize = MeasureSpec.getSize(widthMeasureSpec);
+        heightsize = MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(widthsize, heightsize);
+        init();
+    }
+
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+//        Log.e(TAG, "" + heightsize + "==" + widthsize);
+        if (waveData != null) {
+//            Log.e(TAG, "waveData" + waveData.length);
+        }
+        for (int i = 0; i < LUMP_COUNT; i++) {
+            if (waveData == null) {
+                canvas.drawRect((LUMP_WIDTH + LUMP_SPACE) * i,
+                        LUMP_MAX_HEIGHT - LUMP_MIN_HEIGHT,
+                        (LUMP_WIDTH + LUMP_SPACE) * i + LUMP_WIDTH,
+                        LUMP_MAX_HEIGHT,
+                        paint1);
+                continue;
+            }
+            drawLump(canvas, i, false);
+        }
+    }
+
+
+    private Visualizer.OnDataCaptureListener dataCaptureListener = new Visualizer.OnDataCaptureListener() {
+        @Override
+        public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
+//            Log.i("xiaozhu", "waveform" + waveform.length);
+            long v = 0;
+            for (int i = 0; i < waveform.length; i++) {
+                v += Math.pow(waveform[i], 2);
+            }
+            double volume = 10 * Math.log10(v / (double) waveform.length);
+        }
+
+        @Override
+        public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
+            setWaveData(fft);
+        }
+    };
+
+    private byte[] waveData;
+
+    private void setWaveData(byte[] fft) {
+        waveData = readyData(fft);
+//        LOG.i(TAG, "setWaveData:" + waveData);
+        invalidate();
+    }
+
+    /**
+     * 绘制矩形条
+     */
+    private void drawLump(Canvas canvas, int i, boolean reversal) {
+        int minus = reversal ? -1 : 1;
+
+        if (waveData[i] < 0) {
+//            LOG.i(TAG, "waveData[i] < 0 data: %s" + waveData[i]);
+        }
+        float top = (LUMP_MAX_HEIGHT - (LUMP_MIN_HEIGHT + waveData[i] * SCALE) * minus);
+
+
+        canvas.drawLine(LUMP_SIZE * i, LUMP_MAX_HEIGHT,
+                LUMP_SIZE * i, top, paint1);
+//        canvas.drawRect(LUMP_SIZE * i,
+//                top,
+//                LUMP_SIZE * i + LUMP_WIDTH,
+//                LUMP_MAX_HEIGHT,
+//                paint1);
+    }
+
+    /**
+     * 预处理数据
+     *
+     * @return
+     */
+    private byte[] readyData(byte[] fft) {
+        byte[] newData = new byte[LUMP_COUNT];
+        byte abs;
+        for (int i = 0; i < LUMP_COUNT; i++) {
+            abs = (byte) Math.abs(fft[i]);
+            //描述:Math.abs -128时越界
+            newData[i] = abs < 0 ? 127 : abs;
+        }
+        return newData;
+    }
+
+    /**
+     * 只需传入 mediaPlayer 即可
+     *
+     * @param mediaPlayer
+     */
+    public void setMediaPlayer(final MediaPlayer mediaPlayer) {
+        if (visualizer == null) {
+            visualizer = new Visualizer(mediaPlayer.getAudioSessionId());
+
+            int captureSize = Visualizer.getCaptureSizeRange()[1];
+            int captureRate = Visualizer.getMaxCaptureRate() * 3 / 4;
+            // 3:设置参数
+            visualizer.setCaptureSize(captureSize);
+            visualizer.setDataCaptureListener(dataCaptureListener, captureRate, true, true);
+            visualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED);
+            visualizer.setEnabled(true);
+        }
+    }
+
+    public void setVisualizerEnable(boolean isEnable) {
+        if (visualizer != null) {
+            visualizer.setEnabled(isEnable);
+        }
+    }
+
+    public void release() {
+        if (visualizer != null) {
+            visualizer.release();
+        }
+    }
+
+
+    /**
+     * 设置频率数据
+     *
+     * @param data
+     */
+    public void setMusicData(int[] data) {
+
+    }
+
+    /**
+     * 初始化画笔
+     */
+    void init() {
+        setLayerType(LAYER_TYPE_SOFTWARE, null); //部分手机不显示阴影效果 配合setShadowLayer
+        paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setColor(itemColor);
+        paint.setStyle(Paint.Style.FILL);
+        paint.setFilterBitmap(true);
+        paint.setShadowLayer(2, 9, 5, Color.parseColor("#55000000"));
+        paint.setStrokeWidth(heightsize / 100f);
+
+
+        paint1 = new Paint();
+        paint1.setAntiAlias(true);
+        paint1.setColor(itemColor);
+        paint1.setStrokeCap(Paint.Cap.ROUND);
+        paint1.setTextAlign(Paint.Align.LEFT);
+        paint1.setStyle(Paint.Style.FILL);
+//        paint1.setStrokeWidth(heightsize / 100f);
+        paint1.setStrokeWidth(LUMP_WIDTH);
+
+
+    }
+}

+ 177 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/widget/UploadCoverTipDialog.java

@@ -0,0 +1,177 @@
+package com.cooleshow.musicmerge.widget;
+
+import android.content.DialogInterface;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.cooleshow.base.utils.GlideImageLoaderUtils;
+import com.cooleshow.base.widgets.BaseDialog;
+import com.cooleshow.base.widgets.ViewHolder;
+import com.cooleshow.musicmerge.R;
+import com.cooleshow.musicmerge.bean.MusicInfoBean;
+import com.cooleshow.musicmerge.viewmodel.MusicMergeViewModel;
+import com.cooleshow.usercenter.helper.UserHelper;
+
+import java.util.Locale;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * Author by pq, Date on 2023/11/2.
+ */
+public class UploadCoverTipDialog extends BaseDialog implements View.OnClickListener {
+    private ImageView mIvClose;
+    private TextView mTvPublish;
+    private EditText mEtContent;
+
+    private TextWatcher mTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            int cLength = mEtContent.getText().toString().trim().length();
+            mTvTextNum.setText(String.format(Locale.getDefault(), "%d/150", cLength));
+        }
+    };
+    private TextView mTvTextNum;
+    private ImageView mIvIcon;
+    private TextView mTvMusicTitle;
+    private TextView mTvName;
+    private MusicMergeViewModel mViewModel;
+
+    @Override
+    public int intLayoutId() {
+        return R.layout.dialog_upload_cover_tip_layout;
+    }
+
+    @Override
+    public void convertView(ViewHolder holder, BaseDialog dialog) {
+        mIvClose = holder.getView(R.id.iv_close);
+        mTvPublish = holder.getView(R.id.tv_publish);
+        mEtContent = holder.getView(R.id.et_content);
+        mTvTextNum = holder.getView(R.id.tv_text_num);
+        mTvMusicTitle = holder.getView(R.id.tv_music_title);
+        mTvName = holder.getView(R.id.tv_name);
+        mIvIcon = holder.getView(R.id.iv_icon);
+        String userName = UserHelper.getUserName();
+        mTvName.setText(userName);
+        setCancelable(false);
+        setOutCancel(false);
+        initViewModel();
+        initListener();
+    }
+
+    private void initViewModel() {
+        ViewModelProvider.AndroidViewModelFactory instance =
+                ViewModelProvider.AndroidViewModelFactory
+                        .getInstance(getActivity().getApplication());
+        mViewModel = new ViewModelProvider(getActivity(), instance)
+                .get(MusicMergeViewModel.class);
+
+        mViewModel.getMusicInfoLiveData().observe(this, new Observer<MusicInfoBean>() {
+            @Override
+            public void onChanged(MusicInfoBean musicInfoBean) {
+                if (musicInfoBean != null) {
+                    String imgCover = musicInfoBean.getPreCover() != null ? musicInfoBean.getPreCover() : musicInfoBean.getCover();
+                    setData(imgCover, musicInfoBean.getMusicTitle(), musicInfoBean.getDes());
+                }
+            }
+        });
+    }
+
+    private void initListener() {
+        mIvClose.setOnClickListener(this);
+        mTvPublish.setOnClickListener(this);
+        mIvIcon.setOnClickListener(this);
+
+        mEtContent.addTextChangedListener(mTextWatcher);
+    }
+
+    public void setData(String cover, String musicName, String des) {
+        if (mTvMusicTitle != null) {
+            mTvMusicTitle.setText(musicName);
+        }
+        if (mEtContent != null) {
+            mEtContent.setText(des);
+        }
+        if (mIvIcon != null) {
+            GlideImageLoaderUtils.getInstance().loadImage(getContext(), cover, mIvIcon, R.drawable.icon_default_music_song_cover);
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        int id = v.getId();
+        if (id == R.id.iv_close) {
+            saveDes("", null);
+            dismiss();
+            return;
+        }
+
+        if (id == R.id.iv_icon) {
+            if (mEventListener != null) {
+                mEventListener.onUploadCover();
+            }
+        }
+
+        if (id == R.id.tv_publish) {
+            String trim = mEtContent.getText().toString().trim();
+            dismiss();
+            if (mEventListener != null) {
+                if (TextUtils.isEmpty(trim)) {
+                    trim = "我发布了一首演奏作品,快来听听吧~";
+                }
+                mEventListener.onPublish(trim);
+            }
+            return;
+        }
+    }
+
+    @Override
+    public void onDismiss(@NonNull DialogInterface dialog) {
+        super.onDismiss(dialog);
+    }
+
+    private void saveDes(String des, String url) {
+        if (mEtContent != null) {
+            mViewModel.refreshMusicDesAndPreCover(des, url);
+        }
+    }
+
+    private OnEventListener mEventListener;
+
+    public void setEventListener(OnEventListener eventListener) {
+        mEventListener = eventListener;
+    }
+
+    public interface OnEventListener {
+        void onUploadCover();
+
+        void onPublish(String des);
+    }
+
+
+    @Override
+    public void onDestroy() {
+        if (mEtContent != null) {
+            mEtContent.removeTextChangedListener(mTextWatcher);
+            mTextWatcher = null;
+        }
+        super.onDestroy();
+    }
+}

BIN
musicMerge/src/main/res/drawable-xhdpi/bg_music_merge_gramophone.png


BIN
musicMerge/src/main/res/drawable-xhdpi/bg_offset_progress.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_ablum_tag.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_album.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_offset_add.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_offset_reduce.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_pause.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_pause_white.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_play.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_play_white.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_pointer.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_music_merge_shadow.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_rerecord.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_save.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_seekbar_value_bg.png


BIN
musicMerge/src/main/res/drawable-xhdpi/icon_shrink_setting_arrow.png


BIN
musicMerge/src/main/res/drawable-xhdpi/tc_icon_left_arrow.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/bg_music_merge_gramophone.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/bg_musicmerge.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/bg_offset_progress.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_default_music_song_cover.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_ablum_tag.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_album.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_offset_add.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_offset_reduce.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_pause.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_pause_white.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_play.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_play_white.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_pointer.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_music_merge_shadow.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_offset_seekbar_thumb.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_rerecord.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_save.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_seekbar_value_bg.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_shrink_setting_arrow.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/tc_icon_left_arrow.png


+ 7 - 0
musicMerge/src/main/res/drawable/shape_000000_to_70000000.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient android:startColor="#00000000"
+        android:angle="270"
+        android:endColor="#70000000"/>
+
+</shape>

+ 5 - 0
musicMerge/src/main/res/drawable/shape_269efe_18dp.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/main_style_color"/>
+    <corners android:radius="18dp"/>
+</shape>

+ 7 - 0
musicMerge/src/main/res/drawable/shape_70000000_to_tran.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient android:startColor="#70000000"
+        android:angle="270"
+        android:endColor="@color/transparent"/>
+
+</shape>

+ 5 - 0
musicMerge/src/main/res/drawable/shape_dfe7f2_2dp.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#DFE7F2"/>
+    <corners android:radius="2dp"/>
+</shape>

+ 6 - 0
musicMerge/src/main/res/drawable/shape_left_16dp_white.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/white"/>
+    <corners android:topLeftRadius="16dp"
+        android:bottomLeftRadius="16dp"/>
+</shape>

+ 7 - 0
musicMerge/src/main/res/drawable/shape_offset_seekbar_thumb.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/main_style_color" />
+    <size
+        android:width="2dp"
+        android:height="15dp" />
+</shape>

+ 27 - 0
musicMerge/src/main/res/drawable/shape_play_progress_seekbar_bg.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- 背景 -->
+    <item android:id="@android:id/background">
+        <shape>
+            <!-- 圆角 -->
+            <corners android:radius="2dp" />
+            <!--            背景色-->
+            <solid android:color="@color/white"/>
+            <size android:height="4dp"/>
+        </shape>
+    </item>
+    <!--    滑动条-->
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape>
+                <!-- 圆角 -->
+                <corners android:radius="2dp" />
+                <gradient android:startColor="@color/main_style_color"
+                    android:endColor="@color/main_style_color"/>
+                <!--            背景色-->
+<!--                <solid android:color="@color/color_2dc7aa"/>-->
+                <size android:height="4dp"/>
+            </shape>
+        </clip>
+    </item>
+</layer-list>

+ 27 - 0
musicMerge/src/main/res/drawable/shape_play_progress_seekbar_bg2.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- 背景 -->
+    <item android:id="@android:id/background">
+        <shape>
+            <!-- 圆角 -->
+            <corners android:radius="2dp" />
+            <!--            背景色-->
+            <solid android:color="#4dffffff"/>
+            <size android:height="4dp"/>
+        </shape>
+    </item>
+    <!--    滑动条-->
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape>
+                <!-- 圆角 -->
+                <corners android:radius="2dp" />
+                <gradient android:startColor="@color/white"
+                    android:endColor="@color/white"/>
+                <!--            背景色-->
+<!--                <solid android:color="@color/color_2dc7aa"/>-->
+                <size android:height="4dp"/>
+            </shape>
+        </clip>
+    </item>
+</layer-list>

+ 8 - 0
musicMerge/src/main/res/drawable/shape_volume_progreesbar_thumb.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="@color/main_style_color" />
+    <size
+        android:width="18dp"
+        android:height="18dp" />
+</shape>

+ 8 - 0
musicMerge/src/main/res/drawable/shape_volume_progreesbar_thumb2.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="@color/white" />
+    <size
+        android:width="12dp"
+        android:height="12dp" />
+</shape>

+ 27 - 0
musicMerge/src/main/res/drawable/shape_volume_seekbar_bg.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- 背景 -->
+    <item android:id="@android:id/background">
+        <shape>
+            <!-- 圆角 -->
+            <corners android:radius="2dp" />
+            <!--            背景色-->
+            <solid android:color="#E8ECF1"/>
+            <size android:height="4dp"/>
+        </shape>
+    </item>
+    <!--    滑动条-->
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape>
+                <!-- 圆角 -->
+                <corners android:radius="2dp" />
+                <gradient android:startColor="@color/main_style_color"
+                    android:endColor="@color/main_style_color"/>
+                <!--            背景色-->
+<!--                <solid android:color="@color/color_2dc7aa"/>-->
+                <size android:height="4dp"/>
+            </shape>
+        </clip>
+    </item>
+</layer-list>

+ 337 - 0
musicMerge/src/main/res/layout/ac_music_handle_layout.xml

@@ -0,0 +1,337 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/bg_musicmerge">
+
+    <View
+        android:id="@+id/view_video_bg"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/black"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <FrameLayout
+        android:id="@+id/fl_setting"
+        android:layout_width="297dp"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+
+    <androidx.constraintlayout.widget.Group
+        android:id="@+id/group_setting"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="visible"
+        app:constraint_referenced_ids="fl_setting,fl_setting" />
+
+
+    <View
+        android:layout_marginBottom="12dp"
+        android:id="@+id/view_center_point"
+        app:layout_constraintRight_toRightOf="@+id/fl_ablum"
+        app:layout_constraintLeft_toLeftOf="@+id/fl_ablum"
+        app:layout_constraintBottom_toBottomOf="@+id/fl_ablum"
+        app:layout_constraintTop_toTopOf="@+id/fl_ablum"
+        android:layout_width="1px"
+        android:layout_height="1px"/>
+
+    <ImageView
+        android:id="@+id/iv_music_merge_shadow"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/icon_music_merge_shadow"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_gramophone_bg"
+        app:layout_constraintRight_toRightOf="@+id/iv_gramophone_bg"
+        app:layout_constraintTop_toBottomOf="@+id/view_center_point" />
+
+
+    <com.cooleshow.musicmerge.widget.MusicFrequencyView
+        android:layout_width="358dp"
+        android:id="@+id/music_frequency_view"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintBottom_toBottomOf="@+id/fl_ablum"
+        app:layout_constraintTop_toTopOf="@+id/fl_ablum"
+        android:layout_height="80dp"/>
+    
+    <ImageView
+        android:id="@+id/iv_gramophone_bg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="15dp"
+        android:src="@drawable/bg_music_merge_gramophone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting"
+        app:layout_constraintTop_toTopOf="parent" />
+
+
+    <FrameLayout
+        android:id="@+id/fl_ablum"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="15dp"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_gramophone_bg"
+        app:layout_constraintRight_toRightOf="@+id/iv_gramophone_bg"
+        app:layout_constraintTop_toTopOf="@+id/iv_gramophone_bg">
+
+        <ImageView
+            android:id="@+id/iv_album"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/icon_music_merge_album"
+            app:layout_constraintLeft_toLeftOf="@+id/iv_gramophone_bg"
+            app:layout_constraintRight_toRightOf="@+id/iv_gramophone_bg"
+            app:layout_constraintTop_toTopOf="@+id/iv_gramophone_bg" />
+
+        <de.hdodenhof.circleimageview.CircleImageView
+            android:id="@+id/iv_cover"
+            android:layout_width="79dp"
+            android:layout_height="79dp"
+            android:layout_gravity="center"
+            android:src="@drawable/icon_default_music_song_cover"
+            app:civ_border_color="@color/color_48494b"
+            app:civ_border_width="5dp"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_album"
+            app:layout_constraintLeft_toLeftOf="@+id/iv_album"
+            app:layout_constraintRight_toRightOf="@+id/iv_album"
+            app:layout_constraintTop_toTopOf="@+id/iv_album" />
+
+        <ImageView
+            android:id="@+id/iv_ablum_tag"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/icon_music_merge_ablum_tag"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_cover"
+            app:layout_constraintLeft_toLeftOf="@+id/iv_cover"
+            app:layout_constraintRight_toRightOf="@+id/iv_cover"
+            app:layout_constraintTop_toTopOf="@+id/iv_cover" />
+    </FrameLayout>
+
+    <View
+        android:id="@+id/view_line"
+        android:layout_width="1px"
+        android:layout_height="1px"
+        android:layout_marginTop="15dp"
+        android:layout_marginEnd="20dp"
+        app:layout_constraintRight_toRightOf="@+id/iv_gramophone_bg"
+        app:layout_constraintTop_toTopOf="@+id/iv_gramophone_bg" />
+
+    <ImageView
+        android:id="@+id/iv_play_pointer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="21dp"
+        android:rotation="92"
+        android:src="@drawable/icon_music_merge_pointer"
+        app:layout_constraintBottom_toBottomOf="@+id/view_line"
+        app:layout_constraintLeft_toLeftOf="@+id/view_line"
+        app:layout_constraintRight_toRightOf="@+id/view_line"
+        app:layout_constraintTop_toTopOf="@+id/view_line" />
+
+    <androidx.constraintlayout.widget.Group
+        android:id="@+id/group_audio_view"
+        app:constraint_referenced_ids="iv_play_pointer,fl_ablum,iv_gramophone_bg,music_frequency_view,iv_music_merge_shadow"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <FrameLayout
+        android:id="@+id/fl_surface"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting"
+        app:layout_constraintTop_toTopOf="parent">
+        
+    </FrameLayout>
+
+    <ImageView
+        android:id="@+id/iv_unfold_sentting"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="18dp"
+        android:src="@drawable/tc_icon_left_arrow"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting"
+        app:layout_constraintTop_toTopOf="parent" />
+
+
+    <View
+        android:id="@+id/view_video_bottom_bg"
+        android:visibility="gone"
+        app:layout_constraintRight_toRightOf="@+id/fl_surface"
+        app:layout_constraintLeft_toLeftOf="@+id/fl_surface"
+        app:layout_constraintBottom_toBottomOf="@+id/fl_surface"
+        android:background="@drawable/shape_000000_to_70000000"
+        android:layout_width="0dp"
+        android:layout_height="80dp"/>
+
+    <View
+        android:id="@+id/view_video_top_bg"
+        android:visibility="gone"
+        app:layout_constraintRight_toRightOf="@+id/fl_surface"
+        app:layout_constraintLeft_toLeftOf="@+id/fl_surface"
+        app:layout_constraintTop_toTopOf="@+id/fl_surface"
+        android:background="@drawable/shape_70000000_to_tran"
+        android:layout_width="0dp"
+        android:layout_height="80dp"/>
+
+    <ImageView
+        android:id="@+id/iv_play"
+        android:layout_width="42dp"
+        android:layout_height="42dp"
+        android:layout_marginStart="28dp"
+        android:layout_marginBottom="29dp"
+        android:padding="12dp"
+        android:src="@drawable/icon_music_merge_play"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent" />
+
+    <TextView
+        android:gravity="center"
+        android:id="@+id/tv_current_progress"
+        android:layout_width="42dp"
+        android:layout_height="wrap_content"
+        android:text="00:00"
+        android:textColor="@color/color_131415"
+        android:textSize="14dp"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_play"
+        app:layout_constraintLeft_toRightOf="@+id/iv_play"
+        app:layout_constraintTop_toTopOf="@+id/iv_play" />
+
+    <androidx.appcompat.widget.AppCompatSeekBar
+        android:id="@+id/seek_play"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="@null"
+        android:max="100"
+        android:maxHeight="4dp"
+        android:minHeight="4dp"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
+        android:progress="0"
+        android:progressDrawable="@drawable/shape_play_progress_seekbar_bg"
+        android:splitTrack="false"
+        android:thumb="@drawable/shape_volume_progreesbar_thumb"
+        android:visibility="visible"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_current_progress"
+        app:layout_constraintLeft_toRightOf="@+id/tv_current_progress"
+        app:layout_constraintRight_toLeftOf="@+id/tv_total_progress"
+        app:layout_constraintTop_toTopOf="@+id/tv_current_progress" />
+
+    <TextView
+        android:gravity="center"
+        android:id="@+id/tv_total_progress"
+        android:layout_width="42dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="40dp"
+        android:text="00:00"
+        android:textColor="@color/color_131415"
+        android:textSize="14dp"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_play"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting"
+        app:layout_constraintTop_toTopOf="@+id/iv_play" />
+
+
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/tv_current_progress2"
+        android:layout_width="40dp"
+        android:layout_height="wrap_content"
+        android:text="00:00"
+        android:textColor="@color/color_131415"
+        android:textSize="14dp"
+        app:layout_constraintBottom_toBottomOf="@+id/seek_play2"
+        app:layout_constraintLeft_toRightOf="@+id/iv_play"
+        app:layout_constraintTop_toTopOf="@+id/seek_play2" />
+
+    <androidx.appcompat.widget.AppCompatSeekBar
+        android:id="@+id/seek_play2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:background="@null"
+        android:max="100"
+        android:maxHeight="4dp"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
+        android:progress="0"
+        android:progressDrawable="@drawable/shape_play_progress_seekbar_bg"
+        android:splitTrack="false"
+        android:thumb="@drawable/shape_volume_progreesbar_thumb"
+        android:visibility="gone"
+        app:layout_constraintLeft_toRightOf="@+id/tv_current_progress"
+        app:layout_constraintRight_toLeftOf="@+id/tv_total_progress"
+        app:layout_constraintTop_toBottomOf="@+id/seek_play" />
+
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/tv_total_progress2"
+        android:layout_width="40dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="20dp"
+        android:text="00:00"
+        android:textColor="@color/color_131415"
+        android:textSize="14dp"
+        app:layout_constraintBottom_toBottomOf="@+id/seek_play2"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting"
+        app:layout_constraintTop_toTopOf="@+id/seek_play2" />
+
+
+    <ImageView
+        android:id="@+id/iv_back"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingStart="20dp"
+        android:paddingTop="20dp"
+        android:paddingEnd="12dp"
+        android:paddingBottom="20dp"
+        android:src="@drawable/icon_back_black"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_131415"
+        android:textSize="@dimen/sp_16"
+        android:textStyle="bold"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_back"
+        app:layout_constraintLeft_toRightOf="@+id/iv_back"
+        app:layout_constraintTop_toTopOf="@+id/iv_back"
+        tools:text="悬崖上的金鱼姬" />
+
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/tv_toast_view"
+        android:paddingEnd="26dp"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:paddingStart="26dp"
+        tools:text="发布成功"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        android:textSize="@dimen/sp_15"
+        android:textColor="@color/white"
+        android:background="@drawable/shape_toast"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 6 - 0
musicMerge/src/main/res/layout/ac_select_video_frame_layout.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 187 - 0
musicMerge/src/main/res/layout/dialog_upload_cover_tip_layout.xml

@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:layout_gravity="center">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="316dp"
+        android:background="@drawable/shape_f8f8f8_10dp"
+        android:layout_height="wrap_content">
+
+
+        <TextView
+            android:id="@+id/tv_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="15dp"
+            android:paddingBottom="15dp"
+            android:text="作品详情"
+            android:textColor="@color/color_131415"
+            android:textSize="@dimen/sp_16"
+            android:textStyle="bold"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <ImageView
+            android:id="@+id/iv_close"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10dp"
+            android:src="@drawable/icon_close_dialog"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <FrameLayout
+            android:id="@+id/fl_input"
+            android:layout_width="match_parent"
+            android:layout_height="87dp"
+            android:layout_marginStart="12dp"
+            android:layout_marginEnd="12dp"
+            android:background="@drawable/bg_white_10dp"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/tv_title">
+
+
+            <androidx.appcompat.widget.AppCompatEditText
+                android:id="@+id/et_content"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginBottom="27dp"
+                android:background="@color/transparent"
+                android:gravity="left"
+                android:theme="@style/MyEditText"
+                android:hint="我发布了一首演奏作品,快来听听吧~"
+                android:maxLength="150"
+                android:paddingLeft="12dp"
+                android:paddingTop="12dp"
+                android:paddingRight="12dp"
+                android:textColor="@color/black_333"
+                android:textColorHint="@color/color_aaaaaa"
+                android:textSize="@dimen/sp_14"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+
+            <TextView
+                android:id="@+id/tv_text_num"
+                android:layout_width="wrap_content"
+                android:layout_height="20dp"
+                android:layout_marginEnd="12dp"
+                android:layout_marginBottom="7dp"
+                android:text="0/150"
+                android:gravity="center"
+                android:layout_gravity="bottom|right"
+                android:textColor="@color/color_aaaaaa"
+                android:textSize="@dimen/sp_12"
+                app:layout_constraintBottom_toBottomOf="@+id/fl_input"
+                app:layout_constraintRight_toRightOf="@+id/fl_input" />
+        </FrameLayout>
+
+
+        <View
+            android:id="@+id/view_info_bg"
+            android:layout_width="match_parent"
+            android:layout_height="86dp"
+            android:layout_marginStart="12dp"
+            android:layout_marginTop="12dp"
+            android:layout_marginEnd="12dp"
+            android:background="@drawable/bg_white_10dp"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/fl_input" />
+
+        <View
+            android:layout_width="61dp"
+            android:layout_height="61dp"
+            android:layout_marginStart="7dp"
+            android:background="@drawable/shape_434343_to_666666_round"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_icon"
+            app:layout_constraintLeft_toLeftOf="@+id/iv_icon"
+            app:layout_constraintTop_toTopOf="@+id/iv_icon" />
+
+
+        <com.cooleshow.base.widgets.QMUIRadiusImageView
+            android:id="@+id/iv_icon"
+            android:layout_width="62dp"
+            android:layout_height="62dp"
+            android:layout_marginStart="12dp"
+            android:src="@drawable/icon_default_music_song_cover"
+            app:layout_constraintBottom_toBottomOf="@+id/view_info_bg"
+            app:layout_constraintLeft_toLeftOf="@+id/view_info_bg"
+            app:layout_constraintTop_toTopOf="@+id/view_info_bg"
+            app:layout_goneMarginStart="0dp"
+            app:qmui_corner_radius="8dp" />
+
+        <TextView
+            android:background="@drawable/shape_5e000000_bottom_5dp"
+            android:includeFontPadding="false"
+            android:gravity="center"
+            android:textColor="@color/white"
+            android:textSize="@dimen/sp_13"
+            android:text="选封面"
+            app:layout_constraintRight_toRightOf="@+id/iv_icon"
+            app:layout_constraintLeft_toLeftOf="@+id/iv_icon"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_icon"
+            android:layout_width="0dp"
+            android:layout_height="19dp"/>
+
+        <TextView
+            android:layout_marginEnd="12dp"
+            android:maxLines="1"
+            android:ellipsize="end"
+            app:layout_constraintRight_toRightOf="@+id/view_info_bg"
+            android:id="@+id/tv_music_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="15dp"
+            android:textColor="@color/color_131415"
+            android:textSize="@dimen/sp_16"
+            android:textStyle="bold"
+            android:includeFontPadding="false"
+            app:layout_constraintBottom_toTopOf="@+id/tv_name"
+            app:layout_constraintLeft_toRightOf="@+id/iv_icon"
+            app:layout_constraintTop_toTopOf="@+id/iv_icon"
+            tools:text="山茶花读不懂白玫瑰" />
+
+        <TextView
+            android:layout_marginEnd="12dp"
+            android:maxLines="1"
+            android:ellipsize="end"
+            app:layout_constraintRight_toRightOf="@+id/view_info_bg"
+            android:id="@+id/tv_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            android:drawablePadding="5dp"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_777777"
+            android:textSize="@dimen/sp_14"
+            app:layout_constraintBottom_toBottomOf="@+id/iv_icon"
+            app:layout_constraintLeft_toLeftOf="@+id/tv_music_title"
+            app:layout_constraintTop_toBottomOf="@+id/tv_music_title"
+            tools:text="张子珊" />
+
+
+        <TextView
+            android:id="@+id/tv_publish"
+            android:layout_width="220dp"
+            android:layout_height="38dp"
+            android:layout_marginTop="18dp"
+            android:layout_marginBottom="18dp"
+            android:background="@drawable/shape_login_bt_bg"
+            android:gravity="center"
+            android:text="发布作品"
+            android:textColor="@color/white"
+            android:textStyle="bold"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/view_info_bg" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</LinearLayout>

+ 302 - 0
musicMerge/src/main/res/layout/fg_music_handle_setting_layout.xml

@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:layout_width="300dp"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:background="@drawable/shape_left_16dp_white">
+
+
+    <TextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="14dp"
+        android:text="音量调节"
+        android:textColor="@color/color_131415"
+        android:textSize="@dimen/sp_16"
+        android:textStyle="bold"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <View
+        android:background="@color/color_f2f2f2"
+        app:layout_constraintLeft_toLeftOf="parent"
+        android:layout_marginTop="13dp"
+        app:layout_constraintTop_toBottomOf="@+id/tv_title"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"/>
+
+    <TextView
+        android:id="@+id/tv_title2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="25dp"
+        android:layout_marginStart="30dp"
+        android:text="演奏音量"
+        android:textColor="@color/color_131415"
+        android:textSize="@dimen/sp_14"
+        android:includeFontPadding="false"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tv_title"/>
+
+        <View
+            android:layout_marginStart="6dp"
+            android:layout_marginEnd="6dp"
+            android:id="@+id/view_line"
+            app:layout_constraintRight_toRightOf="@+id/seek_volume"
+            app:layout_constraintLeft_toLeftOf="@+id/seek_volume"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            android:layout_width="1px"
+            android:layout_height="1px"/>
+
+        <TextView
+            android:visibility="gone"
+            android:id="@+id/tv_record_volume_value"
+            android:textStyle="bold"
+            android:textColor="@color/color_131415"
+            android:gravity="center_horizontal"
+            android:layout_marginBottom="5dp"
+            android:paddingTop="3dp"
+            app:layout_constraintBottom_toTopOf="@+id/seek_volume"
+            android:background="@drawable/icon_seekbar_value_bg"
+            app:layout_constraintRight_toRightOf="@+id/view_line"
+            app:layout_constraintLeft_toLeftOf="@+id/view_line"
+            android:text="100"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+
+    <androidx.appcompat.widget.AppCompatSeekBar
+        android:layout_marginStart="24dp"
+        android:layout_marginEnd="28dp"
+        android:id="@+id/seek_volume"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:max="100"
+        android:background="@null"
+        android:maxHeight="4dp"
+        android:progress="100"
+        android:paddingEnd="9dp"
+        android:paddingStart="9dp"
+        android:progressDrawable="@drawable/shape_volume_seekbar_bg"
+        android:splitTrack="false"
+        android:thumb="@drawable/shape_volume_progreesbar_thumb"
+        android:visibility="visible"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tv_title2" />
+
+    <TextView
+        android:id="@+id/tv_title3"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="23dp"
+        android:text="伴奏音量"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_131415"
+        android:textSize="@dimen/sp_14"
+        app:layout_constraintLeft_toLeftOf="@+id/tv_title2"
+        app:layout_constraintTop_toBottomOf="@+id/seek_volume" />
+
+    <androidx.appcompat.widget.AppCompatSeekBar
+        android:layout_marginStart="24dp"
+        android:layout_marginEnd="28dp"
+        android:paddingEnd="9dp"
+        android:paddingStart="9dp"
+        android:id="@+id/seek_volume2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:max="100"
+        android:background="@null"
+        android:maxHeight="4dp"
+        android:progress="100"
+        android:progressDrawable="@drawable/shape_volume_seekbar_bg"
+        android:splitTrack="false"
+        android:thumb="@drawable/shape_volume_progreesbar_thumb"
+        android:visibility="visible"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tv_title3" />
+
+    <View
+        android:layout_marginStart="6dp"
+        android:layout_marginEnd="6dp"
+        android:id="@+id/view_line2"
+        app:layout_constraintRight_toRightOf="@+id/seek_volume2"
+        app:layout_constraintLeft_toLeftOf="@+id/seek_volume2"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintHorizontal_bias="1"
+        android:layout_width="1px"
+        android:layout_height="1px"/>
+
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/tv_accompany_volume_value"
+        android:textStyle="bold"
+        android:textColor="@color/color_131415"
+        android:gravity="center_horizontal"
+        android:layout_marginBottom="5dp"
+        android:paddingTop="3dp"
+        app:layout_constraintBottom_toTopOf="@+id/seek_volume2"
+        android:background="@drawable/icon_seekbar_value_bg"
+        app:layout_constraintRight_toRightOf="@+id/view_line2"
+        app:layout_constraintLeft_toLeftOf="@+id/view_line2"
+        android:text="100"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:includeFontPadding="false"
+        android:id="@+id/tv_title4"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:text="伴奏对齐"
+        android:textColor="@color/color_131415"
+        android:textSize="@dimen/sp_14"
+        app:layout_constraintLeft_toLeftOf="@+id/tv_title2"
+        app:layout_constraintTop_toBottomOf="@+id/seek_volume2" />
+
+
+    <ImageView
+        android:id="@+id/iv_reduce"
+        android:layout_marginTop="12dp"
+        app:layout_constraintLeft_toLeftOf="@+id/tv_title2"
+        app:layout_constraintTop_toBottomOf="@+id/tv_title4"
+        android:src="@drawable/icon_music_merge_offset_reduce"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <ImageView
+        android:layout_marginEnd="34dp"
+        android:id="@+id/iv_add"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce"
+        android:src="@drawable/icon_music_merge_offset_add"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <ImageView
+        android:id="@+id/iv_offset_bg"
+        android:layout_marginEnd="6dp"
+        android:layout_marginStart="6dp"
+        android:adjustViewBounds="true"
+        android:paddingStart="10.5px"
+        android:paddingEnd="10.5px"
+        android:src="@drawable/bg_offset_progress"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_reduce"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce"
+        app:layout_constraintRight_toLeftOf="@+id/iv_add"
+        app:layout_constraintLeft_toRightOf="@+id/iv_reduce"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"/>
+
+    <androidx.appcompat.widget.AppCompatSeekBar
+        app:layout_constraintRight_toRightOf="@+id/iv_offset_bg"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_offset_bg"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_reduce"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:max="600"
+        android:id="@+id/seek_bar_offset"
+        android:background="@null"
+        android:maxHeight="4dp"
+        android:progress="300"
+        android:paddingStart="10.5px"
+        android:paddingEnd="10.5px"
+        android:progressDrawable="@color/transparent"
+        android:splitTrack="false"
+        android:thumbOffset="10.5px"
+        android:thumb="@drawable/icon_offset_seekbar_thumb"
+        android:visibility="visible" />
+
+
+    <TextView
+        android:id="@+id/tv_offset_result"
+        android:layout_marginTop="6dp"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/iv_reduce"
+        android:textColor="@color/color_8f8f8f"
+        android:textSize="@dimen/sp_11"
+        tools:text="已为您对齐演奏伴奏,演奏提前10毫秒"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:layout_marginStart="29dp"
+        android:id="@+id/tv_record"
+        android:padding="4dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:layout_marginBottom="14dp"
+        android:textSize="@dimen/sp_10"
+        android:textColor="@color/color_777777"
+        android:text="重录"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        android:drawableTop="@drawable/icon_rerecord"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:layout_marginEnd="29dp"
+        android:id="@+id/tv_save"
+        android:padding="4dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:layout_marginBottom="14dp"
+        android:textSize="@dimen/sp_10"
+        android:textColor="@color/color_777777"
+        android:text="保存"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        android:drawableTop="@drawable/icon_save"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+
+    <TextView
+        android:id="@+id/tv_save_works"
+        android:layout_marginBottom="20dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:textSize="@dimen/sp_14"
+        android:gravity="center"
+        android:background="@drawable/shape_269efe_18dp"
+        android:layout_marginEnd="20dp"
+        android:layout_marginStart="20dp"
+        app:layout_constraintRight_toLeftOf="@+id/tv_save"
+        app:layout_constraintLeft_toRightOf="@+id/tv_record"
+        android:textColor="@color/white"
+        android:text="发布作品"
+        android:layout_width="0dp"
+        android:layout_height="35dp"/>
+
+    <View
+        android:background="@color/color_f2f2f2"
+        app:layout_constraintLeft_toLeftOf="parent"
+        android:layout_marginBottom="12dp"
+        app:layout_constraintBottom_toTopOf="@+id/tv_save_works"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"/>
+
+
+    <ImageView
+        android:id="@+id/iv_shrink_arrow"
+        android:paddingEnd="8dp"
+        android:paddingBottom="30dp"
+        android:paddingTop="30dp"
+        android:paddingStart="8dp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:src="@drawable/icon_shrink_setting_arrow"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 16 - 0
musicMerge/src/main/res/values/attrs.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="crilProgress">
+        <attr name="initialColor" format="color"></attr>
+        <attr name="finishColor" format="color"></attr>
+    </declare-styleable>
+
+    <declare-styleable name="waterProgress">
+        <attr name="bgColor" format="color"></attr>
+        <attr name="textColor" format="color"></attr>
+    </declare-styleable>
+
+    <declare-styleable name="musicView">
+        <attr name="itemColor" format="color"></attr>
+    </declare-styleable>
+</resources>

+ 8 - 0
musicMerge/src/main/res/values/colors.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="main_style_color">#2dc7aa</color>
+    <color name="color_131415">#131415</color>
+    <color name="color_8f8f8f">#8F8F8F</color>
+    <color name="color_48494b">#48494B</color>
+</resources>
+

+ 17 - 0
musicMerge/src/test/java/com/cooleshow/musicmerge/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.cooleshow.musicmerge;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 1 - 0
settings.gradle

@@ -83,3 +83,4 @@ include ':chatModule'
 include ':tclive'
 include ':classRoom'
 include ':institution'
+include ':musicMerge'