Skip to content
2 changes: 1 addition & 1 deletion examples/microphone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.prompt()?;

let input = MicrophoneBuilder::new()
.device(input.into_inner())?
.device(input)?
.default_config()?
.open_stream()?;

Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub use cpal::{
mod common;
mod sink;
mod spatial_sink;
#[cfg(all(feature = "playback", feature = "experimental"))]
pub mod speakers;
#[cfg(feature = "playback")]
pub mod stream;
#[cfg(feature = "wav_output")]
Expand Down
2 changes: 1 addition & 1 deletion src/microphone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
//!
//! // Use a specific device (e.g., the second one)
//! let mic = MicrophoneBuilder::new()
//! .device(inputs[1].clone().into_inner())?
//! .device(inputs[1].clone())?
//! .default_config()?
//! .open_stream()?;
//! # Ok(())
Expand Down
26 changes: 15 additions & 11 deletions src/microphone/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ pub enum Error {
}
assert_error_traits! {Error}

/// Generic on the `MicrophoneBuilder` which is only present when a config has been set.
/// Generic on the `MicrophoneBuilder` which is only present when a device has been set.
/// Methods needing a config are only available on MicrophoneBuilder with this
/// Generic set.
pub struct DeviceIsSet;
/// Generic on the `MicrophoneBuilder` which is only present when a device has been set.
/// Generic on the `MicrophoneBuilder` which is only present when a config has been set.
/// Methods needing a device set are only available on MicrophoneBuilder with this
/// Generic set.
pub struct ConfigIsSet;
Expand Down Expand Up @@ -129,14 +129,14 @@ where
/// ```no_run
/// # use rodio::microphone::{MicrophoneBuilder, available_inputs};
/// let input = available_inputs()?.remove(2);
/// let builder = MicrophoneBuilder::new().device(input.into_inner())?;
/// let builder = MicrophoneBuilder::new().device(input)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn device(
&self,
device: impl Into<cpal::Device>,
device: super::Input,
) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigNotSet, E>, Error> {
let device = device.into();
let device = device.into_inner();
let supported_configs = device
.supported_input_configs()
.map_err(|source| Error::InputConfigs {
Expand Down Expand Up @@ -283,7 +283,12 @@ where
Ok(())
}
}
}

impl<E> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
/// Sets the sample rate for input.
///
/// # Error
Expand Down Expand Up @@ -474,8 +479,7 @@ where
/// let builder = MicrophoneBuilder::new()
/// .default_device()?
/// .default_config()?
/// // We want mono, if thats not possible give
/// // us the lowest channel count
/// // Multiples of two work well for us
/// .prefer_buffer_sizes([
/// 2048.try_into().expect("not zero"),
/// 4096.try_into().expect("not_zero"),
Expand All @@ -489,8 +493,8 @@ where
/// let builder = MicrophoneBuilder::new()
/// .default_device()?
/// .default_config()?
/// // We want mono, if thats not possible give
/// // us the lowest channel count
/// // We need a minimum buffer of 4096
/// // or we get glitches.
/// .prefer_buffer_sizes(4096..);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
Expand Down Expand Up @@ -523,8 +527,8 @@ where
/// println!("Channel count: {}", config.channel_count.get());
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn get_config(&self) -> &InputConfig {
self.config.as_ref().expect("ConfigIsSet")
pub fn get_config(&self) -> InputConfig {
self.config.expect("ConfigIsSet")
}
}

Expand Down
7 changes: 3 additions & 4 deletions src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,9 @@ impl SourcesQueueInput {
where
T: Source + Send + 'static,
{
self.next_sounds
.lock()
.unwrap()
.push((Box::new(source) as Box<_>, None));
let mut next_sounds = self.next_sounds.lock().unwrap();
next_sounds.push((Box::new(source) as Box<_>, None));
next_sounds.len();
}

/// Adds a new source to the end of the queue.
Expand Down
190 changes: 190 additions & 0 deletions src/speakers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//! A speakers sink
//!
//! An audio *stream* originates at a [Source] and flows to a Sink. This is a
//! Sink that plays audio over the systems speakers or headphones through an
//! audio output device;
//!
//! # Basic Usage
//!
//! ```no_run
//! # use rodio::speakers::SpeakersBuilder;
//! # use rodio::{Source, source::SineWave};
//! # use std::time::Duration;
//! let speakers = SpeakersBuilder::new()
//! .default_device()?
//! .default_config()?
//! .open_stream()?;
//! let mixer = speakers.mixer();
//!
//! // Play a beep for 4 seconds
//! mixer.add(SineWave::new(440.).take_duration(Duration::from_secs(4)));
//! std::thread::sleep(Duration::from_secs(4));
//!
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Use preferred parameters if supported
//! Attempt to set a specific channel count, sample rate and buffer size but
//! fall back to the default if the device does not support these
//!
//! ```no_run
//! use rodio::speakers::SpeakersBuilder;
//! use rodio::Source;
//! use std::time::Duration;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut builder = SpeakersBuilder::new()
//! .default_device()?
//! .default_config()?
//! .prefer_channel_counts([
//! 1.try_into().expect("not zero"),
//! 2.try_into().expect("not zero"),
//! ])
//! .prefer_sample_rates([
//! 16_000.try_into().expect("not zero"),
//! 32_000.try_into().expect("not zero"),
//! ])
//! .prefer_buffer_sizes(512..);
//!
//! let mic = builder.open_stream()?;
//! # Ok(())
//! # }
//! ```
//!
//! # Configuration with Error Handling
//! Attempt to set a specific channel count but fall back to the default if
//! the device doesn't support it:
//!
//! ```no_run
//! use rodio::speakers::SpeakersBuilder;
//! use rodio::Source;
//! use std::time::Duration;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut builder = SpeakersBuilder::new()
//! .default_device()?
//! .default_config()?;
//!
//! // Try to set stereo recording (2 channels), but continue with default if unsupported
//! if let Ok(configured_builder) = builder.try_channels(2.try_into()?) {
//! builder = configured_builder;
//! } else {
//! println!("Stereo recording not supported, using default channel configuration");
//! // builder remains unchanged with default configuration
//! }
//!
//! let mic = builder.open_stream()?;
//! # Ok(())
//! # }
//! ```
//!
//! # Device Selection
//!
//! ```no_run
//! use rodio::speakers::{SpeakersBuilder, available_outputs};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // List all available output devices
//! let outputs = available_outputs()?;
//! for (i, output) in outputs.iter().enumerate() {
//! println!("output {}: {}", i, output);
//! }
//!
//! // Use a specific device (e.g., the second one)
//! let mic = SpeakersBuilder::new()
//! .device(outputs[1].clone())?
//! .default_config()?
//! .open_stream()?;
//! # Ok(())
//! # }
//! ```

use core::fmt;

use cpal::{
traits::{DeviceTrait, HostTrait},
Device,
};

use crate::{common::assert_error_traits, StreamError};

mod builder;
mod config;

pub use builder::SpeakersBuilder;
pub use config::OutputConfig;

struct Speakers;

/// Error that can occur when we can not list the output devices
#[derive(Debug, thiserror::Error, Clone)]
#[error("Could not list output devices")]
pub struct ListError(#[source] cpal::DevicesError);
assert_error_traits! {ListError}

/// An output device
#[derive(Clone)]
pub struct Output {
inner: cpal::Device,
default: bool,
}

impl Output {
/// TODO doc comment also mirror to microphone api
pub fn is_default(&self) -> bool {
self.default
}

pub(crate) fn into_inner(self) -> cpal::Device {
self.inner
}
}

impl fmt::Debug for Output {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device")
.field(
"inner",
&self
.inner
.description()
.map(|d| d.name().to_string())
.unwrap_or("unknown".to_string()),
)
.finish()
}
}

impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
self.inner
.description()
.map(|d| d.name().to_string())
.unwrap_or("unknown".to_string()),
)
}
}

/// Returns a list of available output devices on the system.
pub fn available_outputs() -> Result<Vec<Output>, ListError> {
let host = cpal::default_host();
let default = host.default_output_device().map(|d| d.id());
let devices = host.output_devices().map_err(ListError)?.map(|dev| Output {
default: Some(dev.id()) == default,
inner: dev,
});
Ok(devices.collect())
}

impl Speakers {
fn open(
device: Device,
config: OutputConfig,
error_callback: impl FnMut(cpal::StreamError) + Send + 'static,
) -> Result<crate::stream::OutputStream, StreamError> {
crate::stream::OutputStream::open(&device, &config.into_cpal_config(), error_callback)
}
}
Loading