@@ -7,6 +7,7 @@ use crate::buffer::AudioBuffer;
77use crate :: context:: { AudioContextState , BaseAudioContext , ConcreteBaseAudioContext } ;
88use crate :: render:: RenderThread ;
99use crate :: { assert_valid_sample_rate, RENDER_QUANTUM_SIZE } ;
10+ use crate :: { Event , OfflineAudioCompletionEvent } ;
1011
1112use futures_channel:: { mpsc, oneshot} ;
1213use 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
5255impl 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) ]
363408mod 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}
0 commit comments