mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
fix butterchurn init and cleanup
This commit is contained in:
-2
@@ -2110,8 +2110,6 @@ const ButterChurnCycleSettings = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { updateProperty, visualizer } = useUpdateButterchurn();
|
const { updateProperty, visualizer } = useUpdateButterchurn();
|
||||||
|
|
||||||
console.log(butterchurnPresets, 'number of presets');
|
|
||||||
|
|
||||||
const presetOptions = useMemo(() => {
|
const presetOptions = useMemo(() => {
|
||||||
const presets = butterchurnPresets;
|
const presets = butterchurnPresets;
|
||||||
return Object.keys(presets).map((presetName) => ({
|
return Object.keys(presets).map((presetName) => ({
|
||||||
|
|||||||
@@ -24,31 +24,67 @@ const VisualizerInner = () => {
|
|||||||
const { webAudio } = useWebAudio();
|
const { webAudio } = useWebAudio();
|
||||||
const canvasRef = createRef<HTMLCanvasElement>();
|
const canvasRef = createRef<HTMLCanvasElement>();
|
||||||
const containerRef = createRef<HTMLDivElement>();
|
const containerRef = createRef<HTMLDivElement>();
|
||||||
const [visualizer, setVisualizer] = useState<ButterchurnVisualizer>();
|
const visualizerRef = useRef<ButterchurnVisualizer | undefined>(undefined);
|
||||||
|
const isInitializedRef = useRef(false);
|
||||||
|
const [isVisualizerReady, setIsVisualizerReady] = useState(false);
|
||||||
const animationFrameRef = useRef<number | undefined>(undefined);
|
const animationFrameRef = useRef<number | undefined>(undefined);
|
||||||
const resizeObserverRef = useRef<ResizeObserver | undefined>(undefined);
|
const resizeObserverRef = useRef<ResizeObserver | undefined>(undefined);
|
||||||
const cycleTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
const cycleTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
const cycleStartTimeRef = useRef<number | undefined>(undefined);
|
const cycleStartTimeRef = useRef<number | undefined>(undefined);
|
||||||
const pauseTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
const pauseTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
|
const initialPresetLoadedRef = useRef(false);
|
||||||
const butterchurnSettings = useSettingsStore((store) => store.visualizer.butterchurn);
|
const butterchurnSettings = useSettingsStore((store) => store.visualizer.butterchurn);
|
||||||
const opacity = useSettingsStore((store) => store.visualizer.butterchurn.opacity);
|
const opacity = useSettingsStore((store) => store.visualizer.butterchurn.opacity);
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
const playerStatus = usePlayerStatus();
|
const playerStatus = usePlayerStatus();
|
||||||
const isPlaying = playerStatus === PlayerStatus.PLAYING;
|
const isPlaying = playerStatus === PlayerStatus.PLAYING;
|
||||||
|
|
||||||
|
const cleanupVisualizer = () => {
|
||||||
|
if (animationFrameRef.current) {
|
||||||
|
cancelAnimationFrame(animationFrameRef.current);
|
||||||
|
animationFrameRef.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycleTimerRef.current) {
|
||||||
|
clearInterval(cycleTimerRef.current);
|
||||||
|
cycleTimerRef.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pauseTimerRef.current) {
|
||||||
|
clearTimeout(pauseTimerRef.current);
|
||||||
|
pauseTimerRef.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeObserverRef.current) {
|
||||||
|
resizeObserverRef.current.disconnect();
|
||||||
|
resizeObserverRef.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
visualizerRef.current = undefined;
|
||||||
|
isInitializedRef.current = false;
|
||||||
|
initialPresetLoadedRef.current = false;
|
||||||
|
setIsVisualizerReady(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize butterchurn instance
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { context, gains } = webAudio || {};
|
const { context, gains } = webAudio || {};
|
||||||
if (
|
|
||||||
context &&
|
|
||||||
gains &&
|
|
||||||
canvasRef.current &&
|
|
||||||
containerRef.current &&
|
|
||||||
!visualizer &&
|
|
||||||
isPlaying
|
|
||||||
) {
|
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
|
|
||||||
|
const needsInitialization =
|
||||||
|
context &&
|
||||||
|
gains &&
|
||||||
|
gains.length > 0 &&
|
||||||
|
canvas &&
|
||||||
|
container &&
|
||||||
|
isPlaying &&
|
||||||
|
(!isInitializedRef.current || !visualizerRef.current);
|
||||||
|
|
||||||
|
if (!needsInitialization) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const getDimensions = () => {
|
const getDimensions = () => {
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
@@ -72,7 +108,7 @@ const VisualizerInner = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initializeVisualizer(width: number, height: number) {
|
function initializeVisualizer(width: number, height: number) {
|
||||||
if (!gains || gains.length === 0) return;
|
if (!gains || gains.length === 0 || !canvas || !context) return;
|
||||||
|
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
@@ -87,58 +123,22 @@ const VisualizerInner = () => {
|
|||||||
butterchurnInstance.connectAudio(gain);
|
butterchurnInstance.connectAudio(gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
const presets = butterchurnPresets;
|
visualizerRef.current = butterchurnInstance;
|
||||||
const presetNames = Object.keys(presets);
|
isInitializedRef.current = true;
|
||||||
|
setIsVisualizerReady(true);
|
||||||
if (presetNames.length > 0) {
|
|
||||||
const presetName =
|
|
||||||
butterchurnSettings.currentPreset &&
|
|
||||||
presets[butterchurnSettings.currentPreset]
|
|
||||||
? butterchurnSettings.currentPreset
|
|
||||||
: presetNames[0];
|
|
||||||
const preset = presets[presetName];
|
|
||||||
butterchurnInstance.loadPreset(
|
|
||||||
preset,
|
|
||||||
butterchurnSettings.blendTime || 0.0,
|
|
||||||
);
|
|
||||||
// Initialize cycle timer
|
|
||||||
cycleStartTimeRef.current = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
setVisualizer(butterchurnInstance);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create butterchurn visualizer:', error);
|
console.error('Failed to create butterchurn visualizer:', error);
|
||||||
}
|
isInitializedRef.current = false;
|
||||||
|
visualizerRef.current = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (animationFrameRef.current) {
|
// Cleanup on unmount or when webAudio changes
|
||||||
cancelAnimationFrame(animationFrameRef.current);
|
cleanupVisualizer();
|
||||||
animationFrameRef.current = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleTimerRef.current) {
|
|
||||||
clearInterval(cycleTimerRef.current);
|
|
||||||
cycleTimerRef.current = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pauseTimerRef.current) {
|
|
||||||
clearTimeout(pauseTimerRef.current);
|
|
||||||
pauseTimerRef.current = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resizeObserverRef.current) {
|
|
||||||
resizeObserverRef.current.disconnect();
|
|
||||||
resizeObserverRef.current = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visualizer) {
|
|
||||||
setVisualizer(undefined);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [webAudio, canvasRef, containerRef, visualizer, isPlaying]);
|
}, [webAudio, isPlaying]);
|
||||||
|
|
||||||
// Kill visualizer after 5 seconds of pause
|
// Kill visualizer after 5 seconds of pause
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -152,25 +152,11 @@ const VisualizerInner = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Player is paused
|
// Player is paused
|
||||||
if (!visualizer) return;
|
if (!visualizerRef.current) return;
|
||||||
|
|
||||||
// Start 5-second timer
|
// Start 5-second timer
|
||||||
pauseTimerRef.current = setTimeout(() => {
|
pauseTimerRef.current = setTimeout(() => {
|
||||||
if (animationFrameRef.current) {
|
cleanupVisualizer();
|
||||||
cancelAnimationFrame(animationFrameRef.current);
|
|
||||||
animationFrameRef.current = undefined;
|
|
||||||
}
|
|
||||||
if (cycleTimerRef.current) {
|
|
||||||
clearInterval(cycleTimerRef.current);
|
|
||||||
cycleTimerRef.current = undefined;
|
|
||||||
}
|
|
||||||
if (resizeObserverRef.current) {
|
|
||||||
resizeObserverRef.current.disconnect();
|
|
||||||
resizeObserverRef.current = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy visualizer
|
|
||||||
setVisualizer(undefined);
|
|
||||||
pauseTimerRef.current = undefined;
|
pauseTimerRef.current = undefined;
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
@@ -180,11 +166,12 @@ const VisualizerInner = () => {
|
|||||||
pauseTimerRef.current = undefined;
|
pauseTimerRef.current = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isPlaying, visualizer]);
|
}, [isPlaying]);
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
|
const visualizer = visualizerRef.current;
|
||||||
if (!container || !visualizer) return;
|
if (!container || !visualizer) return;
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
@@ -207,16 +194,45 @@ const VisualizerInner = () => {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserverRef.current) {
|
||||||
|
resizeObserverRef.current.disconnect();
|
||||||
|
resizeObserverRef.current = undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [visualizer, containerRef, canvasRef]);
|
}, [isVisualizerReady, canvasRef, containerRef]);
|
||||||
|
|
||||||
|
// Load initial preset when visualizer is ready
|
||||||
|
useEffect(() => {
|
||||||
|
const visualizer = visualizerRef.current;
|
||||||
|
if (!visualizer || !isVisualizerReady || initialPresetLoadedRef.current) return;
|
||||||
|
|
||||||
|
const presets = butterchurnPresets;
|
||||||
|
const presetNames = Object.keys(presets);
|
||||||
|
|
||||||
|
if (presetNames.length > 0) {
|
||||||
|
const presetName =
|
||||||
|
butterchurnSettings.currentPreset && presets[butterchurnSettings.currentPreset]
|
||||||
|
? butterchurnSettings.currentPreset
|
||||||
|
: presetNames[0];
|
||||||
|
const preset = presets[presetName];
|
||||||
|
|
||||||
|
if (preset) {
|
||||||
|
visualizer.loadPreset(preset, butterchurnSettings.blendTime || 0.0);
|
||||||
|
cycleStartTimeRef.current = Date.now();
|
||||||
|
initialPresetLoadedRef.current = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isVisualizerReady, butterchurnSettings.currentPreset, butterchurnSettings.blendTime]);
|
||||||
|
|
||||||
// Update preset when currentPreset or blendTime changes (but not when cycling)
|
// Update preset when currentPreset or blendTime changes (but not when cycling)
|
||||||
const isCyclingRef = useRef(false);
|
const isCyclingRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visualizer || !butterchurnSettings.currentPreset) return;
|
const visualizer = visualizerRef.current;
|
||||||
|
if (!visualizer || !butterchurnSettings.currentPreset || !initialPresetLoadedRef.current)
|
||||||
|
return;
|
||||||
|
|
||||||
// Skip if we're currently cycling (to avoid recreating the visualizer)
|
// Skip if we're currently cycling (to avoid reloading preset)
|
||||||
if (isCyclingRef.current) {
|
if (isCyclingRef.current) {
|
||||||
isCyclingRef.current = false;
|
isCyclingRef.current = false;
|
||||||
return;
|
return;
|
||||||
@@ -230,12 +246,13 @@ const VisualizerInner = () => {
|
|||||||
// Reset cycle timer when preset changes manually
|
// Reset cycle timer when preset changes manually
|
||||||
cycleStartTimeRef.current = Date.now();
|
cycleStartTimeRef.current = Date.now();
|
||||||
}
|
}
|
||||||
}, [visualizer, butterchurnSettings.currentPreset, butterchurnSettings.blendTime]);
|
}, [butterchurnSettings.currentPreset, butterchurnSettings.blendTime]);
|
||||||
|
|
||||||
// Handle preset cycling
|
// Handle preset cycling
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visualizer || !butterchurnSettings.cyclePresets) {
|
const visualizer = visualizerRef.current;
|
||||||
// Clear cycle timer if cycling is disabled
|
if (!visualizer || !butterchurnSettings.cyclePresets || !initialPresetLoadedRef.current) {
|
||||||
|
// Clear cycle timer if cycling is disabled or visualizer not ready
|
||||||
if (cycleTimerRef.current) {
|
if (cycleTimerRef.current) {
|
||||||
clearInterval(cycleTimerRef.current);
|
clearInterval(cycleTimerRef.current);
|
||||||
cycleTimerRef.current = undefined;
|
cycleTimerRef.current = undefined;
|
||||||
@@ -266,7 +283,8 @@ const VisualizerInner = () => {
|
|||||||
cycleStartTimeRef.current = Date.now();
|
cycleStartTimeRef.current = Date.now();
|
||||||
|
|
||||||
const cycleToNextPreset = () => {
|
const cycleToNextPreset = () => {
|
||||||
if (!visualizer) return;
|
const currentVisualizer = visualizerRef.current;
|
||||||
|
if (!currentVisualizer) return;
|
||||||
|
|
||||||
const currentPresetName = butterchurnSettings.currentPreset;
|
const currentPresetName = butterchurnSettings.currentPreset;
|
||||||
let nextPresetName: string;
|
let nextPresetName: string;
|
||||||
@@ -298,7 +316,7 @@ const VisualizerInner = () => {
|
|||||||
isCyclingRef.current = true;
|
isCyclingRef.current = true;
|
||||||
|
|
||||||
// Load the preset with blending
|
// Load the preset with blending
|
||||||
visualizer.loadPreset(nextPreset, currentSettings.blendTime || 0.0);
|
currentVisualizer.loadPreset(nextPreset, currentSettings.blendTime || 0.0);
|
||||||
|
|
||||||
// Update currentPreset in settings
|
// Update currentPreset in settings
|
||||||
setSettings({
|
setSettings({
|
||||||
@@ -331,18 +349,38 @@ const VisualizerInner = () => {
|
|||||||
cycleTimerRef.current = undefined;
|
cycleTimerRef.current = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [visualizer, butterchurnSettings, setSettings]);
|
}, [
|
||||||
|
isVisualizerReady,
|
||||||
|
butterchurnSettings.cyclePresets,
|
||||||
|
butterchurnSettings.cycleTime,
|
||||||
|
butterchurnSettings.includeAllPresets,
|
||||||
|
butterchurnSettings.selectedPresets,
|
||||||
|
butterchurnSettings.ignoredPresets,
|
||||||
|
butterchurnSettings.randomizeNextPreset,
|
||||||
|
butterchurnSettings.currentPreset,
|
||||||
|
setSettings,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visualizer) return;
|
const visualizer = visualizerRef.current;
|
||||||
|
if (!visualizer || !isVisualizerReady) return;
|
||||||
|
|
||||||
let lastFrameTime = 0;
|
let lastFrameTime = 0;
|
||||||
const maxFPS = butterchurnSettings.maxFPS;
|
const maxFPS = butterchurnSettings.maxFPS;
|
||||||
const minFrameInterval = maxFPS > 0 ? 1000 / maxFPS : 0;
|
const minFrameInterval = maxFPS > 0 ? 1000 / maxFPS : 0;
|
||||||
|
|
||||||
const render = (currentTime: number) => {
|
const render = (currentTime: number) => {
|
||||||
|
const currentVisualizer = visualizerRef.current;
|
||||||
|
if (!currentVisualizer) {
|
||||||
|
if (animationFrameRef.current) {
|
||||||
|
cancelAnimationFrame(animationFrameRef.current);
|
||||||
|
animationFrameRef.current = undefined;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (maxFPS === 0 || currentTime - lastFrameTime >= minFrameInterval) {
|
if (maxFPS === 0 || currentTime - lastFrameTime >= minFrameInterval) {
|
||||||
visualizer.render();
|
currentVisualizer.render();
|
||||||
lastFrameTime = currentTime;
|
lastFrameTime = currentTime;
|
||||||
}
|
}
|
||||||
animationFrameRef.current = requestAnimationFrame(render);
|
animationFrameRef.current = requestAnimationFrame(render);
|
||||||
@@ -353,13 +391,14 @@ const VisualizerInner = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
if (animationFrameRef.current) {
|
if (animationFrameRef.current) {
|
||||||
cancelAnimationFrame(animationFrameRef.current);
|
cancelAnimationFrame(animationFrameRef.current);
|
||||||
|
animationFrameRef.current = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [visualizer, butterchurnSettings.maxFPS]);
|
}, [isVisualizerReady, butterchurnSettings.maxFPS]);
|
||||||
|
|
||||||
// Render container when playing (for initialization) or when visualizer exists
|
// Render container when playing (for initialization) or when visualizer exists
|
||||||
// Canvas must always be rendered when container is rendered so refs are available
|
// Canvas must always be rendered when container is rendered so refs are available
|
||||||
const shouldRenderContainer = isPlaying || visualizer;
|
const shouldRenderContainer = isPlaying || isVisualizerReady;
|
||||||
|
|
||||||
if (!shouldRenderContainer) {
|
if (!shouldRenderContainer) {
|
||||||
return null;
|
return null;
|
||||||
@@ -369,10 +408,10 @@ const VisualizerInner = () => {
|
|||||||
<div
|
<div
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{ opacity: visualizer ? opacity : 0 }}
|
style={{ opacity: isVisualizerReady ? opacity : 0 }}
|
||||||
>
|
>
|
||||||
<canvas className={styles.canvas} ref={canvasRef} />
|
<canvas className={styles.canvas} ref={canvasRef} />
|
||||||
{visualizer && butterchurnSettings.currentPreset && (
|
{isVisualizerReady && butterchurnSettings.currentPreset && (
|
||||||
<Text className={styles['preset-overlay']} isNoSelect size="sm">
|
<Text className={styles['preset-overlay']} isNoSelect size="sm">
|
||||||
{butterchurnSettings.currentPreset}
|
{butterchurnSettings.currentPreset}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user