1use std::cell::Cell;
6use std::sync::{Arc, 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(None));
86 }
87 let pipeline_id = window.pipeline_id();
88 let context =
89 OfflineAudioContext::new_inherited(channel_count, length, sample_rate, pipeline_id)?;
90 Ok(reflect_dom_object_with_proto(
91 cx,
92 Box::new(context),
93 window,
94 proto,
95 ))
96 }
97}
98
99impl OfflineAudioContextMethods<crate::DomTypeHolder> for OfflineAudioContext {
100 fn Constructor(
102 cx: &mut JSContext,
103 window: &Window,
104 proto: Option<HandleObject>,
105 options: &OfflineAudioContextOptions,
106 ) -> Fallible<DomRoot<OfflineAudioContext>> {
107 OfflineAudioContext::new(
108 cx,
109 window,
110 proto,
111 options.numberOfChannels,
112 options.length,
113 *options.sampleRate,
114 )
115 }
116
117 fn Constructor_(
119 cx: &mut JSContext,
120 window: &Window,
121 proto: Option<HandleObject>,
122 number_of_channels: u32,
123 length: u32,
124 sample_rate: Finite<f32>,
125 ) -> Fallible<DomRoot<OfflineAudioContext>> {
126 OfflineAudioContext::new(cx, window, proto, number_of_channels, length, *sample_rate)
127 }
128
129 event_handler!(complete, GetOncomplete, SetOncomplete);
131
132 fn Length(&self) -> u32 {
134 self.length
135 }
136
137 fn StartRendering(&self, cx: &mut CurrentRealm) -> RootedPromise {
139 let promise = Promise::new_in_realm_rooted(cx);
140 if self.rendering_started.get() {
141 promise.reject_error(cx, Error::InvalidState(None));
142 return promise;
143 }
144 self.rendering_started.set(true);
145
146 *self.pending_rendering_promise.safe_borrow_mut(cx.no_gc()) = Some(promise.to_traced());
147
148 let processed_audio = Arc::new(Mutex::new(Vec::new()));
149 let processed_audio_ = processed_audio.clone();
150 let (sender, receiver) = mpsc::channel();
151 let sender = Mutex::new(sender);
152 self.context
153 .audio_context_impl()
154 .lock()
155 .unwrap()
156 .set_eos_callback(Box::new(move |buffer| {
157 processed_audio_
158 .lock()
159 .unwrap()
160 .extend_from_slice((*buffer).as_ref());
161 let _ = sender.lock().unwrap().send(());
162 }));
163
164 let this = Trusted::new(self);
165 let task_source = self
166 .global()
167 .task_manager()
168 .dom_manipulation_task_source()
169 .to_sendable();
170 Builder::new()
171 .name("OfflineACResolver".to_owned())
172 .spawn(move || {
173 let _ = receiver.recv();
174 task_source.queue(task!(resolve: move |cx| {
175 let this = this.root();
176 let processed_audio = processed_audio.lock().unwrap();
177 let mut processed_audio: Vec<_> = processed_audio
178 .chunks(this.length as usize)
179 .map(|channel| channel.to_vec())
180 .collect();
181 if processed_audio.len() != this.length as usize {
183 processed_audio.resize(this.length as usize, Vec::new())
184 }
185 let buffer = AudioBuffer::new(
186 cx,
187 this.global().as_window(),
188 this.channel_count,
189 this.length,
190 *this.context.SampleRate(),
191 Some(processed_audio.as_slice()),
192 );
193 let promise = {
194 (*this
195 .pending_rendering_promise
196 .safe_borrow_mut(cx.no_gc()))
197 .take()
198 .unwrap()
199 .root(cx)
200 };
201 promise.resolve_native(cx, &buffer);
202 let global = &this.global();
203 let window = global.as_window();
204 let event = OfflineAudioCompletionEvent::new(cx, window,
205 atom!("complete"),
206 EventBubbles::DoesNotBubble,
207 EventCancelable::NotCancelable,
208 &buffer);
209 event.upcast::<Event>().fire(cx, this.upcast());
210 }));
211 })
212 .unwrap();
213
214 if self
215 .context
216 .audio_context_impl()
217 .lock()
218 .unwrap()
219 .resume()
220 .is_none()
221 {
222 promise.reject_error(
223 cx,
224 Error::Type(c"Could not start offline rendering".to_owned()),
225 );
226 }
227
228 promise
229 }
230}