1use std::cell::Cell;
6use std::sync::{Mutex, mpsc};
7use std::thread::Builder;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::realm::CurrentRealm;
12use js::rust::HandleObject;
13use script_bindings::cell::DomRefCell;
14use script_bindings::reflector::reflect_dom_object_with_proto;
15use servo_base::id::PipelineId;
16use servo_media::audio::context::OfflineAudioContextOptions as ServoMediaOfflineAudioContextOptions;
17
18use crate::dom::audio::audiobuffer::{AudioBuffer, MAX_SAMPLE_RATE, MIN_SAMPLE_RATE};
19use crate::dom::audio::audionode::MAX_CHANNEL_COUNT;
20use crate::dom::audio::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions};
21use crate::dom::audio::offlineaudiocompletionevent::OfflineAudioCompletionEvent;
22use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContext_Binding::BaseAudioContextMethods;
23use crate::dom::bindings::codegen::Bindings::OfflineAudioContextBinding::{
24 OfflineAudioContextMethods, OfflineAudioContextOptions,
25};
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;
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::DomRoot;
32use crate::dom::event::{Event, EventBubbles, EventCancelable};
33use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
34use crate::dom::window::Window;
35
36#[dom_struct]
37pub(crate) struct OfflineAudioContext {
38 context: BaseAudioContext,
39 channel_count: u32,
40 length: u32,
41 rendering_started: Cell<bool>,
42 pending_rendering_promise: DomRefCell<Option<TracedPromise>>,
43}
44
45impl OfflineAudioContext {
46 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
47 fn new_inherited(
48 channel_count: u32,
49 length: u32,
50 sample_rate: f32,
51 pipeline_id: PipelineId,
52 ) -> Fallible<OfflineAudioContext> {
53 let options = ServoMediaOfflineAudioContextOptions {
54 channels: channel_count as u8,
55 length: length as usize,
56 sample_rate,
57 };
58 let context = BaseAudioContext::new_inherited(
59 BaseAudioContextOptions::OfflineAudioContext(options),
60 pipeline_id,
61 )?;
62 Ok(OfflineAudioContext {
63 context,
64 channel_count,
65 length,
66 rendering_started: Cell::new(false),
67 pending_rendering_promise: Default::default(),
68 })
69 }
70
71 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
72 fn new(
73 cx: &mut JSContext,
74 window: &Window,
75 proto: Option<HandleObject>,
76 channel_count: u32,
77 length: u32,
78 sample_rate: f32,
79 ) -> Fallible<DomRoot<OfflineAudioContext>> {
80 if channel_count > MAX_CHANNEL_COUNT ||
81 channel_count == 0 ||
82 length == 0 ||
83 !(MIN_SAMPLE_RATE..=MAX_SAMPLE_RATE).contains(&sample_rate)
84 {
85 return Err(Error::NotSupported(Some(
86 "Invalid number of channels, length or sample rate".into(),
87 )));
88 }
89 let pipeline_id = window.pipeline_id();
90 let context =
91 OfflineAudioContext::new_inherited(channel_count, length, sample_rate, pipeline_id)?;
92 Ok(reflect_dom_object_with_proto(
93 cx,
94 Box::new(context),
95 window,
96 proto,
97 ))
98 }
99}
100
101impl OfflineAudioContextMethods<crate::DomTypeHolder> for OfflineAudioContext {
102 fn Constructor(
104 cx: &mut JSContext,
105 window: &Window,
106 proto: Option<HandleObject>,
107 options: &OfflineAudioContextOptions,
108 ) -> Fallible<DomRoot<OfflineAudioContext>> {
109 OfflineAudioContext::new(
110 cx,
111 window,
112 proto,
113 options.numberOfChannels,
114 options.length,
115 *options.sampleRate,
116 )
117 }
118
119 fn Constructor_(
121 cx: &mut JSContext,
122 window: &Window,
123 proto: Option<HandleObject>,
124 number_of_channels: u32,
125 length: u32,
126 sample_rate: Finite<f32>,
127 ) -> Fallible<DomRoot<OfflineAudioContext>> {
128 OfflineAudioContext::new(cx, window, proto, number_of_channels, length, *sample_rate)
129 }
130
131 event_handler!(complete, GetOncomplete, SetOncomplete);
133
134 fn Length(&self) -> u32 {
136 self.length
137 }
138
139 fn StartRendering(&self, cx: &mut CurrentRealm) -> RootedPromise {
141 let promise = Promise::new_in_realm(cx);
142 if self.rendering_started.get() {
143 promise.reject_error(
144 cx,
145 Error::InvalidState(Some("Rendering has already started".into())),
146 );
147 return promise;
148 }
149 self.rendering_started.set(true);
150
151 *self.pending_rendering_promise.safe_borrow_mut(cx.no_gc()) = Some(promise.to_traced());
152
153 let length = self.length as usize;
154 let channel_count = self.channel_count as usize;
155 let (sender, receiver) = mpsc::channel();
156 let sender = Mutex::new(sender);
157 self.context
158 .audio_context_impl()
159 .lock()
160 .unwrap()
161 .set_eos_callback(Box::new(move |buffer| {
162 let channels: Vec<Vec<f32>> = (*buffer)
163 .as_ref()
164 .chunks(length)
165 .take(channel_count)
166 .map(|channel| channel.to_vec())
167 .collect();
168 let _ = sender.lock().unwrap().send(channels);
169 }));
170
171 let this = Trusted::new(self);
172 let task_source = self
173 .global()
174 .task_manager()
175 .dom_manipulation_task_source()
176 .to_sendable();
177 Builder::new()
178 .name("OfflineACResolver".to_owned())
179 .spawn(move || {
180 let processed_audio = receiver.recv().unwrap_or_default();
181 task_source.queue(task!(resolve: move |cx| {
182 let this = this.root();
183 let mut processed_audio = processed_audio;
184 processed_audio.resize_with(this.channel_count as usize, || {
186 vec![0.; this.length as usize]
187 });
188 let buffer = AudioBuffer::new_with_channel_data(
189 cx,
190 this.global().as_window(),
191 this.length,
192 *this.context.SampleRate(),
193 processed_audio,
194 );
195 let promise = {
196 (*this
197 .pending_rendering_promise
198 .safe_borrow_mut(cx.no_gc()))
199 .take()
200 .unwrap()
201 .root(cx)
202 };
203 promise.resolve_native(cx, &buffer);
204 let global = &this.global();
205 let window = global.as_window();
206 let event = OfflineAudioCompletionEvent::new(cx, window,
207 atom!("complete"),
208 EventBubbles::DoesNotBubble,
209 EventCancelable::NotCancelable,
210 &buffer);
211 event.upcast::<Event>().fire(cx, this.upcast());
212 }));
213 })
214 .unwrap();
215
216 if self
217 .context
218 .audio_context_impl()
219 .lock()
220 .unwrap()
221 .resume()
222 .is_none()
223 {
224 promise.reject_error(
225 cx,
226 Error::Type(c"Could not start offline rendering".to_owned()),
227 );
228 }
229
230 promise
231 }
232}