bricks/aidocs/rtc.md
2025-10-05 06:39:58 +08:00

435 lines
13 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.

# Bricks WebRTC 通信框架技术文档
本文档描述了 `bricks` 框架中用于实现点对点P2P实时音视频通信的核心模块包括
- `bricks.VideoBox`:用于播放媒体流的视频组件。
- `bricks.Signaling`:信令服务客户端,通过 WebSocket 与服务器通信。
- `bricks.RTCP2PConnect`:基于 WebRTC 的 P2P 连接管理器。
---
## 1. `bricks.VideoBox`
一个封装了 `<video>` 元素的 JavaScript 组件,用于显示媒体流(如摄像头或屏幕共享流)。
### 类定义
```js
class bricks.VideoBox extends bricks.JsWidget
```
### 方法
#### `create()`
创建并初始化 `<video>` DOM 元素,并设置自动播放和静音属性。
**行为:**
- 创建 `video` 元素。
- 设置 `autoplay``muted` 属性为 `true`
> ⚠️ 注意:由于现代浏览器的安全策略,`autoplay` 只能在用户交互后生效,且必须配合 `muted` 才能无提示播放。
#### `get_stream() → MediaStream`
获取当前绑定到该视频元素的媒体流。
**返回值:**
- `{MediaStream}` 当前播放的媒体流对象。
#### `set_stream(stream: MediaStream)`
将指定的媒体流绑定到 `<video>` 元素上进行播放。
**参数:**
- `stream`: `{MediaStream}` 要播放的媒体流。
**行为:**
-`stream` 赋值给内部变量 `this.stream`
- 设置 `this.dom_element.srcObject = stream`,触发视频播放。
---
## 2. `bricks.Signaling`
信令客户端类,负责与信令服务器建立 WebSocket 连接,处理登录、心跳、消息收发及会话分发。
### 构造函数
```js
new bricks.Signaling(opts)
```
**参数 `opts`**
| 参数 | 类型 | 描述 |
|------|------|------|
| `signaling_url` | `String` | WebSocket 信令服务器地址 |
| `info` | `Object` | 客户端信息(如用户 ID、设备信息等在发送数据时附加为 `msgfrom` 字段 |
| `connect_opts` | `Object` | 传递给后续连接模块的通用配置选项 |
| `onclose` | `Function` | WebSocket 关闭时的回调函数 |
| `onopen` | `Function` | WebSocket 成功打开时的回调函数(可选) |
| `onlogin` | `Function(onlinePeers)` | 登录成功后接收在线用户列表的回调 |
| `heartbeat_period` | `Number` | 心跳周期(秒),若不设则不启用 |
### 属性
| 属性 | 类型 | 描述 |
|------|------|------|
| `socket` | `WebSocket` | 当前 WebSocket 实例 |
| `peers` | `Object` | 存储当前在线用户的映射表key: peer.id |
| `sessions` | `Object` | 已建立的会话记录(未使用) |
| `handlers` | `Object` | 每个会话 ID 对应的消息处理器 |
| `sessionhandlers` | `Object` | 按会话类型注册的处理器构造函数 |
| `hb_task` | `TaskHandle` | 心跳定时任务句柄(由 `schedule_once` 返回) |
| `heartbeat_period` | `Number` | 心跳间隔时间(秒) |
### 方法
#### `init_websocket()`
初始化 WebSocket 连接:
- 使用 `bricks.app.get_session()` 获取会话凭证作为子协议。
- 绑定事件监听器:
- `onmessage``signaling_recvdata`
- `onopen``login`
- `onclose` / `onerror``reconnect`
#### `reconnect()`
WebSocket 断开后的处理逻辑:
- 清除心跳任务。
- 触发 `onclose` 回调。
- **注意:当前未实现自动重连机制。**
#### `signaling_recvdata(event)`
异步处理从 WebSocket 接收到的数据包。
**流程:**
1. 解析 JSON 数据。
2. 若包含 `data.session`,查找对应会话处理器:
- 若首次收到,则根据 `sessiontype` 实例化对应的处理器类。
- 注册该会话的 `recvdata_handler`
3. 否则调用默认 `recvdata_handler` 处理全局消息。
#### `add_handler(key, handler)`
注册某个 `sessionid` 的消息处理器。
#### `add_sessionhandler(sessiontype, handlerClass)`
注册某种会话类型的处理器类(构造函数)。例如:
```js
signaling.add_sessionhandler('p2p', bricks.RTCP2PConnect);
```
#### `recvdata_handler(data)`
默认消息处理器,处理非会话级消息。
**支持的消息类型:**
- `online`: 更新在线用户列表,并触发 `onlogin(online)` 回调。
> ❗ 未处理其他消息类型时仅打印日志。
#### `new_session(sessiontype, peer)`
发起一个新的会话请求。
**步骤:**
1. 查找 `sessiontype` 对应的处理器类。
2. 生成唯一 `sessionid`
3. 发送 `{ type: 'new_session', session }` 到服务器。
4. 创建处理器实例并注册其 `recvdata_handler`
5. 返回处理器实例(通常是 `RTCP2PConnect` 实例)。
#### `login()`
向服务器发送登录请求。
**行为:**
- 发送 `{ type: 'login' }`
- 如果设置了 `heartbeat_period > 0`,启动周期性心跳任务。
#### `logout()`
发送登出请求 `{ type: 'logout' }`
#### `send_data(d)`
将对象序列化为 JSON 并通过 WebSocket 发送。
**附加字段:**
- `d.msgfrom = this.info`(标识发送者)
#### `socket_send(s)`
直接发送原始字符串(底层接口,一般不推荐直接使用)。
---
## 3. `bricks.RTCP2PConnect`
WebRTC P2P 连接控制器,处理音视频通话和数据通道通信。
### 构造函数
```js
new bricks.RTCP2PConnect(signaling, session, opts)
```
**参数:**
| 参数 | 类型 | 描述 |
|------|------|------|
| `signaling` | `bricks.Signaling` | 信令客户端实例 |
| `session` | `Object` | 会话元信息 `{ sessionid, sessiontype }` |
| `opts` | `Object` | 配置项,见下表 |
**`opts` 配置说明:**
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `ice_servers` | `Array` | 必需 | ICE 服务器配置STUN/TURN |
| `peer_info` | `Object` | 必需 | 对端用户信息(含 `.id` |
| `auto_callaccept` | `Boolean` | `false` | 是否自动接受呼叫 |
| `media_options` | `Object` | `{ video: true, audio: true }` | 媒体采集选项 |
| `data_connect` | `Boolean` | `false` | 是否启用数据通道 |
| `on_pc_connected(peer)` | `Function` | - | 连接建立成功回调 |
| `on_pc_disconnected(peer)` | `Function` | - | 连接断开回调 |
| `on_dc_open(dc)` | `Function` | - | 数据通道开启回调 |
| `on_dc_close(dc)` | `Function` | - | 数据通道关闭回调 |
| `on_dc_message(dc, data)` | `Function` | - | 收到数据通道消息回调 |
### 属性
| 属性 | 类型 | 描述 |
|------|------|------|
| `id` | `String` | 本地唯一标识UUID |
| `signaling` | `Signaling` | 信令客户端引用 |
| `session` | `Object` | 当前会话信息 |
| `requester` | `Boolean` | 是否为主叫方 |
| `peers` | `Object` | 所有远程连接的 `PeerConnection` 管理对象 |
| `signal_handlers` | `Object` | 信令消息处理器映射 |
| `local_stream` | `MediaStream` | 本地摄像头流 |
| `localVideo` | `VideoBox` | 本地视频预览组件 |
| `local_screen` | `MediaStream` | 屏幕共享流(可选) |
---
### 核心方法
#### `add_handler(type, f)`
注册信令消息处理器(如 `'offer'`, `'answer'` 等)。
#### `get_handler(type) → Function`
获取指定类型的消息处理器。
#### `p2pconnect(peer)`
尝试与指定对端建立 P2P 连接。
**流程:**
1. 获取本地媒体流(如果尚未获取)。
2. 若已存在连接则跳过。
3. 调用 `createPeerConnection(peer)` 创建新的 `RTCPeerConnection`
#### `h_sessioncreated(data)`
当会话创建完成时触发。
**行为:**
- 自动调用 `p2pconnect`
- 若有 `peer_info`,主动发送 `callrequest` 请求呼叫。
#### `h_callrequest(data)`
收到呼叫请求。
**行为:**
-`auto_callaccept === true`(或硬编码为 `true`),自动响应:
- 建立连接。
- 发送 `callaccepted` 消息。
#### `h_callaccepted(data)`
对方接受呼叫。
**行为:**
- 创建数据通道(如果启用)。
- 发送 SDP Offer。
#### `h_offer(data)`
收到远程 Offer。
**流程:**
1. 设置 `remoteDescription`
2. 创建 Answer。
3. 设置 `localDescription`
4. 发送 Answer 回去。
5. 如果不是主叫方,再发送一次 Offer可能存在设计问题需确认逻辑
#### `h_answer(data)`
收到远程 Answer。
**行为:**
- 设置 `remoteDescription`
#### `h_icecandidate(data)`
收到 ICE 候选地址。
**行为:**
- 添加到 `RTCPeerConnection` 中。
#### `h_sessionquit(data)`
收到会话结束通知。
**行为:**
- 关闭对应 `RTCPeerConnection`
#### `send_offer(peer, initial = false)`
创建并发送 SDP Offer。
**参数:**
- `peer`: 目标用户。
- `initial`: 是否是首次呼叫(决定角色:`requester` / `responser`
**流程:**
1. 创建 Offer。
2. 设置 `localDescription`
3. 通过信令发送 Offer。
#### `send_candidate(peer, event)`
ICE 候选事件触发时调用,转发候选地址给远端。
> ✅ 使用 `bind(this, peer)` 绑定上下文。
#### `signaling_send(d)`
封装信令消息,附带当前 `session` 信息后发送。
#### `recvdata_handler(data)`
统一入口处理所有信令消息:
- 查找对应处理器并执行。
- 未处理则输出警告日志。
---
### 连接状态监听
#### `ice_statechange(peer, event)`
ICE 连接状态变化时的日志输出。
#### `connection_statechange(peer, event)`
连接状态变化处理:
- `disconnected`: 触发 `peer_close()``on_pc_disconnected` 回调。
- `connected`: 触发 `on_pc_connected` 回调。
---
### 数据通道DataChannel
#### `dc_accepted(peer, event)`
收到远程数据通道请求。
**行为:**
- 保存 `event.channel`
- 调用 `dc_created()` 初始化。
#### `dc_created(peer, dc)`
初始化数据通道事件监听器:
- `onmessage``datachannel_message`
- `onopen``datachannel_open`
- `onclose``datachannel_close`
#### `datachannel_message(peer, event)`
收到数据通道消息。
**行为:**
- 触发 `on_dc_message(dc, event.data)` 回调。
#### `datachannel_open/close(peer)`
分别在数据通道打开/关闭时触发相应回调。
#### `createDataChannel(peer)`
主动创建数据通道(用于主叫方)。
---
### 媒体流控制
#### `createPeerConnection(peer)`
创建并配置 `RTCPeerConnection` 实例。
**关键操作:**
- 使用传入的 `iceServers`
- 添加本地媒体轨道(如有)。
- 绑定以下事件:
- `onicecandidate`
- `oniceconnectionstatechange`
- `onconnectionstatechange`
- `ondatachannel`(注意:拼写错误应为 `ondatachannel`
- `ontrack` → 将远端流绑定到 `VideoBox`
> 🛠️ Bug 提示:`pc.ondatachanel` 应为 `pc.ondatachannel`
#### `changeLocalVideoStream(peer, new_stream)`
动态更换本地视频流(如切换摄像头或屏幕共享)。
**流程:**
1. 移除旧的视频 Track。
2. 添加新流中的视频 Track。
3. 重新发送 Offer 以更新协商。
#### `getLocalStream()`
获取本地摄像头和屏幕共享流。
**行为:**
- 调用 `getUserMedia(media_options)` 获取摄像头流。
- 调用 `getDisplayMedia({ video: true })` 获取屏幕共享流(音频可选)。
- 分别赋值给 `this.local_stream``this.local_screen`
- 创建 `localVideo` 并绑定摄像头流用于预览。
> 🔐 权限提示:需要用户授权麦克风和摄像头权限。
---
#### `peer_close(peer)`
清理与指定对端的所有资源。
**释放内容:**
- 停止所有接收/发送的媒体 Track。
- 停止远端视频流。
- 关闭数据通道和 `RTCPeerConnection`
- 删除 `peers` 中的记录。
- 若无剩余连接,停止本地流并从信令中注销会话处理器。
---
## 使用示例
```js
// 初始化信令
var signaling = new bricks.Signaling({
signaling_url: 'wss://example.com/signaling',
info: { id: 'user123', name: 'Alice' },
heartbeat_period: 30,
onlogin: function(peers) {
console.log('Online users:', peers);
}
});
// 注册 P2P 处理器
signaling.add_sessionhandler('p2p', bricks.RTCP2PConnect);
// 登录(触发 login
signaling.login();
// 发起一个 P2P 会话
var p2p = signaling.new_session('p2p', { id: 'user456' });
```
---
## 注意事项与潜在问题
| 问题 | 描述 | 建议修复 |
|------|------|---------|
| `ondatachanel` 拼写错误 | 应为 `ondatachannel` | 更正拼写 |
| `auto_callaccept || true` | 条件恒真,失去意义 | 改为 `this.opts.auto_callaccept === true` |
| `newStream` 变量名错误 | `changeLocalVideoStream` 中误写为 `newStream` | 改为 `new_stream` |
| 缺少异常处理 | 多处 `await` 未包裹 `try/catch` | 增加错误捕获 |
| 心跳任务未清除 | `schedule_once` 可能导致内存泄漏 | 确保正确取消任务 |
| 未实现自动重连 | `reconnect()` 仅触发回调 | 应加入延迟重试机制 |
---
## 总结
`bricks` 提供了一套完整的 WebRTC 信令与连接管理方案,具有以下特点:
**模块化设计**:分离信令、会话、连接逻辑
**扩展性强**:支持多种会话类型
**易于集成**:提供 VideoBox 等 UI 组件封装
⚠️ **需优化点**:存在若干 bug 与安全限制需处理
适用于开发音视频会议、直播互动、远程协作等实时通信应用。