const $ = id => document.getElementById(id); const SR = 16000; let rec = null, chunks = [], ctx = null; /* Decode + resample in the browser. decodeAudioData handles wav/mp3/m4a/webm, so the server never has to guess a container format or ship ffmpeg. */ async function toSamples(blob) { ctx = ctx || new (window.AudioContext || window.webkitAudioContext)(); const buf = await ctx.decodeAudioData(await blob.arrayBuffer()); const off = new OfflineAudioContext(1, Math.ceil(buf.duration * SR), SR); const src = off.createBufferSource(); // mix to mono first; a stereo file otherwise loses one channel const mono = off.createBuffer(1, buf.length, buf.sampleRate); const m = mono.getChannelData(0); for (let c = 0; c < buf.numberOfChannels; c++) { const d = buf.getChannelData(c); for (let i = 0; i < d.length; i++) m[i] += d[i] / buf.numberOfChannels; } src.buffer = mono; src.connect(off.destination); src.start(); return (await off.startRendering()).getChannelData(0); } function drawWave(s) { const N = 72, step = Math.floor(s.length / N) || 1; let html = ""; for (let i = 0; i < N; i++) { let peak = 0; for (let j = i * step; j < (i + 1) * step && j < s.length; j++) peak = Math.max(peak, Math.abs(s[j])); html += ``; } $("wave").innerHTML = html; } async function send(samples) { drawWave(samples); $("hint").textContent = "بيفرّغ…"; const t0 = performance.now(); try { const r = await fetch("/api/asr", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ samples: Array.from(samples) }) }); const d = await r.json(); if (d.error) { $("hint").textContent = d.error; return; } const wall = Math.round(performance.now() - t0); $("out").insertAdjacentHTML("afterbegin", `
${d.text || "(مفيش كلام اتعرف عليه)"}