修复语音/通话音频断续:改为持续运行的队列处理器
原实现:取出所有块精确调度 → 等播完 → 退出处理器 → 新块到达无人处理 → 下一批启动有间隙 改为:持续运行的处理器循环,新块到达时立即调度到上一块结束时间, 消除批次间的间隙。用 nextScheduleTime 追踪精确调度位置, 防止累积延迟。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e328df95ae
commit
d52073adb4
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user