use cpal::traits::{DeviceTrait, HostTrait}; use cpal::{Device, Host}; use log::{debug, error, info}; use minimp3::{Decoder, Error}; use regex::Regex; use std::fs::File; use std::io::Cursor; use std::io::Read; use std::sync::mpsc::{self, Sender}; use std::thread; pub enum Command { Play(String), Stop, } // todo: implement a device selection with this: pub enum DeviceSelection { SelectFirst, SelectById(usize), FindByPattern(String), } fn list_devices(host: &Host) -> Vec { let devices = host.output_devices().unwrap(); devices.map(|device| device.name().unwrap()).collect() } fn find_device_index_regex(devices: Vec, pattern: &str) -> Option { let re = Regex::new(pattern).unwrap(); devices.iter().position(|device| re.is_match(device)) } fn select_device(host: &Host, i: usize) -> Option { host.output_devices().unwrap().nth(i) } fn select_device_by_pattern(pattern: &str) -> Option { let host = cpal::default_host(); let devices = list_devices(&host); info!("Devices: {:?}", devices); if let Some(index) = find_device_index_regex(devices, pattern) { info!( "Selecting Device from pattern: \"{}\" at index {:?}", pattern, index ); return select_device(&host, index); } return None; } fn select_device_by_id(index: usize) -> Option { let host = cpal::default_host(); return select_device(&host, index); } pub type AudioThread = Sender; pub fn audio_thread(select_device: DeviceSelection) -> AudioThread { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let device = match select_device { DeviceSelection::SelectFirst => select_device_by_id(0).unwrap(), DeviceSelection::SelectById(id) => select_device_by_id(id).unwrap(), DeviceSelection::FindByPattern(pattern) => { select_device_by_pattern(pattern.as_ref()).unwrap() } }; // Create a rodio Sink connected to our device let (_stream, stream_handle) = rodio::OutputStream::try_from_device(&device).unwrap(); let sink = rodio::Sink::try_new(&stream_handle).unwrap(); for command in rx { match command { Command::Play(file_name) => { play_file(&sink, file_name); } Command::Stop => { sink.stop(); } } } }); tx } fn play_file(sink: &rodio::Sink, file_name: String) { let mut mp3_file = File::open(file_name).unwrap(); let mut mp3_data = Vec::new(); mp3_file.read_to_end(&mut mp3_data).unwrap(); // Iterate over the MP3 frames and play them let mut decoder = Decoder::new(Cursor::new(mp3_data)); loop { match decoder.next_frame() { Ok(frame) => { let source = rodio::buffer::SamplesBuffer::new( 2, frame.sample_rate.try_into().unwrap(), &*frame.data, ); sink.append(source); } Err(Error::Eof) => { debug!("EOF"); break; } Err(e) => { error!("{:?}", e); break; } } } }