soundboard/src/vbplay.rs

119 lines
3.4 KiB
Rust

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<String> {
let devices = host.output_devices().unwrap();
devices.map(|device| device.name().unwrap()).collect()
}
fn find_device_index_regex(devices: Vec<String>, pattern: &str) -> Option<usize> {
let re = Regex::new(pattern).unwrap();
devices.iter().position(|device| re.is_match(device))
}
fn select_device(host: &Host, i: usize) -> Option<Device> {
host.output_devices().unwrap().nth(i)
}
fn select_device_by_pattern(pattern: &str) -> Option<Device> {
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<Device> {
let host = cpal::default_host();
return select_device(&host, index);
}
pub type AudioThread = Sender<Command>;
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;
}
}
}
}