fennel_engine/
audio.rs

1use std::{
2    collections::HashMap,
3    fs,
4    io::{BufReader, Cursor},
5    path::{Path, PathBuf},
6    thread,
7};
8
9use anyhow::bail;
10use tokio::sync::mpsc::{self, Sender};
11
12#[derive(Debug, Clone)]
13pub struct Audio {
14    pub channel: Sender<AudioCommand>,
15    pub cached_audio: HashMap<String, Box<Cursor<Vec<u8>>>>,
16}
17
18#[derive(Debug, Clone)]
19pub enum AudioCommand {
20    Play(PathBuf),
21    Stop,
22}
23
24impl Audio {
25    pub fn new() -> Audio {
26        let (tx, mut rx) = mpsc::channel::<AudioCommand>(1);
27
28        tokio::spawn(async move {
29            println!("hey im the audio thread");
30            let stream_handle = rodio::OutputStreamBuilder::open_default_stream()
31                .expect("audio: failed to initialize stream handle");
32            let mixer = stream_handle.mixer().clone();
33
34            let mut current_sink: Option<rodio::Sink> = None; 
35
36            while let Some(message) = rx.recv().await {
37                println!("{:?}", message);
38                if let AudioCommand::Play(pathbuf) = message {
39                    if let Some(sink) = current_sink.take() {
40                        sink.stop();
41                    }
42
43                    let file = std::fs::File::open(pathbuf.to_string_lossy().to_string()).unwrap();
44                    println!("{:?}", file.metadata());
45                    let sink = rodio::play(&mixer, BufReader::new(file)).unwrap();
46                    sink.set_volume(1.0);
47                    current_sink = Some(sink); 
48                }
49                thread::sleep(std::time::Duration::from_millis(100));
50            }
51        });
52
53        Self {
54            channel: tx,
55            cached_audio: HashMap::new(),
56        }
57    }
58
59    pub fn load_asset(&mut self, path: &Path) -> anyhow::Result<()> {
60        if self
61            .cached_audio
62            .contains_key(&path.to_string_lossy().to_string())
63        {
64            bail!("resource already cached")
65        }
66
67        let data: Vec<u8> = fs::read(path)?;
68        self.cached_audio.insert(
69            path.to_string_lossy().to_string(),
70            Box::new(Cursor::new(data)),
71        );
72        Ok(())
73    }
74
75    pub async fn play_audio(
76        &mut self,
77        path: &Path,
78        _interrupt_current_playback: bool,
79    ) -> anyhow::Result<()> {
80        if !self
81            .cached_audio
82            .contains_key(&path.to_string_lossy().to_string())
83        {
84            // as we do not want to crash just because the resource wasn't cached we'll cache it
85            // here and now
86            println!("caching");
87            self.load_asset(path)?;
88        }
89        self.channel
90            .send(AudioCommand::Play(path.to_path_buf()))
91            .await?;
92        /*
93        // .unwrap() here should be safe because we cached the resource if it wasn't already cached
94        // before
95        let resource: Box<Cursor<Vec<u8>>> = self.cached_audio.get(&path.to_string_lossy().to_string()).unwrap().clone();
96
97        if !self.playing.contains(&path.to_string_lossy().to_string()) {
98            println!("playing");
99            self.playing.push(path.to_string_lossy().to_string());
100            rodio::play(&self.mixer, resource)?;
101        }
102        */
103        Ok(())
104    }
105}
106
107impl Default for Audio {
108    fn default() -> Self {
109        Self::new()
110    }
111}