1use 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 destination: MutNullableDom<AudioDestinationNode>,
105 listener: MutNullableDom<AudioListener>,
106 in_flight_resume_promises_queue: DomRefCell<VecDeque<(BoxedSliceOfPromises, ErrorResult)>>,
108 pending_resume_promises: DomRefCell<Vec<TracedPromise>>,
110 decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>,
111 sample_rate: f32,
113 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 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 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 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 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 pub(crate) fn control_thread_state(&self) -> ProcessingState {
234 self.audio_context_impl.lock().unwrap().state()
235 }
236
237 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 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 fn SampleRate(&self) -> Finite<f32> {
286 Finite::wrap(self.sample_rate)
287 }
288
289 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 fn State(&self) -> AudioContextState {
297 self.state.get()
298 }
299
300 fn Resume(&self, cx: &mut CurrentRealm) -> RootedPromise {
302 let promise = Promise::new_in_realm(cx);
304
305 if self.audio_context_impl.lock().unwrap().state() == ProcessingState::Closed {
307 promise.reject_error(cx, Error::InvalidState(None));
308 return promise;
309 }
310
311 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 if !self.is_allowed_to_start() {
321 return promise;
322 }
323
324 self.resume();
326
327 promise
329 }
330
331 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 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 event_handler!(statechange, GetOnstatechange, SetOnstatechange);
353
354 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 fn CreateGain(&self, cx: &mut JSContext) -> Fallible<DomRoot<GainNode>> {
366 GainNode::new(cx, self.global().as_window(), self, &GainOptions::empty())
367 }
368
369 fn CreateDelay(
371 &self,
372 cx: &mut JSContext,
373 max_delay_time: Finite<f64>,
374 ) -> Fallible<DomRoot<DelayNode>> {
375 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 fn CreatePanner(&self, cx: &mut JSContext) -> Fallible<DomRoot<PannerNode>> {
390 PannerNode::new(cx, self.global().as_window(), self, &PannerOptions::empty())
391 }
392
393 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 let mut options = PeriodicWaveOptions::empty();
403 let mut constraints_copy = PeriodicWaveConstraints::empty();
404 constraints_copy.disableNormalization = constraints.disableNormalization;
407 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 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 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 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 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 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 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 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 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 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 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 let promise = Promise::new_in_realm(cx);
533
534 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 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 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 ,
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 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 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 promise
709 }
710
711 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}