diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f149dec..87f34c2c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `DEFAULT_SAMPLE_RATE` set to match `cpal::SAMPLE_RATE_48K`. - Added `Source::resample` and `ResampleConfig` for high-quality sample rate conversion. - Presets for `AGC`. +- Added `with_error_callback` to `MicrophoneBuilder` and `SpeakersBuilder` to replace the default error callback, which logs every stream error. ### Changed diff --git a/src/microphone/builder.rs b/src/microphone/builder.rs index a698424bd..6e6d65d88 100644 --- a/src/microphone/builder.rs +++ b/src/microphone/builder.rs @@ -123,6 +123,52 @@ impl MicrophoneBuilder where E: FnMut(cpal::Error) + Send + Clone + 'static, { + /// Sets a callback that is called when an error occurs with the stream, + /// in place of the default one, which logs every error. + /// + /// # Example + /// Tell the application when the stream needs reopening. A failing stream can + /// report the same error many times a second, so this keeps only the errors + /// that need action, in a bounded channel that never blocks the audio thread. + /// ```no_run + /// # use rodio::microphone::MicrophoneBuilder; + /// use std::sync::mpsc; + /// + /// let (failure_tx, failure_rx) = mpsc::sync_channel(1); + /// let microphone = MicrophoneBuilder::new() + /// .with_error_callback(move |err| { + /// if matches!( + /// err.kind(), + /// cpal::ErrorKind::DeviceNotAvailable | cpal::ErrorKind::StreamInvalidated + /// ) { + /// let _channel_full_or_closed = failure_tx.try_send(err); + /// } + /// }) + /// .default_device()? + /// .default_config()? + /// .open_stream()?; + /// + /// // Somewhere the application checks regularly, such as its main loop. + /// if let Ok(err) = failure_rx.try_recv() { + /// eprintln!("microphone stream failed: {err}"); + /// // Drop `microphone` and open it again, on another device if this one is gone. + /// } + /// # Ok::<(), Box>(()) + /// ``` + pub fn with_error_callback(self, error_callback: F) -> MicrophoneBuilder + where + F: FnMut(cpal::Error) + Send + Clone + 'static, + { + MicrophoneBuilder { + device: self.device, + config: self.config, + error_callback, + + device_set: PhantomData, + config_set: PhantomData, + } + } + /// Sets the input device to use. /// /// # Example diff --git a/src/speakers/builder.rs b/src/speakers/builder.rs index 0ad891656..3061c7e27 100644 --- a/src/speakers/builder.rs +++ b/src/speakers/builder.rs @@ -125,6 +125,52 @@ impl SpeakersBuilder where E: FnMut(cpal::Error) + Send + Clone + 'static, { + /// Sets a callback that is called when an error occurs with the stream, + /// in place of the default one, which logs every error. + /// + /// # Example + /// Tell the application when the stream needs reopening. A failing stream can + /// report the same error many times a second, so this keeps only the errors + /// that need action, in a bounded channel that never blocks the audio thread. + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// use std::sync::mpsc; + /// + /// let (failure_tx, failure_rx) = mpsc::sync_channel(1); + /// let speakers = SpeakersBuilder::new() + /// .with_error_callback(move |err| { + /// if matches!( + /// err.kind(), + /// cpal::ErrorKind::DeviceNotAvailable | cpal::ErrorKind::StreamInvalidated + /// ) { + /// let _channel_full_or_closed = failure_tx.try_send(err); + /// } + /// }) + /// .default_device()? + /// .default_config()? + /// .open_mixer()?; + /// + /// // Somewhere the application checks regularly, such as its main loop. + /// if let Ok(err) = failure_rx.try_recv() { + /// eprintln!("speakers stream failed: {err}"); + /// // Drop `speakers` and open it again, on another device if this one is gone. + /// } + /// # Ok::<(), Box>(()) + /// ``` + pub fn with_error_callback(self, error_callback: F) -> SpeakersBuilder + where + F: FnMut(cpal::Error) + Send + Clone + 'static, + { + SpeakersBuilder { + device: self.device, + config: self.config, + error_callback, + + device_set: PhantomData, + config_set: PhantomData, + } + } + /// Sets the output device to use. /// /// # Example