import React, { useCallback, useState } from "react"; import { Renderer, RendererError } from "./Renderer"; import { ShaderError } from "./Shader"; import { useAppSelector } from "../../hooks"; import { PlayState, musicPlayer } from "../musicplayer/musicPlayerSlice"; function Visualizer() { const playing = useAppSelector((state) => state.musicPlayer.playing); const rendererState = useState(null); let renderer = rendererState[0]; const setRenderer = rendererState[1]; const [renderError, setRenderError] = useState(null); const visualizer = useCallback( (visualizer: HTMLDivElement | null) => { // TODO(tlater): Clean up state management. This is all // but trivial; there's seemingly no good place to keep // these big api objects (WebGLRenderingcontext or // AudioContext). // // It's tricky, too, because obviously react expects to be // in control of the DOM, and be allowed to delete our // canvas and create a new one. // // For the moment, this works, but it's a definite hack. if (renderer) { return; } // Until we start playing music, there is nothing to render. if (playing !== PlayState.Playing) { return; } if (musicPlayer.audioAnalyser === undefined) { throw new Error("MusicPlayer analyser was not set up on time"); } // If we're rendering an error message, we won't be // setting up the visualizer. // // Also, nonintuitively, renderError will be null here on // subsequent iterations, so we can't rely on it to // identify errors. if (visualizer === null) { return; } const canvas = visualizer.children[0]; const overlay = visualizer.children[1]; if ( !(canvas instanceof HTMLCanvasElement) || !(overlay instanceof HTMLSpanElement) ) { throw new Error( "react did not create our visualizer div correctly" ); } if (renderer === null) { renderer = new Renderer( musicPlayer.audioAnalyser, canvas, overlay ); setRenderer(renderer); } try { renderer.initializeScene(); } catch (error) { // Log so we don't lose the stack trace console.log(error); if (error instanceof ShaderError) { setRenderError( Failed to compile shader; This is a bug, feel free to contact me with this error message:
                                
                                    {error.message}
                                
                            
); } else if (error instanceof RendererError) { setRenderError( This browser does not support WebGL 2, sadly. This demo uses WebGL and specifically instanced drawing, so unfortunately this means it can't run on your browser/device. ); } else if (error instanceof Error) { setRenderError( Something went very wrong; apologies, either your browser is not behaving or there's a serious bug. You can contact me with this error message:
                                
                                    {error.message}
                                
                            
); } else { setRenderError( Something went very wrong; apologies, either your browser is not behaving or there's a serious bug. ); } } }, [playing, renderer, musicPlayer.audioAnalyser] ); if (renderError === null) { return (
); } else { return renderError; } } export default Visualizer;