diff --git a/examples/custom.rs b/examples/custom.rs index 2f0e73d14..83f6af9c0 100644 --- a/examples/custom.rs +++ b/examples/custom.rs @@ -48,6 +48,10 @@ impl HostTrait for MyHost { true } + fn new() -> Result { + Ok(Self) + } + fn devices(&self) -> Result { Ok(std::iter::once(MyDevice)) } diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index 2de6551bb..a54f57a48 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -158,16 +158,14 @@ struct BufferTuningState { pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs}; pub type Devices = std::vec::IntoIter; -impl Host { - pub fn new() -> Result { - Ok(Host) - } -} - impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + Ok(Host) + } + fn is_available() -> bool { true } diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 8898e3b4a..d22c8dbc4 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -37,18 +37,6 @@ pub struct Host { } impl Host { - pub fn new() -> Result { - let inner = AlsaContext::new().map_err(|e| { - Error::with_message( - ErrorKind::HostUnavailable, - format!("ALSA is not available: {e}"), - ) - })?; - Ok(Self { - inner: Arc::new(inner), - }) - } - // "default" is a virtual ALSA device that redirects to the configured default. We cannot // determine its actual capabilities without opening it, so we return Unknown direction. fn default_device(&self) -> Device { @@ -65,6 +53,18 @@ impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + let inner = AlsaContext::new().map_err(|e| { + Error::with_message( + ErrorKind::HostUnavailable, + format!("ALSA is not available: {e}"), + ) + })?; + Ok(Self { + inner: Arc::new(inner), + }) + } + fn is_available() -> bool { // Assume ALSA is always available on Linux and BSD. true diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 3d91a1818..882b800ff 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -36,8 +36,11 @@ pub struct Host { asio: Arc, } -impl Host { - pub fn new() -> Result { +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + + fn new() -> Result { com::com_initialized(); let asio = GLOBAL_ASIO .get_or_init(|| Arc::new(sys::Asio::new())) @@ -45,11 +48,6 @@ impl Host { let host = Host { asio }; Ok(host) } -} - -impl HostTrait for Host { - type Devices = Devices; - type Device = Device; fn is_available() -> bool { true diff --git a/src/host/audioworklet/mod.rs b/src/host/audioworklet/mod.rs index b25503580..383a2ae82 100644 --- a/src/host/audioworklet/mod.rs +++ b/src/host/audioworklet/mod.rs @@ -203,8 +203,11 @@ enum Command { Pause, } -impl Host { - pub fn new() -> Result { +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + + fn new() -> Result { if Self::is_available() { Ok(Host) } else { @@ -214,11 +217,6 @@ impl Host { )) } } -} - -impl HostTrait for Host { - type Devices = Devices; - type Device = Device; fn is_available() -> bool { if let Some(window) = web_sys::window() { diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index 9a1eaf898..d664b095f 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -54,16 +54,14 @@ impl fmt::Display for Device { pub struct Host; -impl Host { - pub fn new() -> Result { - Ok(Host) - } -} - impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + Ok(Host) + } + fn is_available() -> bool { true } diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 7b53a40a5..60bbf88c0 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -34,16 +34,14 @@ pub use device::Device; #[derive(Debug)] pub struct Host; -impl Host { - pub fn new() -> Result { - Ok(Host) - } -} - impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + Ok(Host) + } + fn is_available() -> bool { // Assume coreaudio is always available true diff --git a/src/host/custom/mod.rs b/src/host/custom/mod.rs index 2de8b5ff8..0a527f7ea 100644 --- a/src/host/custom/mod.rs +++ b/src/host/custom/mod.rs @@ -28,11 +28,6 @@ use crate::{ pub struct Host(Box); impl Host { - // this only exists for impl_platform_host, which requires it - pub(crate) fn new() -> Result { - Err(Error::new(ErrorKind::HostUnavailable)) - } - /// Construct a custom host from an arbitrary [`HostTrait`] implementation. pub fn from_host(host: T) -> Self where @@ -356,6 +351,10 @@ impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + Err(Error::new(ErrorKind::HostUnavailable)) + } + fn is_available() -> bool { false } diff --git a/src/host/jack/mod.rs b/src/host/jack/mod.rs index 1315aeda2..ca87b199a 100644 --- a/src/host/jack/mod.rs +++ b/src/host/jack/mod.rs @@ -44,17 +44,6 @@ pub struct Host { } impl Host { - pub fn new() -> Result { - let mut host = Host { - name: format!("cpal_client_{}", std::process::id()), - connect_ports_automatically: true, - start_server_automatically: false, - devices_created: vec![], - }; - // Devices don't exist for JACK, they have to be created - host.initialize_default_devices(); - Ok(host) - } /// Configures whether created ports should automatically connect to system playback/capture /// ports. /// @@ -129,6 +118,18 @@ impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + let mut host = Host { + name: format!("cpal_client_{}", std::process::id()), + connect_ports_automatically: true, + start_server_automatically: false, + devices_created: vec![], + }; + // Devices don't exist for JACK, they have to be created + host.initialize_default_devices(); + Ok(host) + } + /// JACK is available if /// - the jack feature flag is set /// - libjack is installed (wouldn't compile without it) diff --git a/src/host/mod.rs b/src/host/mod.rs index 34a92488d..76ec64c45 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -86,20 +86,23 @@ pub(crate) mod webaudio; #[cfg(feature = "custom")] pub(crate) mod custom; -#[cfg(not(any( - windows, - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_vendor = "apple", - target_os = "android", - all( - target_arch = "wasm32", - any(target_os = "emscripten", target_os = "unknown"), - feature = "wasm-bindgen" - ), -)))] +#[cfg(all( + not(feature = "custom"), + not(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_vendor = "apple", + target_os = "android", + all( + target_arch = "wasm32", + any(target_os = "emscripten", target_os = "unknown"), + feature = "wasm-bindgen" + ), + )), +))] pub(crate) mod null; #[cfg(any( diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 2cce813f3..d1e86b199 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -6,8 +6,10 @@ use std::fmt; use std::time::Duration; use crate::{ - CallbackInfo, Data, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error, FrameCount, - SampleFormat, StreamConfig, StreamInstant, SupportedStreamConfig, SupportedStreamConfigRange, + CallbackInfo, Data, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error, + ErrorKind::DeviceNotAvailable, + FrameCount, SampleFormat, StreamConfig, StreamInstant, SupportedStreamConfig, + SupportedStreamConfigRange, traits::{DeviceTrait, HostTrait, StreamTrait}, }; @@ -33,13 +35,6 @@ pub struct SupportedInputConfigs; #[derive(Clone)] pub struct SupportedOutputConfigs; -impl Host { - #[allow(dead_code)] - pub fn new() -> Result { - Ok(Self) - } -} - impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; @@ -54,19 +49,19 @@ impl DeviceTrait for Device { } fn supported_input_configs(&self) -> Result { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn supported_output_configs(&self) -> Result { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn default_input_config(&self) -> Result { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn default_output_config(&self) -> Result { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn build_input_stream_raw( @@ -81,7 +76,7 @@ impl DeviceTrait for Device { D: FnMut(&Data, &CallbackInfo) + Send + 'static, E: FnMut(Error) + Send + 'static, { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } /// Create an output stream. @@ -97,7 +92,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data, &CallbackInfo) + Send + 'static, E: FnMut(Error) + Send + 'static, { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } } @@ -105,6 +100,10 @@ impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + Ok(Self) + } + fn is_available() -> bool { false } @@ -124,23 +123,23 @@ impl HostTrait for Host { impl StreamTrait for Stream { fn start(&self) -> Result<(), Error> { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn pause(&self) -> Result<(), Error> { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn stop(&self, _timeout: Option) -> Result<(), Error> { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } fn now(&self) -> StreamInstant { - unimplemented!() + StreamInstant::ZERO } fn buffer_size(&self) -> Result { - unimplemented!() + Err(Error::with_message(DeviceNotAvailable, "Null host in use")) } } diff --git a/src/host/pipewire/mod.rs b/src/host/pipewire/mod.rs index 412dd3d5c..fb7511d99 100644 --- a/src/host/pipewire/mod.rs +++ b/src/host/pipewire/mod.rs @@ -29,19 +29,6 @@ pub struct Host { } impl Host { - pub fn new() -> Result { - let _pw = PwInitGuard::new(); - let connect_automatically = Arc::new(AtomicBool::new(true)); - let devices = init_devices(connect_automatically.clone()).ok_or_else(|| { - Error::with_message(ErrorKind::HostUnavailable, "PipeWire is not available") - })?; - Ok(Self { - _pw, - devices, - connect_automatically, - }) - } - /// Configures whether created streams should automatically connect to system playback/capture /// nodes via the session manager. /// @@ -60,6 +47,19 @@ impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + let _pw = PwInitGuard::new(); + let connect_automatically = Arc::new(AtomicBool::new(true)); + let devices = init_devices(connect_automatically.clone()).ok_or_else(|| { + Error::with_message(ErrorKind::HostUnavailable, "PipeWire is not available") + })?; + Ok(Self { + _pw, + devices, + connect_automatically, + }) + } + fn is_available() -> bool { utils::find_socket_path().is_some() } diff --git a/src/host/pulseaudio/mod.rs b/src/host/pulseaudio/mod.rs index 6a0280b78..d02712d7b 100644 --- a/src/host/pulseaudio/mod.rs +++ b/src/host/pulseaudio/mod.rs @@ -128,8 +128,11 @@ pub struct Host { client: pulseaudio::Client, } -impl Host { - pub fn new() -> Result { +impl HostTrait for Host { + type Devices = std::vec::IntoIter; + type Device = Device; + + fn new() -> Result { // `Client::from_env` does a blocking auth handshake with no socket timeout. If this never // returns, fall through to the next host with no other option than to leak the thread. let (tx, rx) = mpsc::channel(); @@ -153,11 +156,6 @@ impl Host { Ok(Self { client }) } -} - -impl HostTrait for Host { - type Devices = std::vec::IntoIter; - type Device = Device; fn is_available() -> bool { pulseaudio::socket_path_from_env().is_some() diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index 999d1ab86..487e936d0 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -32,16 +32,14 @@ mod stream; #[derive(Debug)] pub struct Host; -impl Host { - pub fn new() -> Result { - Ok(Host) - } -} - impl HostTrait for Host { type Devices = Devices; type Device = Device; + fn new() -> Result { + Ok(Host) + } + fn is_available() -> bool { // Assume WASAPI is always available on Windows. true diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 422fa26de..e3d20e91f 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -159,8 +159,11 @@ const SCRIPT_PROCESSOR_VALID_BUFFER_SIZES: [usize; 7] = [256, 512, 1024, 2048, 4 const SCRIPT_PROCESSOR_MIN_BUFFER_SIZE: usize = 256; const SCRIPT_PROCESSOR_MAX_BUFFER_SIZE: usize = 16384; -impl Host { - pub fn new() -> Result { +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + + fn new() -> Result { if Self::is_available() { Ok(Self) } else { @@ -170,11 +173,6 @@ impl Host { )) } } -} - -impl HostTrait for Host { - type Devices = Devices; - type Device = Device; fn is_available() -> bool { is_webaudio_available() diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 96da987a3..e5e55ece2 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -581,6 +581,19 @@ macro_rules! impl_platform_host { false } + fn new() -> Result + where + Self: Sized, + { + $( + $(#[cfg($feat)])? + if let Ok(host) = <$Host>::new() { + return Ok(host.into()); + } + )* + Err(crate::Error::new(crate::ErrorKind::HostUnavailable)) + } + fn devices(&self) -> Result { match self.0 { $( @@ -835,7 +848,7 @@ macro_rules! impl_platform_host { $( $(#[cfg($feat)])? HostId::$HostVariant => { - <$Host>::new() + <$Host as crate::traits::HostTrait>::new() .map(HostInner::$HostVariant) .map(Host::from) } @@ -886,17 +899,17 @@ mod platform_impl { pub fn default_host() -> Host { #[cfg(feature = "pipewire")] if ::is_available() { - if let Ok(host) = PipeWireHost::new() { + if let Ok(host) = ::new() { return host.into(); } } #[cfg(feature = "pulseaudio")] if ::is_available() { - if let Ok(host) = PulseAudioHost::new() { + if let Ok(host) = ::new() { return host.into(); } } - AlsaHost::new() + ::new() .expect("the default host should always be available") .into() } @@ -916,7 +929,7 @@ mod platform_impl { /// The default host for the current compilation target platform. pub fn default_host() -> Host { - CoreAudioHost::new() + ::new() .expect("the default host should always be available") .into() } @@ -935,7 +948,6 @@ mod platform_impl { ))] use crate::host::audioworklet::Host as AudioWorkletHost; use crate::host::webaudio::Host as WebAudioHost; - use crate::traits::HostTrait as _; impl_platform_host!( WebAudio => WebAudioHost, @@ -951,11 +963,13 @@ mod platform_impl { /// where `AudioContext` is unavailable. pub fn default_host() -> Host { assert!( - WebAudioHost::is_available(), + ::is_available(), "WebAudio is not available in this context; \ AudioContext requires a Window (not a Worker or Service Worker)" ); - WebAudioHost::new().unwrap().into() + ::new() + .unwrap() + .into() } } @@ -976,7 +990,7 @@ mod platform_impl { /// The default host for the current compilation target platform. pub fn default_host() -> Host { - WasapiHost::new() + ::new() .expect("the default host should always be available") .into() } @@ -992,12 +1006,13 @@ mod platform_impl { /// The default host for the current compilation target platform. pub fn default_host() -> Host { - AAudioHost::new() + ::new() .expect("the default host should always be available") .into() } } +// A compilation on a platform with no known host backends #[cfg(not(any( windows, target_os = "linux", @@ -1013,16 +1028,28 @@ mod platform_impl { ), )))] mod platform_impl { + #[cfg(feature = "custom")] + use crate::host::custom::Host as CustomHost; + #[cfg(not(feature = "custom"))] use crate::host::null::Host as NullHost; impl_platform_host!( - Null => NullHost, - #[cfg(feature = "custom")] Custom => super::CustomHost, + #[cfg(not(feature = "custom"))] Null => NullHost, + #[cfg(feature = "custom")] Custom => CustomHost, ); /// The default host for the current compilation target platform. + #[cfg(feature = "custom")] + pub fn default_host() -> Host { + ::new() + .expect("the default host should always be available") + .into() + } + + /// The default host for the current compilation target platform. + #[cfg(not(feature = "custom"))] pub fn default_host() -> Host { - NullHost::new() + ::new() .expect("the default host should always be available") .into() } diff --git a/src/traits.rs b/src/traits.rs index beeff09d2..69b5e65e7 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -48,6 +48,10 @@ pub trait HostTrait { /// Whether or not the host is available on the system. fn is_available() -> bool; + fn new() -> Result + where + Self: Sized; + /// An iterator yielding all [`Device`](DeviceTrait)s currently available to the host on the system. /// /// Can be empty if the system does not support audio in general.