From 426a4d0dbaa2ef46d272f4657bd6ee82dfc0a2a8 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:53:17 -0600 Subject: [PATCH] fix(player): stop engine before dropping ALSA stream on format change In the Play and PlayStreaming handlers, the old engine's writer thread still holds an Arc when the stream_opt is dropped and a new snd_pcm_open() is attempted. This causes EBUSY on track switches with sample rate changes. Reorder to: stop engine -> drop stream -> open new stream, matching the Stop handler's correct lifecycle. cc --- crates/qbz-player/src/player/mod.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/qbz-player/src/player/mod.rs b/crates/qbz-player/src/player/mod.rs index c8c03ff15..a6b9a1efc 100644 --- a/crates/qbz-player/src/player/mod.rs +++ b/crates/qbz-player/src/player/mod.rs @@ -1145,8 +1145,18 @@ impl Player { } else { log::info!("Creating initial audio stream"); } - // Drop old stream + // Stop engine FIRST so its writer thread releases its + // Arc reference before we drop the stream. + // Without this, snd_pcm_open() races against the old PCM + // handle and fails with EBUSY. + if let Some(engine) = current_engine.take() { + engine.stop(); + std::thread::sleep(Duration::from_millis(50)); + } + // Now this drop is the last Arc ref — PCM actually closes drop(stream_opt.take()); + // Give kernel time to fully release the ALSA device + std::thread::sleep(Duration::from_millis(50)); } log::info!( @@ -1585,7 +1595,18 @@ impl Player { mode ); } + // Stop engine FIRST so its writer thread releases its + // Arc reference before we drop the stream. + // Without this, snd_pcm_open() races against the old PCM + // handle and fails with EBUSY. + if let Some(engine) = current_engine.take() { + engine.stop(); + std::thread::sleep(Duration::from_millis(50)); + } + // Now this drop is the last Arc ref — PCM actually closes drop(stream_opt.take()); + // Give kernel time to fully release the ALSA device + std::thread::sleep(Duration::from_millis(50)); } let stream_result = if let Some(settings) =