OfflineAudioContext

This commit is contained in:
Fernando Jiménez Moreno 2018-07-27 16:44:36 +02:00
parent e034159423
commit 6aaf5806b1
10 changed files with 424 additions and 36 deletions

View file

@ -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<DomRoot<AudioBuffer>> {
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(

View file

@ -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<AudioContext> {
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<AudioContext> {
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<DomRoot<AudioContext>> {
let global = window.upcast::<GlobalScope>();
Ok(AudioContext::new(global, options))
Ok(AudioContext::new(window, options))
}
fn resume(&self) {

View file

@ -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<AudioContextState>,
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::<Window>(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::<Window>(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<BaseAudioContextOptions> for AudioContextOptions {
fn from(options: BaseAudioContextOptions) -> Self {
match options {
BaseAudioContextOptions::AudioContext(options) =>
AudioContextOptions::RealTimeAudioContext(options),
BaseAudioContextOptions::OfflineAudioContext(options) =>
AudioContextOptions::OfflineAudioContext(options),
}
}
}
impl From<ProcessingState> for AudioContextState {
fn from(state: ProcessingState) -> Self {
match state {

View file

@ -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;

View file

@ -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<AudioBuffer>,
}
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<OfflineAudioCompletionEvent> {
let event = Box::new(OfflineAudioCompletionEvent::new_inherited(rendered_buffer));
let ev = reflect_dom_object(event, window, OfflineAudioCompletionEventBinding::Wrap);
{
let event = ev.upcast::<Event>();
event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
}
ev
}
pub fn Constructor(
window: &Window,
type_: DOMString,
init: &OfflineAudioCompletionEventInit,
) -> Fallible<DomRoot<OfflineAudioCompletionEvent>> {
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<AudioBuffer> {
DomRoot::from_ref(&*self.rendered_buffer)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -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<bool>,
#[ignore_malloc_size_of = "promises are hard"]
pending_rendering_promise: DomRefCell<Option<Rc<Promise>>>,
}
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<OfflineAudioContext> {
let context = OfflineAudioContext::new_inherited(options);
reflect_dom_object(Box::new(context), window, OfflineAudioContextBinding::Wrap)
}
pub fn Constructor(
window: &Window,
options: &OfflineAudioContextOptions,
) -> Fallible<DomRoot<OfflineAudioContext>> {
Ok(OfflineAudioContext::new(window, options))
}
pub fn Constructor_(
window: &Window,
number_of_channels: u32,
length: u32,
sample_rate: Finite<f32>,
) -> Fallible<DomRoot<OfflineAudioContext>> {
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<Promise> {
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::<Event>().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,
}
}
}

View file

@ -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;
};

View file

@ -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<AudioBuffer> startRendering();
// Promise<void> suspend(double suspendTime);
};