@@ -244,83 +244,34 @@ impl VolumeControlImpl for MacOSVolumeControl {
244244 }
245245
246246 fn set_change_callback ( & mut self , callback : VolumeChangeCallback ) -> Result < ( ) , String > {
247- // TEMPORARY: Disable property listeners to test if they're causing audio issues
248- // The CoreAudio property listener was interfering with audio playback (static noise)
249- // TODO: Find alternative approach - polling, different API, or accept one-way control
250- eprintln ! ( "[VolumeControl] macOS volume change listener temporarily disabled" ) ;
251- let _ = callback;
252- Ok ( ( ) )
253-
254- /* DISABLED CODE - was causing audio playback issues
255- // Property listener callback - called when volume or mute changes
256- // CRITICAL: This runs on CoreAudio's real-time audio thread and must be FAST
257- // Do minimal work here - just signal that a change occurred
258- // This callback is LOCK-FREE - no mutexes, no allocations
259- #[allow(clippy::items_after_statements)]
260- unsafe extern "C" fn property_listener(
261- _device_id: AudioObjectID,
262- _num_addresses: u32,
263- _addresses: *const AudioObjectPropertyAddress,
264- client_data: *mut std::ffi::c_void,
265- ) -> OSStatus {
266- if client_data.is_null() {
267- return 0;
268- }
269-
270- // Reconstruct the Arc<Sender> from the raw pointer (but keep it alive)
271- let sender_arc = Arc::from_raw(client_data as *const std::sync::mpsc::Sender<()>);
272-
273- // Send signal - this is non-blocking on unbounded channels
274- // If send fails, just ignore it (channel closed, controller dropped)
275- let _ = sender_arc.send(());
276-
277- // Keep the Arc alive for next callback
278- mem::forget(sender_arc);
279-
280- 0
281- }
282-
283- // Create a channel for signaling changes from audio thread
284- let (change_tx, change_rx) = std::sync::mpsc::channel::<()>();
285-
286- // Keep the sender alive for the duration of the controller
287- self._change_signal = Some(change_tx.clone());
288-
289- // Spawn worker thread to handle volume reading off the audio thread
247+ // Use polling instead of property listeners to avoid interfering with audio playback
248+ // CoreAudio property listeners were causing static noise during playback
290249 let device_id = self . device_id ;
291250 let last_self_change = Arc :: clone ( & self . last_self_change ) ;
292- let worker_thread = std::thread::spawn(move || {
293- use std::time::{Duration, Instant};
294251
295- // Rate limiting: minimum time between notifications
296- const MIN_NOTIFICATION_INTERVAL: Duration = Duration::from_millis(50);
297- // Ignore notifications within this window after self-initiated changes
298- const SELF_CHANGE_GRACE_PERIOD: u64 = 200; // milliseconds
252+ let polling_thread = std:: thread:: spawn ( move || {
253+ use std:: time:: Duration ;
254+
255+ const POLL_INTERVAL : Duration = Duration :: from_secs ( 2 ) ;
256+ const SELF_CHANGE_GRACE_PERIOD : u64 = 1000 ; // milliseconds
299257
300- let mut last_notification = Instant::now();
301258 let mut last_values: Option < ( u8 , bool ) > = None ;
302259
303- while let Ok(()) = change_rx.recv() {
304- // Drain any pending signals to coalesce rapid-fire events
305- while change_rx.try_recv().is_ok() {}
260+ loop {
261+ std:: thread:: sleep ( POLL_INTERVAL ) ;
306262
307- // Check if this change was self-initiated (within grace period)
263+ // Check if this was recently self-initiated
308264 let now_ms = SystemTime :: now ( )
309265 . duration_since ( UNIX_EPOCH )
310266 . unwrap ( )
311267 . as_millis ( ) as u64 ;
312268 let last_self_ms = last_self_change. load ( Ordering :: Relaxed ) ;
313269 if now_ms. saturating_sub ( last_self_ms) < SELF_CHANGE_GRACE_PERIOD {
314- // Skip notification - this was triggered by our own volume change
315- continue;
316- }
317-
318- // Rate limit: only process if enough time has passed
319- if last_notification.elapsed() < MIN_NOTIFICATION_INTERVAL {
270+ // Skip - recently set by us
320271 continue ;
321272 }
322273
323- // Read current volume and mute state (off audio thread)
274+ // Read current volume
324275 let volume_result = unsafe {
325276 let property_address = AudioObjectPropertyAddress {
326277 mSelector : kAudioDevicePropertyVolumeScalar,
@@ -347,6 +298,7 @@ impl VolumeControlImpl for MacOSVolumeControl {
347298 }
348299 } ;
349300
301+ // Read current mute state
350302 let mute_result = unsafe {
351303 let property_address = AudioObjectPropertyAddress {
352304 mSelector : kAudioDevicePropertyMute,
@@ -377,83 +329,25 @@ impl VolumeControlImpl for MacOSVolumeControl {
377329 }
378330 } ;
379331
380- // Send notification only if values changed and we successfully read both
332+ // Send notification only if values changed
381333 if let ( Some ( volume) , Some ( muted) ) = ( volume_result, mute_result) {
382334 let current_values = ( volume, muted) ;
383335
384- // Only notify if values actually changed
385- if last_values != Some(current_values) && callback.send(current_values).is_ok()
386- {
387- last_values = Some(current_values);
388- last_notification = Instant::now();
336+ if last_values != Some ( current_values) {
337+ if callback. send ( current_values) . is_ok ( ) {
338+ last_values = Some ( current_values) ;
339+ } else {
340+ // Channel closed, exit thread
341+ break ;
342+ }
389343 }
390344 }
391345 }
392346 } ) ;
393347
394- self._worker_thread = Some(worker_thread);
395-
396- // Wrap the change sender in Arc for sharing across callbacks
397- let sender_arc = Arc::new(change_tx);
398-
399- // Register listener for volume changes
400- let volume_address = AudioObjectPropertyAddress {
401- mSelector: kAudioDevicePropertyVolumeScalar,
402- mScope: kAudioDevicePropertyScopeOutput,
403- mElement: kAudioObjectPropertyElementMain,
404- };
405-
406- let client_data = Arc::into_raw(Arc::clone(&sender_arc)) as *mut std::ffi::c_void;
407-
408- unsafe {
409- let status = AudioObjectAddPropertyListener(
410- self.device_id,
411- &volume_address,
412- Some(property_listener),
413- client_data,
414- );
415-
416- if status != 0 {
417- // Clean up the Arc we created
418- let _ = Arc::from_raw(client_data as *const std::sync::mpsc::Sender<()>);
419- return Err(format!(
420- "Failed to add volume property listener: {}",
421- status
422- ));
423- }
424- }
425-
426- // Register listener for mute changes (if supported)
427- let mute_address = AudioObjectPropertyAddress {
428- mSelector: kAudioDevicePropertyMute,
429- mScope: kAudioDevicePropertyScopeOutput,
430- mElement: kAudioObjectPropertyElementMain,
431- };
432-
433- if unsafe { AudioObjectHasProperty(self.device_id, &mute_address) } != 0 {
434- let client_data = Arc::into_raw(sender_arc) as *mut std::ffi::c_void;
435-
436- unsafe {
437- let status = AudioObjectAddPropertyListener(
438- self.device_id,
439- &mute_address,
440- Some(property_listener),
441- client_data,
442- );
443-
444- if status != 0 {
445- // Clean up the Arc we created
446- let _ = Arc::from_raw(client_data as *const std::sync::mpsc::Sender<()>);
447- eprintln!(
448- "[VolumeControl] Warning: Failed to add mute property listener: {}",
449- status
450- );
451- }
452- }
453- }
348+ self . _worker_thread = Some ( polling_thread) ;
454349
455- eprintln!("[VolumeControl] macOS volume change listener registered ");
350+ eprintln ! ( "[VolumeControl] macOS volume polling enabled (2s interval) " ) ;
456351 Ok ( ( ) )
457- */ // END DISABLED CODE
458352 }
459353}
0 commit comments