make visualizer idle kill consistent for both

This commit is contained in:
jeffvli
2026-04-05 02:35:32 -07:00
parent 84395ce5b4
commit e06877af76
2 changed files with 66 additions and 24 deletions
@@ -11,8 +11,10 @@ import {
useFullScreenPlayerStore, useFullScreenPlayerStore,
useFullScreenPlayerStoreActions, useFullScreenPlayerStoreActions,
} from '/@/renderer/store/full-screen-player.store'; } from '/@/renderer/store/full-screen-player.store';
import { usePlayerStatus } from '/@/renderer/store/player.store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
const VisualizerInner = () => { const VisualizerInner = () => {
const { webAudio } = useWebAudio(); const { webAudio } = useWebAudio();
@@ -24,6 +26,9 @@ const VisualizerInner = () => {
const [motion, setMotion] = useState<any>(); const [motion, setMotion] = useState<any>();
const [libraryLoaded, setLibraryLoaded] = useState(false); const [libraryLoaded, setLibraryLoaded] = useState(false);
const AudioMotionAnalyzerRef = useRef<any>(null); const AudioMotionAnalyzerRef = useRef<any>(null);
const pauseTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
const playerStatus = usePlayerStatus();
const isPlaying = playerStatus === PlayerStatus.PLAYING;
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true;
@@ -218,8 +223,19 @@ const VisualizerInner = () => {
useEffect(() => { useEffect(() => {
const { context } = webAudio || {}; const { context } = webAudio || {};
const inputNodes = getVisualizerAudioNodes(webAudio, playbackType); const inputNodes = getVisualizerAudioNodes(webAudio, playbackType);
const shouldRunForWebPlayback = playbackType === PlayerType.WEB && isPlaying;
const shouldRunForMpvLoopback =
playbackType === PlayerType.LOCAL && isPlaying && inputNodes.length > 0;
let audioMotion: any | undefined; 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; const AudioMotionAnalyzer = AudioMotionAnalyzerRef.current;
if (!AudioMotionAnalyzer) return; if (!AudioMotionAnalyzer) return;
@@ -257,7 +273,11 @@ const VisualizerInner = () => {
return () => { return () => {
if (motion) { if (motion) {
motion.destroy(); try {
motion.destroy();
} catch {
// ignore (e.g. already destroyed by idle timer)
}
setMotion(undefined); setMotion(undefined);
} }
}; };
@@ -272,8 +292,43 @@ const VisualizerInner = () => {
isCustomGradient, isCustomGradient,
motion, motion,
libraryLoaded, 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 // Re-register custom gradients when they change
useEffect(() => { useEffect(() => {
if (motion && visualizer.type === 'audiomotionanalyzer') { if (motion && visualizer.type === 'audiomotionanalyzer') {
@@ -62,9 +62,9 @@ const VisualizerInner = () => {
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const playerStatus = usePlayerStatus(); const playerStatus = usePlayerStatus();
const isPlaying = playerStatus === PlayerStatus.PLAYING; const isPlaying = playerStatus === PlayerStatus.PLAYING;
const [webInitGeneration, setWebInitGeneration] = useState(0); const [resumeInitGeneration, setResumeInitGeneration] = useState(0);
const wasPlayingRef = useRef(false); const wasPlayingRef = useRef(false);
const isFirstWebMountRef = useRef(true); const isFirstMountRef = useRef(true);
const prevPlaybackTypeRef = useRef(playbackType); const prevPlaybackTypeRef = useRef(playbackType);
useEffect(() => { useEffect(() => {
@@ -99,14 +99,8 @@ const VisualizerInner = () => {
useEffect(() => { useEffect(() => {
const prevType = prevPlaybackTypeRef.current; const prevType = prevPlaybackTypeRef.current;
if (playbackType !== PlayerType.WEB) { if (isFirstMountRef.current) {
prevPlaybackTypeRef.current = playbackType; isFirstMountRef.current = false;
wasPlayingRef.current = isPlaying;
return;
}
if (isFirstWebMountRef.current) {
isFirstWebMountRef.current = false;
wasPlayingRef.current = isPlaying; wasPlayingRef.current = isPlaying;
prevPlaybackTypeRef.current = playbackType; prevPlaybackTypeRef.current = playbackType;
return; return;
@@ -115,8 +109,8 @@ const VisualizerInner = () => {
const wasPlaying = wasPlayingRef.current; const wasPlaying = wasPlayingRef.current;
wasPlayingRef.current = isPlaying; wasPlayingRef.current = isPlaying;
if (isPlaying && (!wasPlaying || prevType !== PlayerType.WEB)) { if (isPlaying && (!wasPlaying || prevType !== playbackType)) {
setWebInitGeneration((g) => g + 1); setResumeInitGeneration((g) => g + 1);
} }
prevPlaybackTypeRef.current = playbackType; prevPlaybackTypeRef.current = playbackType;
@@ -157,7 +151,8 @@ const VisualizerInner = () => {
const container = containerRef.current; const container = containerRef.current;
const shouldRunForWebPlayback = playbackType === PlayerType.WEB && isPlaying; 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 = const needsInitialization =
context && context &&
@@ -229,18 +224,10 @@ const VisualizerInner = () => {
cleanupVisualizer(); cleanupVisualizer();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [webAudio, playbackType, librariesLoaded, webInitGeneration]); }, [webAudio, playbackType, librariesLoaded, resumeInitGeneration]);
// Kill visualizer after 5 seconds of pause // Kill visualizer after 5 seconds of pause
useEffect(() => { useEffect(() => {
if (playbackType === PlayerType.LOCAL) {
if (pauseTimerRef.current) {
clearTimeout(pauseTimerRef.current);
pauseTimerRef.current = undefined;
}
return;
}
if (isPlaying) { if (isPlaying) {
// Clear pause timer if player resumes // Clear pause timer if player resumes
if (pauseTimerRef.current) { if (pauseTimerRef.current) {