1use std::cell::Cell;
6use std::collections::hash_map::Entry;
7use std::collections::{HashMap, VecDeque};
8use std::rc::Rc;
9use std::sync::{Arc, Mutex};
10
11use dom_struct::dom_struct;
12use js::context::JSContext;
13use js::realm::CurrentRealm;
14use js::rust::CustomAutoRooterGuard;
15use js::typedarray::{ArrayBuffer, ArrayBufferU8};
16use script_bindings::cell::DomRefCell;
17use script_bindings::cformat;
18use script_bindings::codegen::GenericBindings::PeriodicWaveBinding::PeriodicWaveMethods;
19use servo_base::id::PipelineId;
20use servo_media::audio::context::{
21 AudioContext, AudioContextOptions, OfflineAudioContextOptions, ProcessingState,
22 RealTimeAudioContextOptions,
23};
24use servo_media::audio::decoder::AudioDecoderCallbacksBuilder;
25use servo_media::audio::graph::NodeId;
26use servo_media::{ClientContextId, ServoMedia};
27use uuid::Uuid;
28
29use crate::conversions::Convert;
30use crate::dom::audio::analysernode::AnalyserNode;
31use crate::dom::audio::audiobuffer::AudioBuffer;
32use crate::dom::audio::audiobuffersourcenode::AudioBufferSourceNode;
33use crate::dom::audio::audiodestinationnode::AudioDestinationNode;
34use crate::dom::audio::audiolistener::AudioListener;
35use crate::dom::audio::audionode::MAX_CHANNEL_COUNT;
36use crate::dom::audio::biquadfilternode::BiquadFilterNode;
37use crate::dom::audio::channelmergernode::ChannelMergerNode;
38use crate::dom::audio::channelsplitternode::ChannelSplitterNode;
39use crate::dom::audio::constantsourcenode::ConstantSourceNode;
40use crate::dom::audio::gainnode::GainNode;
41use crate::dom::audio::iirfilternode::IIRFilterNode;
42use crate::dom::audio::oscillatornode::OscillatorNode;
43use crate::dom::audio::pannernode::PannerNode;
44use crate::dom::audio::stereopannernode::StereoPannerNode;
45use crate::dom::bindings::buffer_source::HeapBufferSource;
46use crate::dom::bindings::callback::ExceptionHandling;
47use crate::dom::bindings::codegen::Bindings::AnalyserNodeBinding::AnalyserOptions;
48use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions;
49use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
50 AudioNodeOptions, ChannelCountMode, ChannelInterpretation,
51};
52use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::{
53 AudioContextState, BaseAudioContextMethods, DecodeErrorCallback, DecodeSuccessCallback,
54};
55use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterOptions;
56use crate::dom::bindings::codegen::Bindings::ChannelMergerNodeBinding::ChannelMergerOptions;
57use crate::dom::bindings::codegen::Bindings::ChannelSplitterNodeBinding::ChannelSplitterOptions;
58use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceOptions;
59use crate::dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions;
60use crate::dom::bindings::codegen::Bindings::IIRFilterNodeBinding::IIRFilterOptions;
61use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions;
62use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::PannerOptions;
63use crate::dom::bindings::codegen::Bindings::PeriodicWaveBinding::{
64 PeriodicWaveConstraints, PeriodicWaveOptions,
65};
66use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerOptions;
67use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
68use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
69use crate::dom::bindings::inheritance::Castable;
70use crate::dom::bindings::num::Finite;
71use crate::dom::bindings::refcounted::Trusted;
72use crate::dom::bindings::reflector::DomGlobal;
73use crate::dom::bindings::root::{DomRoot, MutNullableDom};
74use crate::dom::domexception::{DOMErrorName, DOMException};
75use crate::dom::eventtarget::EventTarget;
76use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
77use crate::dom::types::PeriodicWave;
78
79pub(crate) enum BaseAudioContextOptions {
80 AudioContext(RealTimeAudioContextOptions),
81 OfflineAudioContext(OfflineAudioContextOptions),
82}
83
84#[derive(JSTraceable, MallocSizeOf)]
85#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
86struct DecodeResolver {
87 pub(crate) promise: TracedPromise,
88 #[conditional_malloc_size_of]
89 pub(crate) success_callback: Option<Rc<DecodeSuccessCallback>>,
90 #[conditional_malloc_size_of]
91 pub(crate) error_callback: Option<Rc<DecodeErrorCallback>>,
92}
93
94type BoxedSliceOfPromises = Box<[TracedPromise]>;
95
96#[dom_struct]
97pub(crate) struct BaseAudioContext {
98 eventtarget: EventTarget,
99 #[ignore_malloc_size_of = "servo_media"]
100 #[no_trace]
101 audio_context_impl: Arc<Mutex<AudioContext>>,
102 destination: MutNullableDom<AudioDestinationNode>,
104 listener: MutNullableDom<AudioListener>,
105 in_flight_resume_promises_queue: DomRefCell<VecDeque<(BoxedSliceOfPromises, ErrorResult)>>,
107 pending_resume_promises: DomRefCell<Vec<TracedPromise>>,
109 decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>,
110 sample_rate: f32,
112 state: Cell<AudioContextState>,
118 channel_count: u32,
119}
120
121impl BaseAudioContext {
122 pub(crate) fn new_inherited(
123 options: BaseAudioContextOptions,
124 pipeline_id: PipelineId,
125 ) -> Fallible<BaseAudioContext> {
126 let (sample_rate, channel_count) = match options {
127 BaseAudioContextOptions::AudioContext(ref opt) => (opt.sample_rate, 2),
128 BaseAudioContextOptions::OfflineAudioContext(ref opt) => {
129 (opt.sample_rate, opt.channels)
130 },
131 };
132
133 let client_context_id =
134 ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
135 let audio_context_impl = ServoMedia::get()
136 .create_audio_context(&client_context_id, options.convert())
137 .map_err(|_| Error::NotSupported(None))?;
138
139 Ok(BaseAudioContext {
140 eventtarget: EventTarget::new_inherited(),
141 audio_context_impl,
142 destination: Default::default(),
143 listener: Default::default(),
144 in_flight_resume_promises_queue: Default::default(),
145 pending_resume_promises: Default::default(),
146 decode_resolvers: Default::default(),
147 sample_rate,
148 state: Cell::new(AudioContextState::Suspended),
149 channel_count: channel_count.into(),
150 })
151 }
152
153 pub(crate) fn is_offline(&self) -> bool {
155 false
156 }
157
158 pub(crate) fn audio_context_impl(&self) -> Arc<Mutex<AudioContext>> {
159 self.audio_context_impl.clone()
160 }
161
162 pub(crate) fn destination_node(&self) -> NodeId {
163 self.audio_context_impl.lock().unwrap().dest_node()
164 }
165
166 pub(crate) fn listener(&self) -> NodeId {
167 self.audio_context_impl.lock().unwrap().listener()
168 }
169
170 pub(crate) fn is_allowed_to_start(&self) -> bool {
172 self.state.get() == AudioContextState::Suspended
173 }
174
175 fn push_pending_resume_promise(&self, promise: &RootedPromise) {
176 self.pending_resume_promises
177 .borrow_mut()
178 .push(promise.to_traced());
179 }
180
181 fn take_pending_resume_promises(&self, result: ErrorResult) {
192 self.in_flight_resume_promises_queue
193 .borrow_mut()
194 .push_back((
195 std::mem::take(&mut *self.pending_resume_promises.borrow_mut()).into(),
196 result,
197 ));
198 }
199
200 fn fulfill_in_flight_resume_promises<F>(&self, cx: &mut JSContext, f: F)
209 where
210 F: FnOnce(),
211 {
212 let (promises, result) = self
213 .in_flight_resume_promises_queue
214 .borrow_mut()
215 .pop_front()
216 .map(|(promises, result)| {
217 let promises: Vec<RootedPromise> =
218 promises.iter().map(|promise| promise.root(cx)).collect();
219 (promises, result)
220 })
221 .expect("there should be at least one list of in flight resume promises");
222 f();
223 for promise in &promises {
224 match result {
225 Ok(ref value) => promise.resolve_native(cx, value),
226 Err(ref error) => promise.reject_error(cx, error.clone()),
227 }
228 }
229 }
230
231 pub(crate) fn control_thread_state(&self) -> ProcessingState {
233 self.audio_context_impl.lock().unwrap().state()
234 }
235
236 pub(crate) fn set_state_attribute(&self, state: AudioContextState) {
238 self.state.set(state);
239 }
240
241 pub(crate) fn resume(&self) {
242 let this = Trusted::new(self);
243 match self.audio_context_impl.lock().unwrap().resume() {
246 Some(()) => {
247 self.take_pending_resume_promises(Ok(()));
248 self.global().task_manager().dom_manipulation_task_source().queue(
249 task!(resume_success: move |cx| {
250 let this = this.root();
251 this.fulfill_in_flight_resume_promises(cx, || {
252 if this.state.get() != AudioContextState::Running {
253 this.state.set(AudioContextState::Running);
254 this.global().task_manager().dom_manipulation_task_source().queue_simple_event(
255 this.upcast(),
256 atom!("statechange"),
257 );
258 }
259 });
260 })
261 );
262 },
263 None => {
264 self.take_pending_resume_promises(Err(Error::Type(
265 c"Something went wrong".to_owned(),
266 )));
267 self.global()
268 .task_manager()
269 .dom_manipulation_task_source()
270 .queue(task!(resume_error: move |cx| {
271 this.root().fulfill_in_flight_resume_promises(cx, || {})
272 }));
273 },
274 }
275 }
276
277 pub(crate) fn channel_count(&self) -> u32 {
278 self.channel_count
279 }
280}
281
282impl BaseAudioContextMethods<crate::DomTypeHolder> for BaseAudioContext {
283 fn SampleRate(&self) -> Finite<f32> {
285 Finite::wrap(self.sample_rate)
286 }
287
288 fn CurrentTime(&self) -> Finite<f64> {
290 let current_time = self.audio_context_impl.lock().unwrap().current_time();
291 Finite::wrap(current_time)
292 }
293
294 fn State(&self) -> AudioContextState {
296 self.state.get()
297 }
298
299 fn Resume(&self, cx: &mut CurrentRealm) -> RootedPromise {
301 let promise = Promise::new_in_realm_rooted(cx);
303
304 if self.audio_context_impl.lock().unwrap().state() == ProcessingState::Closed {
306 promise.reject_error(cx, Error::InvalidState(None));
307 return promise;
308 }
309
310 if self.state.get() == AudioContextState::Running {
312 promise.resolve_native(cx, &());
313 return promise;
314 }
315
316 self.push_pending_resume_promise(&promise);
317
318 if !self.is_allowed_to_start() {
320 return promise;
321 }
322
323 self.resume();
325
326 promise
328 }
329
330 fn Destination(&self, cx: &mut JSContext) -> DomRoot<AudioDestinationNode> {
332 let global = self.global();
333 self.destination.or_init(|| {
334 let mut options = AudioNodeOptions::empty();
335 options.channelCount = Some(self.channel_count);
336 options.channelCountMode = Some(ChannelCountMode::Explicit);
337 options.channelInterpretation = Some(ChannelInterpretation::Speakers);
338 AudioDestinationNode::new(cx, &global, self, &options)
339 })
340 }
341
342 fn Listener(&self, cx: &mut JSContext) -> DomRoot<AudioListener> {
344 let global = self.global();
345 let window = global.as_window();
346 self.listener
347 .or_init(|| AudioListener::new(cx, window, self))
348 }
349
350 event_handler!(statechange, GetOnstatechange, SetOnstatechange);
352
353 fn CreateOscillator(&self, cx: &mut JSContext) -> Fallible<DomRoot<OscillatorNode>> {
355 OscillatorNode::new(
356 cx,
357 self.global().as_window(),
358 self,
359 &OscillatorOptions::empty(),
360 )
361 }
362
363 fn CreateGain(&self, cx: &mut JSContext) -> Fallible<DomRoot<GainNode>> {
365 GainNode::new(cx, self.global().as_window(), self, &GainOptions::empty())
366 }
367
368 fn CreatePanner(&self, cx: &mut JSContext) -> Fallible<DomRoot<PannerNode>> {
370 PannerNode::new(cx, self.global().as_window(), self, &PannerOptions::empty())
371 }
372
373 fn CreatePeriodicWave(
375 &self,
376 cx: &mut JSContext,
377 real: Vec<Finite<f32>>,
378 imag: Vec<Finite<f32>>,
379 constraints: &PeriodicWaveConstraints,
380 ) -> Fallible<DomRoot<PeriodicWave>> {
381 let mut options = PeriodicWaveOptions::empty();
383 let mut constraints_copy = PeriodicWaveConstraints::empty();
384 constraints_copy.disableNormalization = constraints.disableNormalization;
387 options.real = Some(real);
390 options.imag = Some(imag);
391 options.parent = constraints_copy;
392 PeriodicWave::Constructor(cx, self.global().as_window(), None, self, &options)
393 }
394
395 fn CreateAnalyser(&self, cx: &mut JSContext) -> Fallible<DomRoot<AnalyserNode>> {
397 AnalyserNode::new(
398 cx,
399 self.global().as_window(),
400 self,
401 &AnalyserOptions::empty(),
402 )
403 }
404
405 fn CreateBiquadFilter(&self, cx: &mut JSContext) -> Fallible<DomRoot<BiquadFilterNode>> {
407 BiquadFilterNode::new(
408 cx,
409 self.global().as_window(),
410 self,
411 &BiquadFilterOptions::empty(),
412 )
413 }
414
415 fn CreateStereoPanner(&self, cx: &mut JSContext) -> Fallible<DomRoot<StereoPannerNode>> {
417 StereoPannerNode::new(
418 cx,
419 self.global().as_window(),
420 self,
421 &StereoPannerOptions::empty(),
422 )
423 }
424
425 fn CreateConstantSource(&self, cx: &mut JSContext) -> Fallible<DomRoot<ConstantSourceNode>> {
427 ConstantSourceNode::new(
428 cx,
429 self.global().as_window(),
430 self,
431 &ConstantSourceOptions::empty(),
432 )
433 }
434
435 fn CreateChannelMerger(
437 &self,
438 cx: &mut JSContext,
439 count: u32,
440 ) -> Fallible<DomRoot<ChannelMergerNode>> {
441 let mut opts = ChannelMergerOptions::empty();
442 opts.numberOfInputs = count;
443 ChannelMergerNode::new(cx, self.global().as_window(), self, &opts)
444 }
445
446 fn CreateChannelSplitter(
448 &self,
449 cx: &mut JSContext,
450 count: u32,
451 ) -> Fallible<DomRoot<ChannelSplitterNode>> {
452 let mut opts = ChannelSplitterOptions::empty();
453 opts.numberOfOutputs = count;
454 ChannelSplitterNode::new(cx, self.global().as_window(), self, &opts)
455 }
456
457 fn CreateBuffer(
459 &self,
460 cx: &mut JSContext,
461 number_of_channels: u32,
462 length: u32,
463 sample_rate: Finite<f32>,
464 ) -> Fallible<DomRoot<AudioBuffer>> {
465 if number_of_channels == 0 ||
466 number_of_channels > MAX_CHANNEL_COUNT ||
467 length == 0 ||
468 *sample_rate <= 0.
469 {
470 return Err(Error::NotSupported(None));
471 }
472 Ok(AudioBuffer::new(
473 cx,
474 self.global().as_window(),
475 number_of_channels,
476 length,
477 *sample_rate,
478 None,
479 ))
480 }
481
482 fn CreateBufferSource(&self, cx: &mut JSContext) -> Fallible<DomRoot<AudioBufferSourceNode>> {
484 AudioBufferSourceNode::new(
485 cx,
486 self.global().as_window(),
487 self,
488 &AudioBufferSourceOptions::empty(),
489 )
490 }
491
492 fn DecodeAudioData(
494 &self,
495 cx: &mut CurrentRealm,
496 audio_data: CustomAutoRooterGuard<ArrayBuffer>,
497 decode_success_callback: Option<Option<Rc<DecodeSuccessCallback>>>,
498 decode_error_callback: Option<Option<Rc<DecodeErrorCallback>>>,
499 ) -> RootedPromise {
500 if !self.global().as_window().Document().is_fully_active() {
503 let promise = Promise::new_in_realm_rooted(cx);
504 promise.reject_error(
505 cx,
506 Error::InvalidState(Some("Audio context's document is not fully active.".into())),
507 );
508 return promise;
509 }
510
511 let promise = Promise::new_in_realm_rooted(cx);
513
514 let decode_success_callback = decode_success_callback.flatten();
516 let decode_error_callback = decode_error_callback.flatten();
517
518 let audio_data = HeapBufferSource::<ArrayBufferU8>::from_view(cx, audio_data);
519
520 let audio_data = if !audio_data.is_detached_buffer(cx) {
525 audio_data
526 .get_typed_array()
527 .ok()
528 .and_then(|buffer| buffer.to_vec())
529 .filter(|_| audio_data.detach_buffer(cx))
530 } else {
531 None
532 };
533
534 if let Some(audio_data) = audio_data {
535 let uuid = Uuid::new_v4().simple().to_string();
536 let uuid_ = uuid.clone();
537 self.decode_resolvers.safe_borrow_mut(cx.no_gc()).insert(
538 uuid.clone(),
539 DecodeResolver {
540 promise: promise.to_traced(),
541 success_callback: decode_success_callback,
542 error_callback: decode_error_callback,
543 },
544 );
545 let decoded_audio = Arc::new(Mutex::new(Vec::new()));
546 let decoded_audio_ = decoded_audio.clone();
547 let decoded_audio__ = decoded_audio.clone();
548 let channels = Arc::new(Mutex::new(HashMap::new()));
553 let this = Trusted::new(self);
554 let this_ = this.clone();
555 let task_source = self
556 .global()
557 .task_manager()
558 .dom_manipulation_task_source()
559 .to_sendable();
560 let task_source_clone = task_source.clone();
561 let callbacks = AudioDecoderCallbacksBuilder::default()
562 .ready(move |channel_count| {
563 decoded_audio
564 .lock()
565 .unwrap()
566 .resize(channel_count as usize, Vec::new());
567 })
568 .progress(move |buffer, channel_pos_mask| {
569 let mut decoded_audio = decoded_audio_.lock().unwrap();
570 let mut channels = channels.lock().unwrap();
571 let channel = match channels.entry(channel_pos_mask) {
572 Entry::Occupied(entry) => *entry.get(),
573 Entry::Vacant(entry) => {
574 let x = (channel_pos_mask as f32).log2() as usize;
575 *entry.insert(x)
576 },
577 };
578 decoded_audio[channel].extend_from_slice((*buffer).as_ref());
579 })
580 .eos(move || {
581 task_source.queue(task!(audio_decode_eos: move |cx| {
582 let this = this.root();
583 let decoded_audio = decoded_audio__.lock().unwrap();
584 let length = if !decoded_audio.is_empty() {
585 decoded_audio[0].len()
586 } else {
587 0
588 };
589 let buffer = AudioBuffer::new(
590 cx,
591 this.global().as_window(),
592 decoded_audio.len() as u32 ,
593 length as u32,
594 this.sample_rate,
595 Some(decoded_audio.as_slice()),
596 );
597 let (promise, success_callback) = this
598 .decode_resolvers
599 .safe_borrow_mut(cx.no_gc())
600 .remove(&uuid_)
601 .map(|resolver| (resolver.promise.root(cx), resolver.success_callback))
602 .expect("resolver should exist");
603
604 if let Some(callback) = success_callback {
605 let _ = callback.Call__(cx, &buffer, ExceptionHandling::Report);
606 }
607 promise.resolve_native(cx, &buffer);
608 }));
609 })
610 .error(move |error| {
611 task_source_clone.queue(task!(audio_decode_eos: move |cx| {
612 let this = this_.root();
613 let (promise, error_callback) = this
614 .decode_resolvers
615 .safe_borrow_mut(cx.no_gc())
616 .remove(&uuid)
617 .map(|resolver| (resolver.promise.root(cx), resolver.error_callback))
618 .expect("resolver should exist");
619
620 if let Some(callback) = error_callback {
621 let exception = DOMException::new(
622 cx,
623 &this.global(),
624 DOMErrorName::DataCloneError,
625 );
626 let _ =
627 callback.Call__(cx, &exception, ExceptionHandling::Report);
628 }
629 let error = cformat!("Audio decode error {:?}", error);
630 promise.reject_error(cx, Error::Type(error));
631 }));
632 })
633 .build();
634 self.audio_context_impl
635 .lock()
636 .unwrap()
637 .decode_audio_data(audio_data, callbacks);
638 } else {
639 let exception = DOMException::new(cx, &self.global(), DOMErrorName::DataCloneError);
645 promise.reject_native(cx, &exception);
646
647 if let Some(callback) = decode_error_callback {
648 let uuid = Uuid::new_v4().simple().to_string();
652 let uuid_ = uuid.clone();
653 let this = Trusted::new(self);
654 let exception = Trusted::new(&*exception);
655 let task = task!(decode_audio_data_detached_buffer: move |cx| {
656 let this = this.root();
657 let exception = exception.root();
658 let error_callback = this
659 .decode_resolvers
660 .safe_borrow_mut(cx.no_gc())
661 .remove(&uuid)
662 .map(|resolver| resolver.error_callback)
663 .expect("resolver should exist");
664
665 if let Some(callback) = error_callback {
666 let _ = callback.Call__(
667 cx,
668 &exception,
669 ExceptionHandling::Report
670 );
671 }
672 });
673 self.decode_resolvers.safe_borrow_mut(cx.no_gc()).insert(
674 uuid_,
675 DecodeResolver {
676 promise: promise.to_traced(),
677 success_callback: None,
678 error_callback: Some(callback),
679 },
680 );
681 self.global()
682 .task_manager()
683 .media_element_task_source()
684 .queue(task);
685 }
686 }
687
688 promise
690 }
691
692 fn CreateIIRFilter(
694 &self,
695 cx: &mut JSContext,
696 feedforward: Vec<Finite<f64>>,
697 feedback: Vec<Finite<f64>>,
698 ) -> Fallible<DomRoot<IIRFilterNode>> {
699 let opts = IIRFilterOptions {
700 parent: AudioNodeOptions::empty(),
701 feedback,
702 feedforward,
703 };
704 IIRFilterNode::new(cx, self.global().as_window(), self, &opts)
705 }
706}
707
708impl Convert<AudioContextOptions> for BaseAudioContextOptions {
709 fn convert(self) -> AudioContextOptions {
710 match self {
711 BaseAudioContextOptions::AudioContext(options) => {
712 AudioContextOptions::RealTimeAudioContext(options)
713 },
714 BaseAudioContextOptions::OfflineAudioContext(options) => {
715 AudioContextOptions::OfflineAudioContext(options)
716 },
717 }
718 }
719}
720
721impl Convert<AudioContextState> for ProcessingState {
722 fn convert(self) -> AudioContextState {
723 match self {
724 ProcessingState::Suspended => AudioContextState::Suspended,
725 ProcessingState::Running => AudioContextState::Running,
726 ProcessingState::Closed => AudioContextState::Closed,
727 }
728 }
729}