BetterAudioPlayer: Use CORS Proxy Again

This commit is contained in:
thororen 2026-10-02 08:58:51 -04:00
parent bc68ff9b10
commit cbab61da91
No known key found for this signature in database

View file

@ -13,6 +13,8 @@ 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 | null>;
@ -20,6 +22,12 @@ interface PlayerInstance {
state: { playing: boolean; };
}
interface AudioGraph {
ctx: AudioContext;
analyser: AnalyserNode;
blobUrl: string;
}
function validateColor(value: string, key: string, fallback: string) {
if (/^\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}$/.test(value)) return;
@ -98,62 +106,101 @@ 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);
}
async function createAudioGraph(audio: HTMLAudioElement): Promise<AudioGraph | null> {
const blobUrl = await fetchAudioBlob(audio.currentSrc || audio.src).catch(() => null);
if (!blobUrl) return null;
const wasPlaying = !audio.paused;
const { currentTime } = audio;
audio.src = blobUrl;
audio.currentTime = currentTime;
if (wasPlaying) audio.play().catch(() => { });
const ctx = new AudioContext();
const analyser = ctx.createAnalyser();
analyser.fftSize = 2048;
ctx.createMediaElementSource(audio).connect(analyser);
analyser.connect(ctx.destination);
return { ctx, analyser, blobUrl };
}
function Visualizer({ mediaRef, playing }: { mediaRef: PlayerInstance["mediaRef"]; playing: boolean; }) {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const graphRef = React.useRef<{ ctx: AudioContext; analyser: AnalyserNode; } | null>(null);
const graphRef = React.useRef<Promise<AudioGraph | null> | null>(null);
React.useEffect(() => {
const audio = mediaRef.current;
const canvasCtx = canvasRef.current?.getContext("2d");
if (!playing || !audio || !canvasCtx) return;
if (!graphRef.current) {
const ctx = new AudioContext();
const analyser = ctx.createAnalyser();
analyser.fftSize = 2048;
ctx.createMediaElementSource(audio).connect(analyser);
analyser.connect(ctx.destination);
graphRef.current = { ctx, analyser };
}
graphRef.current ??= createAudioGraph(audio);
const { ctx, analyser } = graphRef.current;
ctx.resume();
const dataArray = new Uint8Array(analyser.frequencyBinCount);
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
let frame = 0;
let stopped = false;
let graphCtx: AudioContext | null = null;
const draw = () => {
frame = requestAnimationFrame(draw);
graphRef.current.then(graph => {
if (stopped || !graph) return;
analyser.getByteTimeDomainData(dataArray);
analyser.getByteFrequencyData(frequencyData);
const { ctx, analyser } = graph;
graphCtx = ctx;
ctx.resume();
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);
}
const dataArray = new Uint8Array(analyser.frequencyBinCount);
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
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);
};
draw();
const draw = () => {
frame = requestAnimationFrame(draw);
analyser.getByteTimeDomainData(dataArray);
analyser.getByteFrequencyData(frequencyData);
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);
};
draw();
});
return () => {
stopped = true;
cancelAnimationFrame(frame);
ctx.suspend();
graphCtx?.suspend();
};
}, [playing]);
React.useEffect(() => () => void graphRef.current?.ctx.close(), []);
React.useEffect(() => () => {
graphRef.current?.then(graph => {
if (!graph) return;
graph.ctx.close();
URL.revokeObjectURL(graph.blobUrl);
});
}, []);
return (
<canvas
@ -209,10 +256,6 @@ export default definePlugin({
{
find: "}renderPlayIcon(){",
replacement: [
{
match: /"audio",\{(?=className:)/,
replace: '$&crossOrigin:"anonymous",',
},
{
match: /this\.renderAudio\(\):this\.renderVideo\(\)/,
replace: "$&,$self.renderVisualizer(this)",