sound 播放逻辑+1
This commit is contained in:
parent
1eb8b02ef0
commit
2d967c52a0
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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<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()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
@ -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<String, CopyOnWriteArrayList<AudioPlayListener>>()
|
||||
// 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}")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue