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