sound 播放逻辑+1

This commit is contained in:
renhaoting 2025-11-06 14:46:07 +08:00
parent 1eb8b02ef0
commit 2d967c52a0
5 changed files with 118 additions and 350 deletions

View File

@ -14,6 +14,7 @@ data class ChatSound(
val nameLanguage: String = "", val nameLanguage: String = "",
val headPortrait: String = "", val headPortrait: String = "",
var isSelected: Boolean = false, var isSelected: Boolean = false,
var isPlaying: Boolean = false,
// Other needed // Other needed
var sampleUrl: String = "" var sampleUrl: String = ""

View File

@ -5,31 +5,32 @@ import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Environment
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast
import com.remax.visualnovel.R import com.remax.visualnovel.R
import com.remax.visualnovel.configs.NovelApplication import com.remax.visualnovel.configs.NovelApplication
import com.remax.visualnovel.databinding.LayoutExpandSelectViewBinding import com.remax.visualnovel.databinding.LayoutExpandSelectViewBinding
import com.remax.visualnovel.entity.response.ChatSound import com.remax.visualnovel.entity.response.ChatSound
import com.remax.visualnovel.utils.media.AudioPlayableView import com.remax.visualnovel.utils.media.AudioPlayListener
import java.io.File import com.remax.visualnovel.utils.media.AudioPlayState
import com.remax.visualnovel.utils.media.GlobalAudioPlayerManager
class ExpandSoundSelectView @JvmOverloads constructor( class ExpandSoundSelectView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : AudioPlayableView(context, attrs, defStyleAttr) { ) : LinearLayout(context, attrs, defStyleAttr) {
private lateinit var mBinding: LayoutExpandSelectViewBinding private lateinit var mBinding: LayoutExpandSelectViewBinding
private lateinit var mExpandView : ExpandSoundSubView private lateinit var mExpandView : ExpandSoundSubView
private var isExpanded = false private var isExpanded = false
private var animationDuration = 300 private var animationDuration = 300
private val globalAudioPlayer = GlobalAudioPlayerManager.getInstance()
private var mSampleIsPlaying = false
private lateinit var mEventListener: IEventListener private lateinit var mEventListener: IEventListener
interface IEventListener { interface IEventListener {
@ -161,87 +162,37 @@ class ExpandSoundSelectView @JvmOverloads constructor(
private fun playSoundSample(chatSound: ChatSound) { private fun playSoundSample(chatSound: ChatSound) {
if (audioPlayer.isPlaying()) { if (chatSound.sampleUrl.isEmpty()) {
pauseAudio() // return
} }
if (!chatSound.sampleUrl.isEmpty()) { with (globalAudioPlayer) {
playAudio( chatSound.sampleUrl) globalAudioPlayer.play(NovelApplication.appContext().getExternalFilesDir(null)?.path + "/ringtone.mp3",
} else {
playAudio(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() { override fun onDetachedFromWindow() {
post { super.onDetachedFromWindow()
// 更新UI为播放状态 if (mSampleIsPlaying) {
} globalAudioPlayer.stop()
}
override fun onAudioError(errorMessage: String) {
post {
Toast.makeText(context, "播放失败: $errorMessage", Toast.LENGTH_SHORT).show()
} }
} }
} }
/**
// 在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<View>(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<View>(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()
}
}
*/

View File

@ -3,6 +3,7 @@ package com.remax.visualnovel.ui.chat.setting.customui.expandableSelector
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.drake.brv.annotaion.DividerOrientation import com.drake.brv.annotaion.DividerOrientation
import com.drake.brv.utils.bindingAdapter 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.utils.ResUtil
import com.remax.visualnovel.widget.uitoken.setBgColorDirectly import com.remax.visualnovel.widget.uitoken.setBgColorDirectly
private const val FILTER_ALL:Int = 0 private const val FILTER_ALL:Int = 0
private const val FILTER_MALE:Int = 1 private const val FILTER_MALE:Int = 1
private const val FILTER_FEMALE:Int = 2 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.setTextColor(ResUtil.getColor(if (item.gender == 1) R.color.male_text_color else R.color.female_text_color))
tvSoundDescrible.text = item.desLanguage tvSoundDescrible.text = item.desLanguage
ivSelect.setImageResource(if (item.isSelected) R.drawable.sound_item_selected else R.drawable.sound_item_unselected) 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 mBinding.itemsRv.models = items
} }
fun onItemPlayStateChanged(chatSound: ChatSound, isPlaying: Boolean) {
chatSound.isPlaying = isPlaying
mBinding.itemsRv.bindingAdapter.notifyDataSetChanged()
}
} }

View File

@ -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) {}
}

View File

@ -13,8 +13,6 @@ import android.util.Log
import com.remax.visualnovel.configs.NovelApplication import com.remax.visualnovel.configs.NovelApplication
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
// Player State // Player State
sealed class AudioPlayState { sealed class AudioPlayState {
@ -23,7 +21,7 @@ sealed class AudioPlayState {
object Playing : AudioPlayState() object Playing : AudioPlayState()
object Paused : AudioPlayState() object Paused : AudioPlayState()
object Stopped : 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() object Completed : AudioPlayState()
} }
@ -56,9 +54,9 @@ data class AudioPlayerConfig(
// Player state listener // Player state listener
interface AudioPlayListener { interface AudioPlayListener {
fun onStateChanged(state: AudioPlayState, source: String) fun onStateChanged(state: AudioPlayState)
fun onProgressChanged(position: Int, duration: Int, source: String) fun onProgressChanged(position: Int, duration: Int)
fun onBufferingUpdate(percent: Int, source: String) 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 var currentState: AudioPlayState = AudioPlayState.Idle
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
private var currentSource: String = ""
private var previousSource: String = ""
private var isAudioFocusGranted = false private var isAudioFocusGranted = false
private var audioFocusRequest: AudioFocusRequest? = null private var audioFocusRequest: AudioFocusRequest? = null
private var wasPlayingBeforeFocusLoss = false private var wasPlayingBeforeFocusLoss = false
// Listeners, just notify events about specified file(Map, key) // key: hash code of
private val sourceListeners = ConcurrentHashMap<String, CopyOnWriteArrayList<AudioPlayListener>>() @Volatile
private var mCurListener: AudioPlayListener? = null
// progress runnable // progress runnable
private var progressUpdateRunnable: Runnable? = null private var progressUpdateRunnable: Runnable? = null
@Volatile
private var isReleased = false private var isReleased = false
init { init {
@ -120,7 +119,7 @@ class GlobalAudioPlayerManager private constructor(
if (isReleased) return if (isReleased) return
try { try {
mediaPlayer = MediaPlayer().apply { mMediaPlayer = MediaPlayer().apply {
setAudioAttributes( setAudioAttributes(
AudioAttributes.Builder() AudioAttributes.Builder()
.setUsage(config.usage) .setUsage(config.usage)
@ -133,9 +132,9 @@ class GlobalAudioPlayerManager private constructor(
setOnBufferingUpdateListener(::onBufferingUpdate) setOnBufferingUpdateListener(::onBufferingUpdate)
isLooping = config.loop isLooping = config.loop
} }
setState(AudioPlayState.Idle, currentSource) setState(AudioPlayState.Idle)
} catch (e: Exception) { } 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. * 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) { if (isReleased) {
Log.w("AudioPlayer", "Player released, play failed") Log.w("AudioPlayer", "Player released, play failed")
return false return false
@ -152,103 +151,64 @@ class GlobalAudioPlayerManager private constructor(
val sourceString = convertSourceToString(source) val sourceString = convertSourceToString(source)
if (sourceString.isBlank()) { if (sourceString.isBlank()) {
handleError(AudioErrorCode.INVALID_DATA_SOURCE, "Invalid source", sourceString) handleError(AudioErrorCode.INVALID_DATA_SOURCE, "Invalid source")
return false return false
} }
// 停止上一个播放如果是不同的source // Stop playing one
if (currentSource.isNotEmpty() && currentSource != sourceString) { if (isPlaying()) {
stopCurrentPlayback() stopCurrentPlayback()
} }
// 注册监听器 mCurListener = listener
listener?.let { addListener(sourceString, it, tag) }
currentSource = sourceString
return startPlayback(source) 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的监听器 * Pause
*/
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过滤
}
}
}
/**
* 暂停播放
*/ */
fun pause(): Boolean { fun pause(): Boolean {
if (isReleased || currentState != AudioPlayState.Playing) return false if (isReleased || currentState != AudioPlayState.Playing) return false
return try { return try {
mediaPlayer?.pause() mMediaPlayer?.pause()
setState(AudioPlayState.Paused, currentSource) setState(AudioPlayState.Paused)
stopProgressUpdates() stopProgressUpdates()
true true
} catch (e: Exception) { } catch (e: Exception) {
handleError(AudioErrorCode.UNKNOWN_ERROR, "暂停失败: ${e.message}", currentSource) handleError(AudioErrorCode.UNKNOWN_ERROR, "Pause failed: ${e.message}")
false false
} }
} }
/** /**
* 恢复播放 * resume play
*/ */
fun resume(): Boolean { fun resume(): Boolean {
if (isReleased || currentState != AudioPlayState.Paused) return false if (isReleased || currentState != AudioPlayState.Paused) return false
if (!requestAudioFocus()) { if (!requestAudioFocus()) {
handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "无法获取音频焦点", currentSource) handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "Can't get audio focus")
return false return false
} }
return try { return try {
mediaPlayer?.start() mMediaPlayer?.start()
setState(AudioPlayState.Playing, currentSource) setState(AudioPlayState.Playing)
startProgressUpdates() startProgressUpdates()
true true
} catch (e: Exception) { } catch (e: Exception) {
handleError(AudioErrorCode.UNKNOWN_ERROR, "恢复播放失败: ${e.message}", currentSource) handleError(AudioErrorCode.UNKNOWN_ERROR, "恢复播放失败: ${e.message}")
false false
} }
} }
/** /**
* 停止播放 * stop play
*/ */
fun stop(): Boolean { fun stop(): Boolean {
if (isReleased) return false if (isReleased) return false
stopCurrentPlayback() stopCurrentPlayback()
return true return true
} }
@ -260,7 +220,7 @@ class GlobalAudioPlayerManager private constructor(
if (isReleased) return false if (isReleased) return false
return try { return try {
mediaPlayer?.takeIf { it.duration > 0 }?.let { player -> mMediaPlayer?.takeIf { it.duration > 0 }?.let { player ->
val safePosition = position.coerceIn(0, player.duration) val safePosition = position.coerceIn(0, player.duration)
player.seekTo(safePosition) player.seekTo(safePosition)
true true
@ -270,11 +230,6 @@ class GlobalAudioPlayerManager private constructor(
} }
} }
/**
* 获取当前播放的source
*/
fun getCurrentSource(): String = currentSource
/** /**
* 是否正在播放 * 是否正在播放
*/ */
@ -284,21 +239,20 @@ class GlobalAudioPlayerManager private constructor(
* 获取当前状态 * 获取当前状态
*/ */
fun getCurrentState(): AudioPlayState = currentState fun getCurrentState(): AudioPlayState = currentState
// endregion // endregion
// region 播放控制内部实现
// region 播放控制内部实现
private fun startPlayback(source: Any): Boolean { private fun startPlayback(source: Any): Boolean {
if (!requestAudioFocus()) { if (!requestAudioFocus()) {
handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "无法获取音频焦点", currentSource) handleError(AudioErrorCode.AUDIO_FOCUS_REQUEST_FAILED, "Can not get audio focus")
return false return false
} }
return try { return try {
setState(AudioPlayState.Preparing, currentSource) setState(AudioPlayState.Preparing)
mediaPlayer?.let { player -> mMediaPlayer?.let { player ->
player.reset() player.reset()
when (source) { when (source) {
@ -316,6 +270,7 @@ class GlobalAudioPlayerManager private constructor(
player.prepareAsync() player.prepareAsync()
true true
} ?: false } ?: false
} catch (e: Exception) { } catch (e: Exception) {
handlePlayException(e, "Play prepare failed") handlePlayException(e, "Play prepare failed")
false false
@ -323,7 +278,7 @@ class GlobalAudioPlayerManager private constructor(
} }
private fun setupDataSourceFromString(source: String) { private fun setupDataSourceFromString(source: String) {
mediaPlayer?.let { player -> mMediaPlayer?.let { player ->
when { when {
source.startsWith("http://") || source.startsWith("https://") -> { source.startsWith("http://") || source.startsWith("https://") -> {
player.setDataSource(source) player.setDataSource(source)
@ -349,22 +304,15 @@ class GlobalAudioPlayerManager private constructor(
private fun stopCurrentPlayback() { private fun stopCurrentPlayback() {
try { try {
mediaPlayer?.let { player -> mMediaPlayer.let { player ->
if (player.isPlaying) { player?.stop()
player.stop()
}
} }
setState(AudioPlayState.Stopped, currentSource)
setState(AudioPlayState.Stopped)
stopProgressUpdates() stopProgressUpdates()
abandonAudioFocus() abandonAudioFocus()
// 通知上一个source的监听器停止 mCurListener = null
if (previousSource.isNotEmpty()) {
notifyStateChanged(AudioPlayState.Stopped, previousSource)
}
previousSource = currentSource
currentSource = ""
} catch (e: Exception) { } catch (e: Exception) {
Log.e("AudioPlayer", "Stop playing failed: ${e.message}") Log.e("AudioPlayer", "Stop playing failed: ${e.message}")
} }
@ -466,7 +414,7 @@ class GlobalAudioPlayerManager private constructor(
private fun onAudioFocusDuck() { private fun onAudioFocusDuck() {
if (config.duckOnFocusLoss) { if (config.duckOnFocusLoss) {
mediaPlayer?.setVolume(0.2f, 0.2f) mMediaPlayer?.setVolume(0.2f, 0.2f)
} else { } else {
wasPlayingBeforeFocusLoss = currentState == AudioPlayState.Playing wasPlayingBeforeFocusLoss = currentState == AudioPlayState.Playing
if (wasPlayingBeforeFocusLoss) { if (wasPlayingBeforeFocusLoss) {
@ -484,22 +432,23 @@ class GlobalAudioPlayerManager private constructor(
try { try {
if (config.autoPlay) { if (config.autoPlay) {
mp.start() mp.start()
setState(AudioPlayState.Playing, currentSource) setState(AudioPlayState.Playing)
startProgressUpdates() startProgressUpdates()
} else { } else {
setState(AudioPlayState.Paused, currentSource) setState(AudioPlayState.Paused)
} }
} catch (e: Exception) { } 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) { private fun onCompletion(mp: MediaPlayer) {
mainHandler.post { mainHandler.post {
setState(AudioPlayState.Completed, currentSource) setState(AudioPlayState.Completed)
stopProgressUpdates() stopProgressUpdates()
abandonAudioFocus() abandonAudioFocus()
mCurListener = null
} }
} }
@ -511,14 +460,14 @@ class GlobalAudioPlayerManager private constructor(
MediaPlayer.MEDIA_ERROR_IO -> "I/O ERROR" MediaPlayer.MEDIA_ERROR_IO -> "I/O ERROR"
else -> "Media Error: what=$what, extra=$extra" else -> "Media Error: what=$what, extra=$extra"
} }
handleError(AudioErrorCode.PREPARE_FAILED, errorMessage, currentSource) handleError(AudioErrorCode.PREPARE_FAILED, errorMessage)
} }
return true return true
} }
private fun onBufferingUpdate(mp: MediaPlayer, percent: Int) { private fun onBufferingUpdate(mp: MediaPlayer, percent: Int) {
mainHandler.post { mainHandler.post {
notifyBufferingUpdate(percent, currentSource) notifyBufferingUpdate(percent)
} }
} }
// endregion // endregion
@ -532,9 +481,9 @@ class GlobalAudioPlayerManager private constructor(
override fun run() { override fun run() {
if (currentState == AudioPlayState.Playing && !isReleased) { if (currentState == AudioPlayState.Playing && !isReleased) {
try { try {
val position = mediaPlayer?.currentPosition ?: 0 val position = mMediaPlayer?.currentPosition ?: 0
val duration = mediaPlayer?.duration ?: 0 val duration = mMediaPlayer?.duration ?: 0
notifyProgressChanged(position, duration, currentSource) notifyProgressChanged(position, duration)
} catch (e: Exception) { } catch (e: Exception) {
} }
} }
@ -553,54 +502,42 @@ class GlobalAudioPlayerManager private constructor(
// endregion // endregion
// region Various state notifications // region Various state notifications
private fun setState(state: AudioPlayState, source: String) { private fun setState(state: AudioPlayState) {
if (currentState != state) { if (currentState != state) {
currentState = state currentState = state
notifyStateChanged(state, source) notifyStateChanged(state)
} }
} }
private fun notifyStateChanged(state: AudioPlayState, source: String) { private fun notifyStateChanged(state: AudioPlayState) {
mainHandler.post { val listener = mCurListener
sourceListeners[source]?.forEach { listener -> if (listener != null) {
try { mainHandler.post {
listener.onStateChanged(state, source) listener.onStateChanged(state)
} catch (e: Exception) {
Log.e("AudioPlayer", "onStateChanged exception: ${e.message}")
}
} }
} }
} }
private fun notifyProgressChanged(position: Int, duration: Int, source: String) { private fun notifyProgressChanged(position: Int, duration: Int) {
mainHandler.post { mainHandler.post {
sourceListeners[source]?.forEach { listener -> if (mCurListener != null) {
try { mCurListener?.onProgressChanged(position, duration)
listener.onProgressChanged(position, duration, source)
} catch (e: Exception) {
Log.e("AudioPlayer", "onProgressChanged Exception: ${e.message}")
}
} }
} }
} }
private fun notifyBufferingUpdate(percent: Int, source: String) { private fun notifyBufferingUpdate(percent: Int) {
mainHandler.post { mainHandler.post {
sourceListeners[source]?.forEach { listener -> if (mCurListener != null) {
try { mCurListener?.onBufferingUpdate(percent)
listener.onBufferingUpdate(percent, source)
} catch (e: Exception) {
Log.e("AudioPlayer", "onBufferingUpdate exception: ${e.message}")
}
} }
} }
} }
// endregion // endregion
// region 工具方法 // region Tools
private fun convertSourceToString(source: Any): String { private fun convertSourceToString(source: Any): String {
return when (source) { return when (source) {
is String -> source is String -> source
@ -617,16 +554,16 @@ class GlobalAudioPlayerManager private constructor(
is IllegalArgumentException -> AudioErrorCode.FORMAT_NOT_SUPPORTED is IllegalArgumentException -> AudioErrorCode.FORMAT_NOT_SUPPORTED
else -> AudioErrorCode.UNKNOWN_ERROR else -> AudioErrorCode.UNKNOWN_ERROR
} }
handleError(errorCode, "$message: ${e.message}", currentSource) handleError(errorCode, "$message: ${e.message}")
} }
private fun handleError(errorCode: Int, errorMessage: String, source: String) { private fun handleError(errorCode: Int, errorMessage: String) {
Log.e("AudioPlayer", "Player Error[$errorCode]: $errorMessage, source: $source") setState(AudioPlayState.Error(errorCode, errorMessage))
setState(AudioPlayState.Error(errorCode, errorMessage, source), source)
stopProgressUpdates() stopProgressUpdates()
abandonAudioFocus() abandonAudioFocus()
mCurListener = null
Log.e("AudioPlayer", "Player Error[$errorCode]: $errorMessage")
} }
// endregion // endregion
// region release related // region release related
@ -636,12 +573,11 @@ class GlobalAudioPlayerManager private constructor(
isReleased = true isReleased = true
stopCurrentPlayback() stopCurrentPlayback()
stopProgressUpdates() stopProgressUpdates()
sourceListeners.clear()
abandonAudioFocus() abandonAudioFocus()
try { try {
mediaPlayer?.release() mMediaPlayer?.release()
mediaPlayer = null mMediaPlayer = null
} catch (e: Exception) { } catch (e: Exception) {
Log.e("AudioPlayer", "Release player failed: ${e.message}") Log.e("AudioPlayer", "Release player failed: ${e.message}")
} }