| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| |
| use std::rc::Rc; |
| |
| use base::id::PipelineId; |
| use dom_struct::dom_struct; |
| use js::rust::HandleObject; |
| use servo_media::audio::context::{LatencyCategory, ProcessingState, RealTimeAudioContextOptions}; |
| |
| use crate::conversions::Convert; |
| use crate::dom::audio::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions}; |
| use crate::dom::audio::mediaelementaudiosourcenode::MediaElementAudioSourceNode; |
| use crate::dom::audio::mediastreamaudiodestinationnode::MediaStreamAudioDestinationNode; |
| use crate::dom::audio::mediastreamaudiosourcenode::MediaStreamAudioSourceNode; |
| use crate::dom::audio::mediastreamtrackaudiosourcenode::MediaStreamTrackAudioSourceNode; |
| use crate::dom::bindings::codegen::Bindings::AudioContextBinding::{ |
| AudioContextLatencyCategory, AudioContextMethods, AudioContextOptions, AudioTimestamp, |
| }; |
| use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions; |
| use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState; |
| use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContext_Binding::BaseAudioContextMethods; |
| use crate::dom::bindings::codegen::UnionTypes::AudioContextLatencyCategoryOrDouble; |
| use crate::dom::bindings::error::{Error, Fallible}; |
| use crate::dom::bindings::inheritance::Castable; |
| use crate::dom::bindings::num::Finite; |
| use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; |
| use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto}; |
| use crate::dom::bindings::root::DomRoot; |
| use crate::dom::html::htmlmediaelement::HTMLMediaElement; |
| use crate::dom::mediastream::MediaStream; |
| use crate::dom::mediastreamtrack::MediaStreamTrack; |
| use crate::dom::promise::Promise; |
| use crate::dom::window::Window; |
| use crate::realms::InRealm; |
| use crate::script_runtime::CanGc; |
| |
| #[dom_struct] |
| pub(crate) struct AudioContext { |
| context: BaseAudioContext, |
| latency_hint: AudioContextLatencyCategory, |
| /// <https://webaudio.github.io/web-audio-api/#dom-audiocontext-baselatency> |
| base_latency: f64, |
| /// <https://webaudio.github.io/web-audio-api/#dom-audiocontext-outputlatency> |
| output_latency: f64, |
| } |
| |
| impl AudioContext { |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| // https://webaudio.github.io/web-audio-api/#AudioContext-constructors |
| fn new_inherited( |
| options: &AudioContextOptions, |
| pipeline_id: PipelineId, |
| ) -> Fallible<AudioContext> { |
| // Steps 1-3. |
| let context = BaseAudioContext::new_inherited( |
| BaseAudioContextOptions::AudioContext(options.convert()), |
| pipeline_id, |
| )?; |
| |
| // Step 4.1. |
| let latency_hint = match options.latencyHint { |
| AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => category, |
| AudioContextLatencyCategoryOrDouble::Double(_) => { |
| AudioContextLatencyCategory::Interactive |
| }, // TODO |
| }; |
| |
| // Step 4.2. The sample rate is set during the creation of the BaseAudioContext. |
| // servo-media takes care of setting the default sample rate of the output device |
| // and of resampling the audio output if needed. |
| |
| // Steps 5 and 6 of the construction algorithm will happen in `resume`, |
| // after reflecting dom object. |
| |
| Ok(AudioContext { |
| context, |
| latency_hint, |
| base_latency: 0., // TODO |
| output_latency: 0., // TODO |
| }) |
| } |
| |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| fn new( |
| window: &Window, |
| proto: Option<HandleObject>, |
| options: &AudioContextOptions, |
| can_gc: CanGc, |
| ) -> Fallible<DomRoot<AudioContext>> { |
| let pipeline_id = window.pipeline_id(); |
| let context = AudioContext::new_inherited(options, pipeline_id)?; |
| let context = reflect_dom_object_with_proto(Box::new(context), window, proto, can_gc); |
| context.resume(); |
| Ok(context) |
| } |
| |
| fn resume(&self) { |
| // Step 5. |
| if self.context.is_allowed_to_start() { |
| // Step 6. |
| self.context.resume(); |
| } |
| } |
| |
| pub(crate) fn base(&self) -> DomRoot<BaseAudioContext> { |
| DomRoot::from_ref(&self.context) |
| } |
| } |
| |
| impl AudioContextMethods<crate::DomTypeHolder> for AudioContext { |
| // https://webaudio.github.io/web-audio-api/#AudioContext-constructors |
| fn Constructor( |
| window: &Window, |
| proto: Option<HandleObject>, |
| can_gc: CanGc, |
| options: &AudioContextOptions, |
| ) -> Fallible<DomRoot<AudioContext>> { |
| AudioContext::new(window, proto, options, can_gc) |
| } |
| |
| // https://webaudio.github.io/web-audio-api/#dom-audiocontext-baselatency |
| fn BaseLatency(&self) -> Finite<f64> { |
| Finite::wrap(self.base_latency) |
| } |
| |
| // https://webaudio.github.io/web-audio-api/#dom-audiocontext-outputlatency |
| fn OutputLatency(&self) -> Finite<f64> { |
| Finite::wrap(self.output_latency) |
| } |
| |
| // https://webaudio.github.io/web-audio-api/#dom-audiocontext-outputlatency |
| fn GetOutputTimestamp(&self) -> AudioTimestamp { |
| // TODO |
| AudioTimestamp { |
| contextTime: Some(Finite::wrap(0.)), |
| performanceTime: Some(Finite::wrap(0.)), |
| } |
| } |
| |
| // https://webaudio.github.io/web-audio-api/#dom-audiocontext-suspend |
| fn Suspend(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { |
| // Step 1. |
| let promise = Promise::new_in_current_realm(comp, can_gc); |
| |
| // Step 2. |
| if self.context.control_thread_state() == ProcessingState::Closed { |
| promise.reject_error(Error::InvalidState, can_gc); |
| return promise; |
| } |
| |
| // Step 3. |
| if self.context.State() == AudioContextState::Suspended { |
| promise.resolve_native(&(), can_gc); |
| return promise; |
| } |
| |
| // Steps 4 and 5. |
| let trusted_promise = TrustedPromise::new(promise.clone()); |
| match self.context.audio_context_impl().lock().unwrap().suspend() { |
| Ok(_) => { |
| let base_context = Trusted::new(&self.context); |
| let context = Trusted::new(self); |
| self.global().task_manager().dom_manipulation_task_source().queue( |
| task!(suspend_ok: move || { |
| let base_context = base_context.root(); |
| let context = context.root(); |
| let promise = trusted_promise.root(); |
| promise.resolve_native(&(), CanGc::note()); |
| if base_context.State() != AudioContextState::Suspended { |
| base_context.set_state_attribute(AudioContextState::Suspended); |
| context.global().task_manager().dom_manipulation_task_source().queue_simple_event( |
| context.upcast(), |
| atom!("statechange"), |
| ); |
| } |
| }) |
| ); |
| }, |
| Err(_) => { |
| // The spec does not define the error case and `suspend` should |
| // never fail, but we handle the case here for completion. |
| self.global() |
| .task_manager() |
| .dom_manipulation_task_source() |
| .queue(task!(suspend_error: move || { |
| let promise = trusted_promise.root(); |
| promise.reject_error(Error::Type("Something went wrong".to_owned()), CanGc::note()); |
| })); |
| }, |
| }; |
| |
| // Step 6. |
| promise |
| } |
| |
| // https://webaudio.github.io/web-audio-api/#dom-audiocontext-close |
| fn Close(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { |
| // Step 1. |
| let promise = Promise::new_in_current_realm(comp, can_gc); |
| |
| // Step 2. |
| if self.context.control_thread_state() == ProcessingState::Closed { |
| promise.reject_error(Error::InvalidState, can_gc); |
| return promise; |
| } |
| |
| // Step 3. |
| if self.context.State() == AudioContextState::Closed { |
| promise.resolve_native(&(), can_gc); |
| return promise; |
| } |
| |
| // Steps 4 and 5. |
| let trusted_promise = TrustedPromise::new(promise.clone()); |
| match self.context.audio_context_impl().lock().unwrap().close() { |
| Ok(_) => { |
| let base_context = Trusted::new(&self.context); |
| let context = Trusted::new(self); |
| self.global().task_manager().dom_manipulation_task_source().queue( |
| task!(suspend_ok: move || { |
| let base_context = base_context.root(); |
| let context = context.root(); |
| let promise = trusted_promise.root(); |
| promise.resolve_native(&(), CanGc::note()); |
| if base_context.State() != AudioContextState::Closed { |
| base_context.set_state_attribute(AudioContextState::Closed); |
| context.global().task_manager().dom_manipulation_task_source().queue_simple_event( |
| context.upcast(), |
| atom!("statechange"), |
| ); |
| } |
| }) |
| ); |
| }, |
| Err(_) => { |
| // The spec does not define the error case and `suspend` should |
| // never fail, but we handle the case here for completion. |
| self.global() |
| .task_manager() |
| .dom_manipulation_task_source() |
| .queue(task!(suspend_error: move || { |
| let promise = trusted_promise.root(); |
| promise.reject_error(Error::Type("Something went wrong".to_owned()), CanGc::note()); |
| })); |
| }, |
| }; |
| |
| // Step 6. |
| promise |
| } |
| |
| /// <https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediaelementsource> |
| fn CreateMediaElementSource( |
| &self, |
| media_element: &HTMLMediaElement, |
| can_gc: CanGc, |
| ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { |
| let global = self.global(); |
| let window = global.as_window(); |
| MediaElementAudioSourceNode::new(window, self, media_element, can_gc) |
| } |
| |
| /// <https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediastreamsource> |
| fn CreateMediaStreamSource( |
| &self, |
| stream: &MediaStream, |
| can_gc: CanGc, |
| ) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> { |
| let global = self.global(); |
| let window = global.as_window(); |
| MediaStreamAudioSourceNode::new(window, self, stream, can_gc) |
| } |
| |
| /// <https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediastreamtracksource> |
| fn CreateMediaStreamTrackSource( |
| &self, |
| track: &MediaStreamTrack, |
| can_gc: CanGc, |
| ) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> { |
| let global = self.global(); |
| let window = global.as_window(); |
| MediaStreamTrackAudioSourceNode::new(window, self, track, can_gc) |
| } |
| |
| /// <https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediastreamdestination> |
| fn CreateMediaStreamDestination( |
| &self, |
| can_gc: CanGc, |
| ) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> { |
| let global = self.global(); |
| let window = global.as_window(); |
| MediaStreamAudioDestinationNode::new(window, self, &AudioNodeOptions::empty(), can_gc) |
| } |
| } |
| |
| impl Convert<LatencyCategory> for AudioContextLatencyCategory { |
| fn convert(self) -> LatencyCategory { |
| match self { |
| AudioContextLatencyCategory::Balanced => LatencyCategory::Balanced, |
| AudioContextLatencyCategory::Interactive => LatencyCategory::Interactive, |
| AudioContextLatencyCategory::Playback => LatencyCategory::Playback, |
| } |
| } |
| } |
| |
| impl Convert<RealTimeAudioContextOptions> for &AudioContextOptions { |
| fn convert(self) -> RealTimeAudioContextOptions { |
| RealTimeAudioContextOptions { |
| sample_rate: *self.sampleRate.unwrap_or(Finite::wrap(44100.)), |
| latency_hint: match self.latencyHint { |
| AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => { |
| category.convert() |
| }, |
| AudioContextLatencyCategoryOrDouble::Double(_) => LatencyCategory::Interactive, // TODO |
| }, |
| } |
| } |
| } |