Files
kiosk/app/src/main/java/com/yzx/kiosk/audio/AudioPlayService.kt
T
2026-08-04 13:55:20 +08:00

198 lines
6.1 KiB
Kotlin

package com.yzx.kiosk.audio
import android.content.Context
import android.net.Uri
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.yzx.kiosk.utils.LogUtils
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton
/**
* 音频播放服务
* 用于播放在线音频,支持切换音频时及时播放新的音频
*/
@Singleton
class AudioPlayService @Inject constructor(
private val context: Context
) {
private var exoPlayer: ExoPlayer? = null
private var currentAudioUrl: String? = null
// 播放状态
private val _isPlaying = MutableStateFlow(false)
val isPlaying: StateFlow<Boolean> = _isPlaying.asStateFlow()
// 当前播放的音频URL
private val _currentUrl = MutableStateFlow<String?>(null)
val currentUrl: StateFlow<String?> = _currentUrl.asStateFlow()
companion object {
private const val TAG = "AudioPlayService"
}
init {
initializePlayer()
}
/**
* 初始化播放器
*/
private fun initializePlayer() {
if (exoPlayer == null) {
exoPlayer = ExoPlayer.Builder(context).build().apply {
repeatMode = Player.REPEAT_MODE_OFF // 默认单次播放,不循环
addListener(object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_READY -> {
LogUtils.d(TAG, "音频准备就绪")
}
Player.STATE_ENDED -> {
LogUtils.d(TAG, "音频播放结束")
_isPlaying.value = false
}
Player.STATE_BUFFERING -> {
LogUtils.d(TAG, "音频缓冲中")
}
Player.STATE_IDLE -> {
LogUtils.d(TAG, "音频空闲")
}
}
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
_isPlaying.value = isPlaying
LogUtils.d(TAG, "播放状态变化: $isPlaying")
}
override fun onPlayerError(error: androidx.media3.common.PlaybackException) {
LogUtils.e(TAG, "音频播放错误: ${error.message}")
_isPlaying.value = false
}
})
}
}
}
/**
* 播放音频
* @param audioUrl 音频URL
* @param forceRestart 是否强制重新开始(即使URL相同)
*/
fun play(audioUrl: String?, forceRestart: Boolean = false) {
if (audioUrl.isNullOrEmpty()) {
LogUtils.i(TAG, "音频URL为空,停止播放")
stop()
return
}
val player = exoPlayer ?: run {
initializePlayer()
exoPlayer
} ?: return
// 如果URL相同且不强制重启,则继续播放或恢复播放
if (audioUrl == currentAudioUrl && !forceRestart) {
if (!player.isPlaying) {
player.play()
LogUtils.d(TAG, "恢复播放音频: $audioUrl")
} else {
LogUtils.d(TAG, "音频已在播放: $audioUrl")
}
return
}
// URL不同或强制重启,切换音频
LogUtils.d(TAG, "切换音频: $currentAudioUrl -> $audioUrl")
try {
// 停止当前播放
if (player.isPlaying) {
player.stop()
}
// 设置新的音频
val mediaItem = MediaItem.fromUri(Uri.parse(audioUrl))
player.setMediaItem(mediaItem)
player.prepare()
// 开始播放
player.play()
// 更新当前URL
currentAudioUrl = audioUrl
_currentUrl.value = audioUrl
LogUtils.d(TAG, "开始播放音频: $audioUrl")
} catch (e: Exception) {
LogUtils.e(TAG, "播放音频失败: ${e.message}")
_isPlaying.value = false
}
}
/**
* 暂停播放
*/
fun pause() {
exoPlayer?.pause()
LogUtils.d(TAG, "暂停播放")
}
/**
* 恢复播放
*/
fun resume() {
exoPlayer?.play()
LogUtils.d(TAG, "恢复播放")
}
/**
* 停止播放
*/
fun stop() {
exoPlayer?.stop()
currentAudioUrl = null
_currentUrl.value = null
_isPlaying.value = false
LogUtils.d(TAG, "停止播放")
}
/**
* 释放播放器资源
*/
fun release() {
exoPlayer?.release()
exoPlayer = null
currentAudioUrl = null
_currentUrl.value = null
_isPlaying.value = false
LogUtils.d(TAG, "释放播放器资源")
}
/**
* 设置音量
* @param volume 音量值,范围 0.0f - 1.0f
*/
fun setVolume(volume: Float) {
exoPlayer?.volume = volume.coerceIn(0f, 1f)
}
/**
* 设置循环播放模式
* @param repeatMode 循环模式:
* - Player.REPEAT_MODE_OFF: 不循环(单次播放)
* - Player.REPEAT_MODE_ONE: 循环播放当前音频
* - Player.REPEAT_MODE_ALL: 循环播放播放列表中的所有音频
*/
fun setRepeatMode(repeatMode: Int) {
exoPlayer?.repeatMode = repeatMode
LogUtils.d(TAG, "设置循环模式: $repeatMode")
}
}