import { useEffect , useState , useRef } from "react" ; import { VISUALIZERS } from "../../constants" ; import * as Selectors from "../../selectors" ; import { TransitionType } from "../../types" ; import { useTypedSelector } from "../../hooks" ; type ButterchurnVisualizer = { setRendererSize ( width , height ) : void ; loadPreset ( preset , transitionTime ) : void ; launchSongTitleAnim ( title ) : void ; render ( ) : void ; } ; interface Props { analyser : AnalyserNode ; height : number ; width : number ; } const TRANSITION_TYPE_DURATIONS = { [ TransitionType . DEFAULT ] : 2.7 , [ TransitionType . IMMEDIATE ] : 0 , [ TransitionType . USER_PRESET ] : 5.7 , } ; function Visualizer ( { analyser , width , height } ) { const visualizerStyle = useTypedSelector ( Selectors . getVisualizerStyle ) ; const playing = useTypedSelector ( Selectors . getMediaIsPlaying ) ; const butterchurn = useTypedSelector ( Selectors . getButterchurn ) ; const trackTitle = useTypedSelector ( Selectors . getCurrentTrackDisplayName ) ; const currentPreset = useTypedSelector ( Selectors . getCurrentPreset ) ; const transitionType = useTypedSelector ( Selectors . getPresetTransitionType ) ; const message = useTypedSelector ( Selectors . getMilkdropMessage ) ; const isEnabledVisualizer = visualizerStyle === VISUALIZERS . MILKDROP ; const canvasRef = useRef ( null ) ; const [ visualizer , setVisualizer ] = useState < ButterchurnVisualizer | null > ( null ) ; useEffect ( ( ) => { if ( canvasRef . current == null || butterchurn == null ) { return ; } if ( visualizer != null ) { return ; } const _visualizer = butterchurn . createVisualizer ( analyser . context , canvasRef . current , { width , height , meshWidth : 32 , meshHeight : 24 , pixelRatio : window . devicePixelRatio || 1 , } ) ; _visualizer . connectAudio ( analyser ) ; setVisualizer ( _visualizer ) ; } , [ butterchurn , analyser , height , width , visualizer ] ) ; useEffect ( ( ) => { if ( visualizer == null ) { return ; } visualizer . setRendererSize ( width , height ) ; } , [ visualizer , width , height ] ) ; const hasLoadedPreset = useRef < boolean > ( false ) ; useEffect ( ( ) => { if ( visualizer == null || currentPreset == null ) { return ; } if ( hasLoadedPreset . current ) { visualizer . loadPreset ( currentPreset , TRANSITION_TYPE_DURATIONS [ transitionType ] ) ; } else { visualizer . loadPreset ( currentPreset , TRANSITION_TYPE_DURATIONS [ TransitionType . IMMEDIATE ] ) ; hasLoadedPreset . current = true ; } } , [ visualizer , currentPreset ] ) ; useEffect ( ( ) => { if ( visualizer == null || ! trackTitle ) { return ; } visualizer . launchSongTitleAnim ( trackTitle ) ; } , [ visualizer , trackTitle ] ) ; const lastShownMessage = useRef < null | number > ( null ) ; useEffect ( ( ) => { if ( visualizer == null || message == null ) { return ; } if ( lastShownMessage . current == null || message . time > lastShownMessage . current ) { lastShownMessage . current = Date . now ( ) ; visualizer . launchSongTitleAnim ( message . text ) ; } } , [ visualizer , message ] ) ; const shouldAnimate = playing && isEnabledVisualizer ; useEffect ( ( ) => { if ( ! shouldAnimate || visualizer == null ) { return ; } let animationFrameRequest = null ; const loop = ( ) => { visualizer . render ( ) ; animationFrameRequest = window . requestAnimationFrame ( loop ) ; } ; loop ( ) ; return ( ) => { if ( animationFrameRequest != null ) { window . cancelAnimationFrame ( animationFrameRequest ) ; } } ; } , [ visualizer , shouldAnimate ] ) ; return ( < canvas height = { height } width = { width } style = { { height : "100%" , width : "100%" , display : isEnabledVisualizer ? "block" : "none" , } } ref = { canvasRef } / > ) ; } export default Visualizer ;