use crate::error::DecibriError;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct MicrophoneInfo {
pub index: usize,
pub name: String,
pub id: String,
pub max_input_channels: u16,
pub default_sample_rate: u32,
pub is_default: bool,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SpeakerInfo {
pub index: usize,
pub name: String,
pub id: String,
pub max_output_channels: u16,
pub default_sample_rate: u32,
pub is_default: bool,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum DeviceSelector {
Default,
Index(usize),
Name(String),
Id(String),
}
#[cfg(any(feature = "capture", feature = "playback"))]
pub(crate) struct ComputedRow {
pub(crate) index: usize,
pub(crate) name: String,
pub(crate) id: String,
pub(crate) channels: u16,
pub(crate) sample_rate: u32,
pub(crate) is_default: bool,
}
#[cfg(any(feature = "capture", feature = "playback"))]
pub(crate) fn compute_is_default<Id: PartialEq + ToString>(
rows: Vec<(Option<Id>, String, u16, u32)>,
default_id: Option<Id>,
) -> Vec<ComputedRow> {
rows.into_iter()
.enumerate()
.map(|(index, (id, name, channels, sample_rate))| {
let is_default = match (id.as_ref(), default_id.as_ref()) {
(Some(row_id), Some(default)) => row_id == default,
_ => false,
};
let id = id.map(|x| x.to_string()).unwrap_or_default();
ComputedRow {
index,
name,
id,
channels,
sample_rate,
is_default,
}
})
.collect()
}
#[cfg(any(feature = "capture", feature = "playback"))]
pub fn input_devices() -> Result<Vec<MicrophoneInfo>, DecibriError> {
use crate::backend::AudioBackend;
crate::backend::CpalBackend.input_devices()
}
#[cfg(any(feature = "capture", feature = "playback"))]
pub fn output_devices() -> Result<Vec<SpeakerInfo>, DecibriError> {
use crate::backend::AudioBackend;
crate::backend::CpalBackend.output_devices()
}
#[cfg(all(test, any(feature = "capture", feature = "playback")))]
mod tests {
use super::*;
fn row(id: Option<&str>, name: &str) -> (Option<String>, String, u16, u32) {
(id.map(String::from), name.to_string(), 2, 48_000)
}
#[test]
fn test_is_default_two_devices_same_name_different_ids() {
let rows = vec![
row(Some("usb-mic-A"), "Microphone"),
row(Some("usb-mic-B"), "Microphone"),
];
let result = compute_is_default(rows, Some("usb-mic-B".to_string()));
assert_eq!(result.len(), 2);
assert!(
!result[0].is_default,
"first duplicate-named mic must NOT be flagged when default is the second"
);
assert!(
result[1].is_default,
"second duplicate-named mic (matching default id) must be flagged"
);
assert_eq!(result[0].name, "Microphone");
assert_eq!(result[1].name, "Microphone");
}
#[test]
fn test_is_default_no_default_reported() {
let rows = vec![row(Some("mic-A"), "Mic A"), row(Some("mic-B"), "Mic B")];
let result = compute_is_default::<String>(rows, None);
assert_eq!(result.len(), 2);
assert!(
result.iter().all(|r| !r.is_default),
"no row may be flagged when host reports no default"
);
}
#[test]
fn test_is_default_row_with_failed_id() {
let rows = vec![
row(None, "Mystery device with unavailable id"),
row(Some("mic-B"), "Mic B"),
];
let result = compute_is_default(rows, Some("mic-B".to_string()));
assert_eq!(result.len(), 2);
assert!(
!result[0].is_default,
"row with id() == None must never be flagged default"
);
assert!(
result[1].is_default,
"row whose id matches the default must be flagged"
);
}
#[test]
fn test_is_default_empty_device_list() {
let rows: Vec<(Option<String>, String, u16, u32)> = vec![];
let result_no_default = compute_is_default::<String>(rows.clone(), None);
let result_with_default = compute_is_default(rows, Some("phantom-mic".to_string()));
assert!(result_no_default.is_empty());
assert!(result_with_default.is_empty());
}
#[test]
fn test_id_is_propagated_to_computed_row() {
let rows = vec![
row(Some("mic-with-id"), "Microphone A"),
row(None, "Microphone without id"),
];
let result = compute_is_default(rows, Some("mic-with-id".to_string()));
assert_eq!(result.len(), 2);
assert_eq!(
result[0].id, "mic-with-id",
"Some(id) must be preserved as a non-empty string"
);
assert_eq!(
result[1].id, "",
"None id must map to an empty string so the device is unreachable via DeviceSelector::Id"
);
}
}