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,