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.
|
* Seed a fresh radio queue from [trackId]. The `source` tag is
|
||||||
* Replaces the existing queue. The `source` tag is "radio:<id>"
|
* "radio:<id>" so the server-side rotation reporter can
|
||||||
* so the server-side rotation reporter can distinguish radio
|
* distinguish radio plays from album / playlist plays.
|
||||||
* plays from album / playlist plays. No-op on an empty server
|
*
|
||||||
* response; throws on transport / non-2xx for the caller to
|
* Two modes:
|
||||||
* surface in a snackbar.
|
* - 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) {
|
suspend fun startRadio(trackId: String) {
|
||||||
|
val controller = mediaController ?: return
|
||||||
val tracks = radio.seed(trackId)
|
val tracks = radio.seed(trackId)
|
||||||
if (tracks.isEmpty()) return
|
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 ──────────
|
// ── Internal: async connect + Listener-driven UI state sync ──────────
|
||||||
|
|||||||
Reference in New Issue
Block a user