Tag: Live API

1 Posts

把 Gemini Live 实时语音接进个人网站,以及顺手解决的音字同步问题
最近把 Google 的 Gemini Multimodal Live API 接到了个人网站上。 本来是冲着 Gemini 那个能实时语音、甚至能实时传视频流的酷炫效果去的,跟它官方 App 的体验差不多。不过做着做着就发现,理想很丰满,现实的带宽和安全墙很骨感: 1. **安全第一,Key 不能裸奔**:如果完全在前端调用它的 WebSocket,那 API Key 就不得不暴露在浏览器里,这肯定不能忍。再加上国内服务器访问 Google 接口得挂一层代理,所以必须在后端起个网关做一层 WebSocket 转发。 2. **带宽刺客**:如果要开摄像头实时传视频,视频帧需要以极高的频率进行数据传输(要么走 WebRTC,要么在 WebSocket 里狂塞图片)。而我这腾讯云轻量服务器只有区区 3M 带宽,一旦转发视频,服务器的带宽瞬间就会被吃满,卡成 PPT。所以最后我索性砍掉了视频传输,只做纯语音和文字的实时对话。 虽然没有摄像头,但做出来的普通对话体验依然很赞,而且在折腾的过程中,顺手解决了一个很经典的问题——**字幕与声音的同步**。 以前做这种语音对话,大模型的文本流(Transcription)和音频流(Audio)通常是异步发过来的。如果是文本先跑出来,声音还在慢吞吞地读,体验就非常诡异,像是在看“剧透字幕”;如果自己用复杂的定时器或者每一帧去算字数 pacing,又容易因为网络抖动产生累计误差。 后面折腾了半天,让 AI 帮我一起研究,发现 Gemini 返回的字段结构其实很丰富。我就想了一个比较取巧且优雅的方案:**音字分组机制**。 在 Node.js 中转层里,我定义了一个 `group` 缓冲区。一旦接收到上游发来的文本片段,就标志着一个新分组的开始,同时把在这之后到达的所有音频切片(Audio Chunks)通通塞进这个分组里,并把它们作为一个整体的 `chunk_group` 包打包发给前端。 在前端,播放逻辑的核心思路是: * 依次把分组内的音频 buffer 调度到 Web Audio API 的时间线上连续播放。 * 重点来了:**把这组文本的显示时机,直接绑定到该组最后一个音频切片的 `ended` 事件上**。 这样一来,前端根本不需要用任何复杂的时间轴计算或定时器,字幕会在声音刚好播完的瞬间自然跳出来,声音和文字完美咬合,绝对不会出现“字比音快”或者累计漂移的情况。 前端的核心逻辑大概长这样: ```javascript // 前端音频播放与字幕同步的核心逻辑 let groupDuration = 0; let lastSrc = null; for (let i = 0; i < buffers.length; i++) { const buf = buffers[i]; const src = audioCtx.createBufferSource(); src.buffer = buf; src.connect(audioCtx.destination); // 在时间线上精准排队播放 const startAt = Math.max(nextTime, audioCtx.currentTime); src.start(startAt); nextTime = startAt + buf.duration; groupDuration += buf.duration; lastSrc = src; } // 只有当这一组的最后一个音频切片播放完毕,才把对应的字幕文本渲染到屏幕上 if (group.text && lastSrc) { const textToReveal = group.text; lastSrc.addEventListener('ended', () => { revealSubtitle(textToReveal); }); } ``` 现在这个实时聊天小工具已经在后台跑起来了。虽然只是个没有视频的“阉割版”,但实时对话的响应速度和这个音字同步的字幕效果,用起来已经相当令人满意了。