@@ -61,6 +61,12 @@ type ErrorCallback = Box<dyn FnMut(crate::StreamError) + Send + 'static>;
61
61
/// Manages device disconnection listener on a dedicated thread to ensure the
62
62
/// AudioObjectPropertyListener is always created and dropped on the same thread.
63
63
/// This avoids potential threading issues with CoreAudio APIs.
64
+ ///
65
+ /// When a device disconnects, this manager:
66
+ /// 1. Attempts to pause the stream to stop audio I/O
67
+ /// 2. Calls the error callback with `StreamError::DeviceNotAvailable`
68
+ ///
69
+ /// The dedicated thread architecture ensures `Stream` can implement `Send`.
64
70
struct DisconnectManager {
65
71
_shutdown_tx : mpsc:: Sender < ( ) > ,
66
72
}
@@ -74,6 +80,7 @@ impl DisconnectManager {
74
80
) -> Result < Self , crate :: BuildStreamError > {
75
81
let ( shutdown_tx, shutdown_rx) = mpsc:: channel ( ) ;
76
82
let ( disconnect_tx, disconnect_rx) = mpsc:: channel ( ) ;
83
+ let ( ready_tx, ready_rx) = mpsc:: channel ( ) ;
77
84
78
85
// Spawn dedicated thread to own the AudioObjectPropertyListener
79
86
let disconnect_tx_clone = disconnect_tx. clone ( ) ;
@@ -85,16 +92,29 @@ impl DisconnectManager {
85
92
} ;
86
93
87
94
// Create the listener on this dedicated thread
88
- let _listener =
89
- AudioObjectPropertyListener :: new ( device_id, property_address, move || {
90
- let _ = disconnect_tx_clone. send ( ( ) ) ;
91
- } )
92
- . unwrap ( ) ;
93
-
94
- // Drop the listener on this thread after receiving a shutdown signal
95
- let _ = shutdown_rx. recv ( ) ;
95
+ match AudioObjectPropertyListener :: new ( device_id, property_address, move || {
96
+ let _ = disconnect_tx_clone. send ( ( ) ) ;
97
+ } ) {
98
+ Ok ( _listener) => {
99
+ let _ = ready_tx. send ( Ok ( ( ) ) ) ;
100
+ // Drop the listener on this thread after receiving a shutdown signal
101
+ let _ = shutdown_rx. recv ( ) ;
102
+ }
103
+ Err ( e) => {
104
+ let _ = ready_tx. send ( Err ( e) ) ;
105
+ }
106
+ }
96
107
} ) ;
97
108
109
+ // Wait for listener creation to complete or fail
110
+ ready_rx
111
+ . recv ( )
112
+ . map_err ( |_| crate :: BuildStreamError :: BackendSpecific {
113
+ err : BackendSpecificError {
114
+ description : "Disconnect listener thread terminated unexpectedly" . to_string ( ) ,
115
+ } ,
116
+ } ) ??;
117
+
98
118
// Handle disconnect events on the main thread pool
99
119
let stream_weak_clone = stream_weak. clone ( ) ;
100
120
let error_callback_clone = error_callback. clone ( ) ;
@@ -103,21 +123,24 @@ impl DisconnectManager {
103
123
// Check if stream still exists
104
124
if let Some ( stream_arc) = stream_weak_clone. upgrade ( ) {
105
125
// First, try to pause the stream to stop playback
106
- match stream_arc. lock ( ) {
107
- Ok ( mut stream_inner) => {
108
- let _ = stream_inner. pause ( ) ;
109
- }
110
- Err ( _) => {
111
- // Could not acquire lock. This can occur if there are
112
- // overlapping locks, if the stream is already in use, or if a panic
113
- // occurred during a previous lock. Still notify about device
114
- // disconnection even if we can't pause.
115
- }
126
+ if let Ok ( mut stream_inner) = stream_arc. try_lock ( ) {
127
+ let _ = stream_inner. pause ( ) ;
116
128
}
117
129
118
- // Call the error callback to notify about device disconnection
119
- if let Ok ( mut cb) = error_callback_clone. lock ( ) {
120
- cb ( crate :: StreamError :: DeviceNotAvailable ) ;
130
+ // Always try to notify about device disconnection
131
+ match error_callback_clone. try_lock ( ) {
132
+ Ok ( mut cb) => {
133
+ cb ( crate :: StreamError :: DeviceNotAvailable ) ;
134
+ }
135
+ Err ( std:: sync:: TryLockError :: WouldBlock ) => {
136
+ // Error callback is being invoked - skip this notification
137
+ }
138
+ Err ( std:: sync:: TryLockError :: Poisoned ( guard) ) => {
139
+ // Error callback panicked - try to recover and still notify
140
+ // This is critical: device disconnected AND callback is broken
141
+ let mut cb = guard. into_inner ( ) ;
142
+ cb ( crate :: StreamError :: DeviceNotAvailable ) ;
143
+ }
121
144
}
122
145
} else {
123
146
// Stream is gone, exit the handler thread
0 commit comments