fix: revert to scheduled playback with 200ms drift cap
Pull-based ScriptProcessor approach broke audio completely. Back to createBufferSource scheduling which worked, but with tighter 200ms max drift (was 300ms). Snaps back when exceeded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -173,52 +173,32 @@ function startAudioCapture() {
|
|||||||
scriptNode.connect(audioCtx.destination);
|
scriptNode.connect(audioCtx.destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull-based playback with sample-accurate ring buffer
|
// Scheduled playback with aggressive drift correction
|
||||||
let playSamples = new Float32Array(0); // accumulated float samples
|
let nextPlayTime = 0;
|
||||||
let playbackNode = null;
|
|
||||||
const MAX_BUFFERED_SAMPLES = SAMPLE_RATE / 5; // 200ms max (~9600 samples)
|
|
||||||
|
|
||||||
function initPlayback() {
|
|
||||||
if (playbackNode) return;
|
|
||||||
playbackNode = audioCtx.createScriptProcessor(1024, 1, 1);
|
|
||||||
playbackNode.onaudioprocess = (e) => {
|
|
||||||
const output = e.outputBuffer.getChannelData(0);
|
|
||||||
const need = output.length; // 1024
|
|
||||||
|
|
||||||
// Drop excess to cap latency
|
|
||||||
if (playSamples.length > MAX_BUFFERED_SAMPLES) {
|
|
||||||
playSamples = playSamples.slice(playSamples.length - MAX_BUFFERED_SAMPLES);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playSamples.length >= need) {
|
|
||||||
output.set(playSamples.subarray(0, need));
|
|
||||||
playSamples = playSamples.slice(need);
|
|
||||||
} else if (playSamples.length > 0) {
|
|
||||||
// Partial — play what we have, pad with silence
|
|
||||||
output.set(playSamples.subarray(0, playSamples.length));
|
|
||||||
for (let i = playSamples.length; i < need; i++) output[i] = 0;
|
|
||||||
playSamples = new Float32Array(0);
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < need; i++) output[i] = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
playbackNode.connect(audioCtx.destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
function playAudio(pcmInt16) {
|
function playAudio(pcmInt16) {
|
||||||
if (!audioCtx) return;
|
if (!audioCtx) return;
|
||||||
initPlayback();
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append to sample buffer
|
const buffer = audioCtx.createBuffer(1, floatData.length, SAMPLE_RATE);
|
||||||
const combined = new Float32Array(playSamples.length + floatData.length);
|
buffer.getChannelData(0).set(floatData);
|
||||||
combined.set(playSamples);
|
|
||||||
combined.set(floatData, playSamples.length);
|
const source = audioCtx.createBufferSource();
|
||||||
playSamples = combined;
|
source.buffer = buffer;
|
||||||
|
source.connect(audioCtx.destination);
|
||||||
|
|
||||||
|
const now = audioCtx.currentTime;
|
||||||
|
|
||||||
|
if (nextPlayTime < now || nextPlayTime > now + 0.2) {
|
||||||
|
// Behind or drifted too far ahead — snap to now + 40ms
|
||||||
|
nextPlayTime = now + 0.04;
|
||||||
|
}
|
||||||
|
source.start(nextPlayTime);
|
||||||
|
nextPlayTime += buffer.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startStatsUpdate() {
|
function startStatsUpdate() {
|
||||||
|
|||||||
Reference in New Issue
Block a user