mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-23 10:07:35 +00:00
Finally convert visualizer to function component
This commit is contained in:
parent
ab43ef4a2d
commit
87d43ba168
3 changed files with 167 additions and 85 deletions
57
examples/memory/index.html
Executable file
57
examples/memory/index.html
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="position: absolute; top: 50px; left: 50px;">
|
||||
<button id="load">Load</button>
|
||||
<button id="clean">Clean</button>
|
||||
</div>
|
||||
<div id="app" style="height: 100vh">
|
||||
<!-- Webamp will attempt to center itself within this div -->
|
||||
</div>
|
||||
<script src="../../built/webamp.bundle.js"></script>
|
||||
<script>
|
||||
const Webamp = window.Webamp;
|
||||
let webamp = null;
|
||||
async function load() {
|
||||
webamp = new Webamp({
|
||||
initialTracks: [
|
||||
{
|
||||
metaData: {
|
||||
artist: "DJ Mike Llama",
|
||||
title: "Llama Whippin' Intro",
|
||||
},
|
||||
url:
|
||||
"https://cdn.jsdelivr.net/gh/captbaritone/webamp@43434d82cfe0e37286dbbe0666072dc3190a83bc/mp3/llama-2.91.mp3",
|
||||
duration: 5.322286,
|
||||
},
|
||||
],
|
||||
});
|
||||
await webamp.renderWhenReady(document.getElementById("app"));
|
||||
}
|
||||
function clean() {
|
||||
webamp.dispose();
|
||||
webamp = null;
|
||||
}
|
||||
|
||||
async function leak(times = 1) {
|
||||
for(let i = 0; i < times; i++) {
|
||||
await load();
|
||||
clean();
|
||||
}
|
||||
console.log('done')
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("load")
|
||||
.addEventListener("click", load);
|
||||
|
||||
document
|
||||
.getElementById("clean")
|
||||
.addEventListener("click", clean);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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 <Visualizer {...visualizerProps} />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<canvas
|
||||
id="visualizer"
|
||||
ref={node => (this.canvas = node)}
|
||||
style={{ width: renderWidth, height: renderHeight }}
|
||||
width={width}
|
||||
height={height}
|
||||
onClick={this.props.toggleVisualizerStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const { renderWidth, renderHeight, width, height } = props;
|
||||
return (
|
||||
<canvas
|
||||
id="visualizer"
|
||||
ref={canvasRef}
|
||||
style={{ width: renderWidth, height: renderHeight }}
|
||||
width={width}
|
||||
height={height}
|
||||
onClick={props.toggleVisualizerStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
|
|
|||
37
js/hooks.ts
37
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<T>(propValue: Promise<T>): 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<Size>(Utils.getScreenSize());
|
||||
// TODO: We could subscribe to screen size changes.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue