fix(conversation): 修复实时会话 TTS/回复被离屏 WS 抢占

- 列表预热仅预取消息缓存,避免后台 WebSocket 覆盖服务端连接
- RealtimeSession UI 回调按 owner 独占,防止 offscreen 覆盖聊天页
- 列表页聚焦时再 prewarm,会话页 TTS 入队优先 base64
- 管线下发 TTS 同时带 audio_base64 与 audio_url;协议说明同步
- 移除 TTS 排查用前后端调试日志,保留错误/告警
- 补充 WS / RealtimeSession / entry-warmup / 播放器相关单测

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-12 10:42:44 +08:00
parent 93be60f74c
commit 3d01085442
18 changed files with 643 additions and 261 deletions

View File

@@ -94,6 +94,12 @@ export function usePlayer(): UsePlayerResult {
const acquired = await audioFocus.acquireForPlayback();
if (!acquired) {
/**
* 录音占用时 acquire 失败且队列尚未 shift若用户进入会话前焦点已在
* `recorder`,可能不会再次触发 `onOwnerChange('recorder')`,旧的
* `wasBlockedByRecorderRef` 不会被置位,录音结束后也不会重试 playNext。
*/
wasBlockedByRecorderRef.current = true;
setStatus('idle');
return;
}
@@ -172,14 +178,17 @@ export function usePlayer(): UsePlayerResult {
if (owner === null && wasBlockedByRecorderRef.current) {
wasBlockedByRecorderRef.current = false;
if (queueRef.current.length > 0 && status === 'idle') {
playNext();
if (
queueRef.current.length > 0 &&
playbackActiveUriRef.current === null
) {
void playNext();
}
}
});
return unsub;
}, [status, currentSource, playNext]);
}, [currentSource, playNext]);
const enqueue = useCallback(
async (item: PlaybackItem) => {