Three commits on dev since the v2026.06.04 hotfix re-cut. All caught on-device.
80179343 — Sonos queue resync on playlist switch (full reload)
Tapping a different playlist while Sonos was the active route updated the player view but Sonos kept playing the OLD queue. PlayerController.setQueue replaced the local ExoPlayer queue and called play(), which forwarded SOAP Play to Sonos — but Sonos's native queue (loaded once at route selection via removeAllTracks + AddURIToQueue + SetAVTransportURI) was never refreshed.
MinstrelForwardingPlayer.setMediaItems (all 3 overloads) clears holder.active + sets target synchronously so the immediately-following play() drops via isLoadingUpnp().
OutputPickerController observes uiState.queue identity changes and runs a loadQueueOnSonos reload when the joined-ids key shifts.
e7d7cb24 — Incremental Sonos diff for playNext + radio-append
The first commit re-loaded Sonos's full queue on every uiState.queue identity change. Correct for playlist-switch but disruptive for in-queue mutations — playNext and radio-append would restart the currently-playing track on Sonos because removeAllTracks + AddURIToQueue x N + SetAVTransportURI re-anchors the transport.
Now the resync runs a longest-common-prefix / suffix diff first. When the current Sonos track lies in the preserved prefix, applies the minimum-incremental SOAP operations: RemoveTrackRangeFromQueue on the removed middle + AddURIToQueue at the same insertion point. Sonos keeps playing the current track and the new entries land in place. Falls back to the full reload only when the diff would orphan playback (the playlist-switch case from the prior commit).
Adds AVTransportClient.removeTrackRangeFromQueue (Sonos-specific, UpdateID=0 skips the queue-version check).
Cases covered:
Playlist switch → full reload (current track replaced, prefix=0)
playNext insert → 1 AddURIToQueue at slot currentIdx+2
Radio-append → RemoveTrackRangeFromQueue for old tail + N AddURIToQueue for new tracks
On-device logcat showed cold-start playback taking ~25s on fresh install. AudioPrefetcher was kicking off N concurrent CacheWriter jobs the instant setQueue updated uiState — each a full upcoming-track download over the same OkHttp client as the playback DataSource. Five-way bandwidth split plus parallel Coil cover fetches starved the current track until its full body had streamed (~12 MB at ~1 MB/s under contention).
Now reconcile() observes uiState.isPlaying and starts upcoming-track prefetches only when the current track is actually playing. Cancellation of out-of-window jobs still always runs so a queue switch or skip frees the pipe immediately, even while paused.
Expected cold-start time-to-play: ~25s → ~5-7s on the user's network (single-stream throughput plus one-time TLS/DNS tax).
Test plan
Sonos: tap a different playlist while Sonos is playing → audio switches to the new queue immediately, no audio drop while reload runs.
Sonos: tap "Play next" on a track while another plays → inserted track plays AFTER current, current doesn't restart.
Sonos: start radio from a track → trailing queue replaced with radio picks, current track keeps playing.
Fresh install + cold play → first audio within ~5-7s, not ~25s. No multi-tap impatience.
Pause + resume → no behavior regression. Skips still cancel prefetches as before.
Three commits on dev since the v2026.06.04 hotfix re-cut. All caught on-device.
## 80179343 — Sonos queue resync on playlist switch (full reload)
Tapping a different playlist while Sonos was the active route updated the player view but Sonos kept playing the OLD queue. `PlayerController.setQueue` replaced the local ExoPlayer queue and called `play()`, which forwarded SOAP Play to Sonos — but Sonos's native queue (loaded once at route selection via `removeAllTracks + AddURIToQueue + SetAVTransportURI`) was never refreshed.
- `MinstrelForwardingPlayer.setMediaItems` (all 3 overloads) clears `holder.active` + sets target synchronously so the immediately-following `play()` drops via `isLoadingUpnp()`.
- `OutputPickerController` observes `uiState.queue` identity changes and runs a `loadQueueOnSonos` reload when the joined-ids key shifts.
## e7d7cb24 — Incremental Sonos diff for playNext + radio-append
The first commit re-loaded Sonos's full queue on every uiState.queue identity change. Correct for playlist-switch but disruptive for in-queue mutations — `playNext` and radio-append would restart the currently-playing track on Sonos because `removeAllTracks + AddURIToQueue x N + SetAVTransportURI` re-anchors the transport.
Now the resync runs a longest-common-prefix / suffix diff first. When the current Sonos track lies in the preserved prefix, applies the minimum-incremental SOAP operations: `RemoveTrackRangeFromQueue` on the removed middle + `AddURIToQueue` at the same insertion point. Sonos keeps playing the current track and the new entries land in place. Falls back to the full reload only when the diff would orphan playback (the playlist-switch case from the prior commit).
Adds `AVTransportClient.removeTrackRangeFromQueue` (Sonos-specific, `UpdateID=0` skips the queue-version check).
Cases covered:
- Playlist switch → full reload (current track replaced, prefix=0)
- `playNext` insert → 1 `AddURIToQueue` at slot `currentIdx+2`
- Radio-append → `RemoveTrackRangeFromQueue` for old tail + N `AddURIToQueue` for new tracks
## 9a31955f — Gate AudioPrefetcher on isPlaying
On-device logcat showed cold-start playback taking ~25s on fresh install. `AudioPrefetcher` was kicking off N concurrent `CacheWriter` jobs the instant `setQueue` updated `uiState` — each a full upcoming-track download over the same OkHttp client as the playback `DataSource`. Five-way bandwidth split plus parallel Coil cover fetches starved the current track until its full body had streamed (~12 MB at ~1 MB/s under contention).
Now `reconcile()` observes `uiState.isPlaying` and starts upcoming-track prefetches only when the current track is actually playing. Cancellation of out-of-window jobs still always runs so a queue switch or skip frees the pipe immediately, even while paused.
Expected cold-start time-to-play: ~25s → ~5-7s on the user's network (single-stream throughput plus one-time TLS/DNS tax).
## Test plan
- [ ] Sonos: tap a different playlist while Sonos is playing → audio switches to the new queue immediately, no audio drop while reload runs.
- [ ] Sonos: tap "Play next" on a track while another plays → inserted track plays AFTER current, current doesn't restart.
- [ ] Sonos: start radio from a track → trailing queue replaced with radio picks, current track keeps playing.
- [ ] Fresh install + cold play → first audio within ~5-7s, not ~25s. No multi-tap impatience.
- [ ] Pause + resume → no behavior regression. Skips still cancel prefetches as before.
Before: tapping a different playlist while Sonos was the active route
updated the player view but Sonos kept the old queue and played those
tracks (or whatever was last there). PlayerController.setQueue replaced
the local ExoPlayer queue and called play(), which forwarded SOAP Play
to Sonos -- but Sonos's native queue (loaded once at route selection
via removeAllTracks + AddURIToQueue + SetAVTransportURI) was never
touched on subsequent setQueue calls.
Now: MinstrelForwardingPlayer.setMediaItems (all 3 overloads) clears
holder.active + sets target synchronously so the immediately-following
play() drops via isLoadingUpnp(). OutputPickerController observes
uiState.queue identity changes; when target or active is non-null and
the queue key shifted, it re-runs loadQueueOnSonos under the existing
selectUpnpMutex and restores active when done. Sonos resync failures
drop cleanly to local (selectedUpnpRouteIdInternal nulled).
Doesn't touch addMediaItem / radio-append paths -- those leave Sonos's
queue stale and need a separate AddURIToQueue extension hook; out of
scope for this fix.
The previous fix re-loaded Sonos's full queue on every uiState.queue
identity change -- correct for playlist-switch (full replacement) but
disruptive for in-queue mutations: playNext and radio-append would
restart the currently-playing track on Sonos because removeAllTracks
+ AddURIToQueue x N + SetAVTransportURI re-anchors the transport.
Now the resync runs a longest-common-prefix / common-suffix diff first.
When the current Sonos track lies in the preserved prefix, applies the
minimum-incremental SOAP operations -- RemoveTrackRangeFromQueue on the
removed middle, AddURIToQueue at the same insertion point -- so Sonos
keeps playing the current track and the new entries land in place
without interrupting playback. Falls back to the full removeAllTracks
reload when the current track is in the removed slice (playlist
switch).
Adds AVTransportClient.removeTrackRangeFromQueue (Sonos-specific,
UpdateID=0 skips the queue-version check).
Cases now covered:
- Playlist switch -> full reload (current track replaced, prefix=0)
- playNext insert -> 1 AddURIToQueue at the right slot
- Radio-append -> RemoveTrackRangeFromQueue for old tail + N
AddURIToQueue for new tracks at the end
Cold-start playback on a fresh install was taking ~25 s before any
audio played. Logcat showed AudioPrefetcher was kicking off N
concurrent CacheWriter jobs the instant setQueue updated uiState --
each prefetch a full upcoming-track download over the same OkHttp
client as the current-track DataSource. Five-way bandwidth split
plus parallel Coil cover fetches starved the current track until
its full file body had streamed through (~12 MB at ~1 MB/s under
contention).
Now reconcile() observes uiState.isPlaying and starts upcoming-track
prefetches only when the current track is actually playing.
Cancellation of out-of-window jobs always runs so a queue switch or
skip still frees the pipe immediately, even while paused. Cold start
should drop from ~25 s -> 5-7 s on the user's network: just the
single-stream throughput plus the one-time TLS/DNS tax.
Refactored the inline reconcile body into computeTargets /
cancelOutOfWindowLocked / startInWindowLocked helpers to keep
ReturnCount under the detekt cap.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Three commits on dev since the v2026.06.04 hotfix re-cut. All caught on-device.
80179343— Sonos queue resync on playlist switch (full reload)Tapping a different playlist while Sonos was the active route updated the player view but Sonos kept playing the OLD queue.
PlayerController.setQueuereplaced the local ExoPlayer queue and calledplay(), which forwarded SOAP Play to Sonos — but Sonos's native queue (loaded once at route selection viaremoveAllTracks + AddURIToQueue + SetAVTransportURI) was never refreshed.MinstrelForwardingPlayer.setMediaItems(all 3 overloads) clearsholder.active+ sets target synchronously so the immediately-followingplay()drops viaisLoadingUpnp().OutputPickerControllerobservesuiState.queueidentity changes and runs aloadQueueOnSonosreload when the joined-ids key shifts.e7d7cb24— Incremental Sonos diff for playNext + radio-appendThe first commit re-loaded Sonos's full queue on every uiState.queue identity change. Correct for playlist-switch but disruptive for in-queue mutations —
playNextand radio-append would restart the currently-playing track on Sonos becauseremoveAllTracks + AddURIToQueue x N + SetAVTransportURIre-anchors the transport.Now the resync runs a longest-common-prefix / suffix diff first. When the current Sonos track lies in the preserved prefix, applies the minimum-incremental SOAP operations:
RemoveTrackRangeFromQueueon the removed middle +AddURIToQueueat the same insertion point. Sonos keeps playing the current track and the new entries land in place. Falls back to the full reload only when the diff would orphan playback (the playlist-switch case from the prior commit).Adds
AVTransportClient.removeTrackRangeFromQueue(Sonos-specific,UpdateID=0skips the queue-version check).Cases covered:
playNextinsert → 1AddURIToQueueat slotcurrentIdx+2RemoveTrackRangeFromQueuefor old tail + NAddURIToQueuefor new tracks9a31955f— Gate AudioPrefetcher on isPlayingOn-device logcat showed cold-start playback taking ~25s on fresh install.
AudioPrefetcherwas kicking off N concurrentCacheWriterjobs the instantsetQueueupdateduiState— each a full upcoming-track download over the same OkHttp client as the playbackDataSource. Five-way bandwidth split plus parallel Coil cover fetches starved the current track until its full body had streamed (~12 MB at ~1 MB/s under contention).Now
reconcile()observesuiState.isPlayingand starts upcoming-track prefetches only when the current track is actually playing. Cancellation of out-of-window jobs still always runs so a queue switch or skip frees the pipe immediately, even while paused.Expected cold-start time-to-play: ~25s → ~5-7s on the user's network (single-stream throughput plus one-time TLS/DNS tax).
Test plan
The previous fix re-loaded Sonos's full queue on every uiState.queue identity change -- correct for playlist-switch (full replacement) but disruptive for in-queue mutations: playNext and radio-append would restart the currently-playing track on Sonos because removeAllTracks + AddURIToQueue x N + SetAVTransportURI re-anchors the transport. Now the resync runs a longest-common-prefix / common-suffix diff first. When the current Sonos track lies in the preserved prefix, applies the minimum-incremental SOAP operations -- RemoveTrackRangeFromQueue on the removed middle, AddURIToQueue at the same insertion point -- so Sonos keeps playing the current track and the new entries land in place without interrupting playback. Falls back to the full removeAllTracks reload when the current track is in the removed slice (playlist switch). Adds AVTransportClient.removeTrackRangeFromQueue (Sonos-specific, UpdateID=0 skips the queue-version check). Cases now covered: - Playlist switch -> full reload (current track replaced, prefix=0) - playNext insert -> 1 AddURIToQueue at the right slot - Radio-append -> RemoveTrackRangeFromQueue for old tail + N AddURIToQueue for new tracks at the end