1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Run audio on a separate thread, to prevent issue on windows
// when also using winit to create windows.
// More info: https://github.com/RustAudio/cpal/pull/348

use crate::Error;
use rodio::Sink;
use std::sync::mpsc;
use std::thread;

struct PlayBytes {
    bytes: &'static [u8],
    loop_forever: bool,
}

type Request = PlayBytes;
type Response = Sink;

pub struct NativeAudioPlayer {
    _audio_thread: thread::JoinHandle<()>,
    send_request: mpsc::Sender<Request>,
    recv_response: mpsc::Receiver<Response>,
}

impl NativeAudioPlayer {
    pub fn try_new_default_device() -> Result<Self, Error> {
        let (send_request, recv_request) = mpsc::channel();
        let (send_response, recv_response) = mpsc::channel();
        let (send_init, recv_init) = mpsc::channel();
        let _audio_thread = thread::spawn(move || {
            if let Some((_output_stream, output_stream_handle)) =
                crate::common::output_stream_handle()
            {
                send_init.send(Ok(())).unwrap();
                for PlayBytes {
                    bytes,
                    loop_forever,
                } in recv_request
                {
                    let handle = if loop_forever {
                        crate::common::play_bytes_loop(&output_stream_handle, bytes)
                    } else {
                        crate::common::play_bytes(&output_stream_handle, bytes)
                    };
                    send_response.send(handle).expect("failed to send response");
                }
                log::info!("dedicated audio thread stopped");
            } else {
                send_init
                    .send(Err(Error::FailedToCreateOutputStream))
                    .unwrap();
            }
        });
        let () = recv_init
            .recv()
            .expect("unable to get status of dedicated audio thread")?;
        Ok(Self {
            _audio_thread,
            send_request,
            recv_response,
        })
    }

    pub fn new_default_device() -> Self {
        Self::try_new_default_device().unwrap()
    }

    pub fn play_bytes(&self, bytes: &'static [u8]) -> Sink {
        self.play_bytes_gerneral(bytes, false)
    }

    pub fn play_bytes_loop(&self, bytes: &'static [u8]) -> Sink {
        self.play_bytes_gerneral(bytes, true)
    }

    fn play_bytes_gerneral(&self, bytes: &'static [u8], loop_forever: bool) -> Sink {
        match self.send_request.send(PlayBytes {
            bytes,
            loop_forever,
        }) {
            Err(_) => {
                log::error!("can't play audio because dedicated audio thread has stopped");
                Sink::new_idle().0
            }
            Ok(()) => match self.recv_response.recv() {
                Err(_) => {
                    log::error!("no response from dedicated audio thread");
                    Sink::new_idle().0
                }
                Ok(sink) => sink,
            },
        }
    }
}