把对话当作状态机
浏览器语音会话不只有连接与断开。至少区分空闲、请求权限、连接中、聆听、回答、被打断、停止中和失败。麦克风指示灯应反映实际录音状态,而不是只看 WebSocket 是否打开。
供应商事件协议封装在传输适配器中,页面只处理自己的应用状态。
type VoiceState =
| 'idle' | 'connecting' | 'listening'
| 'responding' | 'stopping' | 'failed';
function stopSession(socket: WebSocket, stream: MediaStream) {
stream.getTracks().forEach(track => track.stop());
socket.close(1000, 'User stopped the session');
}
这只是本地清理示例,正式实现还要停止排队音频、释放播放资源,并核对服务端会话是否结束。
从服务端取得短期凭据
浏览器使用官方 ephemeral-token 流程。用户发起会话后,本站先检查登录与额度,再生成短期凭据。长期 API key 不应出现在 JavaScript、source map、URL 参数或浏览器存储中。
只在用户启动会话时发放凭据,不要每次浏览页面都创建连接。失败时展示重试入口,不能后台无限重连。
录音和播放分开处理
按照官方文档核对编码、采样率和事件格式。MediaRecorder 生成的容器不一定就是 API 所需的原始音频。
用户打断时同时清理本地待播放音频,否则即使服务端接受打断,页面仍可能继续说话。
专门测试结束流程
连接中、录音中、播放中分别点击停止。再测离开页面、拒绝权限、拔掉麦克风和短暂断网,确认没有遗留录音轨道或空耗连接。
文字配音演示不需要麦克风。未来增加实时对话时,应在用户明确启动该功能后才请求权限。