260 lines
11 KiB
Markdown
260 lines
11 KiB
Markdown
# 火山引擎 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. 存入 Redux:sceneConfigMap、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 指令
|