@@ -5,10 +5,13 @@ use std::sync::{Arc, Mutex};
55
66use crate :: buffer:: AudioBuffer ;
77use crate :: context:: { AudioContextState , BaseAudioContext , ConcreteBaseAudioContext } ;
8+ use crate :: events:: {
9+ Event , EventDispatch , EventHandler , EventPayload , EventType , OfflineAudioCompletionEvent ,
10+ } ;
811use crate :: render:: RenderThread ;
912use crate :: { assert_valid_sample_rate, RENDER_QUANTUM_SIZE } ;
10- use crate :: { Event , OfflineAudioCompletionEvent } ;
1113
14+ use crate :: events:: EventLoop ;
1215use futures_channel:: { mpsc, oneshot} ;
1316use futures_util:: SinkExt as _;
1417
@@ -48,28 +51,14 @@ struct OfflineAudioContextRenderer {
4851 suspend_callbacks : Vec < ( usize , Box < OfflineAudioContextCallback > ) > ,
4952 /// channel to listen for `resume` calls on a suspended context
5053 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 > > ,
54+ /// event loop to run after each render quantum
55+ event_loop : EventLoop ,
5556}
5657
5758impl BaseAudioContext for OfflineAudioContext {
5859 fn base ( & self ) -> & ConcreteBaseAudioContext {
5960 & self . base
6061 }
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- }
7362}
7463
7564impl OfflineAudioContext {
@@ -100,13 +89,19 @@ impl OfflineAudioContext {
10089 let state = Arc :: new ( AtomicU8 :: new ( AudioContextState :: Suspended as u8 ) ) ;
10190 let state_clone = Arc :: clone ( & state) ;
10291
92+ // Communication channel for events from the render thread to the control thread.
93+ // Use an unbounded channel because we do not require real-time safety.
94+ let ( event_send, event_recv) = crossbeam_channel:: unbounded ( ) ;
95+ let event_loop = EventLoop :: new ( event_recv) ;
96+
10397 // setup the render 'thread', which will run inside the control thread
10498 let renderer = RenderThread :: new (
10599 sample_rate,
106100 number_of_channels,
107101 receiver,
108102 state_clone,
109103 frames_played_clone,
104+ event_send. clone ( ) ,
110105 ) ;
111106
112107 // first, setup the base audio context
@@ -116,7 +111,8 @@ impl OfflineAudioContext {
116111 state,
117112 frames_played,
118113 sender,
119- None ,
114+ event_send,
115+ event_loop. clone ( ) ,
120116 true ,
121117 node_id_consumer,
122118 ) ;
@@ -128,8 +124,7 @@ impl OfflineAudioContext {
128124 suspend_promises : Vec :: new ( ) ,
129125 suspend_callbacks : Vec :: new ( ) ,
130126 resume_receiver,
131- onstatechange_handler : None ,
132- oncomplete_handler : None ,
127+ event_loop,
133128 } ;
134129
135130 Self {
@@ -159,23 +154,25 @@ impl OfflineAudioContext {
159154 . unwrap ( )
160155 . take ( )
161156 . expect ( "InvalidStateError - Cannot call `startRendering` twice" ) ;
157+
162158 let OfflineAudioContextRenderer {
163159 renderer,
164160 suspend_callbacks,
165- oncomplete_handler,
166- mut onstatechange_handler,
161+ event_loop,
167162 ..
168163 } = renderer;
169164
170165 self . base . set_state ( AudioContextState :: Running ) ;
171- Self :: emit_statechange ( & mut onstatechange_handler) ;
172166
173- let result = renderer. render_audiobuffer_sync ( self . length , suspend_callbacks, self ) ;
167+ let result = renderer. render_audiobuffer_sync ( self , suspend_callbacks, & event_loop ) ;
174168
175169 self . base . set_state ( AudioContextState :: Closed ) ;
176- Self :: emit_statechange ( & mut onstatechange_handler) ;
170+ let _ = self
171+ . base
172+ . send_event ( EventDispatch :: complete ( result. clone ( ) ) ) ;
177173
178- Self :: emit_complete ( oncomplete_handler, & result) ;
174+ // spin the event loop once more to handle the statechange/complete events
175+ event_loop. handle_pending_events ( ) ;
179176
180177 result
181178 }
@@ -204,48 +201,27 @@ impl OfflineAudioContext {
204201 renderer,
205202 suspend_promises,
206203 resume_receiver,
207- oncomplete_handler,
208- mut onstatechange_handler,
204+ event_loop,
209205 ..
210206 } = renderer;
211207
212208 self . base . set_state ( AudioContextState :: Running ) ;
213- Self :: emit_statechange ( & mut onstatechange_handler) ;
214209
215210 let result = renderer
216- . render_audiobuffer ( self . length , suspend_promises, resume_receiver)
211+ . render_audiobuffer ( self . length , suspend_promises, resume_receiver, & event_loop )
217212 . await ;
218213
219214 self . base . set_state ( AudioContextState :: Closed ) ;
220- Self :: emit_statechange ( & mut onstatechange_handler) ;
215+ let _ = self
216+ . base
217+ . send_event ( EventDispatch :: complete ( result. clone ( ) ) ) ;
221218
222- Self :: emit_complete ( oncomplete_handler, & result) ;
219+ // spin the event loop once more to handle the statechange/complete events
220+ event_loop. handle_pending_events ( ) ;
223221
224222 result
225223 }
226224
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-
249225 /// get the length of rendering audio buffer
250226 // false positive: OfflineAudioContext is not const
251227 #[ allow( clippy:: missing_const_for_fn, clippy:: unused_self) ]
@@ -422,17 +398,24 @@ impl OfflineAudioContext {
422398 & self ,
423399 callback : F ,
424400 ) {
425- if let Some ( renderer) = self . renderer . lock ( ) . unwrap ( ) . as_mut ( ) {
426- renderer. oncomplete_handler = Some ( Box :: new ( callback) ) ;
427- }
401+ let callback = move |v| match v {
402+ EventPayload :: Complete ( v) => {
403+ let event = OfflineAudioCompletionEvent {
404+ rendered_buffer : v,
405+ event : Event { type_ : "complete" } ,
406+ } ;
407+ callback ( event)
408+ }
409+ _ => unreachable ! ( ) ,
410+ } ;
411+
412+ self . base ( )
413+ . set_event_handler ( EventType :: Complete , EventHandler :: Once ( Box :: new ( callback) ) ) ;
428414 }
429415
430416 /// Unset the callback to run when the rendering has completed
431- #[ allow( clippy:: missing_panics_doc) ]
432417 pub fn clear_oncomplete ( & self ) {
433- if let Some ( renderer) = self . renderer . lock ( ) . unwrap ( ) . as_mut ( ) {
434- renderer. oncomplete_handler = None ;
435- }
418+ self . base ( ) . clear_event_handler ( EventType :: Complete ) ;
436419 }
437420}
438421
@@ -587,6 +570,23 @@ mod tests {
587570 assert ! ( changed. load( Ordering :: Relaxed ) ) ;
588571 }
589572
573+ #[ test]
574+ fn test_onstatechange_async ( ) {
575+ use futures:: executor;
576+
577+ let 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 _ = executor:: block_on ( context. start_rendering ( ) ) ;
586+
587+ assert ! ( changed. load( Ordering :: Relaxed ) ) ;
588+ }
589+
590590 #[ test]
591591 fn test_oncomplete ( ) {
592592 let mut context = OfflineAudioContext :: new ( 2 , 555 , 44_100. ) ;
@@ -602,4 +602,22 @@ mod tests {
602602
603603 assert ! ( complete. load( Ordering :: Relaxed ) ) ;
604604 }
605+
606+ #[ test]
607+ fn test_oncomplete_async ( ) {
608+ use futures:: executor;
609+
610+ let context = OfflineAudioContext :: new ( 2 , 555 , 44_100. ) ;
611+
612+ let complete = Arc :: new ( AtomicBool :: new ( false ) ) ;
613+ let complete_clone = Arc :: clone ( & complete) ;
614+ context. set_oncomplete ( move |event| {
615+ assert_eq ! ( event. rendered_buffer. length( ) , 555 ) ;
616+ complete_clone. store ( true , Ordering :: Relaxed ) ;
617+ } ) ;
618+
619+ let _ = executor:: block_on ( context. start_rendering ( ) ) ;
620+
621+ assert ! ( complete. load( Ordering :: Relaxed ) ) ;
622+ }
605623}
0 commit comments