From 9034882bf6d305a5cc3c51c21f6effdab15450e8 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:49:49 +0200 Subject: [PATCH] fix(audio): bump generation on paused device reopen to stop spurious audio:ended Third defence for #1094: on a device change while paused/stopped, reopen_output_stream stopped the old sink without bumping the engine generation (the bump only happened on a successful internal resume). The still-running progress task could then flip done_flag and emit a spurious audio:ended, which the frontend turns into a restart. Bump the generation before sink.stop() in the non-playing case so the progress task bails out; the active-playback path keeps bumping inside try_resume_after_device_change. --- src-tauri/crates/psysonic-audio/src/device_watcher.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src-tauri/crates/psysonic-audio/src/device_watcher.rs b/src-tauri/crates/psysonic-audio/src/device_watcher.rs index 7c55b544..d409adb6 100644 --- a/src-tauri/crates/psysonic-audio/src/device_watcher.rs +++ b/src-tauri/crates/psysonic-audio/src/device_watcher.rs @@ -82,6 +82,15 @@ pub(crate) async fn reopen_output_stream( if !opened { return false; } + // When we're not actively playing (paused/stopped), bump the generation + // before stopping the old sink so the still-running progress task sees the + // mismatch and bails out instead of emitting a spurious `audio:ended` — + // which would otherwise trigger a frontend restart of paused playback + // (#1094). The active-playback path bumps inside + // `try_resume_after_device_change`, so only guard the non-playing case here. + if !snapshot.is_playing { + engine.generation.fetch_add(1, Ordering::SeqCst); + } if let Some(s) = current.lock().unwrap().sink.take() { s.stop(); }