A state-only SetState resume (playing_state=play, current_track=null)
can reach a renderer whose queue cursor is set but whose engine holds
no loaded audio — the session store restores the queue paused and
unloaded after a restart. The play arm issued a bare engine.resume(),
which dies in the audio thread ("cannot resume - no audio data
available") while the renderer keeps reporting paused 0:00, so a
mid-track handoff from a peer renderer wedges the controlling app
forever.
When the engine has no loaded audio and the cloud state names a
current track, force-stream that track at the reported position
(force_remote_track_stream — the plain track-id guard would skip,
since the restored cursor already reports the track). The
has_loaded_audio gate keeps cloud echoes and live playback on the
plain resume path, and the SetActive takeback fix already established
this exact pattern.
Repro: cast a queue to qbzd, restart the daemon, play on another
renderer, then hand off mid-track by selecting the qbzd renderer in
the app: it sticks at paused 0:00 until a CLI play primes the engine.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 9032e6e027c29b386042fce1b1e95388b20e4f01)
A mid-playback stream error (e.g. an ALSA buffer underrun/EPIPE) was
only visible through rodio's default error callback, which eprintln!s
and drops it: playback died with a bare PlaybackStateChanged(paused)
and no PlaybackError on the bus, so hook/SSE integrators (moOde) could
not tell a device hiccup from a user pause.
Install an error callback on the rodio output stream that records the
error on SharedState, where the playback driver already drains it into
the stream-error latch and the daemon bridges it to
CoreEvent::PlaybackError. Rate-limited to one recording per 5s since a
wedged ALSA device repeats EPIPE every period and each drained message
becomes one bus event (and one forked hook script).
Found while testing the moOde qbzevent.sh integration on-device: a
buffer underrun paused playback with no PlaybackError emitted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>