From e06877af7689a500cd90b16a18133499709b477f Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 5 Apr 2026 02:35:32 -0700 Subject: [PATCH] make visualizer idle kill consistent for both --- .../audiomotionanalyzer/visualizer.tsx | 59 ++++++++++++++++++- .../components/butternchurn/visualizer.tsx | 31 +++------- 2 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx b/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx index f6df8c5d2..6244c5304 100644 --- a/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx +++ b/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx @@ -11,8 +11,10 @@ import { useFullScreenPlayerStore, useFullScreenPlayerStoreActions, } from '/@/renderer/store/full-screen-player.store'; +import { usePlayerStatus } from '/@/renderer/store/player.store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Group } from '/@/shared/components/group/group'; +import { PlayerStatus, PlayerType } from '/@/shared/types/types'; const VisualizerInner = () => { const { webAudio } = useWebAudio(); @@ -24,6 +26,9 @@ const VisualizerInner = () => { const [motion, setMotion] = useState(); const [libraryLoaded, setLibraryLoaded] = useState(false); const AudioMotionAnalyzerRef = useRef(null); + const pauseTimerRef = useRef(undefined); + const playerStatus = usePlayerStatus(); + const isPlaying = playerStatus === PlayerStatus.PLAYING; useEffect(() => { let isMounted = true; @@ -218,8 +223,19 @@ const VisualizerInner = () => { useEffect(() => { const { context } = webAudio || {}; const inputNodes = getVisualizerAudioNodes(webAudio, playbackType); + const shouldRunForWebPlayback = playbackType === PlayerType.WEB && isPlaying; + const shouldRunForMpvLoopback = + playbackType === PlayerType.LOCAL && isPlaying && inputNodes.length > 0; + let audioMotion: any | undefined; - if (inputNodes.length > 0 && context && canvasRef.current && !motion && libraryLoaded) { + if ( + inputNodes.length > 0 && + context && + canvasRef.current && + !motion && + libraryLoaded && + (shouldRunForWebPlayback || shouldRunForMpvLoopback) + ) { const AudioMotionAnalyzer = AudioMotionAnalyzerRef.current; if (!AudioMotionAnalyzer) return; @@ -257,7 +273,11 @@ const VisualizerInner = () => { return () => { if (motion) { - motion.destroy(); + try { + motion.destroy(); + } catch { + // ignore (e.g. already destroyed by idle timer) + } setMotion(undefined); } }; @@ -272,8 +292,43 @@ const VisualizerInner = () => { isCustomGradient, motion, libraryLoaded, + isPlaying, ]); + // Kill visualizer after 5 seconds of pause + useEffect(() => { + if (isPlaying) { + if (pauseTimerRef.current) { + clearTimeout(pauseTimerRef.current); + pauseTimerRef.current = undefined; + } + return; + } + + if (!motion) return; + + pauseTimerRef.current = setTimeout(() => { + setMotion((current) => { + if (current) { + try { + current.destroy(); + } catch { + // ignore + } + } + return undefined; + }); + pauseTimerRef.current = undefined; + }, 5000); + + return () => { + if (pauseTimerRef.current) { + clearTimeout(pauseTimerRef.current); + pauseTimerRef.current = undefined; + } + }; + }, [isPlaying, motion]); + // Re-register custom gradients when they change useEffect(() => { if (motion && visualizer.type === 'audiomotionanalyzer') { diff --git a/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx b/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx index ab4973ecb..7235d4ff6 100644 --- a/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx +++ b/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx @@ -62,9 +62,9 @@ const VisualizerInner = () => { const { setSettings } = useSettingsStoreActions(); const playerStatus = usePlayerStatus(); const isPlaying = playerStatus === PlayerStatus.PLAYING; - const [webInitGeneration, setWebInitGeneration] = useState(0); + const [resumeInitGeneration, setResumeInitGeneration] = useState(0); const wasPlayingRef = useRef(false); - const isFirstWebMountRef = useRef(true); + const isFirstMountRef = useRef(true); const prevPlaybackTypeRef = useRef(playbackType); useEffect(() => { @@ -99,14 +99,8 @@ const VisualizerInner = () => { useEffect(() => { const prevType = prevPlaybackTypeRef.current; - if (playbackType !== PlayerType.WEB) { - prevPlaybackTypeRef.current = playbackType; - wasPlayingRef.current = isPlaying; - return; - } - - if (isFirstWebMountRef.current) { - isFirstWebMountRef.current = false; + if (isFirstMountRef.current) { + isFirstMountRef.current = false; wasPlayingRef.current = isPlaying; prevPlaybackTypeRef.current = playbackType; return; @@ -115,8 +109,8 @@ const VisualizerInner = () => { const wasPlaying = wasPlayingRef.current; wasPlayingRef.current = isPlaying; - if (isPlaying && (!wasPlaying || prevType !== PlayerType.WEB)) { - setWebInitGeneration((g) => g + 1); + if (isPlaying && (!wasPlaying || prevType !== playbackType)) { + setResumeInitGeneration((g) => g + 1); } prevPlaybackTypeRef.current = playbackType; @@ -157,7 +151,8 @@ const VisualizerInner = () => { const container = containerRef.current; const shouldRunForWebPlayback = playbackType === PlayerType.WEB && isPlaying; - const shouldRunForMpvLoopback = playbackType === PlayerType.LOCAL && inputNodes.length > 0; + const shouldRunForMpvLoopback = + playbackType === PlayerType.LOCAL && isPlaying && inputNodes.length > 0; const needsInitialization = context && @@ -229,18 +224,10 @@ const VisualizerInner = () => { cleanupVisualizer(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [webAudio, playbackType, librariesLoaded, webInitGeneration]); + }, [webAudio, playbackType, librariesLoaded, resumeInitGeneration]); // Kill visualizer after 5 seconds of pause useEffect(() => { - if (playbackType === PlayerType.LOCAL) { - if (pauseTimerRef.current) { - clearTimeout(pauseTimerRef.current); - pauseTimerRef.current = undefined; - } - return; - } - if (isPlaying) { // Clear pause timer if player resumes if (pauseTimerRef.current) {