feat(android): Start Radio preserves current playback, queues after
android / Build + lint + test (push) Failing after 1m24s
android / Build + lint + test (push) Failing after 1m24s
Operator: tapping Start Radio while music plays previously
reloaded the current track from position 0 because the radio
seed response includes the seed at index 0 and the handler called
setQueue(tracks, 0) — Flutter's playerActions.startRadio does the
same. They want the current track to keep playing untouched,
upcoming queue cleared, radio results appended after.
PlayerController.startRadio now branches on mediaItemCount:
- Empty queue: existing behavior — setQueue from index 0.
- Active queue: keep currentMediaItem, removeMediaItems from
currentIdx+1 to end, then addMediaItems with the radio list.
When the seed is the currently-playing track (the common
"Start Radio on the song I'm listening to" case), drop the
seed from the appended list so it doesn't immediately repeat
after the current track ends.
queueRefs is updated alongside the controller so the cached
TrackRef list stays consistent. Source tag "radio:<id>" is
preserved for the appended items so play_started attribution
stays correct.
Intentional divergence from Flutter — recorded in the docstring
so future ports notice it.
This commit is contained in:
@@ -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