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::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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination>
103    destination: MutNullableDom<AudioDestinationNode>,
104    listener: MutNullableDom<AudioListener>,
105    /// Resume promises which are soon to be fulfilled by a queued task.
106    in_flight_resume_promises_queue: DomRefCell<VecDeque<(BoxedSliceOfPromises, ErrorResult)>>,
107    /// <https://webaudio.github.io/web-audio-api/#pendingresumepromises>
108    pending_resume_promises: DomRefCell<Vec<TracedPromise>>,
109    decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>,
110    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate>
111    sample_rate: f32,
112    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state>
113    /// Although servo-media already keeps track of the control thread state,
114    /// we keep a state flag here as well. This is so that we can synchronously
115    /// throw when trying to do things on the context when the context has just
116    /// been "closed()".
117    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    /// Tells whether this is an OfflineAudioContext or not.
154    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    // https://webaudio.github.io/web-audio-api/#allowed-to-start
171    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    /// Takes the pending resume promises.
182    ///
183    /// The result with which these promises will be fulfilled is passed here
184    /// and this method returns nothing because we actually just move the
185    /// current list of pending resume promises to the
186    /// `in_flight_resume_promises_queue` field.
187    ///
188    /// Each call to this method must be followed by a call to
189    /// `fulfill_in_flight_resume_promises`, to actually fulfill the promises
190    /// which were taken and moved to the in-flight queue.
191    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    /// Fulfills the next in-flight resume promises queue after running a closure.
201    ///
202    /// See the comment on `take_pending_resume_promises` for why this method
203    /// does not take a list of promises to fulfill. Callers cannot just pop
204    /// the front list off of `in_flight_resume_promises_queue` and later fulfill
205    /// the promises because that would mean putting
206    /// `#[cfg_attr(crown, expect(crown::unrooted_must_root))]` on even more functions, potentially
207    /// hiding actual safety bugs.
208    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    /// Control thread processing state
232    pub(crate) fn control_thread_state(&self) -> ProcessingState {
233        self.audio_context_impl.lock().unwrap().state()
234    }
235
236    /// Set audio context state
237    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        // Set the rendering thread state to 'running' and start
244        // rendering the audio graph.
245        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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate>
284    fn SampleRate(&self) -> Finite<f32> {
285        Finite::wrap(self.sample_rate)
286    }
287
288    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-currenttime>
289    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state>
295    fn State(&self) -> AudioContextState {
296        self.state.get()
297    }
298
299    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-resume>
300    fn Resume(&self, cx: &mut CurrentRealm) -> RootedPromise {
301        // Step 1.
302        let promise = Promise::new_in_realm_rooted(cx);
303
304        // Step 2.
305        if self.audio_context_impl.lock().unwrap().state() == ProcessingState::Closed {
306            promise.reject_error(cx, Error::InvalidState(None));
307            return promise;
308        }
309
310        // Step 3.
311        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        // Step 4.
319        if !self.is_allowed_to_start() {
320            return promise;
321        }
322
323        // Steps 5 and 6.
324        self.resume();
325
326        // Step 7.
327        promise
328    }
329
330    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination>
331    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-listener>
343    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    // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-onstatechange
351    event_handler!(statechange, GetOnstatechange, SetOnstatechange);
352
353    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createoscillator>
354    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-creategain>
364    fn CreateGain(&self, cx: &mut JSContext) -> Fallible<DomRoot<GainNode>> {
365        GainNode::new(cx, self.global().as_window(), self, &GainOptions::empty())
366    }
367
368    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createpanner>
369    fn CreatePanner(&self, cx: &mut JSContext) -> Fallible<DomRoot<PannerNode>> {
370        PannerNode::new(cx, self.global().as_window(), self, &PannerOptions::empty())
371    }
372
373    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createperiodicwave>
374    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        // options is a new object of type PeriodicWaveOptions.
382        let mut options = PeriodicWaveOptions::empty();
383        let mut constraints_copy = PeriodicWaveConstraints::empty();
384        // Set the disableNormalization attribute on options to the value of the
385        // disableNormalization attribute of the constraints attribute passed to the factory method.
386        constraints_copy.disableNormalization = constraints.disableNormalization;
387        // Respectively set the real and imag parameters passed to this factory method to the attributes
388        // of the same name on options.
389        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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createanalyser>
396    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbiquadfilter>
406    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createstereopanner>
416    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createconstantsource>
426    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelmerger>
436    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelsplitter>
447    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer>
458    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffersource>
483    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    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata>
493    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        // Step 1. If this's relevant global object's associated Document is NOT fully active,
501        // return a promise rejected with "InvalidStateError".
502        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        // Step 2. Let promise be a new promise.
512        let promise = Promise::new_in_realm_rooted(cx);
513
514        // flatten the optionally nullable callbacks
515        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        // Step 3. If audio_data is NOT detached, execute the following steps:
521        // - Append promise to [[pending promises]].
522        // - Detach the audio_data ArrayBuffer. If this operation throws, jump to step 4.1.
523        // - Queue a decoding operation to be performed on another thread.
524        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            // servo-media returns an audio channel position along
549            // with the AudioDecoderCallback progress callback, which
550            // may not be the same as the index of the decoded_audio
551            // Vec.
552            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 /* number of channels */,
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            // Step 4.
640            // Else, execute the following error steps:
641            // - Let error be a DataCloneError.
642            // - Reject promise with error, and remove it from [[pending promises]].
643            // - Queue a media element task to invoke errorCallback with error.
644            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                // Build the Task object using a unique uuid as a key to remove the callback resolver entry.
649                // Stash the callback with clone of uuid in the decode_resolvers map.
650                // Enqueue the task after the callback is stashed.
651                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        // Step 5. Return promise.
689        promise
690    }
691
692    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createiirfilter>
693    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}