BetterAudioPlayer: Use CORS Proxy Again
This commit is contained in:
parent
bc68ff9b10
commit
cbab61da91
1 changed files with 83 additions and 40 deletions
|
|
@ -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)",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue