From f75edc50c6791b7934e8781ac26cc2790ecdbcc6 Mon Sep 17 00:00:00 2001 From: Tobias Schlottke Date: Fri, 21 Aug 2026 19:23:47 +0200 Subject: [PATCH] fix(soloist): re-adopt a zone that becomes the active device again with no stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switching a Connect device back to a zone in the last seconds of a track left the zone frozen at end-of-track — playback never advanced. The handoff that moved the account away tears the zone's stream down (finishTrack), and coming back is announced as a `device_changed`, which carries no `status`. onEvent only re-adopts on a `status === 'playing'` event, so the reactivation itself never triggers it. Mid-track the following `playing`/`track_changed` events re-adopt the zone; but when the switch-back lands at the track boundary Soloist reaches the end before any of them arrive and then sends none, leaving the zone owner=`queue`, no stream, stuck at end-of-track while Soloist plays on into a pipe nobody reads. Re-adopt on `device_changed` when the device is active again but has no stream, so the pipe is reopened, playback is consumed, and the track advances. Fixes #352. Verified against a deterministic repro (transfer to zone B -> seek dur-5s -> transfer back to zone A): without the fix the zone pins at progress==duration; with it, it adopts ("took this zone over") and advances to the next track. Co-Authored-By: Claude Opus 4.8 --- .../spotify/soloist/soloistPlaybackService.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/adapters/inputs/spotify/soloist/soloistPlaybackService.ts b/src/adapters/inputs/spotify/soloist/soloistPlaybackService.ts index 69724100..b177b65b 100644 --- a/src/adapters/inputs/spotify/soloist/soloistPlaybackService.ts +++ b/src/adapters/inputs/spotify/soloist/soloistPlaybackService.ts @@ -492,6 +492,20 @@ export class SoloistPlaybackService { return; } + // Just became the active device again, but with nothing carrying our audio. + // + // The handoff that moved the account away tore this zone's stream down (finishTrack), and coming + // back is announced as a `device_changed`, which carries no `status` — so the `status === 'playing'` + // adopt below never fires on it. Mid-track that is harmless: the `playing` and `track_changed` + // events that follow re-adopt it. But when the switch back lands in the last seconds of a track, + // Soloist reaches the track's end before any of those arrive and then sends none, leaving the zone + // owner=`queue`, no stream, stuck at end-of-track while Soloist plays on into a pipe nobody reads. + // Re-adopting here reopens the pipe so playback is consumed again and advances. See #352. + if (event.type === 'device_changed' && !runner.stream) { + void this.adoptConnectPlayback(zoneId, event); + return; + } + // Both lists arrive together on `queue_changed`, unasked after every change. They are what // tells a step backwards from a step forwards, and while the app owns the zone they are also // the only account anyone here has of what is coming.