BetterAudioPlayer: Dont Use Observer
This commit is contained in:
parent
c507c762b6
commit
12c9b21d67
1 changed files with 49 additions and 116 deletions
|
|
@ -13,12 +13,11 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
import { ColorUtils, React, showToast, Toasts } from "@webpack/common";
|
||||
|
||||
const cl = classNameFactory("vc-better-audio-player-");
|
||||
const CORS_PROXY = "https://cors.keiran0.workers.dev?url=";
|
||||
const MAX_FILE_SIZE = 12e6;
|
||||
|
||||
interface PlayerInstance {
|
||||
mediaRef: React.RefObject<HTMLAudioElement>;
|
||||
props: { src: string; type: string; };
|
||||
mediaRef: React.RefObject<HTMLAudioElement | null>;
|
||||
props: { type: string; };
|
||||
state: { playing: boolean; };
|
||||
}
|
||||
|
||||
function validateColor(value: string, key: string, fallback: string) {
|
||||
|
|
@ -99,134 +98,62 @@ function drawSpectrograph(ctx: CanvasRenderingContext2D, w: number, h: number, f
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchAudioBlob(src: string): Promise<string | null> {
|
||||
const url = new URL(src);
|
||||
url.searchParams.set("t", Date.now().toString());
|
||||
|
||||
const response = await fetch(CORS_PROXY + encodeURIComponent(url.href));
|
||||
const contentLength = response.headers.get("content-length");
|
||||
if (contentLength && Number(contentLength) > MAX_FILE_SIZE) return null;
|
||||
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function Visualizer({ playerRef, src }: { playerRef: React.RefObject<HTMLAudioElement>; src: string; }) {
|
||||
function Visualizer({ mediaRef, playing }: { mediaRef: PlayerInstance["mediaRef"]; playing: boolean; }) {
|
||||
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
||||
const audioCtxRef = React.useRef<AudioContext | null>(null);
|
||||
const analyserRef = React.useRef<AnalyserNode | null>(null);
|
||||
const animFrameRef = React.useRef(0);
|
||||
const setupDoneRef = React.useRef(false);
|
||||
const blobUrlRef = React.useRef<string | null>(null);
|
||||
const graphRef = React.useRef<{ ctx: AudioContext; analyser: AnalyserNode; } | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const audio = playerRef.current;
|
||||
const canvas = canvasRef.current;
|
||||
if (!audio || !canvas) return () => { };
|
||||
const audio = mediaRef.current;
|
||||
const canvasCtx = canvasRef.current?.getContext("2d");
|
||||
if (!playing || !audio || !canvasCtx) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const init = async () => {
|
||||
const blobUrl = await fetchAudioBlob(src).catch(() => null);
|
||||
if (cancelled || !blobUrl) return;
|
||||
|
||||
blobUrlRef.current = blobUrl;
|
||||
|
||||
const wasPlaying = !audio.paused;
|
||||
const { currentTime } = audio;
|
||||
audio.src = blobUrl;
|
||||
audio.currentTime = currentTime;
|
||||
|
||||
const audioCtx = new AudioContext();
|
||||
const analyser = audioCtx.createAnalyser();
|
||||
if (!graphRef.current) {
|
||||
const ctx = new AudioContext();
|
||||
const analyser = ctx.createAnalyser();
|
||||
analyser.fftSize = 2048;
|
||||
const source = audioCtx.createMediaElementSource(audio);
|
||||
source.connect(analyser);
|
||||
analyser.connect(audioCtx.destination);
|
||||
audioCtxRef.current = audioCtx;
|
||||
analyserRef.current = analyser;
|
||||
setupDoneRef.current = true;
|
||||
|
||||
if (wasPlaying) {
|
||||
audio.play().catch(() => { });
|
||||
ctx.createMediaElementSource(audio).connect(analyser);
|
||||
analyser.connect(ctx.destination);
|
||||
graphRef.current = { ctx, analyser };
|
||||
}
|
||||
};
|
||||
|
||||
const canvasCtx = canvas.getContext("2d");
|
||||
let dataArray: Uint8Array<ArrayBuffer> | null = null;
|
||||
let frequencyData: Uint8Array<ArrayBuffer> | null = null;
|
||||
const { ctx, analyser } = graphRef.current;
|
||||
ctx.resume();
|
||||
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
|
||||
let frame = 0;
|
||||
|
||||
const draw = () => {
|
||||
const analyser = analyserRef.current;
|
||||
if (!canvasCtx || !analyser) return;
|
||||
|
||||
if (!dataArray || !frequencyData) {
|
||||
const bufferLength = analyser.frequencyBinCount;
|
||||
dataArray = new Uint8Array(bufferLength);
|
||||
frequencyData = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
if (!audio.paused) animFrameRef.current = requestAnimationFrame(draw);
|
||||
frame = requestAnimationFrame(draw);
|
||||
|
||||
analyser.getByteTimeDomainData(dataArray);
|
||||
analyser.getByteFrequencyData(frequencyData);
|
||||
|
||||
const { width, height } = canvas.getBoundingClientRect();
|
||||
const { canvas } = canvasCtx;
|
||||
const width = canvas.clientWidth;
|
||||
const height = canvas.clientHeight;
|
||||
const dpr = window.devicePixelRatio;
|
||||
const pixelWidth = Math.round(width * dpr);
|
||||
const pixelHeight = Math.round(height * dpr);
|
||||
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
|
||||
canvas.width = pixelWidth;
|
||||
canvas.height = pixelHeight;
|
||||
canvasCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
|
||||
canvasCtx.clearRect(0, 0, width, height);
|
||||
if (settings.store.oscilloscope) drawOscilloscope(canvasCtx, width, height, dataArray, dataArray.length);
|
||||
if (settings.store.spectrograph) drawSpectrograph(canvasCtx, width, height, frequencyData, frequencyData.length);
|
||||
};
|
||||
|
||||
const onPlay = () => {
|
||||
if (!setupDoneRef.current) return;
|
||||
if (audioCtxRef.current?.state === "suspended") {
|
||||
audioCtxRef.current.resume();
|
||||
}
|
||||
draw();
|
||||
};
|
||||
|
||||
const onPause = () => {
|
||||
audioCtxRef.current?.suspend();
|
||||
cancelAnimationFrame(animFrameRef.current);
|
||||
};
|
||||
|
||||
audio.addEventListener("play", onPlay);
|
||||
audio.addEventListener("pause", onPause);
|
||||
init();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
audio.removeEventListener("play", onPlay);
|
||||
audio.removeEventListener("pause", onPause);
|
||||
cancelAnimationFrame(animFrameRef.current);
|
||||
audioCtxRef.current?.close();
|
||||
audioCtxRef.current = null;
|
||||
analyserRef.current = null;
|
||||
setupDoneRef.current = false;
|
||||
if (blobUrlRef.current) {
|
||||
URL.revokeObjectURL(blobUrlRef.current);
|
||||
blobUrlRef.current = null;
|
||||
}
|
||||
cancelAnimationFrame(frame);
|
||||
ctx.suspend();
|
||||
};
|
||||
}, [playerRef]);
|
||||
}, [playing]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return () => { };
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * window.devicePixelRatio;
|
||||
canvas.height = rect.height * window.devicePixelRatio;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx?.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
};
|
||||
|
||||
resize();
|
||||
const observer = new ResizeObserver(resize);
|
||||
observer.observe(canvas);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
React.useEffect(() => () => void graphRef.current?.ctx.close(), []);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
|
|
@ -281,15 +208,21 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
find: "}renderPlayIcon(){",
|
||||
replacement: {
|
||||
replacement: [
|
||||
{
|
||||
match: /"audio",\{(?=className:)/,
|
||||
replace: '$&crossOrigin:"anonymous",',
|
||||
},
|
||||
{
|
||||
match: /this\.renderAudio\(\):this\.renderVideo\(\)/,
|
||||
replace: "$&,$self.renderVisualizer(this)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
renderVisualizer(player: PlayerInstance) {
|
||||
if (player.props.type !== "AUDIO") return null;
|
||||
return <Visualizer playerRef={player.mediaRef} src={player.props.src} key={player.props.src} />;
|
||||
return <Visualizer mediaRef={player.mediaRef} playing={player.state.playing} />;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue