1use dom_struct::dom_struct;
6use js::context::JSContext;
7use js::realm::CurrentRealm;
8use js::rust::HandleObject;
9use script_bindings::codegen::GenericBindings::MediaElementAudioSourceNodeBinding::{
10 MediaElementAudioSourceNodeMethods, MediaElementAudioSourceOptions,
11};
12use script_bindings::codegen::GenericBindings::MediaStreamAudioDestinationNodeBinding::MediaStreamAudioDestinationNodeMethods;
13use script_bindings::codegen::GenericBindings::MediaStreamAudioSourceNodeBinding::{
14 MediaStreamAudioSourceNodeMethods, MediaStreamAudioSourceOptions,
15};
16use script_bindings::codegen::GenericBindings::MediaStreamTrackAudioSourceNodeBinding::{
17 MediaStreamTrackAudioSourceNodeMethods, MediaStreamTrackAudioSourceOptions,
18};
19use script_bindings::reflector::reflect_dom_object_with_proto;
20use servo_base::id::PipelineId;
21use servo_media::audio::context::{LatencyCategory, ProcessingState, RealTimeAudioContextOptions};
22
23use crate::conversions::Convert;
24use crate::dom::audio::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions};
25use crate::dom::audio::mediaelementaudiosourcenode::MediaElementAudioSourceNode;
26use crate::dom::audio::mediastreamaudiodestinationnode::MediaStreamAudioDestinationNode;
27use crate::dom::audio::mediastreamaudiosourcenode::MediaStreamAudioSourceNode;
28use crate::dom::audio::mediastreamtrackaudiosourcenode::MediaStreamTrackAudioSourceNode;
29use crate::dom::bindings::codegen::Bindings::AudioContextBinding::{
30 AudioContextLatencyCategory, AudioContextMethods, AudioContextOptions, AudioTimestamp,
31};
32use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions;
33use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState;
34use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContext_Binding::BaseAudioContextMethods;
35use crate::dom::bindings::codegen::UnionTypes::AudioContextLatencyCategoryOrDouble;
36use crate::dom::bindings::error::{Error, Fallible};
37use crate::dom::bindings::inheritance::Castable;
38use crate::dom::bindings::num::Finite;
39use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
40use crate::dom::bindings::reflector::DomGlobal;
41use crate::dom::bindings::root::DomRoot;
42use crate::dom::html::htmlmediaelement::HTMLMediaElement;
43use crate::dom::mediastream::MediaStream;
44use crate::dom::mediastreamtrack::MediaStreamTrack;
45use crate::dom::promise::{Promise, RootedPromise};
46use crate::dom::window::Window;
47
48#[dom_struct]
49pub(crate) struct AudioContext {
50 context: BaseAudioContext,
51 latency_hint: AudioContextLatencyCategory,
52 base_latency: f64,
54 output_latency: f64,
56}
57
58impl AudioContext {
59 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
60 fn new_inherited(
62 options: &AudioContextOptions,
63 pipeline_id: PipelineId,
64 ) -> Fallible<AudioContext> {
65 let context = BaseAudioContext::new_inherited(
67 BaseAudioContextOptions::AudioContext(options.convert()),
68 pipeline_id,
69 )?;
70
71 let latency_hint = match options.latencyHint {
73 AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => category,
74 AudioContextLatencyCategoryOrDouble::Double(_) => {
75 AudioContextLatencyCategory::Interactive
76 }, };
78
79 Ok(AudioContext {
87 context,
88 latency_hint,
89 base_latency: 0., output_latency: 0., })
92 }
93
94 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
95 fn new(
96 cx: &mut js::context::JSContext,
97 window: &Window,
98 proto: Option<HandleObject>,
99 options: &AudioContextOptions,
100 ) -> Fallible<DomRoot<AudioContext>> {
101 let pipeline_id = window.pipeline_id();
102 let context = AudioContext::new_inherited(options, pipeline_id)?;
103 let context = reflect_dom_object_with_proto(cx, Box::new(context), window, proto);
104 context.resume();
105 Ok(context)
106 }
107
108 fn resume(&self) {
109 if self.context.is_allowed_to_start() {
111 self.context.resume();
113 }
114 }
115
116 pub(crate) fn base(&self) -> DomRoot<BaseAudioContext> {
117 DomRoot::from_ref(&self.context)
118 }
119}
120
121impl AudioContextMethods<crate::DomTypeHolder> for AudioContext {
122 fn Constructor(
124 cx: &mut js::context::JSContext,
125 window: &Window,
126 proto: Option<HandleObject>,
127 options: &AudioContextOptions,
128 ) -> Fallible<DomRoot<AudioContext>> {
129 AudioContext::new(cx, window, proto, options)
130 }
131
132 fn BaseLatency(&self) -> Finite<f64> {
134 Finite::wrap(self.base_latency)
135 }
136
137 fn OutputLatency(&self) -> Finite<f64> {
139 Finite::wrap(self.output_latency)
140 }
141
142 fn GetOutputTimestamp(&self) -> AudioTimestamp {
144 AudioTimestamp {
146 contextTime: Some(Finite::wrap(0.)),
147 performanceTime: Some(Finite::wrap(0.)),
148 }
149 }
150
151 fn Suspend(&self, cx: &mut CurrentRealm) -> RootedPromise {
153 let promise = Promise::new_in_realm(cx);
155
156 if self.context.control_thread_state() == ProcessingState::Closed {
158 promise.reject_error(
159 cx,
160 Error::InvalidState(Some("Cannot suspend a closed AudioContext".into())),
161 );
162 return promise;
163 }
164
165 if self.context.State() == AudioContextState::Suspended {
167 promise.resolve_native(cx, &());
168 return promise;
169 }
170
171 let trusted_promise = TrustedPromise::from(&promise);
173 match self.context.audio_context_impl().lock().unwrap().suspend() {
174 Some(_) => {
175 let base_context = Trusted::new(&self.context);
176 let context = Trusted::new(self);
177 self.global().task_manager().dom_manipulation_task_source().queue(
178 task!(suspend_ok: move |cx| {
179 let base_context = base_context.root();
180 let context = context.root();
181 let promise = trusted_promise.root(cx);
182 promise.resolve_native(cx, &());
183 if base_context.State() != AudioContextState::Suspended {
184 base_context.set_state_attribute(AudioContextState::Suspended);
185 context.global().task_manager().dom_manipulation_task_source().queue_simple_event(
186 context.upcast(),
187 atom!("statechange"),
188 );
189 }
190 })
191 );
192 },
193 None => {
194 self.global()
197 .task_manager()
198 .dom_manipulation_task_source()
199 .queue(task!(suspend_error: move |cx| {
200 let promise = trusted_promise.root(cx);
201 promise.reject_error(cx, Error::Type(c"Something went wrong".to_owned()));
202 }));
203 },
204 };
205
206 promise
208 }
209
210 fn Close(&self, cx: &mut CurrentRealm) -> RootedPromise {
212 let promise = Promise::new_in_realm(cx);
214
215 if self.context.control_thread_state() == ProcessingState::Closed {
217 promise.reject_error(
218 cx,
219 Error::InvalidState(Some("AudioContext is already closed".into())),
220 );
221 return promise;
222 }
223
224 if self.context.State() == AudioContextState::Closed {
226 promise.resolve_native(cx, &());
227 return promise;
228 }
229
230 let trusted_promise = TrustedPromise::from(&promise);
232 match self.context.audio_context_impl().lock().unwrap().close() {
233 Some(_) => {
234 let base_context = Trusted::new(&self.context);
235 let context = Trusted::new(self);
236 self.global().task_manager().dom_manipulation_task_source().queue(
237 task!(suspend_ok: move |cx| {
238 let base_context = base_context.root();
239 let context = context.root();
240 let promise = trusted_promise.root(cx);
241 promise.resolve_native(cx, &());
242 if base_context.State() != AudioContextState::Closed {
243 base_context.set_state_attribute(AudioContextState::Closed);
244 context.global().task_manager().dom_manipulation_task_source().queue_simple_event(
245 context.upcast(),
246 atom!("statechange"),
247 );
248 }
249 })
250 );
251 },
252 None => {
253 self.global()
256 .task_manager()
257 .dom_manipulation_task_source()
258 .queue(task!(suspend_error: move |cx| {
259 let promise = trusted_promise.root(cx);
260 promise.reject_error(cx, Error::Type(c"Something went wrong".to_owned()));
261 }));
262 },
263 };
264
265 promise
267 }
268
269 fn CreateMediaElementSource(
271 &self,
272 cx: &mut JSContext,
273 media_element: &HTMLMediaElement,
274 ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> {
275 let global = self.global();
276 let window = global.as_window();
277 let options = MediaElementAudioSourceOptions {
278 mediaElement: DomRoot::from_ref(media_element),
279 };
280 MediaElementAudioSourceNode::Constructor(cx, window, None, self, &options)
281 }
282
283 fn CreateMediaStreamSource(
285 &self,
286 cx: &mut JSContext,
287 stream: &MediaStream,
288 ) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> {
289 let global = self.global();
290 let window = global.as_window();
291 let options = MediaStreamAudioSourceOptions {
292 mediaStream: DomRoot::from_ref(stream),
293 };
294 MediaStreamAudioSourceNode::Constructor(cx, window, None, self, &options)
295 }
296
297 fn CreateMediaStreamTrackSource(
299 &self,
300 cx: &mut JSContext,
301 track: &MediaStreamTrack,
302 ) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> {
303 let global = self.global();
304 let window = global.as_window();
305 let options = MediaStreamTrackAudioSourceOptions {
306 mediaStreamTrack: DomRoot::from_ref(track),
307 };
308 MediaStreamTrackAudioSourceNode::Constructor(cx, window, None, self, &options)
309 }
310
311 fn CreateMediaStreamDestination(
313 &self,
314 cx: &mut JSContext,
315 ) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> {
316 let global = self.global();
317 let window = global.as_window();
318 MediaStreamAudioDestinationNode::Constructor(
319 cx,
320 window,
321 None,
322 self,
323 &AudioNodeOptions::empty(),
324 )
325 }
326}
327
328impl Convert<LatencyCategory> for AudioContextLatencyCategory {
329 fn convert(self) -> LatencyCategory {
330 match self {
331 AudioContextLatencyCategory::Balanced => LatencyCategory::Balanced,
332 AudioContextLatencyCategory::Interactive => LatencyCategory::Interactive,
333 AudioContextLatencyCategory::Playback => LatencyCategory::Playback,
334 }
335 }
336}
337
338impl Convert<RealTimeAudioContextOptions> for &AudioContextOptions {
339 fn convert(self) -> RealTimeAudioContextOptions {
340 RealTimeAudioContextOptions {
341 sample_rate: *self.sampleRate.unwrap_or(Finite::wrap(44100.)),
342 latency_hint: match self.latencyHint {
343 AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => {
344 category.convert()
345 },
346 AudioContextLatencyCategoryOrDouble::Double(_) => LatencyCategory::Interactive, },
348 }
349 }
350}