fix: web audio playback quality — gapless scheduling + sample rate debug

- Schedule each playback buffer to start exactly where the last ended
  (was causing gaps/overlaps with fixed 60ms offset)
- Log AudioContext sample rate to console for debugging
- Reset playback timeline when falling behind

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 18:29:00 +04:00
parent a7afe4ff21
commit 9ad21182a8

View File

@@ -127,6 +127,8 @@ function stopCall() {
function startAudioCapture() { function startAudioCapture() {
audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE }); audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE });
console.log('AudioContext sampleRate:', audioCtx.sampleRate);
setStatus('Connected — mic active (sample rate: ' + audioCtx.sampleRate + 'Hz)');
const source = audioCtx.createMediaStreamSource(mediaStream); const source = audioCtx.createMediaStreamSource(mediaStream);
// ScriptProcessorNode requires power-of-2 buffer. We use 1024 and // ScriptProcessorNode requires power-of-2 buffer. We use 1024 and
@@ -171,10 +173,11 @@ function startAudioCapture() {
scriptNode.connect(audioCtx.destination); scriptNode.connect(audioCtx.destination);
} }
let nextPlayTime = 0;
function playAudio(pcmInt16) { function playAudio(pcmInt16) {
if (!audioCtx) return; if (!audioCtx) return;
// Convert int16 to float32
const floatData = new Float32Array(pcmInt16.length); const floatData = new Float32Array(pcmInt16.length);
for (let i = 0; i < pcmInt16.length; i++) { for (let i = 0; i < pcmInt16.length; i++) {
floatData[i] = pcmInt16[i] / 32768.0; floatData[i] = pcmInt16[i] / 32768.0;
@@ -187,9 +190,14 @@ function playAudio(pcmInt16) {
source.buffer = buffer; source.buffer = buffer;
source.connect(audioCtx.destination); source.connect(audioCtx.destination);
// Schedule playback with small buffer to reduce latency // Schedule seamlessly: each buffer starts exactly where the last one ended
const playTime = audioCtx.currentTime + 0.06; // 60ms buffer const now = audioCtx.currentTime;
source.start(playTime); if (nextPlayTime < now) {
// Fell behind — reset with small lookahead
nextPlayTime = now + 0.04; // 40ms initial buffer
}
source.start(nextPlayTime);
nextPlayTime += buffer.duration;
} }
function startStatsUpdate() { function startStatsUpdate() {