diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-05 21:37:44 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-05 21:37:44 +0200 |
| commit | 1f320d9771bac2780e5a09d6a3bc063b35cf58de (patch) | |
| tree | b56506dcdf15b767d3eeb5857453583b76340d17 /src/daemon.rs | |
| parent | fix(daemon): robust auto-advance; ensure next track starts even if playback f... (diff) | |
| download | rsplayer-1f320d9771bac2780e5a09d6a3bc063b35cf58de.tar.gz rsplayer-1f320d9771bac2780e5a09d6a3bc063b35cf58de.zip | |
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.
Diffstat (limited to 'src/daemon.rs')
| -rw-r--r-- | src/daemon.rs | 47 |
1 files changed, 43 insertions, 4 deletions
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<dyn std::error::Error>> { | |||
| 150 | 150 | ||
| 151 | // Accept connections until shutdown | 151 | // Accept connections until shutdown |
| 152 | listener.set_nonblocking(true)?; | 152 | listener.set_nonblocking(true)?; |
| 153 | // Helper: poll until a specific track actually starts playing or timeout | ||
| 154 | fn wait_for_start(audio: &AudioEngine, target: &PathBuf, timeout_ms: u64) -> bool { | ||
| 155 | use std::time::{Duration, Instant}; | ||
| 156 | let start = Instant::now(); | ||
| 157 | while start.elapsed() < Duration::from_millis(timeout_ms) { | ||
| 158 | // If playing and the current track matches, assume started | ||
| 159 | if audio.is_playing() { | ||
| 160 | if let Some(cur) = audio.current_track() { | ||
| 161 | if &cur == target { | ||
| 162 | return true; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
| 166 | std::thread::sleep(Duration::from_millis(10)); | ||
| 167 | } | ||
| 168 | false | ||
| 169 | } | ||
| 153 | loop { | 170 | loop { |
| 154 | // Check for shutdown signal | 171 | // Check for shutdown signal |
| 155 | if shutdown_flag.load(Ordering::SeqCst) { | 172 | if shutdown_flag.load(Ordering::SeqCst) { |
| 156 | break; | 173 | break; |
| 157 | } | 174 | } |
| 158 | 175 | ||
| 159 | // Check if track ended and play next | 176 | // Check if track ended and play next (robust with startup feedback) |
| 160 | if audio_engine.poll_track_ended() { | 177 | if audio_engine.poll_track_ended() { |
| 161 | if let Ok(mut q) = queue.lock() { | 178 | if let Ok(mut q) = queue.lock() { |
| 162 | if let Some(next_track) = q.next() { | 179 | // Save the current position in case we need to revert |
| 163 | audio_engine.play(next_track); | 180 | let original_position = q.position; |
| 164 | } else { | 181 | // Try a handful of next candidates until one starts |
| 182 | let mut started = false; | ||
| 183 | // We'll attempt up to 4 candidates ahead | ||
| 184 | let max_trials = 4usize; | ||
| 185 | for _ in 0..max_trials { | ||
| 186 | if let Some(next_track) = q.next() { | ||
| 187 | audio_engine.play(next_track.clone()); | ||
| 188 | // Wait a short time to see if playback actually starts | ||
| 189 | if wait_for_start(&audio_engine, &next_track, 500) { | ||
| 190 | started = true; | ||
| 191 | break; | ||
| 192 | } else { | ||
| 193 | // Try next candidate - q.next() already advanced position | ||
| 194 | continue; | ||
| 195 | } | ||
| 196 | } else { | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | if !started { | ||
| 201 | // Revert position since we couldn't start any track | ||
| 202 | q.position = original_position; | ||
| 203 | // No candidate started; stop playback safely | ||
| 165 | audio_engine.stop(); | 204 | audio_engine.stop(); |
| 166 | } | 205 | } |
| 167 | } | 206 | } |
