oxygengine_audio_backend_web/
lib.rs1extern 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 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
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 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}