我正在使用Media3和Jetpack Compose创建一个音乐应用程序.当我通过我的Composable(PlayerControls)更改歌曲时,我的UI会正确更新,显示当前正在播放的歌曲的艺术家、歌曲名称、图像和其他详细信息.
然而,当我通过通知更改歌曲时,它确实可以正确切换,但我的用户界面仍然显示为正在播放上一首歌曲.因此,它没有更新.
如果未通过通知更改索引,则索引会更改,但当通过通知更改时,索引将保持不变.这就是为什么我的用户界面没有更新;它没有监听来self 的通知的事件.
我的宋代数据类:
data class Song(
val mediaId: String = "",
val artist: String = "",
val songName: String = "",
val songUrl: String = "",
val imageUrl: String = "",
var isSelected: Boolean = false,
var state: PlayerStates = PlayerStates.STATE_IDLE
)
我的播放器状态:
enum class PlayerStates {
STATE_IDLE,
STATE_READY,
STATE_BUFFERING,
STATE_ERROR,
STATE_END,
STATE_PLAYING,
STATE_PAUSE,
STATE_CHANGE_SONG
}
我的播放器事件:
interface PlayerEvents {
fun onPlayPauseClick()
fun onPreviousClick()
fun onNextClick()
fun onSongClick(song: Song)
fun onSeekBarPositionChanged(position: Long)
}
我的SongServiceHandler
class SongServiceHandler @Inject constructor(
private val player: ExoPlayer
) : Player.Listener {
val mediaState = MutableStateFlow(PlayerStates.STATE_IDLE)
val currentPlaybackPosition: Long
get() = if (player.currentPosition > 0) player.currentPosition else 0L
val currentSongDuration: Long
get() = if (player.duration > 0) player.duration else 0L
private var job: Job? = null
init {
player.addListener(this)
job = Job()
}
fun initPlayer(songList: MutableList<MediaItem>) {
player.setMediaItems(songList)
player.prepare()
}
fun setUpSong(index: Int, isSongPlay: Boolean) {
if (player.playbackState == Player.STATE_IDLE) player.prepare()
player.seekTo(index, 0)
if (isSongPlay) player.playWhenReady = true
Log.d("service", "fun setUpSong()")
}
fun playPause() {
if (player.playbackState == Player.STATE_IDLE) player.prepare()
player.playWhenReady = !player.playWhenReady
}
fun releasePlayer() {
player.release()
}
fun seekToPosition(position: Long) {
player.seekTo(position)
}
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
mediaState.tryEmit(PlayerStates.STATE_ERROR)
Log.d("service", "override fun onPlayerError(error = ${mediaState.value})")
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
if (player.playbackState == Player.STATE_READY) {
if (playWhenReady) {
mediaState.tryEmit(PlayerStates.STATE_PLAYING)
} else {
mediaState.tryEmit(PlayerStates.STATE_PAUSE)
}
}
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
super.onMediaItemTransition(mediaItem, reason)
if (reason == MEDIA_ITEM_TRANSITION_REASON_AUTO) {
mediaState.tryEmit(PlayerStates.STATE_CHANGE_SONG)
mediaState.tryEmit(PlayerStates.STATE_PLAYING)
}
}
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_IDLE -> {
mediaState.tryEmit(PlayerStates.STATE_IDLE)
}
Player.STATE_BUFFERING -> {
mediaState.tryEmit(PlayerStates.STATE_BUFFERING)
}
Player.STATE_READY -> {
mediaState.tryEmit(PlayerStates.STATE_READY)
if (player.playWhenReady) {
mediaState.tryEmit(PlayerStates.STATE_PLAYING)
} else {
mediaState.tryEmit(PlayerStates.STATE_PAUSE)
}
}
Player.STATE_ENDED -> {
mediaState.tryEmit(PlayerStates.STATE_END)
}
}
Log.d("service", "override fun onPlaybackStateChanged(playbackState = $playbackState)")
}
}
我的分机:
fun MutableList<Song>.resetSongs() {
this.forEach { song ->
song.isSelected = false
song.state = PlayerStates.STATE_IDLE
}
}
fun CoroutineScope.collectPlayerState(
songServiceHandler: SongServiceHandler,
updateState: (PlayerStates) -> Unit
) {
this.launch {
songServiceHandler.mediaState.collect {
updateState(it)
}
}
}
fun CoroutineScope.launchPlaybackStateJob(
playbackStateFlow: MutableStateFlow<PlaybackState>,
state: PlayerStates,
songServiceHandler: SongServiceHandler
) = launch {
do {
playbackStateFlow.emit(
PlaybackState(
currentPlaybackPosition = songServiceHandler.currentPlaybackPosition,
currentSongDuration = songServiceHandler.currentSongDuration
)
)
delay(1000)
} while (state == PlayerStates.STATE_PLAYING && isActive)
}
我的SongViewModel:
@HiltViewModel
class SongViewModel @Inject constructor(
private val songServiceHandler: SongServiceHandler,
private val repository: SongRepository
) : ViewModel(), PlayerEvents {
private val _songs = mutableStateListOf<Song>()
val songs: List<Song> get() = _songs
private var isSongPlay: Boolean = false
var selectedSong: Song? by mutableStateOf(null)
private set
private var selectedSongIndex: Int by mutableStateOf(-1)
private val _playbackState = MutableStateFlow(PlaybackState(0L, 0L))
val playbackState: StateFlow<PlaybackState> get() = _playbackState
var isServiceRunning = false
private var playbackStateJob: Job? = null
private var isAuto: Boolean = false
init {
viewModelScope.launch {
loadData()
observePlayerState()
}
}
private fun loadData() = viewModelScope.launch {
_songs.addAll(repository.getAllSongs())
songServiceHandler.initPlayer(
_songs.map { song ->
MediaItem.Builder()
.setMediaId(song.mediaId)
.setUri(song.songUrl.toUri())
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(song.songName)
.setArtist(song.artist)
.setArtworkUri(song.imageUrl.toUri())
.build()
).build()
}.toMutableList()
)
}
private fun onSongSelected(index: Int) {
if (selectedSongIndex == -1) isSongPlay = true
if (selectedSongIndex == -1 || selectedSongIndex != index) {
_songs.resetSongs()
selectedSongIndex = index
setUpSong()
}
}
private fun setUpSong() {
if (!isAuto) {
songServiceHandler.setUpSong(
selectedSongIndex,
isSongPlay
)
isAuto = false
}
}
private fun updateState(state: PlayerStates) {
if (selectedSongIndex != -1) {
isSongPlay = state == PlayerStates.STATE_PLAYING
_songs[selectedSongIndex].state = state
_songs[selectedSongIndex].isSelected = true
selectedSong = null
selectedSong = _songs[selectedSongIndex]
updatePlaybackState(state)
if (state == PlayerStates.STATE_CHANGE_SONG) {
isAuto = true
onNextClick()
}
if (state == PlayerStates.STATE_END) {
onSongSelected(0)
}
}
}
private fun updatePlaybackState(state: PlayerStates) {
playbackStateJob?.cancel()
playbackStateJob = viewModelScope
.launchPlaybackStateJob(
_playbackState,
state,
songServiceHandler
)
}
private fun observePlayerState() {
viewModelScope.collectPlayerState(songServiceHandler, ::updateState)
}
override fun onCleared() {
super.onCleared()
songServiceHandler.releasePlayer()
}
override fun onPlayPauseClick() {
songServiceHandler.playPause()
}
override fun onPreviousClick() {
if (selectedSongIndex > 0) {
onSongSelected(selectedSongIndex - 1)
}
}
override fun onNextClick() {
if (selectedSongIndex < _songs.size - 1) {
onSongSelected(selectedSongIndex + 1)
}
}
override fun onSongClick(song: Song) {
onSongSelected(_songs.indexOf(song))
}
override fun onSeekBarPositionChanged(position: Long) {
viewModelScope.launch { songServiceHandler.seekToPosition(position) }
}
}
我希望当通过通知(下一个/上一个索引)更改音乐时,我的用户界面会更新,就像不使用通知更改音乐时一样.