@@ -435,8 +435,15 @@ impl AudioContext {
435
435
/// * The audio device is not available
436
436
/// * For a `BackendSpecificError`
437
437
pub async fn suspend ( & self ) {
438
+ // Don't lock the backend manager because we can't hold is across the await point
438
439
log:: debug!( "Suspend called" ) ;
439
- // First, pause rendering via a control message
440
+
441
+ if self . state ( ) != AudioContextState :: Running {
442
+ log:: debug!( "Suspend no-op - context is not running" ) ;
443
+ return ;
444
+ }
445
+
446
+ // Pause rendering via a control message
440
447
let ( sender, receiver) = oneshot:: channel ( ) ;
441
448
let notify = OneshotNotify :: Async ( sender) ;
442
449
self . base
@@ -450,6 +457,7 @@ impl AudioContext {
450
457
// Then ask the audio host to suspend the stream
451
458
log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
452
459
self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
460
+
453
461
log:: debug!( "Suspended audio stream" ) ;
454
462
}
455
463
@@ -462,10 +470,19 @@ impl AudioContext {
462
470
///
463
471
/// * The audio device is not available
464
472
/// * For a `BackendSpecificError`
473
+ #[ allow( clippy:: await_holding_lock) ] // false positive due to explicit drop
465
474
pub async fn resume ( & self ) {
466
- log:: debug!( "Resume called" ) ;
467
- // First ask the audio host to resume the stream
468
- self . backend_manager . lock ( ) . unwrap ( ) . resume ( ) ;
475
+ // Lock the backend manager mutex to avoid concurrent calls
476
+ log:: debug!( "Resume called, locking backend manager" ) ;
477
+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
478
+
479
+ if self . state ( ) != AudioContextState :: Suspended {
480
+ log:: debug!( "Resume no-op - context is not suspended" ) ;
481
+ return ;
482
+ }
483
+
484
+ // Ask the audio host to resume the stream
485
+ backend_manager_guard. resume ( ) ;
469
486
470
487
// Then, ask to resume rendering via a control message
471
488
log:: debug!( "Resumed audio stream, waking audio graph" ) ;
@@ -474,6 +491,9 @@ impl AudioContext {
474
491
self . base
475
492
. send_control_msg ( ControlMessage :: Resume { notify } ) ;
476
493
494
+ // Drop the Mutex guard so we won't hold it across an await
495
+ drop ( backend_manager_guard) ;
496
+
477
497
// Wait for the render thread to have processed the resume message
478
498
// The AudioContextState will be updated by the render thread.
479
499
receiver. await . unwrap ( ) ;
@@ -489,17 +509,28 @@ impl AudioContext {
489
509
///
490
510
/// Will panic when this function is called multiple times
491
511
pub async fn close ( & self ) {
512
+ // Don't lock the backend manager because we can't hold is across the await point
492
513
log:: debug!( "Close called" ) ;
493
514
494
- // First, stop rendering via a control message
495
- let ( sender , receiver ) = oneshot :: channel ( ) ;
496
- let notify = OneshotNotify :: Async ( sender ) ;
497
- self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
515
+ if self . state ( ) == AudioContextState :: Closed {
516
+ log :: debug! ( "Close no-op - context is already closed" ) ;
517
+ return ;
518
+ }
498
519
499
- // Wait for the render thread to have processed the suspend message.
500
- // The AudioContextState will be updated by the render thread.
501
- log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
502
- receiver. await . unwrap ( ) ;
520
+ if self . state ( ) == AudioContextState :: Running {
521
+ // First, stop rendering via a control message
522
+ let ( sender, receiver) = oneshot:: channel ( ) ;
523
+ let notify = OneshotNotify :: Async ( sender) ;
524
+ self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
525
+
526
+ // Wait for the render thread to have processed the suspend message.
527
+ // The AudioContextState will be updated by the render thread.
528
+ log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
529
+ receiver. await . unwrap ( ) ;
530
+ } else {
531
+ // if the context is not running, change the state manually
532
+ self . base . set_state ( AudioContextState :: Closed ) ;
533
+ }
503
534
504
535
// Then ask the audio host to close the stream
505
536
log:: debug!( "Suspended audio graph. Closing audio stream.." ) ;
@@ -526,8 +557,16 @@ impl AudioContext {
526
557
/// * The audio device is not available
527
558
/// * For a `BackendSpecificError`
528
559
pub fn suspend_sync ( & self ) {
529
- log:: debug!( "Suspend_sync called" ) ;
530
- // First, pause rendering via a control message
560
+ // Lock the backend manager mutex to avoid concurrent calls
561
+ log:: debug!( "Suspend_sync called, locking backend manager" ) ;
562
+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
563
+
564
+ if self . state ( ) != AudioContextState :: Running {
565
+ log:: debug!( "Suspend_sync no-op - context is not running" ) ;
566
+ return ;
567
+ }
568
+
569
+ // Pause rendering via a control message
531
570
let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
532
571
let notify = OneshotNotify :: Sync ( sender) ;
533
572
self . base
@@ -537,10 +576,11 @@ impl AudioContext {
537
576
// The AudioContextState will be updated by the render thread.
538
577
log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
539
578
receiver. recv ( ) . ok ( ) ;
540
- log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
541
579
542
580
// Then ask the audio host to suspend the stream
543
- self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
581
+ log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
582
+ backend_manager_guard. suspend ( ) ;
583
+
544
584
log:: debug!( "Suspended audio stream" ) ;
545
585
}
546
586
@@ -557,9 +597,17 @@ impl AudioContext {
557
597
/// * The audio device is not available
558
598
/// * For a `BackendSpecificError`
559
599
pub fn resume_sync ( & self ) {
560
- log:: debug!( "Resume_sync called" ) ;
561
- // First ask the audio host to resume the stream
562
- self . backend_manager . lock ( ) . unwrap ( ) . resume ( ) ;
600
+ // Lock the backend manager mutex to avoid concurrent calls
601
+ log:: debug!( "Resume_sync called, locking backend manager" ) ;
602
+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
603
+
604
+ if self . state ( ) != AudioContextState :: Suspended {
605
+ log:: debug!( "Resume no-op - context is not suspended" ) ;
606
+ return ;
607
+ }
608
+
609
+ // Ask the audio host to resume the stream
610
+ backend_manager_guard. resume ( ) ;
563
611
564
612
// Then, ask to resume rendering via a control message
565
613
log:: debug!( "Resumed audio stream, waking audio graph" ) ;
@@ -586,21 +634,33 @@ impl AudioContext {
586
634
///
587
635
/// Will panic when this function is called multiple times
588
636
pub fn close_sync ( & self ) {
589
- log:: debug!( "Close_sync called" ) ;
637
+ // Lock the backend manager mutex to avoid concurrent calls
638
+ log:: debug!( "Close_sync called, locking backend manager" ) ;
639
+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
590
640
591
- // First, stop rendering via a control message
592
- let ( sender , receiver ) = crossbeam_channel :: bounded ( 0 ) ;
593
- let notify = OneshotNotify :: Sync ( sender ) ;
594
- self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
641
+ if self . state ( ) == AudioContextState :: Closed {
642
+ log :: debug! ( "Close no-op - context is already closed" ) ;
643
+ return ;
644
+ }
595
645
596
- // Wait for the render thread to have processed the suspend message.
597
- // The AudioContextState will be updated by the render thread.
598
- log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
599
- receiver. recv ( ) . ok ( ) ;
646
+ // First, stop rendering via a control message
647
+ if self . state ( ) == AudioContextState :: Running {
648
+ let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
649
+ let notify = OneshotNotify :: Sync ( sender) ;
650
+ self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
651
+
652
+ // Wait for the render thread to have processed the suspend message.
653
+ // The AudioContextState will be updated by the render thread.
654
+ log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
655
+ receiver. recv ( ) . ok ( ) ;
656
+ } else {
657
+ // if the context is not running, change the state manually
658
+ self . base . set_state ( AudioContextState :: Closed ) ;
659
+ }
600
660
601
661
// Then ask the audio host to close the stream
602
662
log:: debug!( "Suspended audio graph. Closing audio stream.." ) ;
603
- self . backend_manager . lock ( ) . unwrap ( ) . close ( ) ;
663
+ backend_manager_guard . close ( ) ;
604
664
605
665
// Stop the AudioRenderCapacity collection thread
606
666
self . render_capacity . stop ( ) ;
0 commit comments