diff --git a/Justfile b/Justfile index 4a0e2a2..8ad9ab9 100644 --- a/Justfile +++ b/Justfile @@ -9,3 +9,6 @@ bin args='': hello: @echo "Hello, world!" + +watch: + cargo watch -c -q -w src -w templates -x run diff --git a/src/main.rs b/src/main.rs index 124cde5..f7161c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use log::{debug, info}; use serde::Serialize; use std::sync::{Arc, Mutex}; -use vbplay::{AudioThread, DeviceSelection}; +use vbplay::{AudioThread, DeviceSelection, SoundDecoder}; mod soundclips; mod state; @@ -21,15 +21,19 @@ async fn main() { jinja.set_loader(path_loader("templates")); let template_engine = Engine::from(jinja); - let audio = vbplay::audio_thread(DeviceSelection::FindByPattern( - ".*VB-Audio Virtual Cable.*".to_owned(), - )); + let audio = vbplay::audio_thread( + DeviceSelection::FindByPattern(".*VB-Audio Virtual Cable.*".to_owned()), + SoundDecoder::Detect, + ); let audio: Arc> = Arc::new(Mutex::new(audio)); let app_state = AppState { engine: template_engine, - clips: soundclips::scan_directory_for_clips("E:/sounds/soundboard/all", &["mp3"]) - .expect("No Soundclips found."), + clips: soundclips::scan_directory_for_clips( + "E:/sounds/soundboard/all", + &["mp3", "ogg", "wav", "flac"], + ) + .expect("No Soundclips found."), player: audio, playback: None, }; diff --git a/src/vbplay.rs b/src/vbplay.rs index 56a7bf5..a6c26c9 100644 --- a/src/vbplay.rs +++ b/src/vbplay.rs @@ -4,11 +4,18 @@ 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::io::{BufReader, Cursor}; use std::sync::mpsc::{self, Sender}; use std::thread; +#[derive(Clone)] +pub enum SoundDecoder { + Detect, + Mp3Mini, + Rodio, +} + pub enum Command { Play(String), Stop, @@ -57,7 +64,7 @@ fn select_device_by_id(index: usize) -> Option { pub type AudioThread = Sender; -pub fn audio_thread(select_device: DeviceSelection) -> AudioThread { +pub fn audio_thread(select_device: DeviceSelection, select_decoder: SoundDecoder) -> AudioThread { let (tx, rx) = mpsc::channel(); thread::spawn(move || { @@ -76,7 +83,7 @@ pub fn audio_thread(select_device: DeviceSelection) -> AudioThread { for command in rx { match command { Command::Play(file_name) => { - play_file(&sink, file_name); + play_file(&sink, file_name, &select_decoder); } Command::Stop => { sink.stop(); @@ -88,31 +95,52 @@ pub fn audio_thread(select_device: DeviceSelection) -> AudioThread { 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; - } - } +fn detect_decoder(file_name: &str, sound_decoder: &SoundDecoder) -> SoundDecoder { + // TODO: File detection via ending or whitelisting? + // This function MUST NOT return SoundDecoder::Detect + match sound_decoder { + SoundDecoder::Detect => SoundDecoder::Rodio, + other => other.clone(), + } +} + +fn play_file(sink: &rodio::Sink, file_name: String, sound_decoder: &SoundDecoder) { + match detect_decoder(&file_name, sound_decoder) { + SoundDecoder::Mp3Mini => { + // MP3 Mini provides low latency playback + let mut sound_file = File::open(file_name).unwrap(); + let mut file_data = Vec::new(); + sound_file.read_to_end(&mut file_data).unwrap(); + // Iterate over the MP3 frames and play them + let mut decoder = Decoder::new(Cursor::new(file_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; + } + } + } + } + SoundDecoder::Rodio => { + // Rodio currently supports Ogg, Mp3, WAV and Flac + let file = File::open(&file_name).unwrap(); + let source = rodio::Decoder::new(BufReader::new(file)).expect("Failed to open Decoder"); + sink.append(source); + //sink.sleep_until_end(); + } + SoundDecoder::Detect => error!("This should never happen"), } } diff --git a/templates/index.jinja b/templates/index.jinja index f99738f..235fde5 100644 --- a/templates/index.jinja +++ b/templates/index.jinja @@ -5,15 +5,48 @@ - - + + + - - - - - Play + Soundboard + -
-
- Category 1 - - -
-
-
- {% for clip in clips %} - - {% endfor %} -
-
-
- + + +
+ +
+ +
+ {% for clip in clips %} + + {% endfor %} + +
+
+
+
+ + + \ No newline at end of file