From 390b231e06c3d8c54619ed389aa6bc2c7d46f3aa Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 21 Jul 2026 00:29:20 -0700 Subject: [PATCH] fix web player resuming on queue modification (#2250) - During queue edits, stream URL reloads briefly reported a bogus duration (~0.07s). The gapless handler treated that as near end-of-track and called .play() while the player was still paused --- .../audio-player/engine/web-player-engine.tsx | 6 ++-- .../player/audio-player/web-player.tsx | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/renderer/features/player/audio-player/engine/web-player-engine.tsx b/src/renderer/features/player/audio-player/engine/web-player-engine.tsx index 165c1a3f8..20ebcec9b 100644 --- a/src/renderer/features/player/audio-player/engine/web-player-engine.tsx +++ b/src/renderer/features/player/audio-player/engine/web-player-engine.tsx @@ -263,13 +263,15 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => { networkRetryCount2.current = 0; }, [src1, src2]); - // When not transitioning, ensure only the active player can play (e.g. after seek/prev during transition) + // When not playing, always pause both players — even during a transition useEffect(() => { - if (isTransitioning) return; if (playerStatus !== PlayerStatus.PLAYING) { pauseBothPlayers(); return; } + if (isTransitioning) { + return; + } if (playerNum === 1) { player2Ref.current?.getInternalPlayer()?.pause(); } else { diff --git a/src/renderer/features/player/audio-player/web-player.tsx b/src/renderer/features/player/audio-player/web-player.tsx index f4f8491d8..75d9f3867 100644 --- a/src/renderer/features/player/audio-player/web-player.tsx +++ b/src/renderer/features/player/audio-player/web-player.tsx @@ -136,6 +136,10 @@ export function WebPlayer() { return; } + if (usePlayerStoreBase.getState().player.status !== PlayerStatus.PLAYING) { + return; + } + switch (transitionType) { case PlayerStyle.CROSSFADE: crossfadeHandler({ @@ -195,6 +199,10 @@ export function WebPlayer() { return; } + if (usePlayerStoreBase.getState().player.status !== PlayerStatus.PLAYING) { + return; + } + switch (transitionType) { case PlayerStyle.CROSSFADE: crossfadeHandler({ @@ -286,6 +294,11 @@ export function WebPlayer() { onCurrentSongChange: () => { setIsTransitioning(false); }, + onPlayerQueueChange: () => { + if (usePlayerStoreBase.getState().player.status !== PlayerStatus.PLAYING) { + setIsTransitioning(false); + } + }, onPlayerSeekToTimestamp: (properties) => { setIsTransitioning(false); @@ -607,6 +620,13 @@ function crossfadeHandler(args: { } = args; const player = `player${playerNum}`; + if (usePlayerStoreBase.getState().player.status !== PlayerStatus.PLAYING) { + if (isTransitioning) { + setIsTransitioning(false); + } + return; + } + // If there is no next song to transition to, ensure we don't enter or stay in a transition if (!hasNextSong) { currentPlayer.setVolume(volume); @@ -714,10 +734,25 @@ function gaplessHandler(args: { setIsTransitioning, } = args; + if (usePlayerStoreBase.getState().player.status !== PlayerStatus.PLAYING) { + if (isTransitioning) { + setIsTransitioning(false); + } + return null; + } + if (!hasNextSong) { return null; } + // Ignore invalid durations (e.g. during URL load or empty source placeholder) + if (!Number.isFinite(duration) || duration < 2) { + if (isTransitioning) { + setIsTransitioning(false); + } + return null; + } + if (!isTransitioning) { if (currentTime > duration - 2) { return setIsTransitioning(true);