fix(player): surface live audio stream errors as PlaybackError
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>
This commit is contained in:
1 parent
8ec1f3c275
commit
83e6f92050
1 file changed
+21
@@ -408,6 +408,7 @@ fn create_output_stream_with_config(
|
||||
sample_rate: u32,
|
||||
channels: u16,
|
||||
exclusive_mode: bool,
|
||||
state: SharedState,
|
||||
) -> Result<MixerDeviceSink, String> {
|
||||
log::info!(
|
||||
"Creating MixerDeviceSink: {}Hz, {} channels, exclusive: {}",
|
||||
@@ -478,9 +479,25 @@ fn create_output_stream_with_config(
|
||||
// Create MixerDeviceSink with custom config
|
||||
match DeviceSinkBuilder::from_device(device) {
|
||||
Ok(builder) => {
|
||||
// rodio's default error callback only eprintln!s a live stream
|
||||
// error (e.g. an ALSA buffer underrun mid-playback), so playback
|
||||
// dies without the driver ever seeing a message to latch. Record
|
||||
// it on the shared state instead so it drains to the event bus as
|
||||
// a PlaybackError. Rate-limited: ALSA can repeat EPIPE every
|
||||
// period on a wedged device, and each recorded message is one
|
||||
// bus event (and one forked hook script) after the drain.
|
||||
let mut last_reported: Option<std::time::Instant> = None;
|
||||
match builder
|
||||
.with_supported_config(&supported_config)
|
||||
.with_buffer_size(cpal_buffer_size)
|
||||
.with_error_callback(move |err| {
|
||||
log::error!("Audio stream error: {err}");
|
||||
let now = std::time::Instant::now();
|
||||
if last_reported.map_or(true, |t| now.duration_since(t).as_secs() >= 5) {
|
||||
last_reported = Some(now);
|
||||
state.record_stream_error(format!("Audio stream error: {err}"));
|
||||
}
|
||||
})
|
||||
.open_stream()
|
||||
{
|
||||
Ok(mixer_sink) => {
|
||||
@@ -1777,6 +1794,7 @@ impl Player {
|
||||
sample_rate,
|
||||
channels,
|
||||
dac_passthrough,
|
||||
thread_state.clone(),
|
||||
)
|
||||
.map(StreamType::rodio)
|
||||
};
|
||||
@@ -1868,6 +1886,7 @@ impl Player {
|
||||
sample_rate,
|
||||
channels,
|
||||
dac_passthrough,
|
||||
thread_state.clone(),
|
||||
)
|
||||
.map(StreamType::rodio)
|
||||
};
|
||||
@@ -2249,6 +2268,7 @@ impl Player {
|
||||
sample_rate,
|
||||
channels,
|
||||
dac_passthrough,
|
||||
thread_state.clone(),
|
||||
)
|
||||
.map(StreamType::rodio)
|
||||
};
|
||||
@@ -2323,6 +2343,7 @@ impl Player {
|
||||
sample_rate,
|
||||
channels,
|
||||
dac_passthrough,
|
||||
thread_state.clone(),
|
||||
)
|
||||
.map(StreamType::rodio)
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user