Skip to content

Commit 124fa4f

Browse files
authored
Merge pull request #456 from orottier/feature/offline-events
OfflineAudioContext event handlers
2 parents 3518169 + be576a4 commit 124fa4f

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

src/context/base.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::{node, AudioListener};
2121
#[allow(clippy::module_name_repetitions)]
2222
pub trait BaseAudioContext {
2323
/// Returns the [`BaseAudioContext`] concrete type associated with this `AudioContext`
24+
#[doc(hidden)] // we'd rather not expose the ConcreteBaseAudioContext
2425
fn base(&self) -> &ConcreteBaseAudioContext;
2526

2627
/// Construct a new pair of [`AudioNode`] and [`AudioProcessor`]
@@ -319,6 +320,7 @@ pub trait BaseAudioContext {
319320
);
320321
}
321322

323+
/// Unset the callback to run when the state of the AudioContext has changed
322324
fn clear_onstatechange(&self) {
323325
self.base().clear_event_handler(EventType::StateChange);
324326
}

src/context/offline.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::buffer::AudioBuffer;
77
use crate::context::{AudioContextState, BaseAudioContext, ConcreteBaseAudioContext};
88
use crate::render::RenderThread;
99
use crate::{assert_valid_sample_rate, RENDER_QUANTUM_SIZE};
10+
use crate::{Event, OfflineAudioCompletionEvent};
1011

1112
use futures_channel::{mpsc, oneshot};
1213
use futures_util::SinkExt as _;
@@ -47,12 +48,28 @@ struct OfflineAudioContextRenderer {
4748
suspend_callbacks: Vec<(usize, Box<OfflineAudioContextCallback>)>,
4849
/// channel to listen for `resume` calls on a suspended context
4950
resume_receiver: mpsc::Receiver<()>,
51+
/// event handler for statechange event
52+
onstatechange_handler: Option<Box<dyn FnMut(Event) + Send + 'static>>,
53+
/// event handler for complete event
54+
oncomplete_handler: Option<Box<dyn FnOnce(OfflineAudioCompletionEvent) + Send + 'static>>,
5055
}
5156

5257
impl BaseAudioContext for OfflineAudioContext {
5358
fn base(&self) -> &ConcreteBaseAudioContext {
5459
&self.base
5560
}
61+
62+
fn set_onstatechange<F: FnMut(Event) + Send + 'static>(&self, callback: F) {
63+
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
64+
renderer.onstatechange_handler = Some(Box::new(callback));
65+
}
66+
}
67+
68+
fn clear_onstatechange(&self) {
69+
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
70+
renderer.onstatechange_handler = None;
71+
}
72+
}
5673
}
5774

5875
impl OfflineAudioContext {
@@ -111,6 +128,8 @@ impl OfflineAudioContext {
111128
suspend_promises: Vec::new(),
112129
suspend_callbacks: Vec::new(),
113130
resume_receiver,
131+
onstatechange_handler: None,
132+
oncomplete_handler: None,
114133
};
115134

116135
Self {
@@ -143,12 +162,20 @@ impl OfflineAudioContext {
143162
let OfflineAudioContextRenderer {
144163
renderer,
145164
suspend_callbacks,
165+
oncomplete_handler,
166+
mut onstatechange_handler,
146167
..
147168
} = renderer;
148169

149170
self.base.set_state(AudioContextState::Running);
171+
Self::emit_statechange(&mut onstatechange_handler);
172+
150173
let result = renderer.render_audiobuffer_sync(self.length, suspend_callbacks, self);
174+
151175
self.base.set_state(AudioContextState::Closed);
176+
Self::emit_statechange(&mut onstatechange_handler);
177+
178+
Self::emit_complete(oncomplete_handler, &result);
152179

153180
result
154181
}
@@ -177,20 +204,48 @@ impl OfflineAudioContext {
177204
renderer,
178205
suspend_promises,
179206
resume_receiver,
207+
oncomplete_handler,
208+
mut onstatechange_handler,
180209
..
181210
} = renderer;
182211

183212
self.base.set_state(AudioContextState::Running);
213+
Self::emit_statechange(&mut onstatechange_handler);
184214

185215
let result = renderer
186216
.render_audiobuffer(self.length, suspend_promises, resume_receiver)
187217
.await;
188218

189219
self.base.set_state(AudioContextState::Closed);
220+
Self::emit_statechange(&mut onstatechange_handler);
221+
222+
Self::emit_complete(oncomplete_handler, &result);
190223

191224
result
192225
}
193226

227+
fn emit_complete(
228+
oncomplete_handler: Option<Box<dyn FnOnce(OfflineAudioCompletionEvent) + Send>>,
229+
result: &AudioBuffer,
230+
) {
231+
if let Some(callback) = oncomplete_handler {
232+
let event = OfflineAudioCompletionEvent {
233+
rendered_buffer: result.clone(),
234+
event: Event { type_: "complete" },
235+
};
236+
(callback)(event);
237+
}
238+
}
239+
240+
fn emit_statechange(onstatechange_handler: &mut Option<Box<dyn FnMut(Event) + Send>>) {
241+
if let Some(callback) = onstatechange_handler.as_mut() {
242+
let event = Event {
243+
type_: "statechange",
244+
};
245+
(callback)(event);
246+
}
247+
}
248+
194249
/// get the length of rendering audio buffer
195250
// false positive: OfflineAudioContext is not const
196251
#[allow(clippy::missing_const_for_fn, clippy::unused_self)]
@@ -357,12 +412,35 @@ impl OfflineAudioContext {
357412
self.base().set_state(AudioContextState::Running);
358413
self.resume_sender.clone().send(()).await.unwrap()
359414
}
415+
416+
/// Register callback to run when the rendering has completed
417+
///
418+
/// Only a single event handler is active at any time. Calling this method multiple times will
419+
/// override the previous event handler.
420+
#[allow(clippy::missing_panics_doc)]
421+
pub fn set_oncomplete<F: FnOnce(OfflineAudioCompletionEvent) + Send + 'static>(
422+
&mut self,
423+
callback: F,
424+
) {
425+
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
426+
renderer.oncomplete_handler = Some(Box::new(callback));
427+
}
428+
}
429+
430+
/// Unset the callback to run when the rendering has completed
431+
#[allow(clippy::missing_panics_doc)]
432+
pub fn clear_oncomplete(&mut self) {
433+
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
434+
renderer.oncomplete_handler = None;
435+
}
436+
}
360437
}
361438

362439
#[cfg(test)]
363440
mod tests {
364441
use super::*;
365442
use float_eq::assert_float_eq;
443+
use std::sync::atomic::{AtomicBool, Ordering};
366444

367445
use crate::node::AudioNode;
368446
use crate::node::AudioScheduledSourceNode;
@@ -493,4 +571,35 @@ mod tests {
493571
context.suspend_sync(0.0, |_| ());
494572
context.suspend_sync(0.0, |_| ());
495573
}
574+
575+
#[test]
576+
fn test_onstatechange() {
577+
let mut context = OfflineAudioContext::new(2, 555, 44_100.);
578+
579+
let changed = Arc::new(AtomicBool::new(false));
580+
let changed_clone = Arc::clone(&changed);
581+
context.set_onstatechange(move |_event| {
582+
changed_clone.store(true, Ordering::Relaxed);
583+
});
584+
585+
let _ = context.start_rendering_sync();
586+
587+
assert!(changed.load(Ordering::Relaxed));
588+
}
589+
590+
#[test]
591+
fn test_oncomplete() {
592+
let mut context = OfflineAudioContext::new(2, 555, 44_100.);
593+
594+
let complete = Arc::new(AtomicBool::new(false));
595+
let complete_clone = Arc::clone(&complete);
596+
context.set_oncomplete(move |event| {
597+
assert_eq!(event.rendered_buffer.length(), 555);
598+
complete_clone.store(true, Ordering::Relaxed);
599+
});
600+
601+
let _ = context.start_rendering_sync();
602+
603+
assert!(complete.load(Ordering::Relaxed));
604+
}
496605
}

src/events.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::context::AudioNodeId;
2-
use crate::AudioRenderCapacityEvent;
2+
use crate::{AudioBuffer, AudioRenderCapacityEvent};
33

44
use std::any::Any;
55
use std::collections::HashMap;
@@ -37,6 +37,16 @@ pub struct ErrorEvent {
3737
pub event: Event,
3838
}
3939

40+
/// The OfflineAudioCompletionEvent Event interface
41+
#[non_exhaustive]
42+
#[derive(Debug)]
43+
pub struct OfflineAudioCompletionEvent {
44+
/// The rendered AudioBuffer
45+
pub rendered_buffer: AudioBuffer,
46+
/// Inherits from this base Event
47+
pub event: Event,
48+
}
49+
4050
#[derive(Debug)]
4151
pub(crate) enum EventPayload {
4252
None,

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub mod media_streams;
3131
pub mod node;
3232

3333
mod events;
34-
pub use events::{ErrorEvent, Event};
34+
pub use events::{ErrorEvent, Event, OfflineAudioCompletionEvent};
3535

3636
mod param;
3737
pub use param::*;

0 commit comments

Comments
 (0)