oxygengine_audio_backend_web/
lib.rs

1extern crate oxygengine_audio as audio;
2extern crate oxygengine_backend_web as web;
3extern crate oxygengine_core as core;
4
5use audio::resource::*;
6use core::{
7    assets::{asset::AssetId, database::AssetsDatabase},
8    ecs::Entity,
9    Scalar,
10};
11use futures::{future, TryFutureExt};
12use js_sys::*;
13use std::{
14    collections::HashMap,
15    sync::{
16        atomic::{AtomicBool, Ordering},
17        Arc,
18    },
19};
20use wasm_bindgen::{prelude::*, JsCast};
21use wasm_bindgen_futures::{future_to_promise, JsFuture};
22use web::closure::WebClosure;
23use web_sys::*;
24
25pub mod prelude {
26    pub use crate::*;
27}
28
29#[derive(Debug)]
30enum AudioCache {
31    /// (source, (gain, notify ended))
32    Buffered(
33        AudioBufferSourceNode,
34        (GainNode, Arc<AtomicBool>, WebClosure),
35    ),
36    Streaming(HtmlAudioElement, MediaElementAudioSourceNode),
37}
38
39pub struct WebAudio {
40    context: AudioContext,
41    table_forward: HashMap<String, AssetId>,
42    table_backward: HashMap<AssetId, String>,
43    sources_cache: HashMap<Entity, AudioCache>,
44}
45
46unsafe impl Send for WebAudio {}
47unsafe impl Sync for WebAudio {}
48
49impl Default for WebAudio {
50    fn default() -> Self {
51        Self {
52            context: AudioContext::new().unwrap(),
53            table_forward: Default::default(),
54            table_backward: Default::default(),
55            sources_cache: Default::default(),
56        }
57    }
58}
59
60impl Audio for WebAudio {
61    fn create_source(
62        &mut self,
63        entity: Entity,
64        data: &[u8],
65        streaming: bool,
66        looped: bool,
67        playback_rate: Scalar,
68        volume: Scalar,
69        play: bool,
70        notify_ready: Arc<AtomicBool>,
71    ) {
72        let buffer = Uint8Array::from(data);
73        let cache = if streaming {
74            let buffer_val: &JsValue = buffer.as_ref();
75            let parts = Array::new_with_length(1);
76            parts.set(0, buffer_val.clone());
77            let blob = Blob::new_with_u8_array_sequence(parts.as_ref()).unwrap();
78            let audio = HtmlAudioElement::new().unwrap();
79            audio.set_src(&Url::create_object_url_with_blob(&blob).unwrap());
80            let node = self
81                .context
82                .create_media_element_source(audio.as_ref())
83                .unwrap();
84            node.connect_with_audio_node(&self.context.destination())
85                .expect("Could not connect audio stream source with audio output");
86            audio.load();
87            audio.set_loop(looped);
88            audio.set_playback_rate(playback_rate as f64);
89            audio.set_volume(volume as f64);
90            if play {
91                if self.context.state() != AudioContextState::Running {
92                    drop(self.context.resume());
93                }
94                audio.set_current_time(0.0);
95                drop(audio.play().expect("Could not start audio source"));
96            }
97            notify_ready.store(true, Ordering::Relaxed);
98            AudioCache::Streaming(audio, node)
99        } else {
100            let audio = self.context.create_buffer_source().unwrap();
101            let notify_ended = Arc::new(AtomicBool::new(false));
102            let notify_ended2 = notify_ended.clone();
103            let closure = Closure::wrap(Box::new(move |_: web_sys::Event| {
104                notify_ended.store(true, Ordering::Relaxed);
105            }) as Box<dyn FnMut(_)>);
106            // audio.set_onended(Some(closure.as_ref().unchecked_ref()));
107            audio
108                .add_event_listener_with_callback("ended", closure.as_ref().unchecked_ref())
109                .unwrap();
110            let ended_closure = WebClosure::acquire(closure);
111            let audio2 = audio.clone();
112            let gain = self.context.create_gain().unwrap();
113            let gain2 = gain.clone();
114            let destination = self.context.destination();
115            let context = self.context.clone();
116            let promise = self.context.decode_audio_data(&buffer.buffer()).unwrap();
117            let future = JsFuture::from(promise).and_then(move |buff| {
118                assert!(buff.is_instance_of::<AudioBuffer>());
119                let buff: AudioBuffer = buff.dyn_into().unwrap();
120                audio
121                    .connect_with_audio_node(gain.as_ref())
122                    .expect("Could not connect audio source with gain");
123                gain.connect_with_audio_node(destination.as_ref())
124                    .expect("Could not connect gain with audio output");
125                audio.set_buffer(Some(&buff));
126                audio.set_loop(looped);
127                audio.playback_rate().set_value(playback_rate);
128                gain.gain().set_value(volume);
129                if play {
130                    if context.state() != AudioContextState::Running {
131                        drop(context.resume());
132                    }
133                    audio.start().expect("Could not start audio source");
134                }
135                notify_ready.store(true, Ordering::Relaxed);
136                future::ok(JsValue::null())
137            });
138            // TODO: fail process on error catch.
139            drop(future_to_promise(future));
140            AudioCache::Buffered(audio2, (gain2, notify_ended2, ended_closure))
141        };
142        self.sources_cache.insert(entity, cache);
143    }
144
145    fn destroy_source(&mut self, entity: Entity) {
146        if let Some(audio) = self.sources_cache.remove(&entity) {
147            match audio {
148                AudioCache::Buffered(audio, (gain, notify_ended, mut ended_closure)) => {
149                    audio
150                        .disconnect()
151                        .expect("Could not disconnect audio source from gain");
152                    gain.disconnect()
153                        .expect("Could not disconnect gain from audio output");
154                    notify_ended.store(true, Ordering::Relaxed);
155                    ended_closure.release();
156                }
157                AudioCache::Streaming(_, audio) => audio
158                    .disconnect()
159                    .expect("Could not disconnect audio stream source from audio output"),
160            }
161        }
162    }
163
164    fn has_source(&mut self, entity: Entity) -> bool {
165        self.sources_cache.contains_key(&entity)
166    }
167
168    fn update_source(
169        &mut self,
170        entity: Entity,
171        looped: bool,
172        playback_rate: Scalar,
173        volume: Scalar,
174        play: Option<bool>,
175    ) {
176        if let Some(audio) = self.sources_cache.get(&entity) {
177            match audio {
178                AudioCache::Buffered(audio, (gain, _, _)) => {
179                    if audio.buffer().is_some() {
180                        audio.set_loop(looped);
181                        audio.playback_rate().set_value(playback_rate);
182                        gain.gain().set_value(volume);
183                        if let Some(play) = play {
184                            if play {
185                                audio.start().expect("Could not start audio source");
186                            } else {
187                                audio.stop().expect("Could not stop audio source");
188                            }
189                        }
190                    }
191                }
192                AudioCache::Streaming(audio, _) => {
193                    audio.set_loop(looped);
194                    audio.set_playback_rate(playback_rate as f64);
195                    audio.set_volume(volume as f64);
196                    if let Some(play) = play {
197                        if play {
198                            if self.context.state() != AudioContextState::Running {
199                                drop(self.context.resume());
200                            }
201                            audio.set_current_time(0.0);
202                            drop(audio.play().expect("Could not start audio source"));
203                        } else {
204                            audio.pause().expect("Could not stop audio source");
205                        }
206                    }
207                }
208            }
209        }
210    }
211
212    fn get_source_state(&self, entity: Entity) -> Option<AudioState> {
213        self.sources_cache.get(&entity).map(|audio| match audio {
214            AudioCache::Buffered(_, (_, notify_ended, _)) => AudioState {
215                current_time: None,
216                is_playing: AudioPlayState::Ended(notify_ended.swap(false, Ordering::Relaxed)),
217            },
218            AudioCache::Streaming(audio, _) => AudioState {
219                current_time: Some(audio.current_time() as Scalar),
220                is_playing: AudioPlayState::State(!audio.paused()),
221            },
222        })
223    }
224
225    fn get_asset_id(&self, path: &str) -> Option<AssetId> {
226        self.table_forward.get(path).copied()
227    }
228
229    fn update_cache(&mut self, assets: &AssetsDatabase) {
230        for id in assets.lately_loaded_protocol("audio") {
231            let id = *id;
232            let asset = assets
233                .asset_by_id(id)
234                .expect("trying to use not loaded audio asset");
235            let path = asset.path().to_owned();
236            self.table_forward.insert(path.clone(), id);
237            self.table_backward.insert(id, path);
238        }
239        for id in assets.lately_unloaded_protocol("audio") {
240            if let Some(path) = self.table_backward.remove(id) {
241                self.table_forward.remove(&path);
242            }
243        }
244    }
245}