@@ -279,21 +279,26 @@ impl AudioContext {
279
279
/// is currently not implemented.
280
280
#[ allow( clippy:: needless_collect, clippy:: missing_panics_doc) ]
281
281
pub fn set_sink_id_sync ( & self , sink_id : String ) -> Result < ( ) , Box < dyn Error > > {
282
+ log:: debug!( "SinkChange requested" ) ;
282
283
if self . sink_id ( ) == sink_id {
284
+ log:: debug!( "SinkChange: no-op" ) ;
283
285
return Ok ( ( ) ) ; // sink is already active
284
286
}
285
287
286
288
if !is_valid_sink_id ( & sink_id) {
287
289
Err ( format ! ( "NotFoundError: invalid sinkId {sink_id}" ) ) ?;
288
290
} ;
289
291
292
+ log:: debug!( "SinkChange: locking backend manager" ) ;
290
293
let mut backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
291
294
let original_state = self . state ( ) ;
292
295
if original_state == AudioContextState :: Closed {
296
+ log:: debug!( "SinkChange: context is closed" ) ;
293
297
return Ok ( ( ) ) ;
294
298
}
295
299
296
300
// Acquire exclusive lock on ctrl msg sender
301
+ log:: debug!( "SinkChange: locking message channel" ) ;
297
302
let ctrl_msg_send = self . base . lock_control_msg_sender ( ) ;
298
303
299
304
// Flush out the ctrl msg receiver, cache
@@ -303,13 +308,17 @@ impl AudioContext {
303
308
let graph = if matches ! ( pending_msgs. first( ) , Some ( ControlMessage :: Startup { .. } ) ) {
304
309
// Handle the edge case where the previous backend was suspended for its entire lifetime.
305
310
// In this case, the `Startup` control message was never processed.
311
+ log:: debug!( "SinkChange: recover unstarted graph" ) ;
312
+
306
313
let msg = pending_msgs. remove ( 0 ) ;
307
314
match msg {
308
315
ControlMessage :: Startup { graph } => graph,
309
316
_ => unreachable ! ( ) ,
310
317
}
311
318
} else {
312
319
// Acquire the audio graph from the current render thread, shutting it down
320
+ log:: debug!( "SinkChange: recover graph from render thread" ) ;
321
+
313
322
let ( graph_send, graph_recv) = crossbeam_channel:: bounded ( 1 ) ;
314
323
let message = ControlMessage :: CloseAndRecycle { sender : graph_send } ;
315
324
ctrl_msg_send. send ( message) . unwrap ( ) ;
@@ -321,6 +330,7 @@ impl AudioContext {
321
330
graph_recv. recv ( ) . unwrap ( )
322
331
} ;
323
332
333
+ log:: debug!( "SinkChange: closing audio stream" ) ;
324
334
backend_manager_guard. close ( ) ;
325
335
326
336
// hotswap the backend
@@ -330,10 +340,12 @@ impl AudioContext {
330
340
sink_id,
331
341
render_size_hint : AudioContextRenderSizeCategory :: default ( ) , // todo reuse existing setting
332
342
} ;
343
+ log:: debug!( "SinkChange: starting audio stream" ) ;
333
344
* backend_manager_guard = io:: build_output ( options, self . render_thread_init . clone ( ) ) ;
334
345
335
346
// if the previous backend state was suspend, suspend the new one before shipping the graph
336
347
if original_state == AudioContextState :: Suspended {
348
+ log:: debug!( "SinkChange: suspending audio stream" ) ;
337
349
backend_manager_guard. suspend ( ) ;
338
350
}
339
351
@@ -352,6 +364,7 @@ impl AudioContext {
352
364
// trigger event when all the work is done
353
365
let _ = self . base . send_event ( EventDispatch :: sink_change ( ) ) ;
354
366
367
+ log:: debug!( "SinkChange: done" ) ;
355
368
Ok ( ( ) )
356
369
}
357
370
@@ -422,18 +435,30 @@ impl AudioContext {
422
435
/// * The audio device is not available
423
436
/// * For a `BackendSpecificError`
424
437
pub async fn suspend ( & self ) {
425
- // First, pause rendering via a control message
438
+ // Don't lock the backend manager because we can't hold is across the await point
439
+ log:: debug!( "Suspend called" ) ;
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
426
447
let ( sender, receiver) = oneshot:: channel ( ) ;
427
448
let notify = OneshotNotify :: Async ( sender) ;
428
449
self . base
429
450
. send_control_msg ( ControlMessage :: Suspend { notify } ) ;
430
451
431
452
// Wait for the render thread to have processed the suspend message.
432
453
// The AudioContextState will be updated by the render thread.
454
+ log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
433
455
receiver. await . unwrap ( ) ;
434
456
435
457
// Then ask the audio host to suspend the stream
458
+ log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
436
459
self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
460
+
461
+ log:: debug!( "Suspended audio stream" ) ;
437
462
}
438
463
439
464
/// Resumes the progression of time in an audio context that has previously been
@@ -445,19 +470,34 @@ impl AudioContext {
445
470
///
446
471
/// * The audio device is not available
447
472
/// * For a `BackendSpecificError`
473
+ #[ allow( clippy:: await_holding_lock) ] // false positive due to explicit drop
448
474
pub async fn resume ( & self ) {
449
- // First ask the audio host to resume the stream
450
- 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 ( ) ;
451
486
452
487
// Then, ask to resume rendering via a control message
488
+ log:: debug!( "Resumed audio stream, waking audio graph" ) ;
453
489
let ( sender, receiver) = oneshot:: channel ( ) ;
454
490
let notify = OneshotNotify :: Async ( sender) ;
455
491
self . base
456
492
. send_control_msg ( ControlMessage :: Resume { notify } ) ;
457
493
494
+ // Drop the Mutex guard so we won't hold it across an await
495
+ drop ( backend_manager_guard) ;
496
+
458
497
// Wait for the render thread to have processed the resume message
459
498
// The AudioContextState will be updated by the render thread.
460
499
receiver. await . unwrap ( ) ;
500
+ log:: debug!( "Resumed audio graph" ) ;
461
501
}
462
502
463
503
/// Closes the `AudioContext`, releasing the system resources being used.
@@ -469,22 +509,37 @@ impl AudioContext {
469
509
///
470
510
/// Will panic when this function is called multiple times
471
511
pub async fn close ( & self ) {
472
- // First, stop rendering via a control message
473
- let ( sender, receiver) = oneshot:: channel ( ) ;
474
- let notify = OneshotNotify :: Async ( sender) ;
475
- self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
512
+ // Don't lock the backend manager because we can't hold is across the await point
513
+ log:: debug!( "Close called" ) ;
476
514
477
- // Wait for the render thread to have processed the suspend message.
478
- // The AudioContextState will be updated by the render thread.
479
- receiver. await . unwrap ( ) ;
515
+ if self . state ( ) == AudioContextState :: Closed {
516
+ log:: debug!( "Close no-op - context is already closed" ) ;
517
+ return ;
518
+ }
519
+
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
+ }
480
534
481
535
// Then ask the audio host to close the stream
536
+ log:: debug!( "Suspended audio graph. Closing audio stream.." ) ;
482
537
self . backend_manager . lock ( ) . unwrap ( ) . close ( ) ;
483
538
484
539
// Stop the AudioRenderCapacity collection thread
485
540
self . render_capacity . stop ( ) ;
486
541
487
- // TODO stop the event loop <https://github.com/orottier/web- audio-api-rs/issues/421>
542
+ log :: debug! ( "Closed audio stream" ) ;
488
543
}
489
544
490
545
/// Suspends the progression of time in the audio context.
@@ -502,18 +557,31 @@ impl AudioContext {
502
557
/// * The audio device is not available
503
558
/// * For a `BackendSpecificError`
504
559
pub fn suspend_sync ( & self ) {
505
- // 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
506
570
let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
507
571
let notify = OneshotNotify :: Sync ( sender) ;
508
572
self . base
509
573
. send_control_msg ( ControlMessage :: Suspend { notify } ) ;
510
574
511
575
// Wait for the render thread to have processed the suspend message.
512
576
// The AudioContextState will be updated by the render thread.
577
+ log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
513
578
receiver. recv ( ) . ok ( ) ;
514
579
515
580
// Then ask the audio host to suspend the stream
516
- self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
581
+ log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
582
+ backend_manager_guard. suspend ( ) ;
583
+
584
+ log:: debug!( "Suspended audio stream" ) ;
517
585
}
518
586
519
587
/// Resumes the progression of time in an audio context that has previously been
@@ -529,10 +597,20 @@ impl AudioContext {
529
597
/// * The audio device is not available
530
598
/// * For a `BackendSpecificError`
531
599
pub fn resume_sync ( & self ) {
532
- // First ask the audio host to resume the stream
533
- 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 ( ) ;
534
611
535
612
// Then, ask to resume rendering via a control message
613
+ log:: debug!( "Resumed audio stream, waking audio graph" ) ;
536
614
let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
537
615
let notify = OneshotNotify :: Sync ( sender) ;
538
616
self . base
@@ -541,6 +619,7 @@ impl AudioContext {
541
619
// Wait for the render thread to have processed the resume message
542
620
// The AudioContextState will be updated by the render thread.
543
621
receiver. recv ( ) . ok ( ) ;
622
+ log:: debug!( "Resumed audio graph" ) ;
544
623
}
545
624
546
625
/// Closes the `AudioContext`, releasing the system resources being used.
@@ -555,22 +634,38 @@ impl AudioContext {
555
634
///
556
635
/// Will panic when this function is called multiple times
557
636
pub fn close_sync ( & self ) {
558
- // First, stop rendering via a control message
559
- let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
560
- let notify = OneshotNotify :: Sync ( sender) ;
561
- self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
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 ( ) ;
562
640
563
- // Wait for the render thread to have processed the suspend message.
564
- // The AudioContextState will be updated by the render thread.
565
- receiver. recv ( ) . ok ( ) ;
641
+ if self . state ( ) == AudioContextState :: Closed {
642
+ log:: debug!( "Close no-op - context is already closed" ) ;
643
+ return ;
644
+ }
645
+
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
+ }
566
660
567
661
// Then ask the audio host to close the stream
568
- self . backend_manager . lock ( ) . unwrap ( ) . close ( ) ;
662
+ log:: debug!( "Suspended audio graph. Closing audio stream.." ) ;
663
+ backend_manager_guard. close ( ) ;
569
664
570
665
// Stop the AudioRenderCapacity collection thread
571
666
self . render_capacity . stop ( ) ;
572
667
573
- // TODO stop the event loop <https://github.com/orottier/web- audio-api-rs/issues/421>
668
+ log :: debug! ( "Closed audio stream" ) ;
574
669
}
575
670
576
671
/// Creates a [`MediaStreamAudioSourceNode`](node::MediaStreamAudioSourceNode) from a
0 commit comments