@@ -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,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
5257impl 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
5875impl 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) ]
363440mod 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}
0 commit comments