1#![doc = include_str!("../README.md")]
2
3use std::{
4 collections::HashMap,
5 fs::File,
6 io,
7 path::{Path, PathBuf},
8};
9
10use arci::{Speaker, WaitFuture};
11use thiserror::Error;
12use tokio::sync::oneshot;
13use tracing::error;
14
15#[derive(Error, Debug)]
16#[non_exhaustive]
17pub enum Error {
18 #[error("io: {:?}", .0)]
19 Io(#[from] std::io::Error),
20 #[error("rodio: {:?}", .0)]
21 Decoder(#[from] rodio::decoder::DecoderError),
22 #[error("rodio: {:?}", .0)]
23 Stream(#[from] rodio::StreamError),
24 #[error("rodio: {:?}", .0)]
25 Play(#[from] rodio::PlayError),
26 #[error("not found: {:?}", .0)]
27 HashNotFound(String),
28}
29
30#[derive(Debug)]
31pub struct AudioSpeaker {
32 message_to_file_path: HashMap<String, PathBuf>,
33}
34
35impl AudioSpeaker {
36 pub fn new(hashmap: HashMap<String, PathBuf>) -> Self {
38 Self {
39 message_to_file_path: hashmap,
40 }
41 }
42}
43
44impl Speaker for AudioSpeaker {
45 fn speak(&self, message: &str) -> Result<WaitFuture, arci::Error> {
46 match self.message_to_file_path.get(message) {
47 Some(path) => play_audio_file(path),
48 None => Err(Error::HashNotFound(message.to_string())),
49 }
50 .map_err(|e| arci::Error::Other(e.into()))
51 }
52}
53
54fn play_audio_file(path: &Path) -> Result<WaitFuture, Error> {
55 let file = File::open(path)?;
56 let source = rodio::Decoder::new(io::BufReader::new(file))?;
57
58 let (sender, receiver) = oneshot::channel();
59 std::thread::spawn(move || {
60 let res: Result<_, Error> = (|| {
61 let (_stream, stream_handle) = rodio::OutputStream::try_default()?;
63 let sink = rodio::Sink::try_new(&stream_handle)?;
64 sink.append(source);
65 sink.sleep_until_end();
66 Ok(())
67 })();
68 let _ = sender.send(res);
69 });
70
71 Ok(WaitFuture::new(async move {
72 receiver
73 .await
74 .map_err(|e| arci::Error::Other(e.into()))?
75 .map_err(|e| arci::Error::Other(e.into()))
76 }))
77}
78
79#[cfg(test)]
80mod test {
81 use super::*;
82
83 #[test]
84 fn test_audio_speaker_new() {
85 let audio_speaker = AudioSpeaker::new(HashMap::from([(
86 String::from("name"),
87 PathBuf::from("path"),
88 )]));
89 assert_eq!(
90 audio_speaker.message_to_file_path["name"],
91 PathBuf::from("path")
92 );
93 }
94
95 #[test]
96 fn test_audio_speaker_speak() {
97 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
98 let root_dir = manifest_dir.parent().unwrap();
99 let audio_path = root_dir.join("openrr-apps/audio/sine.mp3");
100 let audio_speaker = AudioSpeaker::new(HashMap::from([(String::from("name"), audio_path)]));
101
102 assert!(audio_speaker.speak("name").is_ok());
103 assert!(audio_speaker.speak("not_exist").is_err());
104 }
105
106 #[test]
107 fn test_play_audio_file() {
108 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
109 let root_dir = manifest_dir.parent().unwrap();
110 let audio_path = root_dir.join("openrr-apps/audio/sine.mp3");
111 let fake_path = root_dir.join("fake/audio/sine.mp3");
112
113 assert!(play_audio_file(&audio_path).is_ok());
114 assert!(play_audio_file(&fake_path).is_err());
115 }
116}