Sfoglia il codice sorgente

feat: 录音/播放控件

DoggyZhang 2 mesi fa
parent
commit
abc0d08136

+ 81 - 52
app/src/main/java/com/adealink/weparty/module/playmate/widget/SoundView.kt

@@ -26,7 +26,6 @@ import com.adealink.frame.util.onClick
 import com.adealink.frame.util.runOnUiThread
 import com.adealink.weparty.App
 import com.adealink.weparty.R
-import com.adealink.weparty.commonui.ext.dp
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.databinding.LayoutSoundViewBinding
 import com.adealink.weparty.module.profile.data.VoiceData
@@ -51,19 +50,18 @@ class SoundView @JvmOverloads constructor(
     }
 
     data class LoadSoundTask(
-        val url: String
+        val voice: VoiceData
     )
 
     private val binding = LayoutSoundViewBinding.inflate(LayoutInflater.from(context), this)
 
-    private var durationTextSize = 12.dp()
+    private var durationTextSize = 12
 
     private var showPlayButton: Boolean = true
     private var showWave: Boolean = true
 
-    private var soundUrl: String? = null
-    private var soundPath: String? = null
-    private var soundDuration: Long? = null //单位:毫秒
+    private var voiceData: VoiceData? = null
+    private var soundDuration: Long? = null
 
     private var loadingAnimation: Animation? = null
 
@@ -80,7 +78,7 @@ class SoundView @JvmOverloads constructor(
                     val currentPosition =
                         max((AudioPlayer.getInstance().playPosition / 1000), 1)
                     binding.tvDuration.text = "${max(soundDuration - currentPosition, 0)}\""
-                    if (AudioPlayer.getInstance().isPlaying && AudioPlayer.getInstance().path == soundPath) {
+                    if (AudioPlayer.getInstance().isPlaying && AudioPlayer.getInstance().path == voiceData?.path) {
                         sendEmptyMessageDelayed(MSG_UPDATE_PLAY_DURATION, 1000L)
                     } else {
                         sendEmptyMessage(MSG_UPDATE_DURATION)
@@ -110,7 +108,7 @@ class SoundView @JvmOverloads constructor(
         ) {
             durationTextSize = getDimensionPixelSize(
                 R.styleable.SoundView_sound_duration_text_size,
-                durationTextSize
+                12
             )
             textGravity = getInt(
                 R.styleable.SoundView_sound_duration_text_gravity,
@@ -142,79 +140,99 @@ class SoundView @JvmOverloads constructor(
     }
 
     fun setSoundUrl(url: String?) {
-        this.soundUrl = url
-        this.soundPath = null
+        this.voiceData = VoiceData(url = url, null)
         this.soundDuration = null
         stopPlay()
         loadSound()
     }
 
-    fun setSound(voice: VoiceData?){
-
+    fun setSound(voice: VoiceData?) {
+        this.voiceData = voice
+        stopPlay()
+        loadSound()
     }
 
     private fun loadSound() {
-        val url = soundUrl
-        if (url.isNullOrEmpty()) {
-            Log.e(TAG, "playSound fail, url is null")
+        val voiceData = this.voiceData
+        if (voiceData == null || voiceData.isNull()) {
+            Log.d(TAG, "loadSound return, for voiceData is null")
             return
         }
 
-        downloadSound(
-            onFinish = { soundPath ->
-                Log.d(TAG, "loadSound, path:$soundPath")
-                val loadSoundTask = LoadSoundTask(url)
-                MediaPlayer().apply {
-                    try {
-                        setDataSource(soundPath)
-
-                        // 设置准备完成监听器
-                        setOnPreparedListener { mp ->
-                            Log.e(TAG, "loadSound, onPrepared")
-                            if (loadSoundTask.url == soundUrl) {
-                                soundDuration = mp.duration.toLong()
-                                Log.e(TAG, "loadSound, url:$soundUrl, duration:$soundDuration")
-                                handler.sendEmptyMessage(MSG_UPDATE_DURATION)
-                            }
-
-                            //释放
-                            release()
+        fun onDownloadFinish(downloadPath: String) {
+            Log.d(TAG, "loadSound, path:$downloadPath")
+            val loadSoundTask = LoadSoundTask(voiceData)
+            MediaPlayer().apply {
+                try {
+                    setDataSource(downloadPath)
+
+                    // 设置准备完成监听器
+                    setOnPreparedListener { mp ->
+                        Log.d(TAG, "loadSound, onPrepared")
+                        if (loadSoundTask.voice == this@SoundView.voiceData) {
+                            soundDuration = mp.duration.toLong()
+                            Log.d(TAG, "loadSound, voice:$voiceData, duration:$soundDuration")
+                            handler.sendEmptyMessage(MSG_UPDATE_DURATION)
                         }
 
-                        // 设置错误监听器
-                        setOnErrorListener { _, what, extra ->
-                            Log.e(TAG, "loadSound error")
-                            if (loadSoundTask.url == soundUrl) {
-                                soundDuration = 0
-                                handler.sendEmptyMessage(MSG_UPDATE_DURATION)
-                            }
-                            false
-                        }
+                        //释放
+                        release()
+                    }
 
-                        setOnCompletionListener {
-                            release()
+                    // 设置错误监听器
+                    setOnErrorListener { _, what, extra ->
+                        Log.e(TAG, "loadSound error")
+                        if (loadSoundTask.voice == this@SoundView.voiceData) {
+                            soundDuration = 0
+                            handler.sendEmptyMessage(MSG_UPDATE_DURATION)
                         }
+                        false
+                    }
 
-                        // 异步准备音频
-                        prepareAsync()
-                    } catch (e: Exception) {
-                        Log.d(TAG, "loadSound fail, for ${e.message}", e)
+                    setOnCompletionListener {
                         release()
                     }
+
+                    // 异步准备音频
+                    prepareAsync()
+                } catch (e: Exception) {
+                    Log.d(TAG, "loadSound fail, for ${e.message}", e)
+                    release()
                 }
             }
+        }
+
+
+        downloadSound(
+            voiceData,
+            onFinish = { soundPath: String ->
+                onDownloadFinish(soundPath)
+            }
         )
     }
 
     private fun downloadSound(
+        voiceData: VoiceData?,
         onStartDownload: (() -> Unit)? = null,
         onDownloadProgress: (() -> Unit)? = null,
         onFinish: ((soundPath: String) -> Unit)? = null,
         onError: (() -> Unit)? = null
     ) {
-        val url = soundUrl
+        if (voiceData == null || voiceData.isNull()) {
+            onError?.invoke()
+            return
+        }
+
+        val path = voiceData.path
+        if (!path.isNullOrEmpty()) {
+            onFinish?.invoke(path)
+            return
+        }
+
+        val url = voiceData.url
         if (url.isNullOrEmpty()) {
             Log.e(TAG, "downloadSound fail, url is null")
+            onError?.invoke()
             return
         }
         val soundPath = url.getFilePathBy(audioPath, UploadFile.FileType.AUDIO)
@@ -222,7 +240,7 @@ class SoundView @JvmOverloads constructor(
             Log.e(TAG, "downloadSound fail, can not getFilePath for $url")
             return
         }
-        this.soundPath = soundPath
+        voiceData.path = soundPath
         val soundFile = storageService.file.createWeFile(soundPath)
         if (soundFile.exists()) {
             Log.d(TAG, "downloadSound return, for sound exists, path:$soundPath")
@@ -270,7 +288,12 @@ class SoundView @JvmOverloads constructor(
     }
 
     fun playOrStop() {
+        if (AudioPlayer.getInstance().isPlaying) {
+            stopPlay()
+            return
+        }
         downloadSound(
+            voiceData,
             onFinish = { soundPath ->
                 if (AudioPlayer.getInstance().isPlaying && AudioPlayer.getInstance().path == soundPath) {
                     stopPlay()
@@ -322,7 +345,7 @@ class SoundView @JvmOverloads constructor(
         AudioPlayer.getInstance().stopPlay()
         AudioPlayer.getInstance().startPlay(soundPath, object : AudioPlayer.Callback {
             override fun onCompletion(success: Boolean?) {
-                if (isAttachedToWindow && soundPath == this@SoundView.soundPath) {
+                if (isAttachedToWindow && soundPath == this@SoundView.voiceData?.path) {
                     runOnUiThread {
                         onStopPlay()
                     }
@@ -352,11 +375,17 @@ class SoundView @JvmOverloads constructor(
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
+        if (isInEditMode) {
+            return
+        }
         stopPlay()
     }
 
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
+        if (isInEditMode) {
+            return
+        }
         stopPlay()
     }
 

+ 3 - 3
app/src/main/java/com/adealink/weparty/module/profile/data/ProfileData.kt

@@ -214,11 +214,11 @@ data class VoiceData(
     var url: String?,
 
     //本地路径
-    val path: String?,
+    var path: String?,
 
-    val duration: Int? = null //单位:毫秒
+    var duration: Int? = null //单位:毫秒
 ) {
-    fun isRemoteImage(): Boolean {
+    fun isRemote(): Boolean {
         return !url.isNullOrEmpty()
     }
 

BIN
app/src/main/res/drawable-xhdpi/common_widget_voice_rerecord_ic.png


+ 1 - 1
app/src/main/res/values-zh/strings.xml

@@ -322,7 +322,7 @@
     <string name="profile_edit_talent_voice_record">点击开始录音</string>
     <string name="profile_edit_talent_voice_stop">点击结束录音</string>
     <string name="profile_edit_talent_voice_re_record">重录</string>
-    <string name="profile_edit_talent_voice_preview">试听</string>
+    <string name="profile_edit_talent_voice_preview">点击试听</string>
     <string name="profile_record_voice_permission_request_tips">录制声音需要授予麦克风权限</string>
     <string name="profile_record_voice_less_than_3_seconds">录音时间不能低于3秒哦</string>
     <string name="common_widget_validate_require">%s不能为空,请按照要求填写</string>

+ 2 - 12
module/joinus/src/main/java/com/adealink/weparty/widget/form/field/voice/VoiceWidgetView.kt

@@ -115,9 +115,6 @@ class VoiceWidgetView(
         binding.btnReRecord.onClick {
             clickReRecord()
         }
-        binding.btnPreview.onClick {
-            audioPreview()
-        }
 
         val currentValue = (widgetData?.fieldData as? StringStepField)?.fieldValue
         voiceData = if (currentValue.isNullOrEmpty()) {
@@ -132,9 +129,11 @@ class VoiceWidgetView(
         if (voiceData == null || voiceData?.isNull() == true) {
             binding.clRecord.show()
             binding.clPreview.gone()
+            binding.btnPreview.setSound(voiceData)
         } else {
             binding.clRecord.gone()
             binding.clPreview.show()
+            binding.btnPreview.setSound(voiceData)
         }
     }
 
@@ -155,15 +154,6 @@ class VoiceWidgetView(
         startRecord()
     }
 
-    private fun audioPreview() {
-        val voicePath = voiceData?.path
-        if (voicePath.isNullOrEmpty()) {
-            return
-        }
-        AudioPlayer.getInstance().stopPlay()
-        AudioPlayer.getInstance().startPlay(voicePath) { }
-    }
-
     private fun startRecord() {
         //录音前关闭音频播放
         AudioPlayer.getInstance().stopPlay()

+ 33 - 43
module/joinus/src/main/res/layout/widget_voice.xml

@@ -72,7 +72,7 @@
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
-            tools:visibility="visible">
+            tools:visibility="gone">
 
             <androidx.appcompat.widget.AppCompatTextView
                 android:id="@+id/tv_duration"
@@ -130,37 +130,54 @@
             app:layout_constraintTop_toTopOf="parent"
             tools:visibility="visible">
 
+            <com.adealink.weparty.module.playmate.widget.SoundView
+                android:id="@+id/btn_preview"
+                android:layout_width="200dp"
+                android:layout_height="38dp"
+                android:layout_marginEnd="26dp"
+                app:layout_constraintBottom_toBottomOf="@id/btn_re_record"
+                app:layout_constraintEnd_toStartOf="@id/btn_re_record"
+                app:layout_constraintHorizontal_chainStyle="packed"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="@id/btn_re_record"
+                app:layout_constraintVertical_chainStyle="packed"
+                app:sound_duration_text_gravity="end"
+                app:sound_duration_text_size="12sp"
+                app:sound_show_play_button="true"
+                app:sound_show_wave="true" />
+
             <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/tv_preview_duration"
+                android:id="@+id/tv_preview"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:gravity="center"
                 android:includeFontPadding="false"
+                android:maxLines="2"
+                android:text="@string/profile_edit_talent_voice_preview"
                 android:textColor="@color/color_FF1D2129"
                 android:textSize="14sp"
-                app:layout_constraintBottom_toTopOf="@id/btn_re_record"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintEnd_toEndOf="@id/btn_preview"
+                app:layout_constraintStart_toStartOf="@id/btn_preview"
+                app:layout_constraintTop_toTopOf="@id/tv_re_record"
                 app:layout_constraintVertical_chainStyle="packed"
-                tools:text="00:00" />
+                app:layout_constraintWidth_max="120dp" />
 
             <androidx.appcompat.widget.AppCompatImageView
                 android:id="@+id/btn_re_record"
-                android:layout_width="70dp"
-                android:layout_height="70dp"
-                android:layout_marginTop="12dp"
+                android:layout_width="34dp"
+                android:layout_height="34dp"
+                android:layout_marginBottom="15dp"
                 app:layout_constraintBottom_toTopOf="@id/tv_re_record"
-                app:layout_constraintEnd_toStartOf="@id/btn_preview"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/tv_preview_duration"
-                app:srcCompat="@drawable/profile_edit_talent_voice_re_record_ic" />
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/btn_preview"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_chainStyle="packed"
+                app:srcCompat="@drawable/common_widget_voice_rerecord_ic" />
 
             <androidx.appcompat.widget.AppCompatTextView
                 android:id="@+id/tv_re_record"
-                android:layout_width="wrap_content"
+                android:layout_width="80dp"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="12dp"
                 android:gravity="center"
                 android:includeFontPadding="false"
                 android:maxLines="2"
@@ -174,33 +191,6 @@
                 app:layout_constraintWidth_max="120dp" />
 
 
-            <androidx.appcompat.widget.AppCompatImageView
-                android:id="@+id/btn_preview"
-                android:layout_width="70dp"
-                android:layout_height="70dp"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@id/btn_re_record"
-                app:layout_constraintTop_toTopOf="@id/btn_re_record"
-                app:layout_constraintVertical_chainStyle="packed"
-                app:srcCompat="@drawable/profile_edit_talent_voice_preview_ic" />
-
-            <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/tv_preview"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="12dp"
-                android:gravity="center"
-                android:includeFontPadding="false"
-                android:maxLines="2"
-                android:text="@string/profile_edit_talent_voice_preview"
-                android:textColor="@color/color_FF1D2129"
-                android:textSize="14sp"
-                app:layout_constraintEnd_toEndOf="@id/btn_preview"
-                app:layout_constraintStart_toStartOf="@id/btn_preview"
-                app:layout_constraintTop_toBottomOf="@id/btn_preview"
-                app:layout_constraintVertical_chainStyle="packed"
-                app:layout_constraintWidth_max="120dp" />
-
         </androidx.constraintlayout.widget.ConstraintLayout>
 
     </androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
module/playmate/src/main/res/layout/item_playmate_detail_content.xml

@@ -84,6 +84,7 @@
         android:layout_marginBottom="10dp"
         app:layout_constraintBottom_toBottomOf="@id/banner_img"
         app:layout_constraintEnd_toEndOf="@id/banner_img"
+        app:sound_duration_text_size="12sp"
         app:sound_show_play_button="true"
         app:sound_show_wave="true" />
 

+ 5 - 4
module/playmate/src/main/res/layout/item_playmate_home_list.xml

@@ -37,6 +37,7 @@
         app:layout_constraintBottom_toTopOf="@id/v_price"
         app:layout_constraintEnd_toEndOf="@id/iv_avatar"
         app:layout_constraintStart_toStartOf="@id/iv_avatar"
+        app:sound_duration_text_size="12sp"
         app:sound_show_play_button="false"
         app:sound_show_wave="true" />
 
@@ -44,13 +45,13 @@
         android:id="@+id/v_price"
         style="@style/CommonPriceView"
         android:layout_marginTop="3.5dp"
+        app:layout_constraintEnd_toEndOf="@id/iv_avatar"
+        app:layout_constraintStart_toStartOf="@id/iv_avatar"
+        app:layout_constraintTop_toBottomOf="@id/iv_avatar"
         app:price_icon_size="14.4dp"
         app:price_text_bold="true"
         app:price_text_size="14sp"
-        app:unit_text_size="12sp"
-        app:layout_constraintEnd_toEndOf="@id/iv_avatar"
-        app:layout_constraintStart_toStartOf="@id/iv_avatar"
-        app:layout_constraintTop_toBottomOf="@id/iv_avatar" />
+        app:unit_text_size="12sp" />
 
     <androidx.constraintlayout.widget.Barrier
         android:id="@+id/v_barrier_start"

+ 3 - 0
module/profile/src/main/java/com/adealink/weparty/profile/datasource/remote/ProfileHttpService.kt

@@ -48,4 +48,7 @@ interface ProfileHttpService {
         @Body req: UserVoiceApplyReq
     ): Rlt<Res<Any>>
 
+    @POST("user/clean/myVoiceBar")
+    suspend fun clearUserVoice(): Rlt<Res<Any>>
+
 }

+ 1 - 0
module/profile/src/main/java/com/adealink/weparty/profile/edit/dialog/fragment/VoiceFragment.kt

@@ -35,6 +35,7 @@ class VoiceFragment : BaseFragment(R.layout.fragment_talent_voice) {
     }
 
     private fun clearAndRecode() {
+        viewModel.clearVoice()
         viewModel.setVoiceData(null)
         viewModel.showPage(EditVoicePage.EDIT)
     }

+ 9 - 0
module/profile/src/main/java/com/adealink/weparty/profile/edit/dialog/viewmodel/EditVoiceViewModel.kt

@@ -51,6 +51,15 @@ class EditVoiceViewModel : BaseViewModel() {
     }
 
 
+    fun clearVoice(): LiveData<Rlt<Any>>{
+        val liveData = OnceMutableLiveData<Rlt<Any>>()
+        viewModelScope.launch {
+            val rlt = profileHttpService.clearUserVoice()
+            liveData.send(rlt)
+        }
+        return liveData
+    }
+
     /**
      * 录制声音
      */

+ 1 - 0
module/profile/src/main/res/layout/layout_user_profile_header.xml

@@ -35,6 +35,7 @@
         android:layout_marginBottom="6dp"
         app:layout_constraintBottom_toTopOf="@id/cl_user_info"
         app:layout_constraintStart_toStartOf="parent"
+        app:sound_duration_text_size="12sp"
         app:sound_show_play_button="true"
         app:sound_show_wave="false" />
 

+ 0 - 2
module/profile/src/main/res/values-in/strings.xml

@@ -62,8 +62,6 @@
     <string name="profile_edit_talent_voice_title">Atur suara Anda</string>
     <string name="profile_edit_talent_voice_record">Klik untuk memulai perekaman</string>
     <string name="profile_edit_talent_voice_stop">Klik untuk menghentikan perekaman</string>
-    <string name="profile_edit_talent_voice_re_record">Rekam ulang</string>
-    <string name="profile_edit_talent_voice_preview">Pratinjau</string>
     <string name="profile_edit_talent_voice_delete_record">Hapus dan rekam ulang</string>
     <string name="profile_follow_list_title">%s Diikuti</string>
     <string name="profile_fans_list_title">%s Penggemar</string>

+ 0 - 2
module/profile/src/main/res/values/strings.xml

@@ -65,8 +65,6 @@
     <string name="profile_edit_talent_voice_title">Set your voice</string>
     <string name="profile_edit_talent_voice_record">Click to start recording</string>
     <string name="profile_edit_talent_voice_stop">Click to stop recording</string>
-    <string name="profile_edit_talent_voice_re_record">Re-record</string>
-    <string name="profile_edit_talent_voice_preview">Preview</string>
     <string name="profile_edit_talent_voice_delete_record">Delete and re-record</string>
     <string name="profile_follow_list_title">%s Followed</string>
     <string name="profile_fans_list_title">%s Fans</string>