@@ -13,6 +13,8 @@ use crate::render::graph::Graph;
13
13
use crate :: MediaElement ;
14
14
use crate :: { AudioRenderCapacity , Event } ;
15
15
16
+ use futures:: channel:: oneshot;
17
+
16
18
/// Check if the provided sink_id is available for playback
17
19
///
18
20
/// It should be "", "none" or a valid output `sinkId` returned from [`enumerate_devices_sync`]
@@ -378,8 +380,86 @@ impl AudioContext {
378
380
/// This will temporarily halt audio hardware access and reducing CPU/battery usage in the
379
381
/// process.
380
382
///
381
- /// This function operates synchronously and might block the current thread. An async version
382
- /// is currently not implemented.
383
+ /// # Panics
384
+ ///
385
+ /// Will panic if:
386
+ ///
387
+ /// * The audio device is not available
388
+ /// * For a `BackendSpecificError`
389
+ pub async fn suspend ( & self ) {
390
+ // First, pause rendering via a control message
391
+ let ( sender, receiver) = oneshot:: channel ( ) ;
392
+ let notify = OneshotNotify :: Async ( sender) ;
393
+ let suspend_msg = ControlMessage :: Suspend { notify } ;
394
+ self . base . send_control_msg ( suspend_msg) ;
395
+
396
+ // Wait for the render thread to have processed the suspend message.
397
+ // The AudioContextState will be updated by the render thread.
398
+ receiver. await . unwrap ( ) ;
399
+
400
+ // Then ask the audio host to suspend the stream
401
+ self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
402
+ }
403
+
404
+ /// Resumes the progression of time in an audio context that has previously been
405
+ /// suspended/paused.
406
+ ///
407
+ /// # Panics
408
+ ///
409
+ /// Will panic if:
410
+ ///
411
+ /// * The audio device is not available
412
+ /// * For a `BackendSpecificError`
413
+ pub async fn resume ( & self ) {
414
+ // First ask the audio host to resume the stream
415
+ self . backend_manager . lock ( ) . unwrap ( ) . resume ( ) ;
416
+
417
+ // Then, ask to resume rendering via a control message
418
+ let ( sender, receiver) = oneshot:: channel ( ) ;
419
+ let notify = OneshotNotify :: Async ( sender) ;
420
+ let suspend_msg = ControlMessage :: Resume { notify } ;
421
+ self . base . send_control_msg ( suspend_msg) ;
422
+
423
+ // Wait for the render thread to have processed the resume message
424
+ // The AudioContextState will be updated by the render thread.
425
+ receiver. await . unwrap ( ) ;
426
+ }
427
+
428
+ /// Closes the `AudioContext`, releasing the system resources being used.
429
+ ///
430
+ /// This will not automatically release all `AudioContext`-created objects, but will suspend
431
+ /// the progression of the currentTime, and stop processing audio data.
432
+ ///
433
+ /// # Panics
434
+ ///
435
+ /// Will panic when this function is called multiple times
436
+ pub async fn close ( & self ) {
437
+ // First, stop rendering via a control message
438
+ let ( sender, receiver) = oneshot:: channel ( ) ;
439
+ let notify = OneshotNotify :: Async ( sender) ;
440
+ let suspend_msg = ControlMessage :: Close { notify } ;
441
+ self . base . send_control_msg ( suspend_msg) ;
442
+
443
+ // Wait for the render thread to have processed the suspend message.
444
+ // The AudioContextState will be updated by the render thread.
445
+ receiver. await . unwrap ( ) ;
446
+
447
+ // Then ask the audio host to close the stream
448
+ self . backend_manager . lock ( ) . unwrap ( ) . close ( ) ;
449
+
450
+ // Stop the AudioRenderCapacity collection thread
451
+ self . render_capacity . stop ( ) ;
452
+
453
+ // TODO stop the event loop <https://github.com/orottier/web-audio-api-rs/issues/421>
454
+ }
455
+
456
+ /// Suspends the progression of time in the audio context.
457
+ ///
458
+ /// This will temporarily halt audio hardware access and reducing CPU/battery usage in the
459
+ /// process.
460
+ ///
461
+ /// This function operates synchronously and blocks the current thread until the audio thread
462
+ /// has stopped processing.
383
463
///
384
464
/// # Panics
385
465
///
@@ -405,8 +485,8 @@ impl AudioContext {
405
485
/// Resumes the progression of time in an audio context that has previously been
406
486
/// suspended/paused.
407
487
///
408
- /// This function operates synchronously and might block the current thread. An async version
409
- /// is currently not implemented .
488
+ /// This function operates synchronously and blocks the current thread until the audio thread
489
+ /// has started processing again .
410
490
///
411
491
/// # Panics
412
492
///
@@ -434,13 +514,12 @@ impl AudioContext {
434
514
/// This will not automatically release all `AudioContext`-created objects, but will suspend
435
515
/// the progression of the currentTime, and stop processing audio data.
436
516
///
437
- /// This function operates synchronously and might block the current thread. An async version
438
- /// is currently not implemented .
517
+ /// This function operates synchronously and blocks the current thread until the audio thread
518
+ /// has stopped processing .
439
519
///
440
520
/// # Panics
441
521
///
442
522
/// Will panic when this function is called multiple times
443
- #[ allow( clippy:: missing_const_for_fn, clippy:: unused_self) ]
444
523
pub fn close_sync ( & self ) {
445
524
// First, stop rendering via a control message
446
525
let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
@@ -511,3 +590,53 @@ impl AudioContext {
511
590
& self . render_capacity
512
591
}
513
592
}
593
+
594
+ #[ cfg( test) ]
595
+ mod tests {
596
+ use super :: * ;
597
+ use futures:: executor;
598
+
599
+ #[ test]
600
+ fn test_suspend_resume_close ( ) {
601
+ let options = AudioContextOptions {
602
+ sink_id : "none" . into ( ) ,
603
+ ..AudioContextOptions :: default ( )
604
+ } ;
605
+
606
+ // construct with 'none' sink_id
607
+ let context = AudioContext :: new ( options) ;
608
+
609
+ // allow some time to progress
610
+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 1 ) ) ;
611
+
612
+ executor:: block_on ( context. suspend ( ) ) ;
613
+ assert_eq ! ( context. state( ) , AudioContextState :: Suspended ) ;
614
+ let time1 = context. current_time ( ) ;
615
+ assert ! ( time1 >= 0. ) ;
616
+
617
+ // allow some time to progress
618
+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 1 ) ) ;
619
+ let time2 = context. current_time ( ) ;
620
+ assert_eq ! ( time1, time2) ; // no progression of time
621
+
622
+ executor:: block_on ( context. resume ( ) ) ;
623
+ assert_eq ! ( context. state( ) , AudioContextState :: Running ) ;
624
+
625
+ // allow some time to progress
626
+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 1 ) ) ;
627
+
628
+ let time3 = context. current_time ( ) ;
629
+ assert ! ( time3 > time2) ; // time is progressing
630
+
631
+ executor:: block_on ( context. close ( ) ) ;
632
+ assert_eq ! ( context. state( ) , AudioContextState :: Closed ) ;
633
+
634
+ let time4 = context. current_time ( ) ;
635
+
636
+ // allow some time to progress
637
+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 1 ) ) ;
638
+
639
+ let time5 = context. current_time ( ) ;
640
+ assert_eq ! ( time5, time4) ; // no progression of time
641
+ }
642
+ }
0 commit comments