1use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use js::realm::CurrentRealm;
9use js::rust::HandleObject;
10use script_bindings::reflector::reflect_dom_object_with_proto_and_cx;
11use servo_base::id::PipelineId;
12use servo_media::audio::context::{LatencyCategory, ProcessingState, RealTimeAudioContextOptions};
13
14use crate::conversions::Convert;
15use crate::dom::audio::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions};
16use crate::dom::audio::mediaelementaudiosourcenode::MediaElementAudioSourceNode;
17use crate::dom::audio::mediastreamaudiodestinationnode::MediaStreamAudioDestinationNode;
18use crate::dom::audio::mediastreamaudiosourcenode::MediaStreamAudioSourceNode;
19use crate::dom::audio::mediastreamtrackaudiosourcenode::MediaStreamTrackAudioSourceNode;
20use crate::dom::bindings::codegen::Bindings::AudioContextBinding::{
21 AudioContextLatencyCategory, AudioContextMethods, AudioContextOptions, AudioTimestamp,
22};
23use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions;
24use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState;
25use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContext_Binding::BaseAudioContextMethods;
26use crate::dom::bindings::codegen::UnionTypes::AudioContextLatencyCategoryOrDouble;
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::num::Finite;
30use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
31use crate::dom::bindings::reflector::DomGlobal;
32use crate::dom::bindings::root::DomRoot;
33use crate::dom::html::htmlmediaelement::HTMLMediaElement;
34use crate::dom::mediastream::MediaStream;
35use crate::dom::mediastreamtrack::MediaStreamTrack;
36use crate::dom::promise::Promise;
37use crate::dom::window::Window;
38use crate::script_runtime::CanGc;
39
40#[dom_struct]
41pub(crate) struct AudioContext {
42 context: BaseAudioContext,
43 latency_hint: AudioContextLatencyCategory,
44 base_latency: f64,
46 output_latency: f64,
48}
49
50impl AudioContext {
51 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
52 fn new_inherited(
54 options: &AudioContextOptions,
55 pipeline_id: PipelineId,
56 ) -> Fallible<AudioContext> {
57 let context = BaseAudioContext::new_inherited(
59 BaseAudioContextOptions::AudioContext(options.convert()),
60 pipeline_id,
61 )?;
62
63 let latency_hint = match options.latencyHint {
65 AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => category,
66 AudioContextLatencyCategoryOrDouble::Double(_) => {
67 AudioContextLatencyCategory::Interactive
68 }, };
70
71 Ok(AudioContext {
79 context,
80 latency_hint,
81 base_latency: 0., output_latency: 0., })
84 }
85
86 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
87 fn new(
88 cx: &mut js::context::JSContext,
89 window: &Window,
90 proto: Option<HandleObject>,
91 options: &AudioContextOptions,
92 ) -> Fallible<DomRoot<AudioContext>> {
93 let pipeline_id = window.pipeline_id();
94 let context = AudioContext::new_inherited(options, pipeline_id)?;
95 let context = reflect_dom_object_with_proto_and_cx(Box::new(context), window, proto, cx);
96 context.resume();
97 Ok(context)
98 }
99
100 fn resume(&self) {
101 if self.context.is_allowed_to_start() {
103 self.context.resume();
105 }
106 }
107
108 pub(crate) fn base(&self) -> DomRoot<BaseAudioContext> {
109 DomRoot::from_ref(&self.context)
110 }
111}
112
113impl AudioContextMethods<crate::DomTypeHolder> for AudioContext {
114 fn Constructor(
116 cx: &mut js::context::JSContext,
117 window: &Window,
118 proto: Option<HandleObject>,
119 options: &AudioContextOptions,
120 ) -> Fallible<DomRoot<AudioContext>> {
121 AudioContext::new(cx, window, proto, options)
122 }
123
124 fn BaseLatency(&self) -> Finite<f64> {
126 Finite::wrap(self.base_latency)
127 }
128
129 fn OutputLatency(&self) -> Finite<f64> {
131 Finite::wrap(self.output_latency)
132 }
133
134 fn GetOutputTimestamp(&self) -> AudioTimestamp {
136 AudioTimestamp {
138 contextTime: Some(Finite::wrap(0.)),
139 performanceTime: Some(Finite::wrap(0.)),
140 }
141 }
142
143 fn Suspend(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
145 let promise = Promise::new_in_realm(cx);
147
148 if self.context.control_thread_state() == ProcessingState::Closed {
150 promise.reject_error(Error::InvalidState(None), CanGc::from_cx(cx));
151 return promise;
152 }
153
154 if self.context.State() == AudioContextState::Suspended {
156 promise.resolve_native(&(), CanGc::from_cx(cx));
157 return promise;
158 }
159
160 let trusted_promise = TrustedPromise::new(promise.clone());
162 match self.context.audio_context_impl().lock().unwrap().suspend() {
163 Some(_) => {
164 let base_context = Trusted::new(&self.context);
165 let context = Trusted::new(self);
166 self.global().task_manager().dom_manipulation_task_source().queue(
167 task!(suspend_ok: move |cx| {
168 let base_context = base_context.root();
169 let context = context.root();
170 let promise = trusted_promise.root();
171 promise.resolve_native(&(), CanGc::from_cx(cx));
172 if base_context.State() != AudioContextState::Suspended {
173 base_context.set_state_attribute(AudioContextState::Suspended);
174 context.global().task_manager().dom_manipulation_task_source().queue_simple_event(
175 context.upcast(),
176 atom!("statechange"),
177 );
178 }
179 })
180 );
181 },
182 None => {
183 self.global()
186 .task_manager()
187 .dom_manipulation_task_source()
188 .queue(task!(suspend_error: move |cx| {
189 let promise = trusted_promise.root();
190 promise.reject_error(Error::Type(c"Something went wrong".to_owned()), CanGc::from_cx(cx));
191 }));
192 },
193 };
194
195 promise
197 }
198
199 fn Close(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
201 let promise = Promise::new_in_realm(cx);
203
204 if self.context.control_thread_state() == ProcessingState::Closed {
206 promise.reject_error(Error::InvalidState(None), CanGc::from_cx(cx));
207 return promise;
208 }
209
210 if self.context.State() == AudioContextState::Closed {
212 promise.resolve_native(&(), CanGc::from_cx(cx));
213 return promise;
214 }
215
216 let trusted_promise = TrustedPromise::new(promise.clone());
218 match self.context.audio_context_impl().lock().unwrap().close() {
219 Some(_) => {
220 let base_context = Trusted::new(&self.context);
221 let context = Trusted::new(self);
222 self.global().task_manager().dom_manipulation_task_source().queue(
223 task!(suspend_ok: move |cx| {
224 let base_context = base_context.root();
225 let context = context.root();
226 let promise = trusted_promise.root();
227 promise.resolve_native(&(), CanGc::from_cx(cx));
228 if base_context.State() != AudioContextState::Closed {
229 base_context.set_state_attribute(AudioContextState::Closed);
230 context.global().task_manager().dom_manipulation_task_source().queue_simple_event(
231 context.upcast(),
232 atom!("statechange"),
233 );
234 }
235 })
236 );
237 },
238 None => {
239 self.global()
242 .task_manager()
243 .dom_manipulation_task_source()
244 .queue(task!(suspend_error: move |cx| {
245 let promise = trusted_promise.root();
246 promise.reject_error(Error::Type(c"Something went wrong".to_owned()), CanGc::from_cx(cx));
247 }));
248 },
249 };
250
251 promise
253 }
254
255 fn CreateMediaElementSource(
257 &self,
258 cx: &mut js::context::JSContext,
259 media_element: &HTMLMediaElement,
260 ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> {
261 let global = self.global();
262 let window = global.as_window();
263 MediaElementAudioSourceNode::new(window, self, media_element, cx)
264 }
265
266 fn CreateMediaStreamSource(
268 &self,
269 cx: &mut js::context::JSContext,
270 stream: &MediaStream,
271 ) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> {
272 let global = self.global();
273 let window = global.as_window();
274 MediaStreamAudioSourceNode::new(cx, window, self, stream)
275 }
276
277 fn CreateMediaStreamTrackSource(
279 &self,
280 cx: &mut js::context::JSContext,
281 track: &MediaStreamTrack,
282 ) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> {
283 let global = self.global();
284 let window = global.as_window();
285 MediaStreamTrackAudioSourceNode::new(cx, window, self, track)
286 }
287
288 fn CreateMediaStreamDestination(
290 &self,
291 cx: &mut js::context::JSContext,
292 ) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> {
293 let global = self.global();
294 let window = global.as_window();
295 MediaStreamAudioDestinationNode::new(cx, window, self, &AudioNodeOptions::empty())
296 }
297}
298
299impl Convert<LatencyCategory> for AudioContextLatencyCategory {
300 fn convert(self) -> LatencyCategory {
301 match self {
302 AudioContextLatencyCategory::Balanced => LatencyCategory::Balanced,
303 AudioContextLatencyCategory::Interactive => LatencyCategory::Interactive,
304 AudioContextLatencyCategory::Playback => LatencyCategory::Playback,
305 }
306 }
307}
308
309impl Convert<RealTimeAudioContextOptions> for &AudioContextOptions {
310 fn convert(self) -> RealTimeAudioContextOptions {
311 RealTimeAudioContextOptions {
312 sample_rate: *self.sampleRate.unwrap_or(Finite::wrap(44100.)),
313 latency_hint: match self.latencyHint {
314 AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => {
315 category.convert()
316 },
317 AudioContextLatencyCategoryOrDouble::Double(_) => LatencyCategory::Interactive, },
319 }
320 }
321}