Skip to main content

script/dom/audio/
baseaudiocontext.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::collections::hash_map::Entry;
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::realm::CurrentRealm;
13use js::rust::CustomAutoRooterGuard;
14use js::typedarray::{ArrayBuffer, ArrayBufferU8};
15use script_bindings::cell::DomRefCell;
16use script_bindings::cformat;
17use script_bindings::codegen::GenericBindings::DelayNodeBinding::{DelayNodeMethods, DelayOptions};
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::delaynode::DelayNode;
41use crate::dom::audio::gainnode::GainNode;
42use crate::dom::audio::iirfilternode::IIRFilterNode;
43use crate::dom::audio::oscillatornode::OscillatorNode;
44use crate::dom::audio::pannernode::PannerNode;
45use crate::dom::audio::periodicwave::PeriodicWave;
46use crate::dom::audio::stereopannernode::StereoPannerNode;
47use crate::dom::bindings::buffer_source::HeapBufferSource;
48use crate::dom::bindings::callback::{ExceptionHandling, RootedCallback, TracedCallback};
49use crate::dom::bindings::codegen::Bindings::AnalyserNodeBinding::AnalyserOptions;
50use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions;
51use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
52    AudioNodeOptions, ChannelCountMode, ChannelInterpretation,
53};
54use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::{
55    AudioContextState, BaseAudioContextMethods, DecodeErrorCallback, DecodeSuccessCallback,
56};
57use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterOptions;
58use crate::dom::bindings::codegen::Bindings::ChannelMergerNodeBinding::ChannelMergerOptions;
59use crate::dom::bindings::codegen::Bindings::ChannelSplitterNodeBinding::ChannelSplitterOptions;
60use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceOptions;
61use crate::dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions;
62use crate::dom::bindings::codegen::Bindings::IIRFilterNodeBinding::IIRFilterOptions;
63use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions;
64use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::PannerOptions;
65use crate::dom::bindings::codegen::Bindings::PeriodicWaveBinding::{
66    PeriodicWaveConstraints, PeriodicWaveOptions,
67};
68use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerOptions;
69use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
70use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
71use crate::dom::bindings::inheritance::Castable;
72use crate::dom::bindings::num::Finite;
73use crate::dom::bindings::refcounted::Trusted;
74use crate::dom::bindings::reflector::DomGlobal;
75use crate::dom::bindings::root::{DomRoot, MutNullableDom};
76use crate::dom::domexception::{DOMErrorName, DOMException};
77use crate::dom::eventtarget::EventTarget;
78use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
79
80pub(crate) enum BaseAudioContextOptions {
81    AudioContext(RealTimeAudioContextOptions),
82    OfflineAudioContext(OfflineAudioContextOptions),
83}
84
85#[derive(JSTraceable, MallocSizeOf)]
86#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
87struct DecodeResolver {
88    pub(crate) promise: TracedPromise,
89    pub(crate) success_callback: Option<TracedCallback<DecodeSuccessCallback>>,
90    pub(crate) error_callback: Option<TracedCallback<DecodeErrorCallback>>,
91}
92
93impl js::gc::Rootable for DecodeResolver {}
94
95type BoxedSliceOfPromises = Box<[TracedPromise]>;
96
97#[dom_struct]
98pub(crate) struct BaseAudioContext {
99    eventtarget: EventTarget,
100    #[ignore_malloc_size_of = "servo_media"]
101    #[no_trace]
102    audio_context_impl: Arc<Mutex<AudioContext>>,
103    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination>
104    destination: MutNullableDom<AudioDestinationNode>,
105    listener: MutNullableDom<AudioListener>,
106    /// Resume promises which are soon to be fulfilled by a queued task.
107    in_flight_resume_promises_queue: DomRefCell<VecDeque<(BoxedSliceOfPromises, ErrorResult)>>,
108    /// <https://webaudio.github.io/web-audio-api/#pendingresumepromises>
109    pending_resume_promises: DomRefCell<Vec<TracedPromise>>,
110    decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>,
111    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate>
112    sample_rate: f32,
113    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state>
114    /// Although servo-media already keeps track of the control thread state,
115    /// we keep a state flag here as well. This is so that we can synchronously
116    /// throw when trying to do things on the context when the context has just
117    /// been "closed()".
118    state: Cell<AudioContextState>,
119    channel_count: u32,
120}
121
122impl BaseAudioContext {
123    pub(crate) fn new_inherited(
124        options: BaseAudioContextOptions,
125        pipeline_id: PipelineId,
126    ) -> Fallible<BaseAudioContext> {
127        let (sample_rate, channel_count) = match options {
128            BaseAudioContextOptions::AudioContext(ref opt) => (opt.sample_rate, 2),
129            BaseAudioContextOptions::OfflineAudioContext(ref opt) => {
130                (opt.sample_rate, opt.channels)
131            },
132        };
133
134        let client_context_id =
135            ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
136        let audio_context_impl = ServoMedia::get()
137            .create_audio_context(&client_context_id, options.convert())
138            .map_err(|_| Error::NotSupported(None))?;
139
140        Ok(BaseAudioContext {
141            eventtarget: EventTarget::new_inherited(),
142            audio_context_impl,
143            destination: Default::default(),
144            listener: Default::default(),
145            in_flight_resume_promises_queue: Default::default(),
146            pending_resume_promises: Default::default(),
147            decode_resolvers: Default::default(),
148            sample_rate,
149            state: Cell::new(AudioContextState::Suspended),
150            channel_count: channel_count.into(),
151        })
152    }
153
154    /// Tells whether this is an OfflineAudioContext or not.
155    pub(crate) fn is_offline(&self) -> bool {
156        false
157    }
158
159    pub(crate) fn audio_context_impl(&self) -> Arc<Mutex<AudioContext>> {
160        self.audio_context_impl.clone()
161    }
162
163    pub(crate) fn destination_node(&self) -> NodeId {
164        self.audio_context_impl.lock().unwrap().dest_node()
165    }
166
167    pub(crate) fn listener(&self) -> NodeId {
168        self.audio_context_impl.lock().unwrap().listener()
169    }
170
171    // https://webaudio.github.io/web-audio-api/#allowed-to-start
172    pub(crate) fn is_allowed_to_start(&self) -> bool {
173        self.state.get() == AudioContextState::Suspended
174    }
175
176    fn push_pending_resume_promise(&self, promise: &RootedPromise) {
177        self.pending_resume_promises
178            .borrow_mut()
179            .push(promise.to_traced());
180    }
181
182    /// Takes the pending resume promises.
183    ///
184    /// The result with which these promises will be fulfilled is passed here
185    /// and this method returns nothing because we actually just move the
186    /// current list of pending resume promises to the
187    /// `in_flight_resume_promises_queue` field.
188    ///
189    /// Each call to this method must be followed by a call to
190    /// `fulfill_in_flight_resume_promises`, to actually fulfill the promises
191    /// which were taken and moved to the in-flight queue.
192    fn take_pending_resume_promises(&self, result: ErrorResult) {
193        self.in_flight_resume_promises_queue
194            .borrow_mut()
195            .push_back((
196                std::mem::take(&mut *self.pending_resume_promises.borrow_mut()).into(),
197                result,
198            ));
199    }
200
201    /// Fulfills the next in-flight resume promises queue after running a closure.
202    ///
203    /// See the comment on `take_pending_resume_promises` for why this method
204    /// does not take a list of promises to fulfill. Callers cannot just pop
205    /// the front list off of `in_flight_resume_promises_queue` and later fulfill
206    /// the promises because that would mean putting
207    /// `#[cfg_attr(crown, expect(crown::unrooted_must_root))]` on even more functions, potentially
208    /// hiding actual safety bugs.
209    fn fulfill_in_flight_resume_promises<F>(&self, cx: &mut JSContext, f: F)
210    where
211        F: FnOnce(),
212    {
213        let (promises, result) = self
214            .in_flight_resume_promises_queue
215            .borrow_mut()
216            .pop_front()
217            .map(|(promises, result)| {
218                let promises: Vec<RootedPromise> =
219                    promises.iter().map(|promise| promise.root(cx)).collect();
220                (promises, result)
221            })
222            .expect("there should be at least one list of in flight resume promises");
223        f();
224        for promise in &promises {
225            match result {
226                Ok(ref value) => promise.resolve_native(cx, value),
227                Err(ref error) => promise.reject_error(cx, error.clone()),
228            }
229        }
230    }
231
232    /// Control thread processing state
233    pub(crate) fn control_thread_state(&self) -> ProcessingState {
234        self.audio_context_impl.lock().unwrap().state()
235    }
236
237    /// Set audio context state
238    pub(crate) fn set_state_attribute(&self, state: AudioContextState) {
239        self.state.set(state);
240    }
241
242    pub(crate) fn resume(&self) {
243        let this = Trusted::new(self);
244        // Set the rendering thread state to 'running' and start
245        // rendering the audio graph.
246        match self.audio_context_impl.lock().unwrap().resume() {
247            Some(()) => {
248                self.take_pending_resume_promises(Ok(()));
249                self.global().task_manager().dom_manipulation_task_source().queue(
250                    task!(resume_success: move |cx| {
251                        let this = this.root();
252                        this.fulfill_in_flight_resume_promises(cx, || {
253                            if this.state.get() != AudioContextState::Running {
254                                this.state.set(AudioContextState::Running);
255                                this.global().task_manager().dom_manipulation_task_source().queue_simple_event(
256                                    this.upcast(),
257                                    atom!("statechange"),
258                                    );
259                            }
260                        });
261                    })
262                );
263            },
264            None => {
265                self.take_pending_resume_promises(Err(Error::Type(
266                    c"Something went wrong".to_owned(),
267                )));
268                self.global()
269                    .task_manager()
270                    .dom_manipulation_task_source()
271                    .queue(task!(resume_error: move |cx| {
272                        this.root().fulfill_in_flight_resume_promises(cx, || {})
273                    }));
274            },
275        }
276    }
277
278    pub(crate) fn channel_count(&self) -> u32 {
279        self.channel_count
280    }
281}
282
283impl BaseAudioContextMethods<crate::DomTypeHolder> for BaseAudioContext {
284    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate>
285    fn SampleRate(&self) -> Finite<f32> {
286        Finite::wrap(self.sample_rate)
287    }
288
289    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-currenttime>
290    fn CurrentTime(&self) -> Finite<f64> {
291        let current_time = self.audio_context_impl.lock().unwrap().current_time();
292        Finite::wrap(current_time)
293    }
294
295    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state>
296    fn State(&self) -> AudioContextState {
297        self.state.get()
298    }
299
300    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-resume>
301    fn Resume(&self, cx: &mut CurrentRealm) -> RootedPromise {
302        // Step 1.
303        let promise = Promise::new_in_realm(cx);
304
305        // Step 2.
306        if self.audio_context_impl.lock().unwrap().state() == ProcessingState::Closed {
307            promise.reject_error(cx, Error::InvalidState(None));
308            return promise;
309        }
310
311        // Step 3.
312        if self.state.get() == AudioContextState::Running {
313            promise.resolve_native(cx, &());
314            return promise;
315        }
316
317        self.push_pending_resume_promise(&promise);
318
319        // Step 4.
320        if !self.is_allowed_to_start() {
321            return promise;
322        }
323
324        // Steps 5 and 6.
325        self.resume();
326
327        // Step 7.
328        promise
329    }
330
331    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination>
332    fn Destination(&self, cx: &mut JSContext) -> DomRoot<AudioDestinationNode> {
333        let global = self.global();
334        self.destination.or_init(|| {
335            let mut options = AudioNodeOptions::empty();
336            options.channelCount = Some(self.channel_count);
337            options.channelCountMode = Some(ChannelCountMode::Explicit);
338            options.channelInterpretation = Some(ChannelInterpretation::Speakers);
339            AudioDestinationNode::new(cx, &global, self, &options)
340        })
341    }
342
343    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-listener>
344    fn Listener(&self, cx: &mut JSContext) -> DomRoot<AudioListener> {
345        let global = self.global();
346        let window = global.as_window();
347        self.listener
348            .or_init(|| AudioListener::new(cx, window, self))
349    }
350
351    // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-onstatechange
352    event_handler!(statechange, GetOnstatechange, SetOnstatechange);
353
354    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createoscillator>
355    fn CreateOscillator(&self, cx: &mut JSContext) -> Fallible<DomRoot<OscillatorNode>> {
356        OscillatorNode::new(
357            cx,
358            self.global().as_window(),
359            self,
360            &OscillatorOptions::empty(),
361        )
362    }
363
364    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-creategain>
365    fn CreateGain(&self, cx: &mut JSContext) -> Fallible<DomRoot<GainNode>> {
366        GainNode::new(cx, self.global().as_window(), self, &GainOptions::empty())
367    }
368
369    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createdelay>
370    fn CreateDelay(
371        &self,
372        cx: &mut JSContext,
373        max_delay_time: Finite<f64>,
374    ) -> Fallible<DomRoot<DelayNode>> {
375        // <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createdelay-maxdelaytime-maxdelaytime>
376        // Specifies the maximum delay time in seconds allowed for the delay line.
377        // If specified, this value MUST be greater than zero and less than three minutes.
378        if *max_delay_time <= 0. || *max_delay_time >= 180. {
379            return Err(Error::NotSupported(Some(String::from(
380                "maxDelayTime is not within 0 - 3 minutes",
381            ))));
382        }
383        let mut options = DelayOptions::empty();
384        options.maxDelayTime = max_delay_time;
385        DelayNode::Constructor(cx, self.global().as_window(), None, self, &options)
386    }
387
388    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createpanner>
389    fn CreatePanner(&self, cx: &mut JSContext) -> Fallible<DomRoot<PannerNode>> {
390        PannerNode::new(cx, self.global().as_window(), self, &PannerOptions::empty())
391    }
392
393    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createperiodicwave>
394    fn CreatePeriodicWave(
395        &self,
396        cx: &mut JSContext,
397        real: Vec<Finite<f32>>,
398        imag: Vec<Finite<f32>>,
399        constraints: &PeriodicWaveConstraints,
400    ) -> Fallible<DomRoot<PeriodicWave>> {
401        // options is a new object of type PeriodicWaveOptions.
402        let mut options = PeriodicWaveOptions::empty();
403        let mut constraints_copy = PeriodicWaveConstraints::empty();
404        // Set the disableNormalization attribute on options to the value of the
405        // disableNormalization attribute of the constraints attribute passed to the factory method.
406        constraints_copy.disableNormalization = constraints.disableNormalization;
407        // Respectively set the real and imag parameters passed to this factory method to the attributes
408        // of the same name on options.
409        options.real = Some(real);
410        options.imag = Some(imag);
411        options.parent = constraints_copy;
412        PeriodicWave::Constructor(cx, self.global().as_window(), None, self, &options)
413    }
414
415    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createanalyser>
416    fn CreateAnalyser(&self, cx: &mut JSContext) -> Fallible<DomRoot<AnalyserNode>> {
417        AnalyserNode::new(
418            cx,
419            self.global().as_window(),
420            self,
421            &AnalyserOptions::empty(),
422        )
423    }
424
425    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbiquadfilter>
426    fn CreateBiquadFilter(&self, cx: &mut JSContext) -> Fallible<DomRoot<BiquadFilterNode>> {
427        BiquadFilterNode::new(
428            cx,
429            self.global().as_window(),
430            self,
431            &BiquadFilterOptions::empty(),
432        )
433    }
434
435    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createstereopanner>
436    fn CreateStereoPanner(&self, cx: &mut JSContext) -> Fallible<DomRoot<StereoPannerNode>> {
437        StereoPannerNode::new(
438            cx,
439            self.global().as_window(),
440            self,
441            &StereoPannerOptions::empty(),
442        )
443    }
444
445    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createconstantsource>
446    fn CreateConstantSource(&self, cx: &mut JSContext) -> Fallible<DomRoot<ConstantSourceNode>> {
447        ConstantSourceNode::new(
448            cx,
449            self.global().as_window(),
450            self,
451            &ConstantSourceOptions::empty(),
452        )
453    }
454
455    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelmerger>
456    fn CreateChannelMerger(
457        &self,
458        cx: &mut JSContext,
459        count: u32,
460    ) -> Fallible<DomRoot<ChannelMergerNode>> {
461        let mut opts = ChannelMergerOptions::empty();
462        opts.numberOfInputs = count;
463        ChannelMergerNode::new(cx, self.global().as_window(), self, &opts)
464    }
465
466    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelsplitter>
467    fn CreateChannelSplitter(
468        &self,
469        cx: &mut JSContext,
470        count: u32,
471    ) -> Fallible<DomRoot<ChannelSplitterNode>> {
472        let mut opts = ChannelSplitterOptions::empty();
473        opts.numberOfOutputs = count;
474        ChannelSplitterNode::new(cx, self.global().as_window(), self, &opts)
475    }
476
477    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer>
478    fn CreateBuffer(
479        &self,
480        cx: &mut JSContext,
481        number_of_channels: u32,
482        length: u32,
483        sample_rate: Finite<f32>,
484    ) -> Fallible<DomRoot<AudioBuffer>> {
485        if number_of_channels == 0 ||
486            number_of_channels > MAX_CHANNEL_COUNT ||
487            length == 0 ||
488            *sample_rate <= 0.
489        {
490            return Err(Error::NotSupported(None));
491        }
492        Ok(AudioBuffer::new(
493            cx,
494            self.global().as_window(),
495            number_of_channels,
496            length,
497            *sample_rate,
498            None,
499        ))
500    }
501
502    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffersource>
503    fn CreateBufferSource(&self, cx: &mut JSContext) -> Fallible<DomRoot<AudioBufferSourceNode>> {
504        AudioBufferSourceNode::new(
505            cx,
506            self.global().as_window(),
507            self,
508            &AudioBufferSourceOptions::empty(),
509        )
510    }
511
512    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata>
513    fn DecodeAudioData(
514        &self,
515        cx: &mut CurrentRealm,
516        audio_data: CustomAutoRooterGuard<ArrayBuffer>,
517        decode_success_callback: Option<Option<RootedCallback<DecodeSuccessCallback>>>,
518        decode_error_callback: Option<Option<RootedCallback<DecodeErrorCallback>>>,
519    ) -> RootedPromise {
520        // Step 1. If this's relevant global object's associated Document is NOT fully active,
521        // return a promise rejected with "InvalidStateError".
522        if !self.global().as_window().Document().is_fully_active() {
523            let promise = Promise::new_in_realm(cx);
524            promise.reject_error(
525                cx,
526                Error::InvalidState(Some("Audio context's document is not fully active.".into())),
527            );
528            return promise;
529        }
530
531        // Step 2. Let promise be a new promise.
532        let promise = Promise::new_in_realm(cx);
533
534        // flatten the optionally nullable callbacks
535        let decode_success_callback = decode_success_callback.flatten();
536        let decode_error_callback = decode_error_callback.flatten();
537
538        let audio_data = HeapBufferSource::<ArrayBufferU8>::from_view(cx, audio_data);
539
540        // Step 3. If audio_data is NOT detached, execute the following steps:
541        // - Append promise to [[pending promises]].
542        // - Detach the audio_data ArrayBuffer. If this operation throws, jump to step 4.1.
543        // - Queue a decoding operation to be performed on another thread.
544        let audio_data = if !audio_data.is_detached_buffer(cx) {
545            audio_data
546                .get_typed_array()
547                .ok()
548                .and_then(|buffer| buffer.to_vec())
549                .filter(|_| audio_data.detach_buffer(cx))
550        } else {
551            None
552        };
553
554        if let Some(audio_data) = audio_data {
555            let uuid = Uuid::new_v4().simple().to_string();
556            let uuid_ = uuid.clone();
557            self.decode_resolvers.safe_borrow_mut(cx.no_gc()).insert(
558                uuid.clone(),
559                DecodeResolver {
560                    promise: promise.to_traced(),
561                    success_callback: decode_success_callback.map(|callback| callback.to_traced()),
562                    error_callback: decode_error_callback.map(|callback| callback.to_traced()),
563                },
564            );
565            let decoded_audio = Arc::new(Mutex::new(Vec::new()));
566            let decoded_audio_ = decoded_audio.clone();
567            let decoded_audio__ = decoded_audio.clone();
568            // servo-media returns an audio channel position along
569            // with the AudioDecoderCallback progress callback, which
570            // may not be the same as the index of the decoded_audio
571            // Vec.
572            let channels = Arc::new(Mutex::new(HashMap::new()));
573            let this = Trusted::new(self);
574            let this_ = this.clone();
575            let task_source = self
576                .global()
577                .task_manager()
578                .dom_manipulation_task_source()
579                .to_sendable();
580            let task_source_clone = task_source.clone();
581            let callbacks = AudioDecoderCallbacksBuilder::default()
582                .ready(move |channel_count| {
583                    decoded_audio
584                        .lock()
585                        .unwrap()
586                        .resize(channel_count as usize, Vec::new());
587                })
588                .progress(move |buffer, channel_pos_mask| {
589                    let mut decoded_audio = decoded_audio_.lock().unwrap();
590                    let mut channels = channels.lock().unwrap();
591                    let channel = match channels.entry(channel_pos_mask) {
592                        Entry::Occupied(entry) => *entry.get(),
593                        Entry::Vacant(entry) => {
594                            let x = (channel_pos_mask as f32).log2() as usize;
595                            *entry.insert(x)
596                        },
597                    };
598                    decoded_audio[channel].extend_from_slice((*buffer).as_ref());
599                })
600                .eos(move || {
601                    task_source.queue(task!(audio_decode_eos: move |cx| {
602                        let this = this.root();
603                        let decoded_audio = decoded_audio__.lock().unwrap();
604                        let length = if !decoded_audio.is_empty() {
605                            decoded_audio[0].len()
606                        } else {
607                            0
608                        };
609                        let buffer = AudioBuffer::new(
610                            cx,
611                            this.global().as_window(),
612                            decoded_audio.len() as u32 /* number of channels */,
613                            length as u32,
614                            this.sample_rate,
615                            Some(decoded_audio.as_slice()),
616                        );
617                        rooted!(&in(cx) let resolver = this
618                            .decode_resolvers
619                            .safe_borrow_mut(cx.no_gc())
620                            .remove(&uuid_)
621                            .expect("resolver should exist"));
622                        let promise = resolver.promise.root(cx);
623
624                        if let Some(callback) = &resolver.success_callback {
625                            let _ = callback.Call__(cx, &buffer, ExceptionHandling::Report);
626                        }
627                        promise.resolve_native(cx, &buffer);
628                    }));
629                })
630                .error(move |error| {
631                    task_source_clone.queue(task!(audio_decode_eos: move |cx| {
632                        let this = this_.root();
633                        rooted!(&in(cx) let resolver = this
634                            .decode_resolvers
635                            .safe_borrow_mut(cx.no_gc())
636                            .remove(&uuid)
637                            .expect("resolver should exist"));
638                        let promise = resolver.promise.root(cx);
639
640                        if let Some(callback) = &resolver.error_callback {
641                            let exception = DOMException::new(
642                                cx,
643                                &this.global(),
644                                DOMErrorName::DataCloneError,
645                            );
646                            let _ =
647                                callback.Call__(cx, &exception, ExceptionHandling::Report);
648                        }
649                        let error = cformat!("Audio decode error {:?}", error);
650                        promise.reject_error(cx, Error::Type(error));
651                    }));
652                })
653                .build();
654            self.audio_context_impl
655                .lock()
656                .unwrap()
657                .decode_audio_data(audio_data, callbacks);
658        } else {
659            // Step 4.
660            // Else, execute the following error steps:
661            // - Let error be a DataCloneError.
662            // - Reject promise with error, and remove it from [[pending promises]].
663            // - Queue a media element task to invoke errorCallback with error.
664            let exception = DOMException::new(cx, &self.global(), DOMErrorName::DataCloneError);
665            promise.reject_native(cx, &exception);
666
667            if let Some(callback) = decode_error_callback {
668                // Build the Task object using a unique uuid as a key to remove the callback resolver entry.
669                // Stash the callback with clone of uuid in the decode_resolvers map.
670                // Enqueue the task after the callback is stashed.
671                let uuid = Uuid::new_v4().simple().to_string();
672                let uuid_ = uuid.clone();
673                let this = Trusted::new(self);
674                let exception = Trusted::new(&*exception);
675                let task = task!(decode_audio_data_detached_buffer: move |cx| {
676                    let this = this.root();
677                    let exception = exception.root();
678                    rooted!(&in(cx) let resolver = this
679                        .decode_resolvers
680                        .safe_borrow_mut(cx.no_gc())
681                        .remove(&uuid)
682                        .expect("resolver should exist"));
683
684                    if let Some(callback) = &resolver.error_callback {
685                        let _ = callback.Call__(
686                            cx,
687                            &exception,
688                            ExceptionHandling::Report
689                        );
690                    }
691                });
692                self.decode_resolvers.safe_borrow_mut(cx.no_gc()).insert(
693                    uuid_,
694                    DecodeResolver {
695                        promise: promise.to_traced(),
696                        success_callback: None,
697                        error_callback: Some(callback.to_traced()),
698                    },
699                );
700                self.global()
701                    .task_manager()
702                    .media_element_task_source()
703                    .queue(task);
704            }
705        }
706
707        // Step 5. Return promise.
708        promise
709    }
710
711    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createiirfilter>
712    fn CreateIIRFilter(
713        &self,
714        cx: &mut JSContext,
715        feedforward: Vec<Finite<f64>>,
716        feedback: Vec<Finite<f64>>,
717    ) -> Fallible<DomRoot<IIRFilterNode>> {
718        let opts = IIRFilterOptions {
719            parent: AudioNodeOptions::empty(),
720            feedback,
721            feedforward,
722        };
723        IIRFilterNode::new(cx, self.global().as_window(), self, &opts)
724    }
725}
726
727impl Convert<AudioContextOptions> for BaseAudioContextOptions {
728    fn convert(self) -> AudioContextOptions {
729        match self {
730            BaseAudioContextOptions::AudioContext(options) => {
731                AudioContextOptions::RealTimeAudioContext(options)
732            },
733            BaseAudioContextOptions::OfflineAudioContext(options) => {
734                AudioContextOptions::OfflineAudioContext(options)
735            },
736        }
737    }
738}
739
740impl Convert<AudioContextState> for ProcessingState {
741    fn convert(self) -> AudioContextState {
742        match self {
743            ProcessingState::Suspended => AudioContextState::Suspended,
744            ProcessingState::Running => AudioContextState::Running,
745            ProcessingState::Closed => AudioContextState::Closed,
746        }
747    }
748}