mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 05:12:57 +02:00
e402bd5424
* added a ton of visualizers * added f11 toggle functionality, f11 makes visualizer cover entire screen when pressed while visualizer is maximized Bundling the additional butterchurn preset packs (extra, md1) pushed renderer build memory past the default ~2GB Node heap on the macOS CI runner, causing OOM crashes during the build. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
187 lines
5.8 KiB
TypeScript
187 lines
5.8 KiB
TypeScript
import { motion, Variants } from 'motion/react';
|
|
import { lazy, memo, ReactNode, Suspense, useEffect, useLayoutEffect, useRef } from 'react';
|
|
import { useLocation } from 'react-router';
|
|
|
|
import styles from './full-screen-visualizer.module.css';
|
|
|
|
import { FullScreenVisualizerSongInfo } from '/@/renderer/features/player/components/full-screen-visualizer-song-info';
|
|
import { VISUALIZER_FULLSCREEN_TARGET_ID } from '/@/renderer/hooks/use-fullscreen-toggle';
|
|
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
|
import { useIsMobile } from '/@/renderer/hooks/use-is-mobile';
|
|
import { useFullScreenPlayerStoreActions } from '/@/renderer/store/full-screen-player.store';
|
|
import {
|
|
usePlaybackSettings,
|
|
useSettingsStore,
|
|
useWindowSettings,
|
|
} from '/@/renderer/store/settings.store';
|
|
import { Platform } from '/@/shared/types/types';
|
|
|
|
const AudioMotionAnalyzerVisualizer = lazy(() =>
|
|
import('../../visualizer/components/audiomotionanalyzer/visualizer').then((module) => ({
|
|
default: module.Visualizer,
|
|
})),
|
|
);
|
|
|
|
const ButterchurnVisualizer = lazy(() =>
|
|
import('../../visualizer/components/butternchurn/visualizer').then((module) => ({
|
|
default: module.Visualizer,
|
|
})),
|
|
);
|
|
|
|
const containerVariants: Variants = {
|
|
closed: (custom) => {
|
|
const { isMobile, windowBarStyle } = custom;
|
|
const height =
|
|
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS
|
|
? 'calc(100vh - 120px)'
|
|
: 'calc(100vh - 90px)';
|
|
|
|
if (isMobile) {
|
|
return {
|
|
height,
|
|
position: 'absolute',
|
|
top: '100vh',
|
|
transition: {
|
|
duration: 0.5,
|
|
ease: 'easeInOut',
|
|
},
|
|
width: '100vw',
|
|
y: 0,
|
|
};
|
|
}
|
|
return {
|
|
height,
|
|
position: 'absolute',
|
|
top: '100vh',
|
|
transition: {
|
|
duration: 0.5,
|
|
ease: 'easeInOut',
|
|
},
|
|
width: '100vw',
|
|
y: 0,
|
|
};
|
|
},
|
|
open: (custom) => {
|
|
const { isMobile, windowBarStyle } = custom;
|
|
const height =
|
|
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS
|
|
? 'calc(100vh - 120px)'
|
|
: 'calc(100vh - 90px)';
|
|
const topOffset =
|
|
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS
|
|
? '30px'
|
|
: '0px';
|
|
|
|
if (isMobile) {
|
|
return {
|
|
height,
|
|
left: 0,
|
|
position: 'absolute',
|
|
top: topOffset,
|
|
transition: {
|
|
delay: 0.1,
|
|
duration: 0.5,
|
|
ease: 'easeInOut',
|
|
},
|
|
width: '100vw',
|
|
y: 0,
|
|
};
|
|
}
|
|
return {
|
|
height,
|
|
left: 0,
|
|
position: 'absolute',
|
|
top: 0,
|
|
transition: {
|
|
delay: 0.1,
|
|
duration: 0.5,
|
|
ease: 'easeInOut',
|
|
},
|
|
width: '100vw',
|
|
y: 0,
|
|
};
|
|
},
|
|
};
|
|
|
|
interface VisualizerContainerProps {
|
|
children: ReactNode;
|
|
isMobile?: boolean;
|
|
windowBarStyle: Platform;
|
|
}
|
|
|
|
const VisualizerContainer = memo(
|
|
({ children, isMobile, windowBarStyle }: VisualizerContainerProps) => {
|
|
return (
|
|
<motion.div
|
|
animate="open"
|
|
className={styles.container}
|
|
custom={{ isMobile, windowBarStyle }}
|
|
exit="closed"
|
|
initial="closed"
|
|
transition={{ duration: 2 }}
|
|
variants={containerVariants}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
);
|
|
},
|
|
);
|
|
|
|
VisualizerContainer.displayName = 'VisualizerContainer';
|
|
|
|
export const FullScreenVisualizer = () => {
|
|
const { setStore } = useFullScreenPlayerStoreActions();
|
|
const { windowBarStyle } = useWindowSettings();
|
|
const { webAudio } = usePlaybackSettings();
|
|
const visualizerType = useSettingsStore((store) => store.visualizer.type);
|
|
const isMobile = useIsMobile();
|
|
|
|
const location = useLocation();
|
|
const isOpenedRef = useRef<boolean | null>(null);
|
|
|
|
const handleCloseVisualizer = () => {
|
|
// While fullscreen, Escape is the browser's own "leave fullscreen" gesture.
|
|
// Let it drop back to the expanded-but-windowed visualizer instead of closing.
|
|
if (document.fullscreenElement) return;
|
|
|
|
setStore({ visualizerExpanded: false });
|
|
};
|
|
|
|
useHotkeys([['Escape', handleCloseVisualizer]]);
|
|
|
|
// Never leave the window stuck in fullscreen if the visualizer goes away while
|
|
// fullscreened (route change, close button, etc.).
|
|
useEffect(() => {
|
|
return () => {
|
|
if (document.fullscreenElement) {
|
|
document.exitFullscreen().catch(() => {});
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
useLayoutEffect(() => {
|
|
if (isOpenedRef.current !== null) {
|
|
setStore({ visualizerExpanded: false });
|
|
}
|
|
|
|
isOpenedRef.current = true;
|
|
}, [location, setStore]);
|
|
|
|
return (
|
|
<VisualizerContainer isMobile={isMobile} windowBarStyle={windowBarStyle}>
|
|
<div className={styles.visualizerContainer} id={VISUALIZER_FULLSCREEN_TARGET_ID}>
|
|
{webAudio ? (
|
|
<Suspense fallback={<></>}>
|
|
{visualizerType === 'butterchurn' ? (
|
|
<ButterchurnVisualizer />
|
|
) : (
|
|
<AudioMotionAnalyzerVisualizer />
|
|
)}
|
|
</Suspense>
|
|
) : null}
|
|
<FullScreenVisualizerSongInfo />
|
|
</div>
|
|
</VisualizerContainer>
|
|
);
|
|
};
|