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

11 KiB
Raw Permalink Blame History

火山引擎 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. 存入 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 凭证和场景配置)、StartVoiceChatStopVoiceChat
  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 指令