ez_audi/cpal_abstraction/
device.rs1use 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
10pub 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 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 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 let samples = samples.lock().unwrap(); 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 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 pub fn play<T: Sample>(self, player: &mut impl SamplesPlayerTrait) -> Error<()> {
79 player.play_on_device(self)
80 }
81
82 fn list_cpal_devices() -> Vec<cpal::Device> {
84 let hosts = cpal::available_hosts();
86
87 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 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 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 pub fn list_device_names() -> Vec<String> {
113 let devices = Device::list_cpal_devices();
114
115 devices.into_iter()
117 .map(|d| d.name())
118 .filter(|r| r.is_ok())
119 .map(|r| r.unwrap())
120 .collect()
121 }
122
123 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 pub fn inner_device(&self) -> &cpal::Device {
137 &self.device
138 }
139}
140
141impl Device {
142 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 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}