From d52073adb43cab268d0c858264c72f4e702f4cea Mon Sep 17 00:00:00 2001 From: wsb1224 Date: Wed, 10 Jun 2026 15:29:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=AD=E9=9F=B3/=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E9=9F=B3=E9=A2=91=E6=96=AD=E7=BB=AD=EF=BC=9A=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=8C=81=E7=BB=AD=E8=BF=90=E8=A1=8C=E7=9A=84=E9=98=9F?= =?UTF-8?q?=E5=88=97=E5=A4=84=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原实现:取出所有块精确调度 → 等播完 → 退出处理器 → 新块到达无人处理 → 下一批启动有间隙 改为:持续运行的处理器循环,新块到达时立即调度到上一块结束时间, 消除批次间的间隙。用 nextScheduleTime 追踪精确调度位置, 防止累积延迟。 Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/stores/chatStore.js | 74 ++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/frontend/src/stores/chatStore.js b/frontend/src/stores/chatStore.js index 4872c47..0897498 100644 --- a/frontend/src/stores/chatStore.js +++ b/frontend/src/stores/chatStore.js @@ -554,7 +554,7 @@ export const useChatStore = defineStore('chat', () => { // 如果没有在播放,开始播放队列 if (!isPlayingQueue.value) { - playAudioQueue() + startAudioQueueProcessor() } } break @@ -991,7 +991,7 @@ export const useChatStore = defineStore('chat', () => { // 如果没有在播放,开始播放队列 if (!isPlayingQueue.value) { - playAudioQueue() + startAudioQueueProcessor() } } break @@ -1433,57 +1433,65 @@ export const useChatStore = defineStore('chat', () => { /** * 播放音频队列 - 顺序播放所有缓冲的音频片段 */ - async function playAudioQueue() { - if (isPlayingQueue.value) return - if (audioQueue.value.length === 0) return + // 持续运行的音频队列处理器 + let audioQueueRunning = false + let nextScheduleTime = 0 + async function startAudioQueueProcessor() { + if (audioQueueRunning) return + audioQueueRunning = true isPlayingQueue.value = true isPlaying.value = true - // 初始化AudioContext if (!currentAudioContext || currentAudioContext.state === 'closed') { currentAudioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 }) } - - // 确保 AudioContext 处于运行状态 - await ensureAudioContext(currentAudioContext); + await ensureAudioContext(currentAudioContext) + nextScheduleTime = currentAudioContext.currentTime try { - // 一次性取出队列中所有音频块 - const chunks = [] - while (audioQueue.value.length > 0) { - const item = audioQueue.value.shift() - if (item) chunks.push(item) - } + while (audioQueueRunning) { + // 取出当前队列中所有块 + const chunks = [] + while (audioQueue.value.length > 0) { + const item = audioQueue.value.shift() + if (item) chunks.push(item) + } - if (chunks.length === 0) return + if (chunks.length === 0) { + // 无新块,短暂等待后继续检查 + await new Promise(r => setTimeout(r, 20)) + continue + } - // 用精确时间调度所有块,消除间隙 - let nextStartTime = currentAudioContext.currentTime - let lastSource = null + // 确保调度时间不早于当前时间(防止累积延迟) + if (nextScheduleTime < currentAudioContext.currentTime) { + nextScheduleTime = currentAudioContext.currentTime + } - for (const chunk of chunks) { - const result = schedulePCMChunk(chunk.data, chunk.sampleRate, nextStartTime) - nextStartTime += result.duration - lastSource = result.source - } - - // 等待最后一块播放完成 - if (lastSource) { - await new Promise((resolve) => { - lastSource.onended = () => resolve() - }) + // 精确调度每个块 + for (const chunk of chunks) { + const result = schedulePCMChunk(chunk.data, chunk.sampleRate, nextScheduleTime) + nextScheduleTime += result.duration + } } } catch (error) { - console.error('[ChatStore] Audio queue playback error:', error) + console.error('[ChatStore] Audio queue processor error:', error) } finally { isPlayingQueue.value = false isPlaying.value = false + audioQueueRunning = false } } + function stopAudioQueueProcessor() { + audioQueueRunning = false + isPlayingQueue.value = false + isPlaying.value = false + } + /** * 将 PCM 数据解码为 AudioBuffer */ @@ -1580,6 +1588,7 @@ export const useChatStore = defineStore('chat', () => { * 重置音频状态 */ function resetAudioState() { + stopAudioQueueProcessor() audioQueue.value = [] isPlayingQueue.value = false isPlaying.value = false @@ -1626,6 +1635,8 @@ export const useChatStore = defineStore('chat', () => { * 停止音频播放 */ function stopAudioPlayback() { + // 停止持续队列处理器 + stopAudioQueueProcessor() // 清空音频队列 audioQueue.value = [] isPlayingQueue.value = false @@ -1854,7 +1865,6 @@ export const useChatStore = defineStore('chat', () => { // 音频播放 playAudio, playPCMAudio, - playAudioQueue, stopAudio, stopAudioPlayback, resetAudioState,