v3: added localization, use global player for uploaded audio/video files

This commit is contained in:
khannurien
2026-04-03 15:29:33 +00:00
parent 378b3ffa46
commit 0ce80398a4
64 changed files with 4248 additions and 941 deletions

44
src/utils/waveform.ts Normal file
View File

@@ -0,0 +1,44 @@
export const NUM_BARS = 150;
export const BAR_W = 3;
export const BAR_GAP = 1;
export const WAVEFORM_H = 48;
export const VIEWBOX_W = NUM_BARS * (BAR_W + BAR_GAP); // 600
// Module-level cache: survives StrictMode double-effect and re-renders
const peaksCache = new Map<string, Float32Array>();
export async function extractPeaks(
url: string,
n: number,
): Promise<Float32Array> {
if (peaksCache.has(url)) return peaksCache.get(url)!;
// /api/files is public (no auth middleware). Plain fetch avoids the CORS
// preflight that credentials/Authorization would trigger — the server's
// oakCors config doesn't set Allow-Credentials so credentialed fetches
// are blocked, while <audio> bypasses CORS entirely.
const res = await fetch(url);
if (!res.ok) throw new Error(`fetch failed: ${res.status}`);
const buf = await res.arrayBuffer();
const actx = new AudioContext();
try {
const decoded = await actx.decodeAudioData(buf);
const data = decoded.getChannelData(0);
const blockSize = Math.floor(data.length / n);
const peaks = new Float32Array(n);
for (let i = 0; i < n; i++) {
let peak = 0;
const offset = i * blockSize;
for (let j = 0; j < blockSize; j++) {
const abs = Math.abs(data[offset + j]);
if (abs > peak) peak = abs;
}
peaks[i] = peak;
}
peaksCache.set(url, peaks);
return peaks;
} finally {
actx.close();
}
}