feat: add RadioPlayerAdapter — thin Player facade for Media3

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-18 05:58:09 -04:00
parent 39653462d1
commit 04225de888

View File

@@ -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<Player.Listener>()
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) }
}
}