Split off from #55, which fixes the #54 playback-clock bug for ALSA. The recovery is correct and bounded now, but it still costs one sync error and a long silence.
What happens
Measured on #55's hardware test, a 14 s USB DAC replug at 48 kHz:
- While the device is absent,
write() swallows audio immediately. Nothing back-pressures the sync task, so it drains its decode pipeline and keeps feeding whatever arrives. The stream pointer ends up ahead of real time by the server's send-ahead.
- At reopen, the retired outage gap resets the playtime estimate to the device's finish timestamp (
SyncTask::process_playback_progress). The playhead is correct from here on.
- The next chunk is not due for about 10.8 s, far past the 5 ms
HARD_SYNC_THRESHOLD_US, so the sync task reports ERROR once (Lost sync (10840302us off)) and inserts silence until the timeline catches up. Sync is regained roughly 11 s later.
So it is one bounded event rather than the loop #54 reported, but an operator still sees an ERROR state and about 11 s of silence after every recovery.
Two ways to avoid it
1. Pace the discard (this repo). While the device is absent, consume audio at roughly real time — waiting out write()'s existing deadline rather than returning at once — so the pipeline keeps its lookahead and the stream pointer stays near real time. The correction at reopen then lands inside the soft-sync range and nothing reports an error. write() already has a deadline and waits in slices, so the shape is there. The risks are a sink that blocks longer than the player expects during an outage, and that it should apply to all four sinks (#58).
2. Signal the outage to the library (needs sendspin-cpp). The sync task already suppresses the ERROR state while aligning, for initial sync and post-seek alignment. A sink that reported "I lost the device and have retired the gap" could get one silent realignment instead of an ERROR/SYNCHRONIZED pair. That needs an upstream API, so it would carry Requires-Upstream.
Option 1 shortens the silence as well as removing the error; option 2 only relabels it. Worth deciding which behaviour is wanted before implementing either.
Split off from #55, which fixes the #54 playback-clock bug for ALSA. The recovery is correct and bounded now, but it still costs one sync error and a long silence.
What happens
Measured on #55's hardware test, a 14 s USB DAC replug at 48 kHz:
write()swallows audio immediately. Nothing back-pressures the sync task, so it drains its decode pipeline and keeps feeding whatever arrives. The stream pointer ends up ahead of real time by the server's send-ahead.SyncTask::process_playback_progress). The playhead is correct from here on.HARD_SYNC_THRESHOLD_US, so the sync task reports ERROR once (Lost sync (10840302us off)) and inserts silence until the timeline catches up. Sync is regained roughly 11 s later.So it is one bounded event rather than the loop #54 reported, but an operator still sees an ERROR state and about 11 s of silence after every recovery.
Two ways to avoid it
1. Pace the discard (this repo). While the device is absent, consume audio at roughly real time — waiting out
write()'s existing deadline rather than returning at once — so the pipeline keeps its lookahead and the stream pointer stays near real time. The correction at reopen then lands inside the soft-sync range and nothing reports an error.write()already has a deadline and waits in slices, so the shape is there. The risks are a sink that blocks longer than the player expects during an outage, and that it should apply to all four sinks (#58).2. Signal the outage to the library (needs sendspin-cpp). The sync task already suppresses the ERROR state while
aligning, for initial sync and post-seek alignment. A sink that reported "I lost the device and have retired the gap" could get one silent realignment instead of an ERROR/SYNCHRONIZED pair. That needs an upstream API, so it would carryRequires-Upstream.Option 1 shortens the silence as well as removing the error; option 2 only relabels it. Worth deciding which behaviour is wanted before implementing either.