Skip to content

Commit

Permalink
Emit complete event for OfflineAudioContext rendering
Browse files Browse the repository at this point in the history
Fixes #411
  • Loading branch information
orottier committed Feb 22, 2024
1 parent 3518169 commit a835a9d
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/context/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ pub trait BaseAudioContext {
);
}

/// Unset the callback to run when the state of the AudioContext has changed
fn clear_onstatechange(&self) {
self.base().clear_event_handler(EventType::StateChange);
}
Expand Down
62 changes: 62 additions & 0 deletions src/context/offline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::buffer::AudioBuffer;
use crate::context::{AudioContextState, BaseAudioContext, ConcreteBaseAudioContext};
use crate::render::RenderThread;
use crate::{assert_valid_sample_rate, RENDER_QUANTUM_SIZE};
use crate::{Event, OfflineAudioCompletionEvent};

use futures_channel::{mpsc, oneshot};
use futures_util::SinkExt as _;
Expand Down Expand Up @@ -47,6 +48,8 @@ struct OfflineAudioContextRenderer {
suspend_callbacks: Vec<(usize, Box<OfflineAudioContextCallback>)>,
/// channel to listen for `resume` calls on a suspended context
resume_receiver: mpsc::Receiver<()>,
/// event handler for oncomplete event
oncomplete_handler: Option<Box<dyn FnOnce(OfflineAudioCompletionEvent) + Send + 'static>>,
}

impl BaseAudioContext for OfflineAudioContext {
Expand Down Expand Up @@ -111,6 +114,7 @@ impl OfflineAudioContext {
suspend_promises: Vec::new(),
suspend_callbacks: Vec::new(),
resume_receiver,
oncomplete_handler: None,
};

Self {
Expand Down Expand Up @@ -143,13 +147,16 @@ impl OfflineAudioContext {
let OfflineAudioContextRenderer {
renderer,
suspend_callbacks,
mut oncomplete_handler,
..
} = renderer;

self.base.set_state(AudioContextState::Running);
let result = renderer.render_audiobuffer_sync(self.length, suspend_callbacks, self);
self.base.set_state(AudioContextState::Closed);

Self::emit_oncomplete(&mut oncomplete_handler, &result);

result
}

Expand Down Expand Up @@ -177,6 +184,7 @@ impl OfflineAudioContext {
renderer,
suspend_promises,
resume_receiver,
mut oncomplete_handler,
..
} = renderer;

Expand All @@ -188,9 +196,24 @@ impl OfflineAudioContext {

self.base.set_state(AudioContextState::Closed);

Self::emit_oncomplete(&mut oncomplete_handler, &result);

result
}

fn emit_oncomplete(
oncomplete_handler: &mut Option<Box<dyn FnOnce(OfflineAudioCompletionEvent) + Send>>,
result: &AudioBuffer,
) {
if let Some(callback) = oncomplete_handler.take() {
let event = OfflineAudioCompletionEvent {
rendered_buffer: result.clone(),
event: Event { type_: "complete" },
};
(callback)(event);
}
}

/// get the length of rendering audio buffer
// false positive: OfflineAudioContext is not const
#[allow(clippy::missing_const_for_fn, clippy::unused_self)]
Expand Down Expand Up @@ -357,12 +380,35 @@ impl OfflineAudioContext {
self.base().set_state(AudioContextState::Running);
self.resume_sender.clone().send(()).await.unwrap()
}

/// Register callback to run when the rendering has completed
///
/// Only a single event handler is active at any time. Calling this method multiple times will
/// override the previous event handler.
#[allow(clippy::missing_panics_doc)]
pub fn set_oncomplete<F: FnOnce(OfflineAudioCompletionEvent) + Send + 'static>(
&mut self,
callback: F,
) {
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
renderer.oncomplete_handler = Some(Box::new(callback));
}
}

/// Unset the callback to run when the rendering has completed
#[allow(clippy::missing_panics_doc)]
pub fn clear_oncomplete(&mut self) {
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
renderer.oncomplete_handler = None;
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use float_eq::assert_float_eq;
use std::sync::atomic::{AtomicBool, Ordering};

use crate::node::AudioNode;
use crate::node::AudioScheduledSourceNode;
Expand Down Expand Up @@ -493,4 +539,20 @@ mod tests {
context.suspend_sync(0.0, |_| ());
context.suspend_sync(0.0, |_| ());
}

#[test]
fn test_oncomplete() {
let mut context = OfflineAudioContext::new(2, 555, 44_100.);

let complete = Arc::new(AtomicBool::new(false));
let complete_clone = Arc::clone(&complete);
context.set_oncomplete(move |event| {
assert_eq!(event.rendered_buffer.length(), 555);
complete_clone.store(true, Ordering::Relaxed);
});

let _ = context.start_rendering_sync();

assert!(complete.load(Ordering::Relaxed));
}
}
12 changes: 11 additions & 1 deletion src/events.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::context::AudioNodeId;
use crate::AudioRenderCapacityEvent;
use crate::{AudioBuffer, AudioRenderCapacityEvent};

use std::any::Any;
use std::collections::HashMap;
Expand Down Expand Up @@ -37,6 +37,16 @@ pub struct ErrorEvent {
pub event: Event,
}

/// The OfflineAudioCompletionEvent Event interface
#[non_exhaustive]
#[derive(Debug)]
pub struct OfflineAudioCompletionEvent {
/// The rendered AudioBuffer
pub rendered_buffer: AudioBuffer,
/// Inherits from this base Event
pub event: Event,
}

#[derive(Debug)]
pub(crate) enum EventPayload {
None,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub mod media_streams;
pub mod node;

mod events;
pub use events::{ErrorEvent, Event};
pub use events::{ErrorEvent, Event, OfflineAudioCompletionEvent};

mod param;
pub use param::*;
Expand Down

0 comments on commit a835a9d

Please sign in to comment.