|
@@ -301,7 +301,8 @@
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const MAX_AUDIO_QUEUE = 2;
|
|
|
+ const MAX_AUDIO_QUEUE = 8;
|
|
|
+ let scheduledTime = 0;
|
|
|
audioSocket.onmessage = function(event) {
|
|
|
if (!audioContext) return;
|
|
|
let pcm = new Int16Array(event.data);
|
|
@@ -311,7 +312,7 @@
|
|
|
}
|
|
|
if (pcm.length % 2 !== 0) {
|
|
|
console.warn("Received PCM data with odd length, dropping last sample");
|
|
|
- pcm = pcm.slice(0, -1); // Drop last sample if odd length
|
|
|
+ pcm = pcm.slice(0, -1);
|
|
|
}
|
|
|
// Convert Int16 PCM to Float32 [-1, 1]
|
|
|
let floatBuf = new Float32Array(pcm.length);
|
|
@@ -320,14 +321,10 @@
|
|
|
}
|
|
|
// Limit queue size to prevent memory overflow
|
|
|
if (audioQueue.length >= MAX_AUDIO_QUEUE) {
|
|
|
- audioQueue.shift(); // Remove oldest audio buffer if queue is full
|
|
|
+ audioQueue.shift();
|
|
|
}
|
|
|
audioQueue.push(floatBuf);
|
|
|
- if (!audioPlaying) {
|
|
|
- audioPlaying = true;
|
|
|
- playAudioQueue();
|
|
|
- }
|
|
|
-
|
|
|
+ scheduleAudioPlayback();
|
|
|
};
|
|
|
|
|
|
audioSocket.onclose = function() {
|
|
@@ -335,32 +332,38 @@
|
|
|
audioSocket = null;
|
|
|
audioPlaying = false;
|
|
|
audioQueue = [];
|
|
|
+ scheduledTime = 0;
|
|
|
};
|
|
|
|
|
|
audioSocket.onerror = function(e) {
|
|
|
console.error("Audio WebSocket error", e);
|
|
|
};
|
|
|
- }
|
|
|
|
|
|
- function playAudioQueue() {
|
|
|
- if (!audioContext || audioQueue.length === 0) {
|
|
|
- audioPlaying = false;
|
|
|
- return;
|
|
|
- }
|
|
|
- let floatBuf = audioQueue.shift();
|
|
|
- let frameCount = floatBuf.length / 2;
|
|
|
- let buffer = audioContext.createBuffer(2, frameCount, 48000);
|
|
|
- for (let ch = 0; ch < 2; ch++) {
|
|
|
- let channelData = buffer.getChannelData(ch);
|
|
|
- for (let i = 0; i < frameCount; i++) {
|
|
|
- channelData[i] = floatBuf[i * 2 + ch];
|
|
|
+ function scheduleAudioPlayback() {
|
|
|
+ if (!audioContext || audioQueue.length === 0) return;
|
|
|
+
|
|
|
+ // Use audioContext.currentTime to schedule buffers back-to-back
|
|
|
+ if (scheduledTime < audioContext.currentTime) {
|
|
|
+ scheduledTime = audioContext.currentTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (audioQueue.length > 0) {
|
|
|
+ let floatBuf = audioQueue.shift();
|
|
|
+ let frameCount = floatBuf.length / 2;
|
|
|
+ let buffer = audioContext.createBuffer(2, frameCount, 48000);
|
|
|
+ for (let ch = 0; ch < 2; ch++) {
|
|
|
+ let channelData = buffer.getChannelData(ch);
|
|
|
+ for (let i = 0; i < frameCount; i++) {
|
|
|
+ channelData[i] = floatBuf[i * 2 + ch];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let source = audioContext.createBufferSource();
|
|
|
+ source.buffer = buffer;
|
|
|
+ source.connect(audioContext.destination);
|
|
|
+ source.start(scheduledTime);
|
|
|
+ scheduledTime += buffer.duration;
|
|
|
}
|
|
|
}
|
|
|
- let source = audioContext.createBufferSource();
|
|
|
- source.buffer = buffer;
|
|
|
- source.connect(audioContext.destination);
|
|
|
- source.onended = playAudioQueue;
|
|
|
- source.start();
|
|
|
}
|
|
|
|
|
|
function stopAudioWebSocket() {
|