use std::rc::Rc;
use dom_struct::dom_struct;
use js::realm::CurrentRealm;
use js::rust::HandleObject;
use servo_base::id::PipelineId;
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_and_cx};
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::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct AudioContext {
context: BaseAudioContext,
latency_hint: AudioContextLatencyCategory,
base_latency: f64,
output_latency: f64,
}
impl AudioContext {
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
fn new_inherited(
options: &AudioContextOptions,
pipeline_id: PipelineId,
) -> Fallible<AudioContext> {
let context = BaseAudioContext::new_inherited(
BaseAudioContextOptions::AudioContext(options.convert()),
pipeline_id,
)?;
let latency_hint = match options.latencyHint {
AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => category,
AudioContextLatencyCategoryOrDouble::Double(_) => {
AudioContextLatencyCategory::Interactive
}, };
Ok(AudioContext {
context,
latency_hint,
base_latency: 0., output_latency: 0., })
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
fn new(
cx: &mut js::context::JSContext,
window: &Window,
proto: Option<HandleObject>,
options: &AudioContextOptions,
) -> Fallible<DomRoot<AudioContext>> {
let pipeline_id = window.pipeline_id();
let context = AudioContext::new_inherited(options, pipeline_id)?;
let context = reflect_dom_object_with_proto_and_cx(Box::new(context), window, proto, cx);
context.resume();
Ok(context)
}
fn resume(&self) {
if self.context.is_allowed_to_start() {
self.context.resume();
}
}
pub(crate) fn base(&self) -> DomRoot<BaseAudioContext> {
DomRoot::from_ref(&self.context)
}
}
impl AudioContextMethods<crate::DomTypeHolder> for AudioContext {
fn Constructor(
cx: &mut js::context::JSContext,
window: &Window,
proto: Option<HandleObject>,
options: &AudioContextOptions,
) -> Fallible<DomRoot<AudioContext>> {
AudioContext::new(cx, window, proto, options)
}
fn BaseLatency(&self) -> Finite<f64> {
Finite::wrap(self.base_latency)
}
fn OutputLatency(&self) -> Finite<f64> {
Finite::wrap(self.output_latency)
}
fn GetOutputTimestamp(&self) -> AudioTimestamp {
AudioTimestamp {
contextTime: Some(Finite::wrap(0.)),
performanceTime: Some(Finite::wrap(0.)),
}
}
fn Suspend(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
let promise = Promise::new_in_realm(cx);
if self.context.control_thread_state() == ProcessingState::Closed {
promise.reject_error(Error::InvalidState(None), CanGc::from_cx(cx));
return promise;
}
if self.context.State() == AudioContextState::Suspended {
promise.resolve_native(&(), CanGc::from_cx(cx));
return promise;
}
let trusted_promise = TrustedPromise::new(promise.clone());
match self.context.audio_context_impl().lock().unwrap().suspend() {
Some(_) => {
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 |cx| {
let base_context = base_context.root();
let context = context.root();
let promise = trusted_promise.root();
promise.resolve_native(&(), CanGc::from_cx(cx));
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"),
);
}
})
);
},
None => {
self.global()
.task_manager()
.dom_manipulation_task_source()
.queue(task!(suspend_error: move |cx| {
let promise = trusted_promise.root();
promise.reject_error(Error::Type(c"Something went wrong".to_owned()), CanGc::from_cx(cx));
}));
},
};
promise
}
fn Close(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
let promise = Promise::new_in_realm(cx);
if self.context.control_thread_state() == ProcessingState::Closed {
promise.reject_error(Error::InvalidState(None), CanGc::from_cx(cx));
return promise;
}
if self.context.State() == AudioContextState::Closed {
promise.resolve_native(&(), CanGc::from_cx(cx));
return promise;
}
let trusted_promise = TrustedPromise::new(promise.clone());
match self.context.audio_context_impl().lock().unwrap().close() {
Some(_) => {
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 |cx| {
let base_context = base_context.root();
let context = context.root();
let promise = trusted_promise.root();
promise.resolve_native(&(), CanGc::from_cx(cx));
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"),
);
}
})
);
},
None => {
self.global()
.task_manager()
.dom_manipulation_task_source()
.queue(task!(suspend_error: move |cx| {
let promise = trusted_promise.root();
promise.reject_error(Error::Type(c"Something went wrong".to_owned()), CanGc::from_cx(cx));
}));
},
};
promise
}
fn CreateMediaElementSource(
&self,
cx: &mut js::context::JSContext,
media_element: &HTMLMediaElement,
) -> Fallible<DomRoot<MediaElementAudioSourceNode>> {
let global = self.global();
let window = global.as_window();
MediaElementAudioSourceNode::new(window, self, media_element, cx)
}
fn CreateMediaStreamSource(
&self,
cx: &mut js::context::JSContext,
stream: &MediaStream,
) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> {
let global = self.global();
let window = global.as_window();
MediaStreamAudioSourceNode::new(cx, window, self, stream)
}
fn CreateMediaStreamTrackSource(
&self,
cx: &mut js::context::JSContext,
track: &MediaStreamTrack,
) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> {
let global = self.global();
let window = global.as_window();
MediaStreamTrackAudioSourceNode::new(cx, window, self, track)
}
fn CreateMediaStreamDestination(
&self,
cx: &mut js::context::JSContext,
) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> {
let global = self.global();
let window = global.as_window();
MediaStreamAudioDestinationNode::new(cx, window, self, &AudioNodeOptions::empty())
}
}
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, },
}
}
}