diff --git a/Cargo.lock b/Cargo.lock index ae734a32cd0..df2e1fddf83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1309,6 +1309,35 @@ dependencies = [ "pkg-config 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gstreamer-player" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-player-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-video 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-player-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-video-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gstreamer-sys" version = "0.5.0" @@ -1321,6 +1350,37 @@ dependencies = [ "pkg-config 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gstreamer-video" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-video-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gvr-sys" version = "0.7.0" @@ -3076,38 +3136,48 @@ dependencies = [ [[package]] name = "servo-media" version = "0.1.0" -source = "git+https://github.com/servo/media#9968654a5602adfcdc01d6a9ba9d54fd8b341eaf" +source = "git+https://github.com/servo/media#dadf87f24830712b75e1d909bad7500f9dae952c" dependencies = [ "servo-media-audio 0.1.0 (git+https://github.com/servo/media)", "servo-media-gstreamer 0.1.0 (git+https://github.com/servo/media)", + "servo-media-player 0.1.0 (git+https://github.com/servo/media)", ] [[package]] name = "servo-media-audio" version = "0.1.0" -source = "git+https://github.com/servo/media#9968654a5602adfcdc01d6a9ba9d54fd8b341eaf" +source = "git+https://github.com/servo/media#dadf87f24830712b75e1d909bad7500f9dae952c" dependencies = [ "byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "servo_media_derive 0.1.0 (git+https://github.com/servo/media)", - "smallvec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "servo-media-gstreamer" version = "0.1.0" -source = "git+https://github.com/servo/media#9968654a5602adfcdc01d6a9ba9d54fd8b341eaf" +source = "git+https://github.com/servo/media#dadf87f24830712b75e1d909bad7500f9dae952c" dependencies = [ "byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-app 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-audio 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-player 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "servo-media-audio 0.1.0 (git+https://github.com/servo/media)", + "servo-media-player 0.1.0 (git+https://github.com/servo/media)", "zip 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "servo-media-player" +version = "0.1.0" +source = "git+https://github.com/servo/media#dadf87f24830712b75e1d909bad7500f9dae952c" + [[package]] name = "servo-skia" version = "0.30000017.0" @@ -3203,7 +3273,7 @@ dependencies = [ [[package]] name = "servo_media_derive" version = "0.1.0" -source = "git+https://github.com/servo/media#9968654a5602adfcdc01d6a9ba9d54fd8b341eaf" +source = "git+https://github.com/servo/media#dadf87f24830712b75e1d909bad7500f9dae952c" dependencies = [ "quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4213,7 +4283,11 @@ dependencies = [ "checksum gstreamer-audio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd60631f2dd055f0aae2831e86bd6c1d45e24528d4c478002cc07490dd84b56e" "checksum gstreamer-base 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05ec7a84b4160b61c72ea27ccf3f46eb9c8f996c5991746623e69e3e532e3cb5" "checksum gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "501a7add44f256aab6cb5b65ef121c449197cf55087d6a7586846c8d1e42e88b" +"checksum gstreamer-player 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "63e81d78e9e7ee5d448ba50c52722005bbbf1bfe606767c4c407f45e8996f050" +"checksum gstreamer-player-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b9476078cc76164446e88b2c4331e81e24a07f7b7c3a8b4bf8975a47998ebd4" "checksum gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b2f51e25a6f97dd4bfd640cba96f192f8759b8766afd66d6d9ea0f82ca14a37" +"checksum gstreamer-video 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75300cf1ed8d8d65811349fc755fac22be05ea55df551ab29e43664d4a575c92" +"checksum gstreamer-video-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed798787e78a0f1c8be06bd3adcab03f962f049a820743aae9f690f56a0d538" "checksum gvr-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1334b94d8ce67319ddc44663daef53d8c1538629a11562530c981dbd9085b9a" "checksum half 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63d68db75012a85555434ee079e7e6337931f87a087ab2988becbadf64673a7f" "checksum harfbuzz-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bad27f59fc853bb88eca58088d469cd365ff0ccf362d24c4b1c0cbb1b21b3489" @@ -4356,6 +4430,7 @@ dependencies = [ "checksum servo-media 0.1.0 (git+https://github.com/servo/media)" = "" "checksum servo-media-audio 0.1.0 (git+https://github.com/servo/media)" = "" "checksum servo-media-gstreamer 0.1.0 (git+https://github.com/servo/media)" = "" +"checksum servo-media-player 0.1.0 (git+https://github.com/servo/media)" = "" "checksum servo-skia 0.30000017.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe33a2592a74f2096daf70bcbee39f2d183efcdc81f536a28af3a357baa6f5b0" "checksum servo-websocket 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6bac1e2295e72f0525147d993c626761811acf0441dac1cee8707f12dc7f3363" "checksum servo_media_derive 0.1.0 (git+https://github.com/servo/media)" = "" diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 41ff8000073..f60c497cf2e 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -12,6 +12,7 @@ checkbox click close color +complete controllerchange cursive date diff --git a/components/script/dom/audiobuffer.rs b/components/script/dom/audiobuffer.rs index 150a8d53ea4..3f1c5f2f441 100644 --- a/components/script/dom/audiobuffer.rs +++ b/components/script/dom/audiobuffer.rs @@ -19,6 +19,11 @@ use std::cmp::min; use std::mem; use std::ptr::{self, NonNull}; +// This range is defined by the spec. +// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer +pub const MIN_SAMPLE_RATE: f32 = 8000.; +pub const MAX_SAMPLE_RATE: f32 = 96000.; + type JSAudioChannel = Heap<*mut JSObject>; #[dom_struct] @@ -112,7 +117,9 @@ impl AudioBuffer { window: &Window, options: &AudioBufferOptions, ) -> Fallible> { - if options.numberOfChannels > MAX_CHANNEL_COUNT { + if options.numberOfChannels > MAX_CHANNEL_COUNT || + *options.sampleRate < MIN_SAMPLE_RATE || + *options.sampleRate > MAX_SAMPLE_RATE { return Err(Error::NotSupported); } Ok(AudioBuffer::new( diff --git a/components/script/dom/audiocontext.rs b/components/script/dom/audiocontext.rs index 24df81b0f54..ec43498e29c 100644 --- a/components/script/dom/audiocontext.rs +++ b/components/script/dom/audiocontext.rs @@ -14,7 +14,6 @@ use dom::bindings::num::Finite; use dom::bindings::refcounted::{Trusted, TrustedPromise}; use dom::bindings::reflector::{DomObject, reflect_dom_object}; use dom::bindings::root::DomRoot; -use dom::globalscope::GlobalScope; use dom::promise::Promise; use dom::window::Window; use dom_struct::dom_struct; @@ -35,10 +34,9 @@ pub struct AudioContext { impl AudioContext { #[allow(unrooted_must_root)] // https://webaudio.github.io/web-audio-api/#AudioContext-constructors - fn new_inherited(global: &GlobalScope, options: &AudioContextOptions) -> AudioContext { + fn new_inherited(options: &AudioContextOptions) -> AudioContext { // Steps 1-3. let context = BaseAudioContext::new_inherited( - global, BaseAudioContextOptions::AudioContext(options.into()), ); @@ -61,9 +59,9 @@ impl AudioContext { } #[allow(unrooted_must_root)] - pub fn new(global: &GlobalScope, options: &AudioContextOptions) -> DomRoot { - let context = AudioContext::new_inherited(global, options); - let context = reflect_dom_object(Box::new(context), global, AudioContextBinding::Wrap); + pub fn new(window: &Window, options: &AudioContextOptions) -> DomRoot { + let context = AudioContext::new_inherited(options); + let context = reflect_dom_object(Box::new(context), window, AudioContextBinding::Wrap); context.resume(); context } @@ -73,8 +71,7 @@ impl AudioContext { window: &Window, options: &AudioContextOptions, ) -> Fallible> { - let global = window.upcast::(); - Ok(AudioContext::new(global, options)) + Ok(AudioContext::new(window, options)) } fn resume(&self) { diff --git a/components/script/dom/baseaudiocontext.rs b/components/script/dom/baseaudiocontext.rs index 683fecf4576..583b1690048 100644 --- a/components/script/dom/baseaudiocontext.rs +++ b/components/script/dom/baseaudiocontext.rs @@ -26,7 +26,6 @@ use dom::bindings::root::{DomRoot, MutNullableDom}; use dom::domexception::{DOMErrorName, DOMException}; use dom::eventtarget::EventTarget; use dom::gainnode::GainNode; -use dom::globalscope::GlobalScope; use dom::oscillatornode::OscillatorNode; use dom::promise::Promise; use dom::window::Window; @@ -34,7 +33,7 @@ use dom_struct::dom_struct; use js::rust::CustomAutoRooterGuard; use js::typedarray::ArrayBuffer; use servo_media::{Backend, ServoMedia}; -use servo_media::audio::context::{AudioContext, ProcessingState}; +use servo_media::audio::context::{AudioContext, AudioContextOptions, ProcessingState}; use servo_media::audio::context::{OfflineAudioContextOptions, RealTimeAudioContextOptions}; use servo_media::audio::decoder::AudioDecoderCallbacks; use servo_media::audio::graph::NodeId; @@ -83,18 +82,17 @@ pub struct BaseAudioContext { /// throw when trying to do things on the context when the context has just /// been "closed()". state: Cell, + channel_count: u32, } impl BaseAudioContext { #[allow(unrooted_must_root)] - pub fn new_inherited(_: &GlobalScope, options: BaseAudioContextOptions) -> BaseAudioContext { - let options = match options { - BaseAudioContextOptions::AudioContext(options) => options, - BaseAudioContextOptions::OfflineAudioContext(_) => unimplemented!(), + pub fn new_inherited(options: BaseAudioContextOptions) -> BaseAudioContext { + let (sample_rate, channel_count) = match options { + BaseAudioContextOptions::AudioContext(ref opt) => (opt.sample_rate, 2), + BaseAudioContextOptions::OfflineAudioContext(ref opt) => (opt.sample_rate, opt.channels), }; - let sample_rate = options.sample_rate; - let context = BaseAudioContext { eventtarget: EventTarget::new_inherited(), audio_context_impl: Rc::new( @@ -108,6 +106,7 @@ impl BaseAudioContext { decode_resolvers: Default::default(), sample_rate, state: Cell::new(AudioContextState::Suspended), + channel_count: channel_count.into(), }; context @@ -206,19 +205,19 @@ impl BaseAudioContext { self.take_pending_resume_promises(Ok(())); let _ = task_source.queue( task!(resume_success: move || { - let this = this.root(); - this.fulfill_in_flight_resume_promises(|| { - if this.state.get() != AudioContextState::Running { - this.state.set(AudioContextState::Running); - let window = DomRoot::downcast::(this.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - this.upcast(), - atom!("statechange"), - &window - ); - } - }); - }), + let this = this.root(); + this.fulfill_in_flight_resume_promises(|| { + if this.state.get() != AudioContextState::Running { + this.state.set(AudioContextState::Running); + let window = DomRoot::downcast::(this.global()).unwrap(); + window.dom_manipulation_task_source().queue_simple_event( + this.upcast(), + atom!("statechange"), + &window + ); + } + }); + }), window.upcast(), ); }, @@ -291,7 +290,7 @@ impl BaseAudioContextMethods for BaseAudioContext { let global = self.global(); self.destination.or_init(|| { let mut options = AudioNodeOptions::empty(); - options.channelCount = Some(2); + options.channelCount = Some(self.channel_count); options.channelCountMode = Some(ChannelCountMode::Explicit); options.channelInterpretation = Some(ChannelInterpretation::Speakers); AudioDestinationNode::new(&global, self, &options) @@ -442,6 +441,17 @@ impl BaseAudioContextMethods for BaseAudioContext { } } +impl From for AudioContextOptions { + fn from(options: BaseAudioContextOptions) -> Self { + match options { + BaseAudioContextOptions::AudioContext(options) => + AudioContextOptions::RealTimeAudioContext(options), + BaseAudioContextOptions::OfflineAudioContext(options) => + AudioContextOptions::OfflineAudioContext(options), + } + } +} + impl From for AudioContextState { fn from(state: ProcessingState) -> Self { match state { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index d58314f9af3..ba1668e9f43 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -401,6 +401,8 @@ pub mod navigatorinfo; pub mod node; pub mod nodeiterator; pub mod nodelist; +pub mod offlineaudiocompletionevent; +pub mod offlineaudiocontext; pub mod oscillatornode; pub mod pagetransitionevent; pub mod paintrenderingcontext2d; diff --git a/components/script/dom/offlineaudiocompletionevent.rs b/components/script/dom/offlineaudiocompletionevent.rs new file mode 100644 index 00000000000..91edf35338c --- /dev/null +++ b/components/script/dom/offlineaudiocompletionevent.rs @@ -0,0 +1,77 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use dom::audiobuffer::AudioBuffer; +use dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use dom::bindings::codegen::Bindings::OfflineAudioCompletionEventBinding; +use dom::bindings::codegen::Bindings::OfflineAudioCompletionEventBinding::OfflineAudioCompletionEventInit; +use dom::bindings::codegen::Bindings::OfflineAudioCompletionEventBinding::OfflineAudioCompletionEventMethods; +use dom::bindings::error::Fallible; +use dom::bindings::inheritance::Castable; +use dom::bindings::reflector::reflect_dom_object; +use dom::bindings::root::{Dom, DomRoot, RootedReference}; +use dom::bindings::str::DOMString; +use dom::event::{Event, EventBubbles, EventCancelable}; +use dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct OfflineAudioCompletionEvent { + event: Event, + rendered_buffer: Dom, +} + +impl OfflineAudioCompletionEvent { + pub fn new_inherited(rendered_buffer: &AudioBuffer) -> OfflineAudioCompletionEvent { + OfflineAudioCompletionEvent { + event: Event::new_inherited(), + rendered_buffer: Dom::from_ref(rendered_buffer), + } + } + + pub fn new( + window: &Window, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + rendered_buffer: &AudioBuffer, + ) -> DomRoot { + let event = Box::new(OfflineAudioCompletionEvent::new_inherited(rendered_buffer)); + let ev = reflect_dom_object(event, window, OfflineAudioCompletionEventBinding::Wrap); + { + let event = ev.upcast::(); + event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); + } + ev + } + + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &OfflineAudioCompletionEventInit, + ) -> Fallible> { + let bubbles = EventBubbles::from(init.parent.bubbles); + let cancelable = EventCancelable::from(init.parent.cancelable); + Ok(OfflineAudioCompletionEvent::new( + window, + Atom::from(type_), + bubbles, + cancelable, + init.renderedBuffer.r(), + )) + } +} + +impl OfflineAudioCompletionEventMethods for OfflineAudioCompletionEvent { + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocompletionevent-renderedbuffer + fn RenderedBuffer(&self) -> DomRoot { + DomRoot::from_ref(&*self.rendered_buffer) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/offlineaudiocontext.rs b/components/script/dom/offlineaudiocontext.rs new file mode 100644 index 00000000000..24c4596b8be --- /dev/null +++ b/components/script/dom/offlineaudiocontext.rs @@ -0,0 +1,178 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use dom::audiobuffer::{AudioBuffer, MAX_SAMPLE_RATE, MIN_SAMPLE_RATE}; +use dom::audionode::MAX_CHANNEL_COUNT; +use dom::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions}; +use dom::bindings::cell::DomRefCell; +use dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextBinding::BaseAudioContextMethods; +use dom::bindings::codegen::Bindings::OfflineAudioContextBinding; +use dom::bindings::codegen::Bindings::OfflineAudioContextBinding::OfflineAudioContextMethods; +use dom::bindings::codegen::Bindings::OfflineAudioContextBinding::OfflineAudioContextOptions; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::inheritance::Castable; +use dom::bindings::num::Finite; +use dom::bindings::refcounted::Trusted; +use dom::bindings::reflector::{DomObject, reflect_dom_object}; +use dom::bindings::root::DomRoot; +use dom::event::{Event, EventBubbles, EventCancelable}; +use dom::offlineaudiocompletionevent::OfflineAudioCompletionEvent; +use dom::promise::Promise; +use dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::context::OfflineAudioContextOptions as ServoMediaOfflineAudioContextOptions; +use std::cell::Cell; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc; +use std::thread::Builder; +use task_source::{TaskSource, TaskSourceName}; + +#[dom_struct] +pub struct OfflineAudioContext { + context: BaseAudioContext, + channel_count: u32, + length: u32, + rendering_started: Cell, + #[ignore_malloc_size_of = "promises are hard"] + pending_rendering_promise: DomRefCell>>, +} + +impl OfflineAudioContext { + #[allow(unrooted_must_root)] + fn new_inherited(options: &OfflineAudioContextOptions) -> OfflineAudioContext { + let context = BaseAudioContext::new_inherited( + BaseAudioContextOptions::OfflineAudioContext(options.into()), + ); + OfflineAudioContext { + context, + channel_count: options.numberOfChannels, + length: options.length, + rendering_started: Cell::new(false), + pending_rendering_promise: Default::default(), + } + } + + #[allow(unrooted_must_root)] + fn new(window: &Window, options: &OfflineAudioContextOptions) -> DomRoot { + let context = OfflineAudioContext::new_inherited(options); + reflect_dom_object(Box::new(context), window, OfflineAudioContextBinding::Wrap) + } + + pub fn Constructor( + window: &Window, + options: &OfflineAudioContextOptions, + ) -> Fallible> { + Ok(OfflineAudioContext::new(window, options)) + } + + pub fn Constructor_( + window: &Window, + number_of_channels: u32, + length: u32, + sample_rate: Finite, + ) -> Fallible> { + if number_of_channels > MAX_CHANNEL_COUNT || + number_of_channels <= 0 || + length <= 0 || + *sample_rate < MIN_SAMPLE_RATE || + *sample_rate > MAX_SAMPLE_RATE + { + return Err(Error::NotSupported); + } + + let mut options = OfflineAudioContextOptions::empty(); + options.numberOfChannels = number_of_channels; + options.length = length; + options.sampleRate = sample_rate; + Ok(OfflineAudioContext::new(window, &options)) + } +} + +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 + #[allow(unrooted_must_root)] + fn StartRendering(&self) -> Rc { + let promise = Promise::new(&self.global()); + 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() + .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 = window.dom_manipulation_task_source(); + let canceller = window.task_canceller(TaskSourceName::DOMManipulation); + 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 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::().fire(this.upcast()); + }), + &canceller, + ); + }) + .unwrap(); + + if self.context.audio_context_impl().resume().is_err() { + promise.reject_error(Error::Type("Could not start offline rendering".to_owned())); + } + + promise + } +} + +impl<'a> From<&'a OfflineAudioContextOptions> for ServoMediaOfflineAudioContextOptions { + fn from(options: &OfflineAudioContextOptions) -> Self { + Self { + channels: options.numberOfChannels as u8, + length: options.length as usize, + sample_rate: *options.sampleRate, + } + } +} diff --git a/components/script/dom/webidls/OfflineAudioCompletionEvent.webidl b/components/script/dom/webidls/OfflineAudioCompletionEvent.webidl new file mode 100644 index 00000000000..d5f2218c7ed --- /dev/null +++ b/components/script/dom/webidls/OfflineAudioCompletionEvent.webidl @@ -0,0 +1,17 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +/* + * For more information on this interface please see + * https://webaudio.github.io/web-audio-api/#offlineaudiocompletionevent + */ + +dictionary OfflineAudioCompletionEventInit : EventInit { + required AudioBuffer renderedBuffer; +}; + +[Exposed=Window, + Constructor(DOMString type, OfflineAudioCompletionEventInit eventInitDict)] +interface OfflineAudioCompletionEvent : Event { + readonly attribute AudioBuffer renderedBuffer; +}; diff --git a/components/script/dom/webidls/OfflineAudioContext.webidl b/components/script/dom/webidls/OfflineAudioContext.webidl new file mode 100644 index 00000000000..688d4ec6f0a --- /dev/null +++ b/components/script/dom/webidls/OfflineAudioContext.webidl @@ -0,0 +1,24 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#OfflineAudioContext + */ + +dictionary OfflineAudioContextOptions { + unsigned long numberOfChannels = 1; + unsigned long length = 0; + float sampleRate = 48000.; +}; + +[Exposed=Window, + Constructor (optional OfflineAudioContextOptions contextOptions), + Constructor (unsigned long numberOfChannels, unsigned long length, float sampleRate)] +interface OfflineAudioContext : BaseAudioContext { + readonly attribute unsigned long length; + attribute EventHandler oncomplete; + + Promise startRendering(); +// Promise suspend(double suspendTime); +};