Files
Android-247-Radio/app/src/main/java/xyz/cottongin/radio247/service/RadioPlayerAdapter.kt
cottongin d451d005c0 fix: register session with addSession() to activate Media3 notifications
MediaSessionService only sets up its internal MediaNotificationManager
when addSession() is called. This normally happens when a MediaController
binds, but since we use custom intents, no controller ever connected.
Calling addSession() after session creation activates the notification
pipeline. Also removed debug logging.

Made-with: Cursor
2026-03-18 06:58:55 -04:00

123 lines
3.9 KiB
Kotlin

package xyz.cottongin.radio247.service
import android.os.Looper
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.common.SimpleBasePlayer
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import xyz.cottongin.radio247.audio.IcyMetadata
import xyz.cottongin.radio247.data.model.Station
class RadioPlayerAdapter(
private val onPlay: () -> Unit,
private val onStop: () -> Unit,
) : SimpleBasePlayer(Looper.getMainLooper()) {
@Volatile
private var _playbackState: Int = Player.STATE_IDLE
@Volatile
private var _playWhenReady: Boolean = false
@Volatile
private var _mediaMetadata: MediaMetadata = MediaMetadata.EMPTY
@Volatile
private var _currentMediaItem: MediaItem? = null
private val 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 getState(): SimpleBasePlayer.State {
val mediaItem = _currentMediaItem
val playlist = if (mediaItem != null) {
listOf(getPlaceholderMediaItemData(mediaItem))
} else {
emptyList()
}
return SimpleBasePlayer.State.Builder()
.setAvailableCommands(commands)
.setPlaybackState(_playbackState)
.setPlayWhenReady(_playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
.setCurrentMediaItemIndex(if (mediaItem != null) 0 else 0)
.setContentPositionMs(0L)
.setIsLoading(false)
.setPlaylist(playlist)
.build()
}
override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
_playWhenReady = playWhenReady
if (playWhenReady) {
onPlay()
} else {
onStop()
}
return Futures.immediateVoidFuture()
}
override fun handleStop(): ListenableFuture<*> {
_playWhenReady = false
onStop()
return Futures.immediateVoidFuture()
}
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
}
_playbackState = newPlayerState
_playWhenReady = newPlayWhenReady
invalidateState()
}
fun updateStation(station: Station) {
_currentMediaItem = MediaItem.Builder()
.setMediaId("station:${station.id}")
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(station.name)
.setIsPlayable(true)
.build()
)
.build()
invalidateState()
}
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()
_currentMediaItem = MediaItem.Builder()
.setMediaId(_currentMediaItem?.mediaId ?: "station:${station.id}")
.setMediaMetadata(_mediaMetadata)
.build()
invalidateState()
}
fun clearState() {
_currentMediaItem = null
_mediaMetadata = MediaMetadata.EMPTY
_playbackState = Player.STATE_IDLE
_playWhenReady = false
}
}