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 event_loop = cpal::EventLoop::new();
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 mut sample_clock = 0f32;

View File

@ -43,8 +43,8 @@ fn main() {
// Play the streams.
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(output_stream_id.clone());
event_loop.play_stream(input_stream_id.clone()).unwrap();
event_loop.play_stream(output_stream_id.clone()).unwrap();
// Run the event loop on a separate thread.
std::thread::spawn(move || {

View File

@ -14,7 +14,7 @@ fn main() {
let event_loop = cpal::EventLoop::new();
let stream_id = event_loop.build_input_stream(&device, &format)
.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.
const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");

View File

@ -9,6 +9,8 @@ use BuildStreamError;
use DefaultFormatError;
use DeviceNameError;
use Format;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use SampleFormat;
use SampleRate;
@ -837,13 +839,15 @@ impl EventLoop {
}
#[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));
Ok(())
}
#[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));
Ok(())
}
}

View File

@ -7,6 +7,8 @@ use BuildStreamError;
use DefaultFormatError;
use DeviceNameError;
use Format;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use Sample;
use SampleFormat;
@ -782,24 +784,34 @@ impl EventLoop {
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 stream = streams[stream.0].as_mut().unwrap();
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;
}
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 stream = streams[stream.0].as_mut().unwrap();
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;
}
Ok(())
}
}

View File

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

View File

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