Add `PlayStreamError` and `PauseStreamError`.

This allows for properly handling potential failure on macOS. We should
also consider propagating the mutex/channel poison errors through these
new types, especially considering the potential removal of the event
loop in favour of switching over to high-priority audio threads on
windows and linux.
This commit is contained in:
mitchmindtree 2019-06-21 03:03:03 +02:00
parent ba8d354e93
commit eae0e18714
9 changed files with 86 additions and 18 deletions

View File

@ -5,7 +5,7 @@ fn main() {
let format = device.default_output_format().expect("Failed to get default output format"); let format = device.default_output_format().expect("Failed to get default output format");
let event_loop = cpal::EventLoop::new(); let event_loop = cpal::EventLoop::new();
let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); let stream_id = event_loop.build_output_stream(&device, &format).unwrap();
event_loop.play_stream(stream_id.clone()); event_loop.play_stream(stream_id.clone()).unwrap();
let sample_rate = format.sample_rate.0 as f32; let sample_rate = format.sample_rate.0 as f32;
let mut sample_clock = 0f32; let mut sample_clock = 0f32;

View File

@ -43,8 +43,8 @@ fn main() {
// Play the streams. // Play the streams.
println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS); println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS);
event_loop.play_stream(input_stream_id.clone()); event_loop.play_stream(input_stream_id.clone()).unwrap();
event_loop.play_stream(output_stream_id.clone()); event_loop.play_stream(output_stream_id.clone()).unwrap();
// Run the event loop on a separate thread. // Run the event loop on a separate thread.
std::thread::spawn(move || { std::thread::spawn(move || {

View File

@ -14,7 +14,7 @@ fn main() {
let event_loop = cpal::EventLoop::new(); let event_loop = cpal::EventLoop::new();
let stream_id = event_loop.build_input_stream(&device, &format) let stream_id = event_loop.build_input_stream(&device, &format)
.expect("Failed to build input stream"); .expect("Failed to build input stream");
event_loop.play_stream(stream_id); event_loop.play_stream(stream_id).unwrap();
// The WAV file we're recording to. // The WAV file we're recording to.
const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");

View File

@ -9,6 +9,8 @@ use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DeviceNameError; use DeviceNameError;
use Format; use Format;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError; use SupportedFormatsError;
use SampleFormat; use SampleFormat;
use SampleRate; use SampleRate;
@ -837,13 +839,15 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, stream_id: StreamId) { pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
self.push_command(Command::PlayStream(stream_id)); self.push_command(Command::PlayStream(stream_id));
Ok(())
} }
#[inline] #[inline]
pub fn pause_stream(&self, stream_id: StreamId) { pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
self.push_command(Command::PauseStream(stream_id)); self.push_command(Command::PauseStream(stream_id));
Ok(())
} }
} }

View File

@ -7,6 +7,8 @@ use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DeviceNameError; use DeviceNameError;
use Format; use Format;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError; use SupportedFormatsError;
use Sample; use Sample;
use SampleFormat; use SampleFormat;
@ -782,24 +784,34 @@ impl EventLoop {
streams[stream_id.0] = None; streams[stream_id.0] = None;
} }
pub fn play_stream(&self, stream: StreamId) { pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
let mut streams = self.streams.lock().unwrap(); let mut streams = self.streams.lock().unwrap();
let stream = streams[stream.0].as_mut().unwrap(); let stream = streams[stream.0].as_mut().unwrap();
if !stream.playing { if !stream.playing {
stream.audio_unit.start().unwrap(); if let Err(e) = stream.audio_unit.start() {
let description = format!("{}", std::error::Error::description(e));
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = true; stream.playing = true;
} }
Ok(())
} }
pub fn pause_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
let mut streams = self.streams.lock().unwrap(); let mut streams = self.streams.lock().unwrap();
let stream = streams[stream.0].as_mut().unwrap(); let stream = streams[stream.0].as_mut().unwrap();
if stream.playing { if stream.playing {
stream.audio_unit.stop().unwrap(); if let Err(e) = stream.audio_unit.stop() {
let description = format!("{}", std::error::Error::description(e));
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = false; stream.playing = false;
} }
Ok(())
} }
} }

View File

@ -13,6 +13,8 @@ use DefaultFormatError;
use DeviceNameError; use DeviceNameError;
use DevicesError; use DevicesError;
use Format; use Format;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError; use SupportedFormatsError;
use StreamData; use StreamData;
use SupportedFormat; use SupportedFormat;
@ -147,23 +149,25 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, stream_id: StreamId) { pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
let streams = self.streams.lock().unwrap(); let streams = self.streams.lock().unwrap();
let stream = streams let stream = streams
.get(stream_id.0) .get(stream_id.0)
.and_then(|v| v.as_ref()) .and_then(|v| v.as_ref())
.expect("invalid stream ID"); .expect("invalid stream ID");
js!(@{stream}.resume()); js!(@{stream}.resume());
Ok(())
} }
#[inline] #[inline]
pub fn pause_stream(&self, stream_id: StreamId) { pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
let streams = self.streams.lock().unwrap(); let streams = self.streams.lock().unwrap();
let stream = streams let stream = streams
.get(stream_id.0) .get(stream_id.0)
.and_then(|v| v.as_ref()) .and_then(|v| v.as_ref())
.expect("invalid stream ID"); .expect("invalid stream ID");
js!(@{stream}.suspend()); js!(@{stream}.suspend());
Ok(())
} }
} }

View File

@ -382,6 +382,36 @@ pub enum BuildStreamError {
} }
} }
/// Errors that might occur when calling `play_stream`.
///
/// As of writing this, only macOS may immediately return an error while calling this method. This
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
/// them immediately.
#[derive(Debug, Fail)]
pub enum PlayStreamError {
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
}
/// Errors that might occur when calling `pause_stream`.
///
/// As of writing this, only macOS may immediately return an error while calling this method. This
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
/// them immediately.
#[derive(Debug, Fail)]
pub enum PauseStreamError {
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
}
/// An iterator yielding all `Device`s currently available to the system. /// An iterator yielding all `Device`s currently available to the system.
/// ///
/// Can be empty if the system does not support audio in general. /// Can be empty if the system does not support audio in general.
@ -522,7 +552,7 @@ impl EventLoop {
/// If the stream does not exist, this function can either panic or be a no-op. /// If the stream does not exist, this function can either panic or be a no-op.
/// ///
#[inline] #[inline]
pub fn play_stream(&self, stream: StreamId) { pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
self.0.play_stream(stream.0) self.0.play_stream(stream.0)
} }
@ -537,7 +567,7 @@ impl EventLoop {
/// If the stream does not exist, this function can either panic or be a no-op. /// If the stream does not exist, this function can either panic or be a no-op.
/// ///
#[inline] #[inline]
pub fn pause_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
self.0.pause_stream(stream.0) self.0.pause_stream(stream.0)
} }
@ -789,6 +819,18 @@ impl From<BackendSpecificError> for BuildStreamError {
} }
} }
impl From<BackendSpecificError> for PlayStreamError {
fn from(err: BackendSpecificError) -> Self {
PlayStreamError::BackendSpecific { err }
}
}
impl From<BackendSpecificError> for PauseStreamError {
fn from(err: BackendSpecificError) -> Self {
PauseStreamError::BackendSpecific { err }
}
}
// If a backend does not provide an API for retrieving supported formats, we query it with a bunch // If a backend does not provide an API for retrieving supported formats, we query it with a bunch
// of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa.
// //

View File

@ -7,6 +7,8 @@ use DefaultFormatError;
use DevicesError; use DevicesError;
use DeviceNameError; use DeviceNameError;
use Format; use Format;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError; use SupportedFormatsError;
use StreamData; use StreamData;
use SupportedFormat; use SupportedFormat;
@ -42,12 +44,12 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, _: StreamId) { pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> {
panic!() panic!()
} }
#[inline] #[inline]
pub fn pause_stream(&self, _: StreamId) { pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> {
panic!() panic!()
} }
} }

View File

@ -23,6 +23,8 @@ use std::sync::atomic::Ordering;
use BackendSpecificError; use BackendSpecificError;
use BuildStreamError; use BuildStreamError;
use Format; use Format;
use PauseStreamError;
use PlayStreamError;
use SampleFormat; use SampleFormat;
use StreamData; use StreamData;
use UnknownTypeOutputBuffer; use UnknownTypeOutputBuffer;
@ -642,13 +644,15 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, stream: StreamId) { pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
self.push_command(Command::PlayStream(stream)); self.push_command(Command::PlayStream(stream));
Ok(())
} }
#[inline] #[inline]
pub fn pause_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
self.push_command(Command::PauseStream(stream)); self.push_command(Command::PauseStream(stream));
Ok(())
} }
#[inline] #[inline]