use anyhow::{anyhow, Context, Result};
use cpal::traits::{DeviceTrait, HostTrait};
use cpal::{Device, Host, SampleFormat, SupportedStreamConfigRange};
use dialoguer::{theme::ColorfulTheme, Select};
use std::collections::HashSet;
use std::io::{self, Write};
use std::sync::Arc;
use tracing::{debug, info, warn};
use crate::audio::device::AudioDevice;
pub fn list_audio_devices(host: &Host) -> Result<Vec<Arc<AudioDevice>>> {
info!("Listing available audio devices...");
let mut devices = Vec::new();
match AudioDevice::list_input_devices() {
Ok(inputs) => devices.extend(inputs),
Err(e) => warn!("Failed to list input devices: {}", e),
}
if devices.is_empty() {
warn!("No audio devices found for host: {:?}", host.id());
Err(anyhow!("No audio devices found"))
} else {
debug!("Found {} audio devices", devices.len());
Ok(devices)
}
}
pub fn select_audio_device(host: &Host) -> Result<Arc<AudioDevice>> {
let devices =
AudioDevice::list_input_devices().context("Failed to list input devices for selection")?;
if devices.is_empty() {
return Err(anyhow!("No input devices available to select"));
}
let device_names: Vec<String> = devices.iter().map(|d| d.name.clone()).collect();
info!("Please select an audio input device:");
let selection = Select::with_theme(&ColorfulTheme::default())
.items(&device_names)
.default(0)
.interact()
.context("Failed to get user selection")?;
info!("Selected device: {}", device_names[selection]);
Ok(devices[selection].clone())
}
pub fn find_device_by_name(host: &Host, name: &str) -> Result<Arc<AudioDevice>> {
debug!("Searching for device: '{}'", name);
let lower_name = name.to_lowercase();
if let Ok(devices) = AudioDevice::list_input_devices() {
if let Some(device) = devices
.into_iter()
.find(|d| d.name.to_lowercase() == lower_name)
{
info!("Found input device: {}", device.name);
return Ok(device);
}
}
warn!("Device '{}' not found.", name);
Err(anyhow!("Device '{}' not found", name))
}
pub fn get_device_capabilities(device: &Device) -> Result<String> {
let device_name = device.name().unwrap_or_else(|_| "Unknown".to_string());
let mut capabilities = format!("Capabilities for device: {}\n", device_name);
append_configs(&mut capabilities, device.supported_input_configs(), "Input")?;
append_configs(
&mut capabilities,
device.supported_output_configs(),
"Output",
)?;
Ok(capabilities)
}
fn append_configs(
caps: &mut String,
configs: Result<
impl Iterator<Item = SupportedStreamConfigRange>,
cpal::SupportedStreamConfigsError,
>,
config_type: &str,
) -> Result<()> {
match configs {
Ok(configs) => {
let mut unique_configs = HashSet::new();
for config in configs {
let config_str = format!(
" {} Channels: {}, Sample Rate: {}-{} Hz, Format: {:?}\n",
config_type,
config.channels(),
config.min_sample_rate().0,
config.max_sample_rate().0,
config.sample_format()
);
if unique_configs.insert(config_str.clone()) {
caps.push_str(&config_str);
}
}
if unique_configs.is_empty() {
caps.push_str(&format!(
" No supported {} configurations found.\n",
config_type
));
}
}
Err(e) => {
let err_msg = format!("Error getting {} configs: {}\n", config_type, e);
warn!("{}", err_msg);
}
}
Ok(())
}