diff --git a/app/src/main/java/xyz/cottongin/radio247/service/RadioPlayerAdapter.kt b/app/src/main/java/xyz/cottongin/radio247/service/RadioPlayerAdapter.kt new file mode 100644 index 0000000..ebe6cd0 --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/service/RadioPlayerAdapter.kt @@ -0,0 +1,160 @@ +package xyz.cottongin.radio247.service + +import android.os.Looper +import androidx.media3.common.BasePlayer +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.Timeline +import xyz.cottongin.radio247.audio.IcyMetadata +import xyz.cottongin.radio247.data.model.Station + +class RadioPlayerAdapter( + private val onPlay: () -> Unit, + private val onStop: () -> Unit, +) : BasePlayer() { + + private val listeners = mutableListOf() + private var _playbackState: Int = Player.STATE_IDLE + private var _playWhenReady: Boolean = false + private var _mediaMetadata: MediaMetadata = MediaMetadata.EMPTY + private var _currentMediaItem: MediaItem? = null + + override fun getApplicationLooper(): Looper = Looper.getMainLooper() + + override fun addListener(listener: Player.Listener) { + listeners.add(listener) + } + + override fun removeListener(listener: Player.Listener) { + listeners.remove(listener) + } + + override fun getAvailableCommands(): Player.Commands = + Player.Commands.Builder() + .add(Player.COMMAND_PLAY_PAUSE) + .add(Player.COMMAND_STOP) + .add(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) + .add(Player.COMMAND_GET_MEDIA_ITEMS_METADATA) + .build() + + override fun getPlaybackState(): Int = _playbackState + override fun getPlayWhenReady(): Boolean = _playWhenReady + override fun isPlaying(): Boolean = _playbackState == Player.STATE_READY && _playWhenReady + override fun getMediaMetadata(): MediaMetadata = _mediaMetadata + override fun getCurrentMediaItem(): MediaItem? = _currentMediaItem + + override fun play() { + _playWhenReady = true + onPlay() + } + + override fun pause() { + _playWhenReady = false + onStop() + } + + override fun stop() { + _playWhenReady = false + onStop() + } + + override fun prepare() {} + + override fun setPlayWhenReady(playWhenReady: Boolean) { + if (playWhenReady) play() else pause() + } + + override fun release() { + listeners.clear() + } + + override fun getPlaybackParameters(): PlaybackParameters = PlaybackParameters.DEFAULT + override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {} + + override fun getRepeatMode(): Int = Player.REPEAT_MODE_OFF + override fun setRepeatMode(repeatMode: Int) {} + + override fun getShuffleModeEnabled(): Boolean = false + override fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) {} + + override fun seekToDefaultPosition() {} + override fun seekToDefaultPosition(mediaItemIndex: Int) {} + override fun seekTo(positionMs: Long) {} + override fun seekTo(mediaItemIndex: Int, positionMs: Long) {} + + override fun getCurrentTimeline(): Timeline = Timeline.EMPTY + override fun getCurrentMediaItemIndex(): Int = 0 + override fun getContentPosition(): Long = 0 + override fun getContentDuration(): Long = C.TIME_UNSET + override fun getCurrentPosition(): Long = 0 + override fun getDuration(): Long = C.TIME_UNSET + override fun getBufferedPosition(): Long = 0 + override fun getTotalBufferedDuration(): Long = 0 + override fun isCurrentMediaItemLive(): Boolean = true + + fun updatePlaybackState(state: PlaybackState) { + val (newPlayerState, newPlayWhenReady) = when (state) { + is PlaybackState.Idle -> Player.STATE_IDLE to false + is PlaybackState.Connecting -> Player.STATE_BUFFERING to true + is PlaybackState.Playing -> Player.STATE_READY to true + is PlaybackState.Paused -> Player.STATE_READY to false + is PlaybackState.Reconnecting -> Player.STATE_BUFFERING to true + } + + val stateChanged = newPlayerState != _playbackState + val playWhenReadyChanged = newPlayWhenReady != _playWhenReady + _playbackState = newPlayerState + _playWhenReady = newPlayWhenReady + + if (stateChanged || playWhenReadyChanged) { + listeners.forEach { listener -> + if (playWhenReadyChanged) { + listener.onPlayWhenReadyChanged( + newPlayWhenReady, + Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST + ) + } + if (stateChanged) { + listener.onPlaybackStateChanged(newPlayerState) + } + listener.onIsPlayingChanged(isPlaying) + } + } + } + + fun updateStation(station: Station) { + _currentMediaItem = MediaItem.Builder() + .setMediaId("station:${station.id}") + .setMediaMetadata( + MediaMetadata.Builder() + .setTitle(station.name) + .setIsPlayable(true) + .build() + ) + .build() + listeners.forEach { + it.onMediaItemTransition( + _currentMediaItem, + Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED + ) + } + } + + fun updateMetadata( + station: Station, + metadata: IcyMetadata?, + artworkUri: android.net.Uri? = null + ) { + _mediaMetadata = MediaMetadata.Builder() + .setStation(station.name) + .setTitle(metadata?.title ?: station.name) + .setArtist(metadata?.artist) + .setArtworkUri(artworkUri) + .setIsPlayable(true) + .build() + listeners.forEach { it.onMediaMetadataChanged(_mediaMetadata) } + } +}