ez_audi/cpal_abstraction/
device.rs

1use std::{fmt::Debug, sync::{Mutex, Arc}};
2
3use cpal::{self, traits::{HostTrait, DeviceTrait}, Host};
4
5use crate::{traits::AudioMetadataTrait, Error, errors::PlayError};
6use crate::samples_player::SamplesPlayerTrait;
7
8use super::{config, Samples, Sample, Stream};
9
10/// An abstraction over cpal::Device, represents a physical output device
11pub struct Device {
12    device: cpal::Device,
13}
14
15impl Device {
16    fn new(device: cpal::Device) -> Device {
17        Device {
18            device
19        }
20    }
21
22    /// Plays the samples on the default device of the default host
23    pub fn play_default_output(player: &mut impl SamplesPlayerTrait) -> Error<()> {
24        let device =  match Device::default_output() {
25            Some(d) => d,
26            None => return Err(PlayError::DeviceDoesNotExist { name: "default".to_string() })
27        };
28    
29        player.play_on_device(device)
30    }
31
32    /// Creates a stream that will play the metadata based on the metadata given
33    pub fn create_stream<T: Sample>(&self, metadata: &impl AudioMetadataTrait, samples: Arc<Mutex<Samples<T>>>) -> Error<Stream> {
34        let config_range = match self.inner_device().supported_output_configs() {
35            Ok(c) => c,
36            Err(e) => return Err(PlayError::DeviceIoError(
37                format!("the device had an issue fetching configs"), Some(Box::new(e))))
38        };
39
40        let config = config::find_fitting_stream_config(metadata, config_range)?;
41
42        let sample_rate = cpal::SampleRate(metadata.sample_rate());
43        let config = config.with_sample_rate(sample_rate);
44
45        let mut index = 0;
46        let data_callback = move |samples_out: &mut [T], _info: &_| {
47            // TODO: This should maybe not crash // Removed expect so that it does not print text
48            let samples = samples.lock().unwrap(); //.expect("samples are inaccessible to audio stream");
49            for sample in samples_out {
50                *sample = match samples.samples.get(index) {
51                    Some(s) => *s,
52                    None => T::EQUILIBRIUM,
53                };
54                index += 1;
55            }
56        };
57
58        let error_callback = |err| {
59            // FIXME: I will puke uncontrolably if this is not removed within a reasonable amount of time :)
60            panic!("{:?}", err);
61        };
62        
63        let stream_err = self
64            .inner_device()
65            .build_output_stream(&config.config(), data_callback, error_callback, None);
66
67        let stream = match stream_err {
68            Ok(s) => s,
69            Err(e) => return Err(PlayError::DeviceIoError(
70                "device had an error while trying to build an audio stream".to_string(),
71                Some(Box::new(e)))),
72        };
73
74        Ok(stream.into())
75    }
76
77    /// Plays the samples in the SamplesPlayer on this device
78    pub fn play<T: Sample>(self, player: &mut impl SamplesPlayerTrait) -> Error<()> {
79        player.play_on_device(self)
80    }
81
82    /// Returns all devices from all hosts
83    fn list_cpal_devices() -> Vec<cpal::Device> {
84        // We like Iterators, You like Iterators, Everybody likes Iterators!
85        let hosts = cpal::available_hosts();
86
87        // Gets all hosts, discards ones that cause an error
88        let hosts = hosts.into_iter()
89            .map(|id| cpal::host_from_id(id))
90            .filter(|r| r.is_ok())
91            .map(|r| r.unwrap())
92            .collect::<Vec<Host>>();
93
94        // Gets all devices form all hosts, discards ones that cause an error
95        hosts.into_iter()
96            .map(|h| h.devices())
97            .filter(|r| r.is_ok())
98            .map(|r| r.unwrap().collect::<Vec<cpal::Device>>())
99            .flatten()
100            .collect()
101    } 
102
103    /// Returns the default output device of the default host.
104    /// Be aware that there may be none
105    pub fn default_output() -> Option<Device> {
106        let inner_device = cpal::default_host().default_output_device()?;
107
108        Some(Device::new(inner_device))
109    }
110
111    /// Gives the name of all devices on all hosts
112    pub fn list_device_names() -> Vec<String> {
113        let devices = Device::list_cpal_devices();
114
115        // Gets all device names, discards ones that cause an error 
116        devices.into_iter()
117            .map(|d| d.name())
118            .filter(|r| r.is_ok())
119            .map(|r| r.unwrap())
120            .collect()
121    }
122
123    /// Creates a new device from its device name
124    pub fn new_from_name(device_name: &str) -> Option<Device> {
125        let devices = Device::list_cpal_devices();
126
127        let the_device = devices.into_iter()
128            .filter(|d| d.name().unwrap_or("".to_string()) == device_name)
129            .next()?;
130
131        Some(Device::new(the_device))
132    }
133
134    #[doc(hidden)]
135    /// Gives a reference to the inner cpal device struct
136    pub fn inner_device(&self) -> &cpal::Device {
137        &self.device
138    }
139}
140
141impl Device {
142    /// Returns the name of the device, if it can find one
143    pub fn name(&self) -> Option<String> {
144        match self.device.name() {
145            Ok(n) => Some(n),
146            Err(_) => None,   
147        }
148    }
149}
150
151impl Debug for Device {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        f.write_str("Device {}")
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    /// This test assumes that the computer that runs the tests at least have 1
163    /// device with a name
164    fn list_device_names_works() {
165        assert_ne!(Device::list_device_names().len(), 0)
166    }
167
168    #[test]
169    fn new_from_name_works() {
170        let device_name = &Device::list_device_names()[0];
171        assert!(Device::new_from_name(device_name).is_some())
172    }
173}