无限循环修复.md 5.5 KB

🔁 音频无限循环播放修复

问题现象

音频反复播放,无法停止:

🎵 音频播放完成
⚡ 预播放完成,开始播放完整音频(6个块)
🎵 音频段开始播放
🎵 音频播放完成
⚡ 预播放完成,开始播放完整音频(6个块)  ← 又触发!
🎵 音频段开始播放
🎵 音频播放完成
⚡ 预播放完成,开始播放完整音频(6个块)  ← 又触发!
... 无限循环 ❌

根本原因

onended 回调判断逻辑缺少"已播放完整音频"的标志:

this.audio.onended = () => {
  // 检查条件
  if (this.allChunksReceived && this.audioChunks.length > 5) {
    // 播放完整音频
    this.playAll()  // ← 这个会再次触发 onended,形成循环
  }
}

循环流程:

1. 预播放完成 → onended触发
2. 检测到 allChunksReceived=true && length=6 > 5
3. 播放完整音频 → 播放完成 → onended触发
4. 再次检测到 allChunksReceived=true && length=6 > 5
5. 再次播放 → 无限循环 ❌

修复方案

添加 hasPlayedFullAudio 标志

文件: frontend/src/utils/audioPlayerFallback.ts

// 类属性
private hasPlayedFullAudio = false  // 标记是否已播放过完整音频

// onended 回调
this.audio.onended = () => {
  console.log('🎵 音频播放完成')
  this.playing = false
  
  // 检查是否需要播放完整音频
  if (this.allChunksReceived && 
      this.audioChunks.length > 5 && 
      !this.hasPlayedFullAudio) {  // ← 关键:检查是否已播放过
    
    console.log(`⚡ 预播放完成,开始播放完整音频`)
    this.hasPlayedFullAudio = true  // ← 设置标志,防止重复
    this.isFirstSegmentPlaying = false
    setTimeout(() => this.playAll(), 100)
    
  } else if (this.allChunksReceived || this.hasPlayedFullAudio) {
    // 完整音频播放完成
    console.log('✅ 所有音频播放完成')
    this.config.onEnded?.()  // ← 触发结束回调
  } else {
    // 预播放完成,等待ai_response_end
    console.log('⏳ 预播放完成,等待ai_response_end信号...')
  }
}

在 clear() 时重置标志

clear() {
  // 重置所有标记
  this.hasStartedPlaying = false
  this.allChunksReceived = false
  this.playedChunksCount = 0
  this.isFirstSegmentPlaying = true
  this.hasPlayedFullAudio = false  // ← 重置
  
  // 清空音频块
  this.audioChunks = []
  this.playing = false
}

修复后的流程

正常播放流程

1. 收到5个块 → 预播放
   └─ playing = true
   └─ 播放前5个块
   └─ onended触发
       └─ hasPlayedFullAudio = false
       └─ 条件满足,播放完整音频
       └─ hasPlayedFullAudio = true  ← 设置标志

2. 完整音频播放
   └─ playing = true
   └─ 播放所有6个块
   └─ onended触发
       └─ hasPlayedFullAudio = true  ← 已设置
       └─ 条件不满足,触发 onEnded() ✅
       └─ 结束播放

关键: hasPlayedFullAudio 防止重复播放


修复效果

修复前(无限循环)

预播放 → 完整播放 → 完整播放 → 完整播放 → ... ❌

修复后(正常)

预播放 → 完整播放 → 结束 ✅

预期日志

正常播放流程

// 1. 开始录音
后端确认开始录音 - 清理播放器准备新一轮
🧹 清理播放器状态 - 丢弃 0 个音频块

// 2. 收集音频
📦 收集音频块: 1754 bytes (总计: 1 块)
📦 收集音频块: 1880 bytes (总计: 2 块)
📦 收集音频块: 2192 bytes (总计: 3 块)
📦 收集音频块: 2124 bytes (总计: 4 块)
📦 收集音频块: 1520 bytes (总计: 5 块)

// 3. 预播放
⚡ 收到5个音频块,立即开始快速预播放!
🎵 播放音频:5个块 / 总计5个块
🎵 音频段开始播放
✅ 首段音频开始播放

// 4. 继续收集
📦 收集音频块: 192 bytes (总计: 6 块)
✅ AI回复完成

// 5. 完整播放
🎵 音频播放完成
⚡ 预播放完成,开始播放完整音频(6个块)
🎵 播放音频:6个块 / 总计6个块
🎵 音频段开始播放

// 6. 结束
🎵 音频播放完成
✅ 所有音频播放完成  ← 不再循环!

测试验证

1. 刷新浏览器页面

清除缓存,重新加载

2. 测试短回复

说: "你好"

预期:

  • ~1秒听到 "Hello"
  • 短暂停顿
  • 播放完整音频 "Hello! How's your day going?"
  • 播放一次后停止 ✅

3. 检查日志

确保只看到一次 "⚡ 预播放完成,开始播放完整音频"


所有修改的文件

  1. ✅ frontend/src/utils/audioPlayerFallback.ts

    • 添加 hasPlayedFullAudio 标志
    • 修改 onended 回调逻辑
    • 在 clear() 时重置标志
    • 不在 playAudioData() 和 stop() 中清空audioChunks
  2. ✅ frontend/src/components/MainPage.tsx

    • ai_response_start: 不清理播放器
    • recording_started: 清理播放器

关键修复点总结

❌ 之前的问题

  1. ai_response_start 时清空audioChunks → 丢失前5个块
  2. onended 无限触发播放 → 无限循环
  3. playAudioData 清空audioChunks → 数据丢失

✅ 修复后

  1. 只在 recording_started 时清空 → 数据不丢失
  2. hasPlayedFullAudio 标志 → 防止循环
  3. 保留所有audioChunks → 完整播放

最终效果

✅ 快速响应: ~1秒听到声音 ✅ 完整播放: 所有音频都播放 ✅ 播放一次: 不再无限循环 ✅ 无错误: OGG格式完整

刷新浏览器,立即测试!应该完美工作了!🎉