From 2d967c52a0f8b44d970aba7d1c8c48c42219f0f7 Mon Sep 17 00:00:00 2001 From: renhaoting <370797079@qq.com> Date: Thu, 6 Nov 2025 14:46:07 +0800 Subject: [PATCH] =?UTF-8?q?sound=20=E6=92=AD=E6=94=BE=E9=80=BB=E8=BE=91+1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../visualnovel/entity/response/ChatSound.kt | 1 + .../ExpandSoundSelectView.kt | 109 +++------ .../expandableSelector/ExpandSoundSubView.kt | 8 + .../utils/media/AudioPlayableView.kt | 128 ---------- .../utils/media/GlobalAudioPlayer.kt | 222 +++++++----------- 5 files changed, 118 insertions(+), 350 deletions(-) delete mode 100644 VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/AudioPlayableView.kt diff --git a/VisualNovel/app/src/main/java/com/remax/visualnovel/entity/response/ChatSound.kt b/VisualNovel/app/src/main/java/com/remax/visualnovel/entity/response/ChatSound.kt index 3cfc772..fa593db 100644 --- a/VisualNovel/app/src/main/java/com/remax/visualnovel/entity/response/ChatSound.kt +++ b/VisualNovel/app/src/main/java/com/remax/visualnovel/entity/response/ChatSound.kt @@ -14,6 +14,7 @@ data class ChatSound( val nameLanguage: String = "", val headPortrait: String = "", var isSelected: Boolean = false, + var isPlaying: Boolean = false, // Other needed var sampleUrl: String = "" diff --git a/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSelectView.kt b/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSelectView.kt index 455b8b1..ff4dc57 100644 --- a/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSelectView.kt +++ b/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSelectView.kt @@ -5,31 +5,32 @@ import android.animation.Animator import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Context -import android.net.Uri -import android.os.Environment import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import android.widget.LinearLayout -import android.widget.Toast import com.remax.visualnovel.R import com.remax.visualnovel.configs.NovelApplication import com.remax.visualnovel.databinding.LayoutExpandSelectViewBinding import com.remax.visualnovel.entity.response.ChatSound -import com.remax.visualnovel.utils.media.AudioPlayableView -import java.io.File +import com.remax.visualnovel.utils.media.AudioPlayListener +import com.remax.visualnovel.utils.media.AudioPlayState +import com.remax.visualnovel.utils.media.GlobalAudioPlayerManager class ExpandSoundSelectView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : AudioPlayableView(context, attrs, defStyleAttr) { +) : LinearLayout(context, attrs, defStyleAttr) { private lateinit var mBinding: LayoutExpandSelectViewBinding private lateinit var mExpandView : ExpandSoundSubView private var isExpanded = false private var animationDuration = 300 + private val globalAudioPlayer = GlobalAudioPlayerManager.getInstance() + private var mSampleIsPlaying = false + private lateinit var mEventListener: IEventListener interface IEventListener { @@ -161,87 +162,37 @@ class ExpandSoundSelectView @JvmOverloads constructor( private fun playSoundSample(chatSound: ChatSound) { - if (audioPlayer.isPlaying()) { - pauseAudio() + if (chatSound.sampleUrl.isEmpty()) { + // return } - if (!chatSound.sampleUrl.isEmpty()) { - playAudio( chatSound.sampleUrl) - } else { - playAudio(NovelApplication.appContext().getExternalFilesDir(null)?.path + "/ringtone.mp3") + with (globalAudioPlayer) { + globalAudioPlayer.play(NovelApplication.appContext().getExternalFilesDir(null)?.path + "/ringtone.mp3", + + object : AudioPlayListener { + override fun onStateChanged(state: AudioPlayState) { + mSampleIsPlaying = state == AudioPlayState.Playing + mExpandView.onItemPlayStateChanged(chatSound, mSampleIsPlaying) + } + + override fun onProgressChanged(position: Int, duration: Int) { + + } + + override fun onBufferingUpdate(percent: Int) { + + } + }) } } - override fun onAudioStarted() { - post { - // 更新UI为暂停状态 - } - } - override fun onAudioPaused() { - post { - // 更新UI为播放状态 - } - } - - override fun onAudioError(errorMessage: String) { - post { - Toast.makeText(context, "播放失败: $errorMessage", Toast.LENGTH_SHORT).show() + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + if (mSampleIsPlaying) { + globalAudioPlayer.stop() } } } - - - - -/** - // 在Activity中使用 - class MainActivity : AppCompatActivity() { - - private lateinit var audioPlayer: AudioPlayerManager - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - audioPlayer = AudioPlayerManager.getInstance(this) - - setupAudioViews() - } - - private fun setupAudioViews() { - // 按钮1播放网络音频 - findViewById(R.id.btnPlay1).setOnClickListener { - audioPlayer.play("https://example.com/audio1.mp3", object : AudioPlayListener { - override fun onStateChanged(state: AudioPlayState, source: String) { - // 只监听这个音频源的状态 - updateButton1State(state) - } - override fun onProgressChanged(position: Int, duration: Int, source: String) {} - override fun onBufferingUpdate(percent: Int, source: String) {} - }) - } - - // 按钮2播放本地文件 - findViewById(R.id.btnPlay2).setOnClickListener { - val file = File(getExternalFilesDir("audio"), "local.mp3") - audioPlayer.play(file, object : AudioPlayListener { - override fun onStateChanged(state: AudioPlayState, source: String) { - updateButton2State(state) - } - override fun onProgressChanged(position: Int, duration: Int, source: String) {} - override fun onBufferingUpdate(percent: Int, source: String) {} - }) - } - } - - override fun onDestroy() { - super.onDestroy() - // 根据需求决定是否释放单例 - // AudioPlayerManager.releaseInstance() - } - } - */ - diff --git a/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSubView.kt b/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSubView.kt index c4ed132..8bbfd8c 100644 --- a/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSubView.kt +++ b/VisualNovel/app/src/main/java/com/remax/visualnovel/ui/chat/setting/customui/expandableSelector/ExpandSoundSubView.kt @@ -3,6 +3,7 @@ package com.remax.visualnovel.ui.chat.setting.customui.expandableSelector import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView import com.drake.brv.annotaion.DividerOrientation import com.drake.brv.utils.bindingAdapter @@ -18,6 +19,7 @@ import com.remax.visualnovel.extension.glide.load import com.remax.visualnovel.utils.ResUtil import com.remax.visualnovel.widget.uitoken.setBgColorDirectly + private const val FILTER_ALL:Int = 0 private const val FILTER_MALE:Int = 1 private const val FILTER_FEMALE:Int = 2 @@ -112,6 +114,7 @@ class ExpandSoundSubView @JvmOverloads constructor( tvSoundDescrible.setTextColor(ResUtil.getColor(if (item.gender == 1) R.color.male_text_color else R.color.female_text_color)) tvSoundDescrible.text = item.desLanguage ivSelect.setImageResource(if (item.isSelected) R.drawable.sound_item_selected else R.drawable.sound_item_unselected) + ivPlaySample.setImageResource(if (!item.isPlaying) R.mipmap.setting_sound_play else R.mipmap.icon_diamond) } } } @@ -122,5 +125,10 @@ class ExpandSoundSubView @JvmOverloads constructor( mBinding.itemsRv.models = items } + fun onItemPlayStateChanged(chatSound: ChatSound, isPlaying: Boolean) { + chatSound.isPlaying = isPlaying + mBinding.itemsRv.bindingAdapter.notifyDataSetChanged() + } + } \ No newline at end of file diff --git a/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/AudioPlayableView.kt b/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/AudioPlayableView.kt deleted file mode 100644 index 7543b56..0000000 --- a/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/AudioPlayableView.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.remax.visualnovel.utils.media - -import android.content.Context -import android.net.Uri -import android.util.AttributeSet -import android.widget.FrameLayout -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner -import java.io.File - - -abstract class AudioPlayableView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr), AudioPlayListener { - - protected val audioPlayer = GlobalAudioPlayerManager.getInstance() - protected var currentAudioSource: String = "" - private var lifecycleObserver: LifecycleEventObserver? = null - - - - fun playAudio(source: Any, autoStopOnDetach: Boolean = true) { - currentAudioSource = when (source) { - is String -> source - is Uri -> source.toString() - is File -> source.absolutePath - else -> return - } - - // Just listen current source - audioPlayer.addListener(currentAudioSource, this, "view_${hashCode()}") - if (!audioPlayer.play(source, this, "view_${hashCode()}")) { - onPlayError("Play Error") - } - } - - /** - * bind lifecycle from fragment or activity - */ - fun bindLifecycle(lifecycleOwner: LifecycleOwner) { - lifecycleObserver = LifecycleEventObserver { _, event -> - when (event) { - Lifecycle.Event.ON_PAUSE -> pauseAudio() - Lifecycle.Event.ON_RESUME -> { /* 可选的恢复逻辑 */ } - Lifecycle.Event.ON_DESTROY -> releaseAudio() - else -> {} - } - } - lifecycleOwner.lifecycle.addObserver(lifecycleObserver!!) - } - - /** - * pause - */ - fun pauseAudio() { - if (audioPlayer.getCurrentSource() == currentAudioSource) { - audioPlayer.pause() - } - } - - /** - * stop - */ - fun stopAudio() { - if (audioPlayer.getCurrentSource() == currentAudioSource) { - audioPlayer.stop() - } - audioPlayer.removeListener(currentAudioSource, this) - } - - /** - * stop automatically while view detach - */ - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - stopAudio() - lifecycleObserver?.let { observer -> - (context as? LifecycleOwner)?.lifecycle?.removeObserver(observer) - } - } - - /** - * release - */ - fun releaseAudio() { - stopAudio() - currentAudioSource = "" - } - - - // Specified source state changed callback - override fun onStateChanged(state: AudioPlayState, source: String) { - if (source != currentAudioSource) return - - when (state) { - is AudioPlayState.Playing -> onAudioStarted() - is AudioPlayState.Paused -> onAudioPaused() - is AudioPlayState.Completed -> onAudioCompleted() - is AudioPlayState.Error -> onAudioError(state.errorMessage) - else -> {} - } - } - - override fun onProgressChanged(position: Int, duration: Int, source: String) { - if (source == currentAudioSource) { - onAudioProgressChanged(position, duration) - } - } - - override fun onBufferingUpdate(percent: Int, source: String) { - if (source == currentAudioSource) { - onAudioBufferingUpdate(percent) - } - } - - - // Sub class could override methods - open fun onPlayError(message: String) {} - open fun onAudioStarted() {} - open fun onAudioPaused() {} - open fun onAudioCompleted() {} - open fun onAudioError(errorMessage: String) {} - open fun onAudioProgressChanged(position: Int, duration: Int) {} - open fun onAudioBufferingUpdate(percent: Int) {} -} \ No newline at end of file diff --git a/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/GlobalAudioPlayer.kt b/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/GlobalAudioPlayer.kt index 2c76ea2..6429132 100644 --- a/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/GlobalAudioPlayer.kt +++ b/VisualNovel/app/src/main/java/com/remax/visualnovel/utils/media/GlobalAudioPlayer.kt @@ -13,8 +13,6 @@ import android.util.Log import com.remax.visualnovel.configs.NovelApplication import java.io.File import java.io.IOException -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArrayList // Player State sealed class AudioPlayState { @@ -23,7 +21,7 @@ sealed class AudioPlayState { object Playing : AudioPlayState() object Paused : AudioPlayState() object Stopped : AudioPlayState() - data class Error(val errorCode: Int, val errorMessage: String, val source: String) : AudioPlayState() + data class Error(val errorCode: Int, val errorMessage: String) : AudioPlayState() object Completed : AudioPlayState() } @@ -56,9 +54,9 @@ data class AudioPlayerConfig( // Player state listener interface AudioPlayListener { - fun onStateChanged(state: AudioPlayState, source: String) - fun onProgressChanged(position: Int, duration: Int, source: String) - fun onBufferingUpdate(percent: Int, source: String) + fun onStateChanged(state: AudioPlayState) + fun onProgressChanged(position: Int, duration: Int) + fun onBufferingUpdate(percent: Int) } @@ -93,23 +91,24 @@ class GlobalAudioPlayerManager private constructor( } - private var mediaPlayer: MediaPlayer? = null + private var mMediaPlayer: MediaPlayer? = null private var currentState: AudioPlayState = AudioPlayState.Idle private val mainHandler = Handler(Looper.getMainLooper()) - private var currentSource: String = "" - private var previousSource: String = "" private var isAudioFocusGranted = false private var audioFocusRequest: AudioFocusRequest? = null private var wasPlayingBeforeFocusLoss = false - // Listeners, just notify events about specified file(Map, key) - private val sourceListeners = ConcurrentHashMap>() + // key: hash code of + @Volatile + private var mCurListener: AudioPlayListener? = null + // progress runnable private var progressUpdateRunnable: Runnable? = null + @Volatile private var isReleased = false init { @@ -120,7 +119,7 @@ class GlobalAudioPlayerManager private constructor( if (isReleased) return try { - mediaPlayer = MediaPlayer().apply { + mMediaPlayer = MediaPlayer().apply { setAudioAttributes( AudioAttributes.Builder() .setUsage(config.usage) @@ -133,9 +132,9 @@ class GlobalAudioPlayerManager private constructor( setOnBufferingUpdateListener(::onBufferingUpdate) isLooping = config.loop } - setState(AudioPlayState.Idle, currentSource) + setState(AudioPlayState.Idle) } catch (e: Exception) { - handleError(AudioErrorCode.UNKNOWN_ERROR, "MediaPlayer init failed: ${e.message}", currentSource) + handleError(AudioErrorCode.UNKNOWN_ERROR, "MediaPlayer init failed: ${e.message}") } } @@ -144,7 +143,7 @@ class GlobalAudioPlayerManager private constructor( /** * play source, also pause previous playing source. */ - fun play(source: Any, listener: AudioPlayListener? = null, tag: String = ""): Boolean { + fun play(source: Any, listener: AudioPlayListener): Boolean { if (isReleased) { Log.w("AudioPlayer", "Player released, play failed") return false @@ -152,103 +151,64 @@ class GlobalAudioPlayerManager private constructor( val sourceString = convertSourceToString(source) if (sourceString.isBlank()) { - handleError(AudioErrorCode.INVALID_DATA_SOURCE, "Invalid source", sourceString) + handleError(AudioErrorCode.INVALID_DATA_SOURCE, "Invalid source") return false } - // 停止上一个播放(如果是不同的source) - if (currentSource.isNotEmpty() && currentSource != sourceString) { + // Stop playing one + if (isPlaying()) { stopCurrentPlayback() } - - // 注册监听器 - listener?.let { addListener(sourceString, it, tag) } - - currentSource = sourceString + + mCurListener = listener return startPlayback(source) } + /** - * 为特定source添加监听器 - */ - fun addListener(source: Any, listener: AudioPlayListener, tag: String = "") { - val sourceString = convertSourceToString(source) - if (sourceString.isBlank()) return - - sourceListeners.getOrPut(sourceString) { CopyOnWriteArrayList() }.add(listener) - Log.d("AudioPlayer", "为source[$sourceString]添加监听器, 当前监听器数: ${sourceListeners[sourceString]?.size}") - } - - /** - * 移除特定source的监听器 - */ - fun removeListener(source: Any, listener: AudioPlayListener) { - val sourceString = convertSourceToString(source) - sourceListeners[sourceString]?.remove(listener) - } - - /** - * 移除特定tag的所有监听器 - */ - fun removeListenersByTag(tag: String, source: Any? = null) { - if (source != null) { - val sourceString = convertSourceToString(source) - sourceListeners[sourceString]?.removeAll { listener -> - // 如果监听器有tag属性,可以根据需要实现 - true // 这里需要根据实际tag实现过滤逻辑 - } - } else { - sourceListeners.values.forEach { listeners -> - listeners.removeAll { true } // 简化实现,实际需要根据tag过滤 - } - } - } - - /** - * 暂停播放 + * Pause */ fun pause(): Boolean { if (isReleased || currentState != AudioPlayState.Playing) return false return try { - mediaPlayer?.pause() - setState(AudioPlayState.Paused, currentSource) + mMediaPlayer?.pause() + setState(AudioPlayState.Paused) stopProgressUpdates() true } catch (e: Exception) { - handleError(AudioErrorCode.UNKNOWN_ERROR, "暂停失败: ${e.message}", currentSource) + handleError(AudioErrorCode.UNKNOWN_ERROR, "Pause failed: ${e.message}") false } } /** - * 恢复播放 + * resume play */ fun resume(): Boolean { if (isReleased || currentState != AudioPlayState.Paused) return false if (!requestAudioFocus()) { - handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "无法获取音频焦点", currentSource) + handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "Can't get audio focus") return false } return try { - mediaPlayer?.start() - setState(AudioPlayState.Playing, currentSource) + mMediaPlayer?.start() + setState(AudioPlayState.Playing) startProgressUpdates() true } catch (e: Exception) { - handleError(AudioErrorCode.UNKNOWN_ERROR, "恢复播放失败: ${e.message}", currentSource) + handleError(AudioErrorCode.UNKNOWN_ERROR, "恢复播放失败: ${e.message}") false } } /** - * 停止播放 + * stop play */ fun stop(): Boolean { if (isReleased) return false - stopCurrentPlayback() return true } @@ -260,7 +220,7 @@ class GlobalAudioPlayerManager private constructor( if (isReleased) return false return try { - mediaPlayer?.takeIf { it.duration > 0 }?.let { player -> + mMediaPlayer?.takeIf { it.duration > 0 }?.let { player -> val safePosition = position.coerceIn(0, player.duration) player.seekTo(safePosition) true @@ -269,12 +229,7 @@ class GlobalAudioPlayerManager private constructor( false } } - - /** - * 获取当前播放的source - */ - fun getCurrentSource(): String = currentSource - + /** * 是否正在播放 */ @@ -284,23 +239,22 @@ class GlobalAudioPlayerManager private constructor( * 获取当前状态 */ fun getCurrentState(): AudioPlayState = currentState - // endregion + // region 播放控制内部实现 - private fun startPlayback(source: Any): Boolean { if (!requestAudioFocus()) { - handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "无法获取音频焦点", currentSource) + handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "Can not get audio focus") return false } return try { - setState(AudioPlayState.Preparing, currentSource) - - mediaPlayer?.let { player -> + setState(AudioPlayState.Preparing) + + mMediaPlayer?.let { player -> player.reset() - + when (source) { is String -> setupDataSourceFromString(source) is Uri -> player.setDataSource(mAppContext, source) @@ -312,10 +266,11 @@ class GlobalAudioPlayerManager private constructor( } else -> throw IllegalArgumentException("Not support media type") } - + player.prepareAsync() true } ?: false + } catch (e: Exception) { handlePlayException(e, "Play prepare failed") false @@ -323,7 +278,7 @@ class GlobalAudioPlayerManager private constructor( } private fun setupDataSourceFromString(source: String) { - mediaPlayer?.let { player -> + mMediaPlayer?.let { player -> when { source.startsWith("http://") || source.startsWith("https://") -> { player.setDataSource(source) @@ -349,22 +304,15 @@ class GlobalAudioPlayerManager private constructor( private fun stopCurrentPlayback() { try { - mediaPlayer?.let { player -> - if (player.isPlaying) { - player.stop() - } + mMediaPlayer.let { player -> + player?.stop() } - setState(AudioPlayState.Stopped, currentSource) + + setState(AudioPlayState.Stopped) stopProgressUpdates() abandonAudioFocus() - - // 通知上一个source的监听器停止 - if (previousSource.isNotEmpty()) { - notifyStateChanged(AudioPlayState.Stopped, previousSource) - } - - previousSource = currentSource - currentSource = "" + + mCurListener = null } catch (e: Exception) { Log.e("AudioPlayer", "Stop playing failed: ${e.message}") } @@ -466,7 +414,7 @@ class GlobalAudioPlayerManager private constructor( private fun onAudioFocusDuck() { if (config.duckOnFocusLoss) { - mediaPlayer?.setVolume(0.2f, 0.2f) + mMediaPlayer?.setVolume(0.2f, 0.2f) } else { wasPlayingBeforeFocusLoss = currentState == AudioPlayState.Playing if (wasPlayingBeforeFocusLoss) { @@ -484,22 +432,23 @@ class GlobalAudioPlayerManager private constructor( try { if (config.autoPlay) { mp.start() - setState(AudioPlayState.Playing, currentSource) + setState(AudioPlayState.Playing) startProgressUpdates() } else { - setState(AudioPlayState.Paused, currentSource) + setState(AudioPlayState.Paused) } } catch (e: Exception) { - handleError(AudioErrorCode.PREPARE_FAILED, "Player prepare failed: ${e.message}", currentSource) + handleError(AudioErrorCode.PREPARE_FAILED, "Player prepare failed: ${e.message}") } } } private fun onCompletion(mp: MediaPlayer) { mainHandler.post { - setState(AudioPlayState.Completed, currentSource) + setState(AudioPlayState.Completed) stopProgressUpdates() abandonAudioFocus() + mCurListener = null } } @@ -511,14 +460,14 @@ class GlobalAudioPlayerManager private constructor( MediaPlayer.MEDIA_ERROR_IO -> "I/O ERROR" else -> "Media Error: what=$what, extra=$extra" } - handleError(AudioErrorCode.PREPARE_FAILED, errorMessage, currentSource) + handleError(AudioErrorCode.PREPARE_FAILED, errorMessage) } return true } private fun onBufferingUpdate(mp: MediaPlayer, percent: Int) { mainHandler.post { - notifyBufferingUpdate(percent, currentSource) + notifyBufferingUpdate(percent) } } // endregion @@ -532,9 +481,9 @@ class GlobalAudioPlayerManager private constructor( override fun run() { if (currentState == AudioPlayState.Playing && !isReleased) { try { - val position = mediaPlayer?.currentPosition ?: 0 - val duration = mediaPlayer?.duration ?: 0 - notifyProgressChanged(position, duration, currentSource) + val position = mMediaPlayer?.currentPosition ?: 0 + val duration = mMediaPlayer?.duration ?: 0 + notifyProgressChanged(position, duration) } catch (e: Exception) { } } @@ -553,54 +502,42 @@ class GlobalAudioPlayerManager private constructor( // endregion // region Various state notifications - private fun setState(state: AudioPlayState, source: String) { + private fun setState(state: AudioPlayState) { if (currentState != state) { currentState = state - notifyStateChanged(state, source) + notifyStateChanged(state) } } - private fun notifyStateChanged(state: AudioPlayState, source: String) { - mainHandler.post { - sourceListeners[source]?.forEach { listener -> - try { - listener.onStateChanged(state, source) - } catch (e: Exception) { - Log.e("AudioPlayer", "onStateChanged exception: ${e.message}") - } + private fun notifyStateChanged(state: AudioPlayState) { + val listener = mCurListener + if (listener != null) { + mainHandler.post { + listener.onStateChanged(state) } } } - private fun notifyProgressChanged(position: Int, duration: Int, source: String) { + private fun notifyProgressChanged(position: Int, duration: Int) { mainHandler.post { - sourceListeners[source]?.forEach { listener -> - try { - listener.onProgressChanged(position, duration, source) - } catch (e: Exception) { - Log.e("AudioPlayer", "onProgressChanged Exception: ${e.message}") - } + if (mCurListener != null) { + mCurListener?.onProgressChanged(position, duration) } } } - private fun notifyBufferingUpdate(percent: Int, source: String) { + private fun notifyBufferingUpdate(percent: Int) { mainHandler.post { - sourceListeners[source]?.forEach { listener -> - try { - listener.onBufferingUpdate(percent, source) - } catch (e: Exception) { - Log.e("AudioPlayer", "onBufferingUpdate exception: ${e.message}") - } + if (mCurListener != null) { + mCurListener?.onBufferingUpdate(percent) } } } - // endregion - // region 工具方法 + // region Tools private fun convertSourceToString(source: Any): String { return when (source) { is String -> source @@ -617,16 +554,16 @@ class GlobalAudioPlayerManager private constructor( is IllegalArgumentException -> AudioErrorCode.FORMAT_NOT_SUPPORTED else -> AudioErrorCode.UNKNOWN_ERROR } - handleError(errorCode, "$message: ${e.message}", currentSource) + handleError(errorCode, "$message: ${e.message}") } - private fun handleError(errorCode: Int, errorMessage: String, source: String) { - Log.e("AudioPlayer", "Player Error[$errorCode]: $errorMessage, source: $source") - setState(AudioPlayState.Error(errorCode, errorMessage, source), source) + private fun handleError(errorCode: Int, errorMessage: String) { + setState(AudioPlayState.Error(errorCode, errorMessage)) stopProgressUpdates() abandonAudioFocus() + mCurListener = null + Log.e("AudioPlayer", "Player Error[$errorCode]: $errorMessage") } - // endregion // region release related @@ -636,12 +573,11 @@ class GlobalAudioPlayerManager private constructor( isReleased = true stopCurrentPlayback() stopProgressUpdates() - sourceListeners.clear() abandonAudioFocus() try { - mediaPlayer?.release() - mediaPlayer = null + mMediaPlayer?.release() + mMediaPlayer = null } catch (e: Exception) { Log.e("AudioPlayer", "Release player failed: ${e.message}") }