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