From 1f320d9771bac2780e5a09d6a3bc063b35cf58de Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 5 May 2026 21:37:44 +0200 Subject: fix(audio): resolve deadlock preventing auto-advance to next track The audio callback deadlocked when trying to lock audio_data twice, preventing track_ended from being set and breaking auto-advance. Also removed erroneous track_ended.store(true) calls from error paths that were causing incorrect queue advancement when playback failed. --- src/audio.rs | 14 +++++++------- src/daemon.rs | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/audio.rs b/src/audio.rs index 86c88f7..4e26263 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -204,6 +204,8 @@ fn audio_thread( let vol = vol.lock().map(|v| *v).unwrap_or(1.0); if let Ok(mut audio_data) = audio_data_clone.lock() { + let mut should_clear = false; + if let Some(ref mut state) = *audio_data { let channels = state.channels as usize; let mut offset = 0usize; @@ -298,16 +300,17 @@ fn audio_thread( if offset == 0 && state.sample_buffer.is_empty() { ended.store(true, Ordering::SeqCst); is_playing_clone.store(false, Ordering::SeqCst); - // Clear audio data so we don't keep triggering ended - if let Ok(mut audio_data) = audio_data_clone.lock() { - *audio_data = None; - } + should_clear = true; } } else { for sample in data.iter_mut() { *sample = 0.0; } } + + if should_clear { + *audio_data = None; + } } }, |err| { @@ -319,7 +322,6 @@ fn audio_thread( if let Err(e) = stream.play() { eprintln!("Failed to start stream: {}", e); is_playing.store(false, Ordering::SeqCst); - track_ended.store(true, Ordering::SeqCst); } else { current_stream = Some(stream); } @@ -327,14 +329,12 @@ fn audio_thread( Err(e) => { eprintln!("Failed to create stream: {}", e); is_playing.store(false, Ordering::SeqCst); - track_ended.store(true, Ordering::SeqCst); } } } Err(e) => { eprintln!("Failed to open audio: {}", e); is_playing.store(false, Ordering::SeqCst); - track_ended.store(true, Ordering::SeqCst); } } } diff --git a/src/daemon.rs b/src/daemon.rs index 8ac3a89..9fa5cea 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -150,18 +150,57 @@ pub fn start_daemon() -> Result<(), Box> { // Accept connections until shutdown listener.set_nonblocking(true)?; + // Helper: poll until a specific track actually starts playing or timeout + fn wait_for_start(audio: &AudioEngine, target: &PathBuf, timeout_ms: u64) -> bool { + use std::time::{Duration, Instant}; + let start = Instant::now(); + while start.elapsed() < Duration::from_millis(timeout_ms) { + // If playing and the current track matches, assume started + if audio.is_playing() { + if let Some(cur) = audio.current_track() { + if &cur == target { + return true; + } + } + } + std::thread::sleep(Duration::from_millis(10)); + } + false + } loop { // Check for shutdown signal if shutdown_flag.load(Ordering::SeqCst) { break; } - // Check if track ended and play next + // Check if track ended and play next (robust with startup feedback) if audio_engine.poll_track_ended() { if let Ok(mut q) = queue.lock() { - if let Some(next_track) = q.next() { - audio_engine.play(next_track); - } else { + // Save the current position in case we need to revert + let original_position = q.position; + // Try a handful of next candidates until one starts + let mut started = false; + // We'll attempt up to 4 candidates ahead + let max_trials = 4usize; + for _ in 0..max_trials { + if let Some(next_track) = q.next() { + audio_engine.play(next_track.clone()); + // Wait a short time to see if playback actually starts + if wait_for_start(&audio_engine, &next_track, 500) { + started = true; + break; + } else { + // Try next candidate - q.next() already advanced position + continue; + } + } else { + break; + } + } + if !started { + // Revert position since we couldn't start any track + q.position = original_position; + // No candidate started; stop playback safely audio_engine.stop(); } } -- cgit v1.2.3