@@ -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,12 +48,28 @@ 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 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 > > ,
50
55
}
51
56
52
57
impl BaseAudioContext for OfflineAudioContext {
53
58
fn base ( & self ) -> & ConcreteBaseAudioContext {
54
59
& self . base
55
60
}
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
+ }
56
73
}
57
74
58
75
impl OfflineAudioContext {
@@ -111,6 +128,8 @@ impl OfflineAudioContext {
111
128
suspend_promises : Vec :: new ( ) ,
112
129
suspend_callbacks : Vec :: new ( ) ,
113
130
resume_receiver,
131
+ onstatechange_handler : None ,
132
+ oncomplete_handler : None ,
114
133
} ;
115
134
116
135
Self {
@@ -143,12 +162,20 @@ impl OfflineAudioContext {
143
162
let OfflineAudioContextRenderer {
144
163
renderer,
145
164
suspend_callbacks,
165
+ oncomplete_handler,
166
+ mut onstatechange_handler,
146
167
..
147
168
} = renderer;
148
169
149
170
self . base . set_state ( AudioContextState :: Running ) ;
171
+ Self :: emit_statechange ( & mut onstatechange_handler) ;
172
+
150
173
let result = renderer. render_audiobuffer_sync ( self . length , suspend_callbacks, self ) ;
174
+
151
175
self . base . set_state ( AudioContextState :: Closed ) ;
176
+ Self :: emit_statechange ( & mut onstatechange_handler) ;
177
+
178
+ Self :: emit_complete ( oncomplete_handler, & result) ;
152
179
153
180
result
154
181
}
@@ -177,20 +204,48 @@ impl OfflineAudioContext {
177
204
renderer,
178
205
suspend_promises,
179
206
resume_receiver,
207
+ oncomplete_handler,
208
+ mut onstatechange_handler,
180
209
..
181
210
} = renderer;
182
211
183
212
self . base . set_state ( AudioContextState :: Running ) ;
213
+ Self :: emit_statechange ( & mut onstatechange_handler) ;
184
214
185
215
let result = renderer
186
216
. render_audiobuffer ( self . length , suspend_promises, resume_receiver)
187
217
. await ;
188
218
189
219
self . base . set_state ( AudioContextState :: Closed ) ;
220
+ Self :: emit_statechange ( & mut onstatechange_handler) ;
221
+
222
+ Self :: emit_complete ( oncomplete_handler, & result) ;
190
223
191
224
result
192
225
}
193
226
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
+
194
249
/// get the length of rendering audio buffer
195
250
// false positive: OfflineAudioContext is not const
196
251
#[ allow( clippy:: missing_const_for_fn, clippy:: unused_self) ]
@@ -357,12 +412,35 @@ impl OfflineAudioContext {
357
412
self . base ( ) . set_state ( AudioContextState :: Running ) ;
358
413
self . resume_sender . clone ( ) . send ( ( ) ) . await . unwrap ( )
359
414
}
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
+ }
360
437
}
361
438
362
439
#[ cfg( test) ]
363
440
mod tests {
364
441
use super :: * ;
365
442
use float_eq:: assert_float_eq;
443
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
366
444
367
445
use crate :: node:: AudioNode ;
368
446
use crate :: node:: AudioScheduledSourceNode ;
@@ -493,4 +571,35 @@ mod tests {
493
571
context. suspend_sync ( 0.0 , |_| ( ) ) ;
494
572
context. suspend_sync ( 0.0 , |_| ( ) ) ;
495
573
}
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
+ }
496
605
}
0 commit comments