Playback errors slice + scrubber polish + various polish #74
@@ -166,17 +166,51 @@ class PlayerController @Inject constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a fresh radio queue from [trackId] and start playback.
|
||||
* Replaces the existing queue. The `source` tag is "radio:<id>"
|
||||
* so the server-side rotation reporter can distinguish radio
|
||||
* plays from album / playlist plays. No-op on an empty server
|
||||
* response; throws on transport / non-2xx for the caller to
|
||||
* surface in a snackbar.
|
||||
* Seed a fresh radio queue from [trackId]. The `source` tag is
|
||||
* "radio:<id>" so the server-side rotation reporter can
|
||||
* distinguish radio plays from album / playlist plays.
|
||||
*
|
||||
* Two modes:
|
||||
* - Nothing in the queue: start the radio cold from track 0
|
||||
* (the seed plays first, recommendations follow).
|
||||
* - Queue already populated: do NOT interrupt — keep the current
|
||||
* track playing as-is, trim every upcoming track, and append
|
||||
* the radio results after the current item. If the seed is the
|
||||
* currently playing track (the common "tap Start Radio on the
|
||||
* song I'm listening to" case), drop it from the appended list
|
||||
* so it doesn't immediately repeat. This is an intentional
|
||||
* divergence from Flutter's `playerActions.startRadio`, which
|
||||
* always calls `playTracks` and restarts playback from 0.
|
||||
*
|
||||
* No-op on an empty server response; throws on transport / non-2xx
|
||||
* for the caller to surface in a snackbar.
|
||||
*/
|
||||
suspend fun startRadio(trackId: String) {
|
||||
val controller = mediaController ?: return
|
||||
val tracks = radio.seed(trackId)
|
||||
if (tracks.isEmpty()) return
|
||||
setQueue(tracks, initialIndex = 0, source = "radio:$trackId")
|
||||
val source = "radio:$trackId"
|
||||
|
||||
if (controller.mediaItemCount == 0) {
|
||||
setQueue(tracks, initialIndex = 0, source = source)
|
||||
return
|
||||
}
|
||||
|
||||
val currentIdx = controller.currentMediaItemIndex
|
||||
val currentTrack = queueRefs.getOrNull(currentIdx)
|
||||
// Server returns seed at index 0. Drop the duplicate when the
|
||||
// seed is the currently playing track — otherwise append the
|
||||
// full list so the current track is followed by the seed +
|
||||
// recommendations.
|
||||
val toAppend = if (currentTrack?.id == trackId) tracks.drop(1) else tracks
|
||||
if (toAppend.isEmpty()) return
|
||||
|
||||
val nextIdx = currentIdx + 1
|
||||
if (nextIdx < controller.mediaItemCount) {
|
||||
controller.removeMediaItems(nextIdx, controller.mediaItemCount)
|
||||
}
|
||||
queueRefs = queueRefs.take(nextIdx) + toAppend
|
||||
controller.addMediaItems(toAppend.map { it.toMediaItem(source) })
|
||||
}
|
||||
|
||||
// ── Internal: async connect + Listener-driven UI state sync ──────────
|
||||
|
||||
Reference in New Issue
Block a user