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:
parent
ba8d354e93
commit
eae0e18714
|
@ -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;
|
||||||
|
|
|
@ -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 || {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
46
src/lib.rs
46
src/lib.rs
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in New Issue