fix: web audio capture buffer size + relay warning

- Use power-of-2 buffer (1024) for ScriptProcessorNode
- Accumulate samples and send exact 960-sample frames
- Remove unused watch import from relay

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 18:27:08 +04:00
parent 3f128936c4
commit a7afe4ff21
2 changed files with 32 additions and 19 deletions

View File

@@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{Mutex, watch};
use tokio::sync::Mutex;
use tracing::{error, info, warn};
use wzp_proto::MediaTransport;

View File

@@ -129,33 +129,46 @@ function startAudioCapture() {
audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE });
const source = audioCtx.createMediaStreamSource(mediaStream);
// ScriptProcessorNode for capturing raw PCM
// (AudioWorklet would be better but this is simpler for a prototype)
scriptNode = audioCtx.createScriptProcessor(FRAME_SIZE, 1, 1);
// ScriptProcessorNode requires power-of-2 buffer. We use 1024 and
// accumulate samples, sending exactly FRAME_SIZE (960) chunks.
const CAPTURE_BUF = 1024;
scriptNode = audioCtx.createScriptProcessor(CAPTURE_BUF, 1, 1);
let accumulator = new Float32Array(0);
scriptNode.onaudioprocess = (e) => {
if (!active || !ws || ws.readyState !== WebSocket.OPEN) return;
const input = e.inputBuffer.getChannelData(0); // Float32 [-1, 1]
const input = e.inputBuffer.getChannelData(0);
// Convert float32 to int16
const pcm = new Int16Array(input.length);
for (let i = 0; i < input.length; i++) {
pcm[i] = Math.max(-32768, Math.min(32767, Math.round(input[i] * 32767)));
// Append to accumulator
const newAcc = new Float32Array(accumulator.length + input.length);
newAcc.set(accumulator);
newAcc.set(input, accumulator.length);
accumulator = newAcc;
// Send complete FRAME_SIZE chunks
while (accumulator.length >= FRAME_SIZE) {
const frame = accumulator.slice(0, FRAME_SIZE);
accumulator = accumulator.slice(FRAME_SIZE);
const pcm = new Int16Array(FRAME_SIZE);
for (let i = 0; i < FRAME_SIZE; i++) {
pcm[i] = Math.max(-32768, Math.min(32767, Math.round(frame[i] * 32767)));
}
// Update level meter
let maxVal = 0;
for (let i = 0; i < pcm.length; i++) maxVal = Math.max(maxVal, Math.abs(pcm[i]));
document.getElementById('levelBar').style.width = (maxVal / 32768 * 100) + '%';
ws.send(pcm.buffer);
framesSent++;
}
// Update level meter
let maxVal = 0;
for (let i = 0; i < pcm.length; i++) maxVal = Math.max(maxVal, Math.abs(pcm[i]));
document.getElementById('levelBar').style.width = (maxVal / 32768 * 100) + '%';
// Send as binary
ws.send(pcm.buffer);
framesSent++;
};
source.connect(scriptNode);
scriptNode.connect(audioCtx.destination); // needed for scriptProcessor to work
scriptNode.connect(audioCtx.destination);
}
function playAudio(pcmInt16) {