
# 实时字幕与翻译

*每路独立 ASR 的实时字幕，可选翻译成多种语言；无需邀请 AI，单人也可用*

字幕由**每条连接（每个人）各自的 ASR** 产生。在边缘对该参与者的独立音频流做识别，字幕归属由连接身份确定（一条流 = 一个人），并可按需翻译成多种语言下发。它和「邀请 AI」**相互独立**：开启字幕不需要 AI，房间里只有你一个人也能边说边出字幕。

> 💡 **语言元数据**：连接时用 `?lang=` 声明你说的语言（如 `zh-CN` / `en`）。混合语言房间里，每个人各自声明，字幕引擎按当前说话人的语言识别。

## 开启 / 关闭

进房后随时通过控制消息开关（WebSocket 文本帧，或 WebRTC（实验）的 `control` 数据通道）。`targets` 是要翻译成的语言列表（逗号分隔）；不填则只出原文。

```json
// 开启：对我说的话做识别，并翻译成这些语言
{ "type": "caption_start", "lang": "zh-CN", "targets": "en,ja,fr" }

// 关闭
{ "type": "caption_stop" }
```

## 字幕事件

字幕通过房间广播给其他参与者，也回显给说话人自己。每条按 `id` 合并：partial 先流式出原文，整句结束（`is_final:true`）定稿，随后翻译作为同 `id` 的更新补入。

| 字段 | 类型 | 说明 |
|---|---|---|
| type | string | `"caption"` |
| id | number | 同一句话的 partial / final / 译文更新共享此 id |
| speaker | string | 说话人名字（进房 name） |
| source_lang | string | 源语言 |
| text | string | 原文（partial 时为当前片段） |
| translations | object | `{ 语言: 译文 }`，final 翻译完成后补入 |
| is_final | bool | false=实时片段；true=整句已定稿 |

```json
// 边说边出（原文，实时）
{ "type":"caption","id":7,"speaker":"小明","source_lang":"zh-CN","text":"今天天气","translations":{},"is_final":false }
// 整句定稿（先发原文，AI 立即据此响应）
{ "type":"caption","id":7,"speaker":"小明","source_lang":"zh-CN","text":"今天天气不错。","translations":{},"is_final":true }
// 翻译补入（同 id 更新）
{ "type":"caption","id":7,"speaker":"小明","source_lang":"zh-CN","text":"今天天气不错。","translations":{"en":"The weather is nice today."},"is_final":true }
```

### 显示模式

客户端自行选择展示：**原文** / **译文**（取 `translations[你选的语言]`）/ **双语**。演示页的「开启实时字幕」面板即是如此。

## 实现要点

网关对每路音频在 **TEN 服务端 VAD** 门控下做 ASR：静音时不耗 ASR；检测到语音起始即给出信号。TEN 运行在平台私有 VAD 服务中，不向浏览器暴露。翻译按句（标点断句）调用一次 LLM，原文不受其延迟影响。

## 与 AI 的配合

房间里有字幕时，**AI 智能体优先采用字幕**而不再自己跑一遍 ASR：有人开口（partial）即打断 AI、有人说完（final 原文）AI 立刻应答——既省一次识别，归属也更准。详见 [AI 智能体](/docs/agents)。
