rustriff_lib/services/device_service.rs
1use crate::domain::dto::audio_device_dto::AudioDeviceDto;
2use cpal::traits::DeviceTrait;
3use cpal::traits::HostTrait;
4use cpal::Host;
5use tracing::error;
6
7/// Service for managing audio device enumeration and lookup.
8///
9/// `DeviceService` wraps a CPAL [`Host`] and provides convenient methods to:
10/// - List available input and output devices
11/// - Look up devices by their ID
12/// - Convert device information into [`AudioDeviceDto`] for frontend consumption
13pub struct DeviceService {
14 host: Host,
15}
16
17impl DeviceService {
18 /// Creates a new `DeviceService` with the given CPAL host.
19 ///
20 /// # Arguments
21 ///
22 /// * `host` - A CPAL [`Host`] instance.
23 pub fn new(host: Host) -> Self {
24 Self { host }
25 }
26
27 /// Retrieves a list of all available input devices.
28 ///
29 /// Queries the CPAL host for input devices, converts them to [`AudioDeviceDto`],
30 /// and returns them. If device enumeration fails, an empty list is returned
31 /// and an error is added to the logs.
32 ///
33 /// # Returns
34 ///
35 /// A [`Vec`] of [`AudioDeviceDto`] representing available input devices.
36 pub fn get_input_devices(&self) -> Vec<AudioDeviceDto> {
37 match self.host.input_devices() {
38 Ok(devices) => devices
39 .filter_map(|device| {
40 let desc = device.description().ok()?;
41 let name = desc.name().to_string();
42 let device_id = device.id().ok()?;
43 let id = format!("{:?}", device_id);
44 let device_config = device.default_input_config().ok()?;
45 let default_sample_rate = device_config.sample_rate();
46
47 Some(AudioDeviceDto {
48 id,
49 name,
50 sample_rate: default_sample_rate,
51 })
52 })
53 .collect(),
54 Err(e) => {
55 error!("Failed to get input devices: {}", e);
56 vec![]
57 }
58 }
59 }
60
61 /// Retrieves a list of all available output devices.
62 ///
63 /// Queries the CPAL host for output devices, converts them to [`AudioDeviceDto`],
64 /// and returns them. If device enumeration fails, an empty list is returned
65 /// and an error is printed to stderr.
66 ///
67 /// # Returns
68 ///
69 /// A [`Vec`] of [`AudioDeviceDto`] representing available output devices.
70 pub fn get_output_devices(&self) -> Vec<AudioDeviceDto> {
71 match self.host.output_devices() {
72 Ok(devices) => devices
73 .filter_map(|device| {
74 let desc = device.description().ok()?;
75 let name = desc.name().to_string();
76 let device_id = device.id().ok()?;
77 let id = format!("{:?}", device_id);
78 let device_config = device.default_output_config().ok()?;
79 let default_sample_rate = device_config.sample_rate();
80
81 Some(AudioDeviceDto {
82 id,
83 name,
84 sample_rate: default_sample_rate,
85 })
86 })
87 .collect(),
88 Err(e) => {
89 eprintln!("Failed to get output devices: {}", e);
90 vec![]
91 }
92 }
93 }
94
95 /// Finds an input device by its string ID.
96 ///
97 /// Searches through the host's input devices for one whose debug-formatted
98 /// ID matches the given string.
99 ///
100 /// # Arguments
101 ///
102 /// * `id` - The device ID string to search for (debug-formatted CPAL device ID).
103 ///
104 /// # Returns
105 ///
106 /// `Some(device)` if a matching input device is found, `None` otherwise.
107 pub fn find_input_device_by_id(&self, id: &str) -> Option<cpal::Device> {
108 let devices = self.host.input_devices().ok()?;
109
110 for device in devices {
111 let device_id = device.id().ok()?;
112 if format!("{:?}", device_id) == id {
113 return Some(device);
114 }
115 }
116
117 None
118 }
119
120 /// Finds an output device by its string ID.
121 ///
122 /// Searches through the host's output devices for one whose debug-formatted
123 /// ID matches the given string.
124 ///
125 /// # Arguments
126 ///
127 /// * `id` - The device ID string to search for (debug-formatted CPAL device ID).
128 ///
129 /// # Returns
130 ///
131 /// `Some(device)` if a matching output device is found, `None` otherwise.
132 pub fn find_output_device_by_id(&self, id: &str) -> Option<cpal::Device> {
133 let devices = self.host.output_devices().ok()?;
134
135 for device in devices {
136 let device_id = device.id().ok()?;
137 if format!("{:?}", device_id) == id {
138 return Some(device);
139 }
140 }
141
142 None
143 }
144}