diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom/offlineaudiocontext.rs | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/dom/offlineaudiocontext.rs')
-rw-r--r-- | components/script/dom/offlineaudiocontext.rs | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/components/script/dom/offlineaudiocontext.rs b/components/script/dom/offlineaudiocontext.rs new file mode 100644 index 00000000000..bbdf9e1a79f --- /dev/null +++ b/components/script/dom/offlineaudiocontext.rs @@ -0,0 +1,205 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::audiobuffer::{AudioBuffer, MAX_SAMPLE_RATE, MIN_SAMPLE_RATE}; +use crate::dom::audionode::MAX_CHANNEL_COUNT; +use crate::dom::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextBinding::BaseAudioContextMethods; +use crate::dom::bindings::codegen::Bindings::OfflineAudioContextBinding::OfflineAudioContextMethods; +use crate::dom::bindings::codegen::Bindings::OfflineAudioContextBinding::OfflineAudioContextOptions; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::offlineaudiocompletionevent::OfflineAudioCompletionEvent; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::realms::InRealm; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use msg::constellation_msg::PipelineId; +use servo_media::audio::context::OfflineAudioContextOptions as ServoMediaOfflineAudioContextOptions; +use std::cell::Cell; +use std::rc::Rc; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread::Builder; + +#[dom_struct] +pub struct OfflineAudioContext { + context: BaseAudioContext, + channel_count: u32, + length: u32, + rendering_started: Cell<bool>, + #[ignore_malloc_size_of = "promises are hard"] + pending_rendering_promise: DomRefCell<Option<Rc<Promise>>>, +} + +#[allow(non_snake_case)] +impl OfflineAudioContext { + #[allow(unrooted_must_root)] + fn new_inherited( + channel_count: u32, + length: u32, + sample_rate: f32, + pipeline_id: PipelineId, + ) -> OfflineAudioContext { + let options = ServoMediaOfflineAudioContextOptions { + channels: channel_count as u8, + length: length as usize, + sample_rate, + }; + let context = BaseAudioContext::new_inherited( + BaseAudioContextOptions::OfflineAudioContext(options), + pipeline_id, + ); + OfflineAudioContext { + context, + channel_count, + length, + rendering_started: Cell::new(false), + pending_rendering_promise: Default::default(), + } + } + + #[allow(unrooted_must_root)] + fn new( + window: &Window, + channel_count: u32, + length: u32, + sample_rate: f32, + ) -> Fallible<DomRoot<OfflineAudioContext>> { + if channel_count > MAX_CHANNEL_COUNT || + channel_count <= 0 || + length <= 0 || + sample_rate < MIN_SAMPLE_RATE || + sample_rate > MAX_SAMPLE_RATE + { + return Err(Error::NotSupported); + } + let pipeline_id = window.pipeline_id(); + let context = + OfflineAudioContext::new_inherited(channel_count, length, sample_rate, pipeline_id); + Ok(reflect_dom_object(Box::new(context), window)) + } + + pub fn Constructor( + window: &Window, + options: &OfflineAudioContextOptions, + ) -> Fallible<DomRoot<OfflineAudioContext>> { + OfflineAudioContext::new( + window, + options.numberOfChannels, + options.length, + *options.sampleRate, + ) + } + + pub fn Constructor_( + window: &Window, + number_of_channels: u32, + length: u32, + sample_rate: Finite<f32>, + ) -> Fallible<DomRoot<OfflineAudioContext>> { + OfflineAudioContext::new(window, number_of_channels, length, *sample_rate) + } +} + +impl OfflineAudioContextMethods for OfflineAudioContext { + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-oncomplete + event_handler!(complete, GetOncomplete, SetOncomplete); + + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-length + fn Length(&self) -> u32 { + self.length + } + + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-startrendering + fn StartRendering(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&self.global(), comp); + if self.rendering_started.get() { + promise.reject_error(Error::InvalidState); + return promise; + } + self.rendering_started.set(true); + + *self.pending_rendering_promise.borrow_mut() = Some(promise.clone()); + + let processed_audio = Arc::new(Mutex::new(Vec::new())); + let processed_audio_ = processed_audio.clone(); + let (sender, receiver) = mpsc::channel(); + let sender = Mutex::new(sender); + self.context + .audio_context_impl() + .lock() + .unwrap() + .set_eos_callback(Box::new(move |buffer| { + processed_audio_ + .lock() + .unwrap() + .extend_from_slice((*buffer).as_ref()); + let _ = sender.lock().unwrap().send(()); + })); + + let this = Trusted::new(self); + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + Builder::new() + .name("OfflineAudioContextResolver".to_owned()) + .spawn(move || { + let _ = receiver.recv(); + let _ = task_source.queue_with_canceller( + task!(resolve: move || { + let this = this.root(); + let processed_audio = processed_audio.lock().unwrap(); + let mut processed_audio: Vec<_> = processed_audio + .chunks(this.length as usize) + .map(|channel| channel.to_vec()) + .collect(); + // it can end up being empty if the task failed + if processed_audio.len() != this.length as usize { + processed_audio.resize(this.length as usize, Vec::new()) + } + let buffer = AudioBuffer::new( + &this.global().as_window(), + this.channel_count, + this.length, + *this.context.SampleRate(), + Some(processed_audio.as_slice())); + (*this.pending_rendering_promise.borrow_mut()).take().unwrap().resolve_native(&buffer); + let global = &this.global(); + let window = global.as_window(); + let event = OfflineAudioCompletionEvent::new(&window, + atom!("complete"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + &buffer); + event.upcast::<Event>().fire(this.upcast()); + }), + &canceller, + ); + }) + .unwrap(); + + if self + .context + .audio_context_impl() + .lock() + .unwrap() + .resume() + .is_err() + { + promise.reject_error(Error::Type("Could not start offline rendering".to_owned())); + } + + promise + } +} |