@@ -7,6 +7,7 @@ use crate::buffer::AudioBuffer;
7
7
use crate :: context:: { AudioContextState , BaseAudioContext , ConcreteBaseAudioContext } ;
8
8
use crate :: render:: RenderThread ;
9
9
use crate :: { assert_valid_sample_rate, RENDER_QUANTUM_SIZE } ;
10
+ use crate :: { Event , OfflineAudioCompletionEvent } ;
10
11
11
12
use futures_channel:: { mpsc, oneshot} ;
12
13
use futures_util:: SinkExt as _;
@@ -47,6 +48,8 @@ struct OfflineAudioContextRenderer {
47
48
suspend_callbacks : Vec < ( usize , Box < OfflineAudioContextCallback > ) > ,
48
49
/// channel to listen for `resume` calls on a suspended context
49
50
resume_receiver : mpsc:: Receiver < ( ) > ,
51
+ /// event handler for oncomplete event
52
+ oncomplete_handler : Option < Box < dyn FnOnce ( OfflineAudioCompletionEvent ) + Send + ' static > > ,
50
53
}
51
54
52
55
impl BaseAudioContext for OfflineAudioContext {
@@ -111,6 +114,7 @@ impl OfflineAudioContext {
111
114
suspend_promises : Vec :: new ( ) ,
112
115
suspend_callbacks : Vec :: new ( ) ,
113
116
resume_receiver,
117
+ oncomplete_handler : None ,
114
118
} ;
115
119
116
120
Self {
@@ -143,13 +147,16 @@ impl OfflineAudioContext {
143
147
let OfflineAudioContextRenderer {
144
148
renderer,
145
149
suspend_callbacks,
150
+ mut oncomplete_handler,
146
151
..
147
152
} = renderer;
148
153
149
154
self . base . set_state ( AudioContextState :: Running ) ;
150
155
let result = renderer. render_audiobuffer_sync ( self . length , suspend_callbacks, self ) ;
151
156
self . base . set_state ( AudioContextState :: Closed ) ;
152
157
158
+ Self :: emit_oncomplete ( & mut oncomplete_handler, & result) ;
159
+
153
160
result
154
161
}
155
162
@@ -177,6 +184,7 @@ impl OfflineAudioContext {
177
184
renderer,
178
185
suspend_promises,
179
186
resume_receiver,
187
+ mut oncomplete_handler,
180
188
..
181
189
} = renderer;
182
190
@@ -188,9 +196,24 @@ impl OfflineAudioContext {
188
196
189
197
self . base . set_state ( AudioContextState :: Closed ) ;
190
198
199
+ Self :: emit_oncomplete ( & mut oncomplete_handler, & result) ;
200
+
191
201
result
192
202
}
193
203
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
+
194
217
/// get the length of rendering audio buffer
195
218
// false positive: OfflineAudioContext is not const
196
219
#[ allow( clippy:: missing_const_for_fn, clippy:: unused_self) ]
@@ -357,12 +380,35 @@ impl OfflineAudioContext {
357
380
self . base ( ) . set_state ( AudioContextState :: Running ) ;
358
381
self . resume_sender . clone ( ) . send ( ( ) ) . await . unwrap ( )
359
382
}
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
+ }
360
405
}
361
406
362
407
#[ cfg( test) ]
363
408
mod tests {
364
409
use super :: * ;
365
410
use float_eq:: assert_float_eq;
411
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
366
412
367
413
use crate :: node:: AudioNode ;
368
414
use crate :: node:: AudioScheduledSourceNode ;
@@ -493,4 +539,20 @@ mod tests {
493
539
context. suspend_sync ( 0.0 , |_| ( ) ) ;
494
540
context. suspend_sync ( 0.0 , |_| ( ) ) ;
495
541
}
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
+ }
496
558
}
0 commit comments