rtc-voice-chat/simple-frontend/frontend-integration.md
2026-04-02 09:40:23 +08:00

260 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 火山引擎 RTC AIGC 前端接入逻辑梳理
## 一、整体架构概览
```
┌──────────────────────────────────────────────────────────────┐
│ 前端 (React) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ MainPage │──>│ useJoin │──>│ RtcClient │ │
│ │(入口页面) │ │(加房hook)│ │(SDK 封装) │ │
│ └──────────┘ └──────────┘ └─────┬─────┘ │
│ │ │ │
│ ┌────▼─────┐ ┌─────────▼──────────┐ │
│ │ Redux │ │ @volcengine/rtc │ │
│ │ Store │ │ (火山引擎 RTC SDK) │ │
│ └──────────┘ └─────────┬──────────┘ │
│ │ │
└──────────────────────────────────────┼───────────────────────┘
┌──────────────────▼──────────────────┐
│ Backend (FastAPI) │
│ /getScenes /proxy /api/chat_cb │
└─────────────────────────────────────┘
```
## 二、核心模块说明
### 2.1 配置层 (`config/index.ts`)
```typescript
// 后端 API 代理地址
export const AIGC_PROXY_HOST = 'http://localhost:3001';
```
### 2.2 API 层 (`app/api.ts` + `app/base.ts`)
定义了 3 个 API
| API | 路径 | 说明 |
|-----|------|------|
| `getScenes` | `POST /getScenes` | 获取场景配置(含 RTC 凭证 AppId/RoomId/UserId/Token |
| `StartVoiceChat` | `POST /proxy?Action=StartVoiceChat` | 启动 AI 语音对话(传 `{ SceneID }` |
| `StopVoiceChat` | `POST /proxy?Action=StopVoiceChat` | 停止 AI 语音对话(传 `{ SceneID }` |
**请求封装逻辑** (`app/base.ts`)
- `generateAPIs()` 根据 API 配置自动生成请求函数
- POST 请求格式:`fetch(AIGC_PROXY_HOST + apiPath + ?Action=xxx, { method: 'post', body: JSON })`
- 响应统一结构:`{ ResponseMetadata: { Action, RequestId, Error? }, Result }`
- `resultHandler()` 检查 Error 字段,成功返回 `Result`,失败抛异常
### 2.3 RTC 客户端封装 (`lib/RtcClient.ts`)
单例模式,核心属性/方法:
```
RTCClient (单例)
├── basicInfo: { app_id, room_id, user_id, token } // 由 getScenes 返回后注入
├── engine: IRTCEngine // RTC 引擎实例
├── audioBotEnabled: boolean // AI Bot 是否启用
├── createEngine() // 创建引擎 + 注册 AI 降噪扩展
├── addEventListeners() // 绑定 15 个 RTC 事件回调
├── joinRoom() // 加入房间(自动发布/订阅音频, RoomProfile=chat
├── leaveRoom() // 离房并销毁引擎
├── startAudioCapture() // 开始麦克风采集
├── stopAudioCapture() // 停止麦克风采集
├── startAgent(sceneId) // 调用 StartVoiceChat API → 启动 AI Bot
├── stopAgent(sceneId) // 调用 StopVoiceChat API → 停止 AI Bot
├── commandAgent() // 通过 Binary Message 向 Bot 发送控制指令(打断等)
└── getDevices() // 枚举音频/视频设备
```
### 2.4 状态管理 (`store/slices/`)
#### room slice — 核心状态
```typescript
{
scene: string; // 当前场景 ID
sceneConfigMap: Record<string, SceneConfig>; // 场景配置botName, isVision 等)
rtcConfigMap: Record<string, RTCConfig>; // RTC 凭证AppId, RoomId, UserId, Token
isJoined: boolean; // 是否已加房
isAIGCEnable: boolean; // AI Bot 是否启用
isAITalking: boolean; // AI 正在说话
isAIThinking: boolean; // AI 正在思考
isUserTalking: boolean; // 用户正在说话
msgHistory: Msg[]; // 对话历史
localUser: LocalUser; // 本地用户(含 publishAudio 状态)
remoteUsers: IUser[]; // 远端用户列表
networkQuality: NetworkQuality; // 网络质量
}
```
#### device slice — 设备状态
```typescript
{
audioInputs: MediaDeviceInfo[]; // 麦克风列表
videoInputs: MediaDeviceInfo[]; // 摄像头列表
selectedMicrophone?: string; // 选中的麦克风 ID
selectedCamera?: string; // 选中的摄像头 ID
devicePermissions: { audio, video }; // 设备权限
}
```
### 2.5 消息处理 (`utils/handler.ts`)
AI Bot 通过 RTC 的 **Binary Message** 机制推送消息,采用 **TLV 编码格式**
```
| type (4字节) | length (4字节, big-endian) | value (JSON字符串) |
```
消息类型:
| Type 标识 | 含义 | 数据结构 |
|-----------|------|----------|
| `conv` | 状态简报 | `{ Stage: { Code, Description } }` — Code: 1=LISTENING, 2=THINKING, 3=SPEAKING, 4=INTERRUPTED, 5=FINISHED |
| `subv` | 字幕 | `{ data: [{ text, definite, userId, paragraph }] }` |
| `tool` | Function Call | `{ tool_calls: [{ id, function: { name } }] }` |
| `ctrl` | 控制指令(发送) | `{ Command, InterruptMode, Message }` |
| `func` | Function Call 响应(发送) | `{ ToolCallID, Content }` |
### 2.6 事件监听 (`lib/listenerHooks.ts`)
监听 15 个 RTC 事件:
| 事件 | 处理逻辑 |
|------|----------|
| `onError` | 处理 DUPLICATE_LOGIN 等错误 |
| `onUserJoined` | AI Bot 加入房间,更新远端用户列表 |
| `onUserLeave` | 远端用户离开 |
| `onUserPublishStream` | 远端用户发布流AI Bot 开始说话) |
| `onUserUnpublishStream` | 远端用户取消发布 |
| `onLocalAudioPropertiesReport` | 本地音量检测(用于 UI 动画) |
| `onRemoteAudioPropertiesReport` | 远端音量检测 |
| `onRoomBinaryMessageReceived` | **核心!** 接收 AI Bot 的 TLV 消息 → 交给 MessageHandler 解析 |
| `onNetworkQuality` | 网络质量监测 |
| `onAudioDeviceStateChanged` | 设备热插拔 |
| `onAutoplayFailed` / `onPlayerEvent` | 自动播放失败处理 |
| `onTrackEnded` | 屏幕共享关闭 |
| `onLocalStreamStats` / `onRemoteStreamStats` | 流统计信息 |
## 三、完整调用流程
### 3.1 页面初始化
```
1. MainPage 挂载
2. 调用 getScenes API → 获取场景列表
3. 存入 ReduxsceneConfigMap、rtcConfigMap
4. 同时将 rtcConfig 注入 RtcClient.basicInfo含 AppId/RoomId/UserId/Token
5. 展示 Antechamber通话前界面
```
### 3.2 用户点击"通话"按钮 → `useJoin.disPatchJoin()`
```
1. 检测浏览器兼容性 → VERTC.isSupported()
2. 创建 RTC 引擎 → RtcClient.createEngine()
└─ 内部VERTC.createEngine(appId) + 注册 AI 降噪扩展
3. 绑定事件监听 → RtcClient.addEventListeners(listeners)
4. 加入房间 → RtcClient.joinRoom()
└─ engine.joinRoom(token, roomId, userInfo, {
isAutoPublish: true,
isAutoSubscribeAudio: true,
roomProfileType: 'chat'
})
5. 枚举音频设备 → RtcClient.getDevices({ audio: true })
6. 更新 Redux 状态 → localJoinRoom, updateSelectedDevice, updateMediaInputs
7. 开启麦克风 → switchMic()
└─ publishStream(AUDIO) + startAudioCapture()
8. 启动 AI Bot → handleAIGCModeStart()
└─ RtcClient.startAgent(sceneId)
└─ 内部调用 POST /proxy?Action=StartVoiceChat { SceneID }
9. 更新状态 → isAIGCEnable = true, isJoined = true
10. UI 切换Antechamber → Room 界面
```
### 3.3 AI 对话过程
```
用户说话 → 麦克风采集 → RTC SDK 自动推流到远端
火山引擎 RTC 服务端
ASR语音识别→ LLM大模型→ TTS语音合成
Bot 将音频流推回房间 + 发送 Binary Message字幕/状态)
前端接收:
- 音频流 → RTC SDK 自动播放isAutoSubscribeAudio: true
- Binary Message → onRoomBinaryMessageReceived → TLV 解码 → MessageHandler
├─ conv(状态): THINKING → 更新 isAIThinking
│ SPEAKING → 更新 isAITalking
│ FINISHED → isAITalking = false
│ INTERRUPTED → 标记消息为已打断
└─ subv(字幕): text/definite/paragraph → 更新 msgHistory
```
### 3.4 打断 AI
```
方式一语音打断isInterruptMode = true 时自动生效,服务端检测)
方式二:点击"打断"按钮 → RtcClient.commandAgent({
agentName: botName,
command: 'interrupt',
}) → engine.sendUserBinaryMessage(botName, TLV 编码({
Command: 'interrupt',
InterruptMode: 0,
Message: ''
}))
```
### 3.5 用户挂断 → `useLeave()`
```
1. 停止音频/视频/屏幕采集
2. 停止 AI Bot → RtcClient.stopAgent(sceneId)
└─ POST /proxy?Action=StopVoiceChat { SceneID }
3. 离开房间 → RtcClient.leaveRoom()
└─ engine.leaveRoom() + VERTC.destroyEngine(engine)
4. 清除 Redux 状态 → clearHistoryMsg, clearCurrentMsg, localLeaveRoom, isAIGCEnable=false
5. UI 切换Room → Antechamber
```
## 四、TLV 协议编解码
### 编码 `string2tlv(str, type)`
```
输入: str = '{"Command":"interrupt"}', type = 'ctrl'
输出 ArrayBuffer:
[0-3] type 字符编码: 'c','t','r','l' → [99, 116, 114, 108]
[4-7] length (big-endian): JSON 字节长度
[8-...) value: UTF-8 编码的 JSON 字符串
```
### 解码 `tlv2String(buffer)`
```
输入: ArrayBuffer
输出: { type: 'subv', value: '{"data":[...]}' }
```
## 五、关键依赖
| 包名 | 用途 |
|------|------|
| `@volcengine/rtc` | 火山引擎 RTC Web SDK |
| `@volcengine/rtc/extension-ainr` | AI 降噪扩展(可选,不影响核心功能) |
## 六、接入要点总结
1. **后端需提供 3 个接口**`getScenes`(返回 RTC 凭证和场景配置)、`StartVoiceChat`、`StopVoiceChat`
2. **RTC 加房参数**`isAutoPublish: true, isAutoSubscribeAudio: true, roomProfileType: chat`
3. **AI 消息通道**:通过 RTC Binary Message (`onRoomBinaryMessageReceived`) 收发TLV 编码
4. **AI Bot 是一个"远端用户"**:它加入同一房间,发布音频流,通过 Binary Message 推送字幕和状态
5. **打断机制**:通过 `sendUserBinaryMessage` 向 Bot 发送 `ctrl` 类型的 TLV 指令