From 87d43ba168bc90ce22ec1ed4348f53898f91f587 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 1 Jan 2020 15:08:55 -0800 Subject: [PATCH] Finally convert visualizer to function component --- examples/memory/index.html | 57 +++++++++++++ js/components/Visualizer.js | 158 +++++++++++++++++------------------- js/hooks.ts | 37 +++++++++ 3 files changed, 167 insertions(+), 85 deletions(-) create mode 100755 examples/memory/index.html diff --git a/examples/memory/index.html b/examples/memory/index.html new file mode 100755 index 00000000..af9c374b --- /dev/null +++ b/examples/memory/index.html @@ -0,0 +1,57 @@ + + + + + + + +
+ + +
+
+ +
+ + + + diff --git a/js/components/Visualizer.js b/js/components/Visualizer.js index 2b0643b1..4abf111c 100644 --- a/js/components/Visualizer.js +++ b/js/components/Visualizer.js @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useEffect } from "react"; +import React, { useMemo, useState, useEffect, useRef } from "react"; import { connect } from "react-redux"; import { toggleVisualizerStyle } from "../actionCreators"; @@ -13,6 +13,7 @@ import { NUM_BARS, PIXEL_DENSITY, } from "./visualizerUtils"; +import { useAnimationLoop } from "../hooks"; function Wrapper(props) { const [barPeaks] = useState(() => new Array(NUM_BARS).fill(0)); @@ -52,103 +53,90 @@ function Wrapper(props) { return octaveBucketsForBufferLength(dataArray.length); }, [dataArray.length]); + const paintFrame = useMemo(() => { + if (dataArray == null || props.status !== MEDIA_STATUS.PLAYING) { + return null; + } + return canvasCtx => { + switch (props.style) { + case VISUALIZERS.OSCILLOSCOPE: + canvasCtx.drawImage(bgCanvas, 0, 0); + paintOscilloscopeFrame({ + analyser: props.analyser, + dataArray, + canvasCtx, + height, + width, + colors: props.colors, + renderWidth, + }); + break; + case VISUALIZERS.BAR: + canvasCtx.drawImage(bgCanvas, 0, 0); + paintBarFrame({ + analyser: props.analyser, + dataArray, + renderHeight, + octaveBuckets, + barPeaks, + barPeakFrames, + height, + canvasCtx, + barCanvas, + windowShade: props.windowShade, + colors: props.colors, + }); + break; + default: + this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + }; + }, [ + barCanvas, + barPeakFrames, + barPeaks, + bgCanvas, + dataArray, + height, + octaveBuckets, + props.analyser, + props.colors, + props.status, + props.style, + props.windowShade, + renderHeight, + renderWidth, + width, + ]); + const visualizerProps = { ...props, renderHeight, renderWidth, height, width, - bgCanvas, - barCanvas, - barPeaks, - barPeakFrames, - dataArray, - octaveBuckets, + paintFrame, }; return ; } -class Visualizer extends React.Component { - componentDidMount() { - this.canvasCtx = this.canvas.getContext("2d"); - this.canvasCtx.imageSmoothingEnabled = false; +function Visualizer(props) { + const canvasRef = useRef(null); - // Kick off the animation loop - const loop = () => { - if (this.props.status === MEDIA_STATUS.PLAYING) { - if (this.props.dummyVizData) { - Object.keys(this.props.dummyVizData).forEach(i => { - this._printBar(i, this.props.dummyVizData[i]); - }); - } else { - this.paintFrame(); - } - } - this._animationRequest = window.requestAnimationFrame(loop); - }; - loop(); - } + useAnimationLoop({ canvas: canvasRef.current, paintFrame: props.paintFrame }); - componentWillUnmount() { - if (this._animationRequest) { - window.cancelAnimationFrame(this._animationRequest); - } - } - - componentDidUpdate() { - // Redraw the current frame, since the skin may have changed. - this.paintFrame(); - } - - paintFrame() { - switch (this.props.style) { - case VISUALIZERS.OSCILLOSCOPE: - this.canvasCtx.drawImage(this.props.bgCanvas, 0, 0); - paintOscilloscopeFrame({ - analyser: this.props.analyser, - dataArray: this.props.dataArray, - canvasCtx: this.canvasCtx, - height: this.props.height, - width: this.props.width, - colors: this.props.colors, - renderWidth: this.props.renderWidth, - }); - break; - case VISUALIZERS.BAR: - this.canvasCtx.drawImage(this.props.bgCanvas, 0, 0); - paintBarFrame({ - analyser: this.props.analyser, - dataArray: this.props.dataArray, - renderHeight: this.props.renderHeight, - octaveBuckets: this.props.octaveBuckets, - barPeaks: this.props.barPeaks, - barPeakFrames: this.props.barPeakFrames, - height: this.props.height, - canvasCtx: this.canvasCtx, - barCanvas: this.props.barCanvas, - windowShade: this.props.windowShade, - colors: this.props.colors, - }); - break; - default: - this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height); - } - } - - render() { - const { renderWidth, renderHeight, width, height } = this.props; - return ( - (this.canvas = node)} - style={{ width: renderWidth, height: renderHeight }} - width={width} - height={height} - onClick={this.props.toggleVisualizerStyle} - /> - ); - } + const { renderWidth, renderHeight, width, height } = props; + return ( + + ); } const mapStateToProps = state => ({ diff --git a/js/hooks.ts b/js/hooks.ts index 564062bf..d1575fb8 100644 --- a/js/hooks.ts +++ b/js/hooks.ts @@ -4,6 +4,7 @@ import { useCallback, useLayoutEffect, useRef, + useMemo, } from "react"; import { useDispatch, useSelector } from "react-redux"; import * as Utils from "./utils"; @@ -43,6 +44,42 @@ export function usePromiseValueOrNull(propValue: Promise): T | null { return value; } +type AnimationLoopOptions = { + canvas: HTMLCanvasElement; + paintFrame: null | ((canvasCtx: CanvasRenderingContext2D) => void); +}; + +export function useAnimationLoop({ canvas, paintFrame }: AnimationLoopOptions) { + const canvasCtx = useMemo(() => { + if (canvas == null) { + return null; + } + + return canvas.getContext("2d"); + }, [canvas]); + + useLayoutEffect(() => { + if (canvasCtx == null || paintFrame == null) { + return; + } + + let animationRequest: number | null = null; + function loop() { + // @ts-ignore This can't be null + paintFrame(canvasCtx); + animationRequest = window.requestAnimationFrame(loop); + } + + loop(); + + return () => { + if (animationRequest != null) { + window.cancelAnimationFrame(animationRequest); + } + }; + }, [canvasCtx, paintFrame]); +} + export function useScreenSize() { const [size] = useState(Utils.getScreenSize()); // TODO: We could subscribe to screen size changes.