Skip to content

Commit a835a9d

Browse files
committed
Emit complete event for OfflineAudioContext rendering
Fixes #411
1 parent 3518169 commit a835a9d

File tree

4 files changed

+75
-2
lines changed

4 files changed

+75
-2
lines changed

src/context/base.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ pub trait BaseAudioContext {
319319
);
320320
}
321321

322+
/// Unset the callback to run when the state of the AudioContext has changed
322323
fn clear_onstatechange(&self) {
323324
self.base().clear_event_handler(EventType::StateChange);
324325
}

src/context/offline.rs

Lines changed: 62 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,6 +48,8 @@ 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 oncomplete event
52+
oncomplete_handler: Option<Box<dyn FnOnce(OfflineAudioCompletionEvent) + Send + 'static>>,
5053
}
5154

5255
impl BaseAudioContext for OfflineAudioContext {
@@ -111,6 +114,7 @@ impl OfflineAudioContext {
111114
suspend_promises: Vec::new(),
112115
suspend_callbacks: Vec::new(),
113116
resume_receiver,
117+
oncomplete_handler: None,
114118
};
115119

116120
Self {
@@ -143,13 +147,16 @@ impl OfflineAudioContext {
143147
let OfflineAudioContextRenderer {
144148
renderer,
145149
suspend_callbacks,
150+
mut oncomplete_handler,
146151
..
147152
} = renderer;
148153

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

158+
Self::emit_oncomplete(&mut oncomplete_handler, &result);
159+
153160
result
154161
}
155162

@@ -177,6 +184,7 @@ impl OfflineAudioContext {
177184
renderer,
178185
suspend_promises,
179186
resume_receiver,
187+
mut oncomplete_handler,
180188
..
181189
} = renderer;
182190

@@ -188,9 +196,24 @@ impl OfflineAudioContext {
188196

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

199+
Self::emit_oncomplete(&mut oncomplete_handler, &result);
200+
191201
result
192202
}
193203

204+
fn emit_oncomplete(
205+
oncomplete_handler: &mut Option<Box<dyn FnOnce(OfflineAudioCompletionEvent) + Send>>,
206+
result: &AudioBuffer,
207+
) {
208+
if let Some(callback) = oncomplete_handler.take() {
209+
let event = OfflineAudioCompletionEvent {
210+
rendered_buffer: result.clone(),
211+
event: Event { type_: "complete" },
212+
};
213+
(callback)(event);
214+
}
215+
}
216+
194217
/// get the length of rendering audio buffer
195218
// false positive: OfflineAudioContext is not const
196219
#[allow(clippy::missing_const_for_fn, clippy::unused_self)]
@@ -357,12 +380,35 @@ impl OfflineAudioContext {
357380
self.base().set_state(AudioContextState::Running);
358381
self.resume_sender.clone().send(()).await.unwrap()
359382
}
383+
384+
/// Register callback to run when the rendering has completed
385+
///
386+
/// Only a single event handler is active at any time. Calling this method multiple times will
387+
/// override the previous event handler.
388+
#[allow(clippy::missing_panics_doc)]
389+
pub fn set_oncomplete<F: FnOnce(OfflineAudioCompletionEvent) + Send + 'static>(
390+
&mut self,
391+
callback: F,
392+
) {
393+
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
394+
renderer.oncomplete_handler = Some(Box::new(callback));
395+
}
396+
}
397+
398+
/// Unset the callback to run when the rendering has completed
399+
#[allow(clippy::missing_panics_doc)]
400+
pub fn clear_oncomplete(&mut self) {
401+
if let Some(renderer) = self.renderer.lock().unwrap().as_mut() {
402+
renderer.oncomplete_handler = None;
403+
}
404+
}
360405
}
361406

362407
#[cfg(test)]
363408
mod tests {
364409
use super::*;
365410
use float_eq::assert_float_eq;
411+
use std::sync::atomic::{AtomicBool, Ordering};
366412

367413
use crate::node::AudioNode;
368414
use crate::node::AudioScheduledSourceNode;
@@ -493,4 +539,20 @@ mod tests {
493539
context.suspend_sync(0.0, |_| ());
494540
context.suspend_sync(0.0, |_| ());
495541
}
542+
543+
#[test]
544+
fn test_oncomplete() {
545+
let mut context = OfflineAudioContext::new(2, 555, 44_100.);
546+
547+
let complete = Arc::new(AtomicBool::new(false));
548+
let complete_clone = Arc::clone(&complete);
549+
context.set_oncomplete(move |event| {
550+
assert_eq!(event.rendered_buffer.length(), 555);
551+
complete_clone.store(true, Ordering::Relaxed);
552+
});
553+
554+
let _ = context.start_rendering_sync();
555+
556+
assert!(complete.load(Ordering::Relaxed));
557+
}
496558
}

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)