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