11 KiB
11 KiB
火山引擎 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)
// 后端 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 — 核心状态
{
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 — 设备状态
{
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 降噪扩展(可选,不影响核心功能) |
六、接入要点总结
- 后端需提供 3 个接口:
getScenes(返回 RTC 凭证和场景配置)、StartVoiceChat、StopVoiceChat - RTC 加房参数:
isAutoPublish: true, isAutoSubscribeAudio: true, roomProfileType: chat - AI 消息通道:通过 RTC Binary Message (
onRoomBinaryMessageReceived) 收发,TLV 编码 - AI Bot 是一个"远端用户":它加入同一房间,发布音频流,通过 Binary Message 推送字幕和状态
- 打断机制:通过
sendUserBinaryMessage向 Bot 发送ctrl类型的 TLV 指令