1use assets::SoundRegistry;
2use futures::{channel::mpsc, StreamExt};
3use gpui2::{AppContext, AssetSource, Executor};
4use rodio::{OutputStream, OutputStreamHandle};
5use util::ResultExt;
6
7mod assets;
8
9pub fn init(source: impl AssetSource, cx: &mut AppContext) {
10 cx.set_global(Audio::new(cx.executor()));
11 cx.set_global(SoundRegistry::new(source));
12}
13
14pub enum Sound {
15 Joined,
16 Leave,
17 Mute,
18 Unmute,
19 StartScreenshare,
20 StopScreenshare,
21}
22
23impl Sound {
24 fn file(&self) -> &'static str {
25 match self {
26 Self::Joined => "joined_call",
27 Self::Leave => "leave_call",
28 Self::Mute => "mute",
29 Self::Unmute => "unmute",
30 Self::StartScreenshare => "start_screenshare",
31 Self::StopScreenshare => "stop_screenshare",
32 }
33 }
34}
35
36pub struct Audio {
37 tx: mpsc::UnboundedSender<Box<dyn FnOnce(&mut AudioState) + Send>>,
38}
39
40struct AudioState {
41 _output_stream: Option<OutputStream>,
42 output_handle: Option<OutputStreamHandle>,
43}
44
45impl AudioState {
46 fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
47 if self.output_handle.is_none() {
48 let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
49 self.output_handle = output_handle;
50 self._output_stream = _output_stream;
51 }
52
53 self.output_handle.as_ref()
54 }
55
56 fn take(&mut self) {
57 self._output_stream.take();
58 self.output_handle.take();
59 }
60}
61
62impl Audio {
63 pub fn new(executor: &Executor) -> Self {
64 let (tx, mut rx) = mpsc::unbounded::<Box<dyn FnOnce(&mut AudioState) + Send>>();
65 executor
66 .spawn_on_main(|| async move {
67 let mut audio = AudioState {
68 _output_stream: None,
69 output_handle: None,
70 };
71
72 while let Some(f) = rx.next().await {
73 (f)(&mut audio);
74 }
75 })
76 .detach();
77
78 Self { tx }
79 }
80
81 pub fn play_sound(sound: Sound, cx: &mut AppContext) {
82 if !cx.has_global::<Self>() {
83 return;
84 }
85
86 let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else {
87 return;
88 };
89
90 let this = cx.global::<Self>();
91 this.tx
92 .unbounded_send(Box::new(move |state| {
93 if let Some(output_handle) = state.ensure_output_exists() {
94 output_handle.play_raw(source).log_err();
95 }
96 }))
97 .ok();
98 }
99
100 pub fn end_call(cx: &AppContext) {
101 if !cx.has_global::<Self>() {
102 return;
103 }
104
105 let this = cx.global::<Self>();
106
107 this.tx
108 .unbounded_send(Box::new(move |state| state.take()))
109 .ok();
110 }
111}