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,
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<any>();
const [libraryLoaded, setLibraryLoaded] = useState(false);
const AudioMotionAnalyzerRef = useRef<any>(null);
const pauseTimerRef = useRef<NodeJS.Timeout | undefined>(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') {
@@ -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) {