This commit is contained in:
2026-05-16 13:24:02 +08:00
parent eae60714b4
commit 6c13817527
42 changed files with 15921 additions and 0 deletions

View File

@@ -0,0 +1,437 @@
# 一对一通信示例
<cite>
**本文引用的文件**
- [models.js](file://client/public/onebyone/models.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [connect.js](file://client/public/onebyone/connect/connect.js)
- [endcall.js](file://client/public/onebyone/endcall/endcall.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [utils.js](file://client/public/onebyone/utils.js)
- [main.js](file://client/public/onebyone/main.js)
- [index.html](file://client/public/onebyone/index.html)
- [connect.html](file://client/public/onebyone/connect/connect.html)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本项目是一对一视频通话示例,基于浏览器 WebRTC 技术实现点对点P2P视频通话包含连接建立、媒体协商、通话管理、聊天消息与用户界面等完整功能。本文档将深入解析代码架构涵盖数据模型、状态管理、视图渲染、连接流程与通话结束处理并提供扩展、定制与集成建议。
## 项目结构
客户端采用模块化组织,核心目录与文件如下:
- onebyone 目录:一对一通话主界面与相关模块
- models.js数据模型定义
- store.js状态管理与 WebRTC 会话控制
- renderer.js视图渲染器负责将状态映射到 DOM
- main.js应用入口绑定事件与初始化
- index.html主界面 HTML 模板
- connect/:连接界面
- connect.html初始连接界面
- connect.js连接界面逻辑
- endcall/:通话结束界面
- endcall.html结束界面
- endcall.js结束逻辑
- chatmessage.js聊天消息模块
- utils.js工具函数
- public/js通用配置与 ICE 设置
- config.js获取服务器配置与 RTC 配置
- icesettings.jsICE 服务器配置持久化与读取
```mermaid
graph TB
subgraph "客户端(onebyone)"
A["index.html<br/>主界面"]
B["main.js<br/>入口与事件绑定"]
C["store.js<br/>状态管理/WebRTC"]
D["renderer.js<br/>视图渲染"]
E["models.js<br/>数据模型"]
F["chatmessage.js<br/>聊天消息"]
G["utils.js<br/>工具函数"]
H["connect/connect.html<br/>连接界面"]
I["connect/connect.js<br/>连接逻辑"]
J["endcall/endcall.js<br/>结束逻辑"]
end
subgraph "公共配置"
K["config.js<br/>服务器/RTC配置"]
L["icesettings.js<br/>ICE服务器持久化"]
end
A --> B
B --> C
B --> D
B --> F
C --> D
C --> F
C --> E
C --> K
K --> L
H --> I
A --> J
```
图表来源
- [index.html](file://client/public/onebyone/index.html)
- [main.js](file://client/public/onebyone/main.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [models.js](file://client/public/onebyone/models.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [utils.js](file://client/public/onebyone/utils.js)
- [connect/connect.html](file://client/public/onebyone/connect/connect.html)
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)
章节来源
- [index.html](file://client/public/onebyone/index.html)
- [main.js](file://client/public/onebyone/main.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [models.js](file://client/public/onebyone/models.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [utils.js](file://client/public/onebyone/utils.js)
- [connect/connect.html](file://client/public/onebyone/connect/connect.html)
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)
## 核心组件
- 数据模型models.js
- 定义通话会话、本地/远端用户、媒体状态、聊天消息等类型与模拟数据
- 状态管理store.js
- 使用简单观察者模式管理应用状态,封装 WebRTC 连接、媒体轨道、编解码器偏好、编码参数、网络质量检测、音频活动检测、消息广播等
- 视图渲染renderer.js
- 将状态映射到 DOM支持多参与者网格、占位符、占位符切换、分辨率自适应、网络质量指示等
- 连接界面connect/connect.js
- 提供创建/加入通话、连接 ID 列表浏览、用户设置头像、昵称、ID、头像上传等功能
- 通话结束界面endcall/endcall.js
- 提供重新连接与离开选项
- 聊天消息chatmessage.js
- 负责消息的发送、接收、显示、未读计数与侧边栏切换
- 工具函数utils.js
- 时间格式化、通知、元素显示/隐藏、按钮状态切换等
- 应用入口main.js
- 初始化渲染器、绑定 DOM 事件、键盘快捷键、挂断确认对话框等
章节来源
- [models.js](file://client/public/onebyone/models.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [utils.js](file://client/public/onebyone/utils.js)
- [main.js](file://client/public/onebyone/main.js)
## 架构总览
整体采用“状态驱动”的架构store.js 维护核心状态renderer.js 响应状态变化更新 UIWebRTC 通过 RenderStreaming 实例进行媒体协商与传输;聊天消息通过 WebSocket 或信令通道传递;配置通过 config.js 与 icesettings.js 提供 STUN/TURN 与媒体约束。
```mermaid
sequenceDiagram
participant U as "用户"
participant M as "main.js"
participant S as "store.js"
participant R as "renderer.js"
participant RS as "RenderStreaming(WebRTC)"
participant WS as "WebSocket/信令"
U->>M : 打开主界面
M->>S : 初始化并加入通话
S->>RS : 创建连接并启动
RS-->>S : 连接建立回调
S->>R : 通知 CALL_STATUS_CHANGE/ONGOING
S->>WS : 发送 user-info / media-state-changed
WS-->>S : 接收 chat-message / media-state-changed / user-info
S->>R : 通知 REMOTE_STREAM_OBTAINED / USER_LIST_UPDATE
R-->>U : 渲染远端视频/用户列表/网络质量
```
图表来源
- [main.js](file://client/public/onebyone/main.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
## 详细组件分析
### 数据模型models.js
- 类型定义
- CallSession通话会话包含通话类型、状态、起始时间、时长、加密标志、本地/远端用户信息
- LocalUser/RemoteUser用户信息含媒体状态与网络质量
- MediaState音频/视频/屏幕共享/录屏/说话检测状态
- ChatMessage消息结构含发送者信息、内容、类型、时间戳与是否自已发送
- 模拟数据
- 提供 mockCallSession 与 mockMessages便于开发调试与演示
章节来源
- [models.js](file://client/public/onebyone/models.js)
### 状态管理store.js
- 核心职责
- 初始化与本地媒体流获取、媒体状态变更、WebRTC 连接建立与回调注册
- 编解码器偏好设置、视频编码参数动态调整、分辨率切换
- 网络质量检测、音频活动检测、统计信息输出
- 聊天消息广播、用户信息同步、成员列表广播
- 通话结束处理(挂断、清理、通知)
- 关键流程
- 连接建立_createSignalingAndRTC -> setUp -> _registerCallbacks -> _startConnection
- 媒体协商onNewPeer/addTransceiver/setCodecPreferences/setVideoEncodingParameters
- 状态通知notify -> renderer 渲染
- 媒体状态变化emitMediaStateChange -> WebSocket 广播
- 网络质量detectNetworkQuality -> 综合丢包率/抖动/RTT 评估
- 音频活动startActivityDetection -> VAD 检测
- 设计要点
- 观察者模式subscribe/notify 解耦状态与 UI
- 可扩展性:编解码器与编码参数策略可按平台能力扩展
- 容错性:轨道替换、占位符延迟通知、分辨率回退
```mermaid
classDiagram
class CallStateManager {
+state
+listeners
+init()
+setUp(connectionId)
+updateLocalMedia(type, value)
+hangUp()
+broadcastParticipantsList()
+setCodecPreferences(participantId)
+setVideoEncodingParameters(participantId)
+changeResolution(width, height)
+emitMediaStateChange()
+detectNetworkQuality()
+startActivityDetection(stream, options)
+showStatsMessage()
}
class UIRenderer {
+render(state, changes)
+renderRemoteStream(stream, connectionId, isHost)
+renderUserList(localUser, remoteUser, participants)
}
CallStateManager --> UIRenderer : "通知状态变化"
```
图表来源
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
章节来源
- [store.js](file://client/public/onebyone/store.js)
### 视图渲染renderer.js
- 渲染策略
- 基于变化类型分派渲染INIT、DURATION_UPDATE、LOCAL_MEDIA_CHANGE、REMOTE_STREAM_OBTAINED、REMOTE_MEDIA_CHANGE、USER_LIST_UPDATE、NETWORK_CHANGE、CALL_STATUS_CHANGE、CALL_ENDED、PARTICIPANT_LEFT、RESOLUTION_CHANGED
- 支持多参与者网格Host 端与单路远端视频Participant 端)
- 占位符与占位符切换:音频先到、视频后到时延迟通知,避免黑屏
- 网络质量指示:根据远端网络质量更新头部与侧边栏
- 分辨率自适应:监听视频轨道 resize 事件调整显示尺寸
- 交互与事件
- 绑定事件监听器(由 main.js 绑定),响应按钮与快捷键
- 侧边栏开关与未读计数联动
章节来源
- [renderer.js](file://client/public/onebyone/renderer.js)
- [index.html](file://client/public/onebyone/index.html)
### 连接建立connect/connect.js
- 功能
- 加入通话:校验连接 ID保存到本地存储跳转主界面
- 创建通话:生成随机连接 ID保存并跳转主界面
- 浏览连接 ID调用 /signaling/connection-ids 获取列表并展示
- 用户设置:昵称、头像、用户 ID支持头像上传/api/upload/avatar
- 交互
- 输入框回车触发加入
- 选择连接 ID 自动填充
- 设置菜单外点击关闭
章节来源
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
- [connect/connect.html](file://client/public/onebyone/connect/connect.html)
### 通话结束处理endcall/endcall.js
- 功能
- 重新连接:跳转主界面
- 离开:清除本地连接 ID回到连接界面
- 交互
- 页面加载时显示断开连接信息(连接 ID 与时间)
章节来源
- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js)
### 聊天消息chatmessage.js
- 功能
- 消息状态管理:消息列表、未读计数、侧边栏开关
- 发送消息:构造消息对象,通过 store 发送到远端
- 接收消息handleChatMessage -> addMessage -> 通知 UI
- 图片消息:限制大小、读取为 DataURL、发送文件消息
- 未读通知:侧边栏关闭时累加未读,打开时清零
- 事件绑定
- sendMessage/handleChatSubmit/openImagePicker/handleImageUpload
章节来源
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
### 工具函数utils.js
- 功能
- formatTime/formatTimestamp时间格式化
- generateId生成唯一 ID
- showNotification通知组件显示与隐藏
- toggleElement/toggleButtonState元素与按钮状态切换
章节来源
- [utils.js](file://client/public/onebyone/utils.js)
### 应用入口main.js
- 功能
- 初始化渲染器与聊天模块
- 绑定 DOM 事件:静音/视频切换、录屏、更多选项、分辨率切换、结束通话确认
- 键盘快捷键Space 静音、Ctrl+V 切换视频
- 接收通话请求弹窗:接受/拒绝
- 页面加载后检查连接 ID初始化并启动 WebRTC 连接
- 交互
- 对话框事件绑定、更多选项菜单外点击关闭
章节来源
- [main.js](file://client/public/onebyone/main.js)
- [index.html](file://client/public/onebyone/index.html)
## 依赖关系分析
- 模块依赖
- main.js 依赖 store、renderer、chatmessage、utils
- store.js 依赖 models、config、icesettings、chatmessage、utils
- renderer.js 依赖 models、chatmessage、utils、store
- connect/connect.js 依赖 utils
- endcall/endcall.js 依赖 utils
- config.js 依赖 icesettings.js
- 外部依赖
- WebRTC APIgetUserMedia、RTCPeerConnection、RTCRtpTransceiver
- WebSocket/信令通道(用于媒体状态、用户信息、聊天消息、参与者列表同步)
- 服务器接口:/config、/api/upload/avatar、/signaling/connection-ids
```mermaid
graph LR
main_js["main.js"] --> store_js["store.js"]
main_js --> renderer_js["renderer.js"]
main_js --> chat_js["chatmessage.js"]
store_js --> models_js["models.js"]
store_js --> config_js["config.js"]
store_js --> chat_js
renderer_js --> models_js
renderer_js --> chat_js
renderer_js --> utils_js["utils.js"]
store_js --> utils_js
connect_js["connect/connect.js"] --> utils_js
endcall_js["endcall/endcall.js"] --> utils_js
config_js --> icesettings_js["icesettings.js"]
```
图表来源
- [main.js](file://client/public/onebyone/main.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [models.js](file://client/public/onebyone/models.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [utils.js](file://client/public/onebyone/utils.js)
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)
章节来源
- [main.js](file://client/public/onebyone/main.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [models.js](file://client/public/onebyone/models.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [utils.js](file://client/public/onebyone/utils.js)
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)
## 性能考量
- 媒体轨道管理
- 音频先到、视频后到时延迟通知,避免黑屏与频繁 UI 刷新
- 替换视频轨道时保留音频轨道,减少资源开销
- 编解码器与编码参数
- 优先选择 AV1/VP9回退 H264提升压缩效率
- 根据采集分辨率动态设置最大比特率,平衡画质与带宽
- 网络质量检测
- 丢包率、抖动、RTT 综合评估,定期更新网络质量指示
- 统计信息
- 定时输出视频/音频统计,便于诊断与优化
- 分辨率切换
- 使用 applyConstraints 实时调整,避免重新获取流
章节来源
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
## 故障排查指南
- 无法获取本地媒体流
- 检查 getUserMedia 权限与约束;若失败,保持媒体状态关闭并通知 UI
- 远端视频黑屏
- 确认音频轨道先到时的延迟通知逻辑;检查轨道是否正确添加到 MediaStream
- 连接状态异常
- 检查 onConnect/onDisconnect 回调;确认信令通道正常
- 媒体状态不同步
- 确认 emitMediaStateChange 是否被调用;检查 onMessage 中 media-state-changed 分发
- 网络质量指示不更新
- 确认 startNetworkQualityDetection 是否启动;检查 detectNetworkQuality 统计字段
- 头像上传失败
- 检查文件类型与大小限制;确认 /api/upload/avatar 接口可用
章节来源
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [connect/connect.js](file://client/public/onebyone/connect/connect.js)
## 结论
本示例通过清晰的模块划分与状态驱动架构,实现了稳定的一对一视频通话体验。其关键优势在于:
- 状态与 UI 解耦,易于扩展与维护
- 完整的媒体协商与网络质量处理
- 丰富的聊天与用户信息同步机制
- 友好的用户界面与交互体验
建议在生产环境中进一步完善:
- 使用成熟的状态管理库(如 Redux/Pinia替代简易观察者模式
- 增强错误处理与重连机制
- 集成更多媒体能力检测与自适应策略
- 优化统计信息展示与告警机制
## 附录
### 开发指南:扩展与定制
- 扩展聊天功能
- 新增消息类型:在 ChatMessage 类型中扩展 type 字段,更新 chatmessage.js 的发送/接收逻辑
- 文件上传:参考 handleImageUpload 的流程,扩展更多文件类型与大小限制
- 自定义界面
- 修改 index.html 的布局与样式,注意与 renderer.js 的 DOM 选择器保持一致
- 新增控制按钮:在 main.js 绑定事件并在 store 中实现对应逻辑
- 集成其他服务
- 服务器配置:通过 /config 接口返回 useWebSocket 等配置store.js 中据此选择 WebSocketSignaling 或 Signaling
- ICE 服务器:通过 icesettings.js 的持久化配置,支持多 STUN/TURN 服务器
- WebRTC 优化
- 编解码器与编码参数:根据设备能力动态调整,提升兼容性与画质
- 分辨率与帧率:结合网络状况动态调整,保证流畅度
章节来源
- [models.js](file://client/public/onebyone/models.js)
- [store.js](file://client/public/onebyone/store.js)
- [renderer.js](file://client/public/onebyone/renderer.js)
- [chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)

View File

@@ -0,0 +1,444 @@
# 双向通信示例
<cite>
**本文档引用的文件**
- [main.js](file://client/public/bidirectional/js/main.js)
- [sendvideo.js](file://client/public/bidirectional/js/sendvideo.js)
- [index.html](file://client/public/bidirectional/index.html)
- [style.css](file://client/public/bidirectional/css/style.css)
- [config.js](file://client/public/js/config.js)
- [icesettings.js](file://client/public/js/icesettings.js)
- [signaling.js](file://client/src/signaling.js)
- [peer.js](file://client/src/peer.js)
- [renderstreaming.js](file://client/src/renderstreaming.js)
- [index.ts](file://src/index.ts)
- [websocket.ts](file://src/websocket.ts)
- [options.ts](file://src/class/options.ts)
- [package.json](file://package.json)
- [run.bat](file://run.bat)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考虑](#性能考虑)
8. [故障排除指南](#故障排除指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本示例演示了基于 WebRTC 的双向音视频通信涵盖从本地媒体采集、信令交互、PeerConnection 建立到媒体流收发的完整流程。前端通过双向页面控制本地摄像头与麦克风,选择分辨率与编解码器偏好,发起连接后自动建立点对点通道,实现本地预览与远端播放的双向传输。后端提供 WebSocket 信令服务器,负责转发 offer/answer/candidate 等信令消息,并支持公共与私有通信模式。
## 项目结构
该项目采用前后端分离架构:
- 前端部分位于 client/public 与 client/src包含双向示例页面、通用配置与工具模块、WebRTC 连接管理与信令封装。
- 后端部分位于 src提供 Express 应用与 WebSocket 信令服务,支持 HTTPS、日志与多种运行参数。
```mermaid
graph TB
subgraph "前端"
A["bidirectional 页面<br/>index.html"]
B["主控制器<br/>main.js"]
C["媒体处理<br/>sendvideo.js"]
D["信令封装<br/>signaling.js"]
E["连接管理<br/>renderstreaming.js"]
F["Peer 封装<br/>peer.js"]
G["配置与ICE<br/>config.js / icesettings.js"]
end
subgraph "后端"
H["入口与参数解析<br/>index.ts"]
I["WebSocket 信令<br/>websocket.ts"]
J["选项接口<br/>options.ts"]
end
A --> B
B --> C
B --> D
B --> E
E --> F
D --> I
H --> I
G --> B
```
图表来源
- [index.html:1-84](file://client/public/bidirectional/index.html#L1-L84)
- [main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383)
- [sendvideo.js:1-54](file://client/public/bidirectional/js/sendvideo.js#L1-L54)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [config.js:1-39](file://client/public/js/config.js#L1-L39)
- [icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104)
- [index.ts:1-109](file://src/index.ts#L1-L109)
- [websocket.ts:1-118](file://src/websocket.ts#L1-L118)
章节来源
- [index.html:1-84](file://client/public/bidirectional/index.html#L1-L84)
- [main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383)
- [index.ts:1-109](file://src/index.ts#L1-L109)
## 核心组件
- 双向页面控制器:负责 UI 控件初始化、媒体设备选择、分辨率与编解码器配置、启动本地媒体、建立 WebRTC 连接、处理统计信息与错误。
- 媒体处理类:封装本地媒体流采集、本地/远端视频播放、本地轨道获取与远端轨道添加。
- 信令封装:提供 HTTP 与 WebSocket 两种信令实现统一事件接口负责会话创建、offer/answer/candidate 发送与接收。
- 连接管理:封装多个 Peer 的生命周期与事件路由,负责 SDP 协商、ICE 候选处理、统计数据查询与消息广播。
- Peer 封装:封装 RTCPeerConnection 生命周期、SDP 描述交换、ICE 候选收集与注入、数据通道创建与事件派发。
- 配置与 ICE提供 RTC 配置sdpSemantics、ICE 服务器、音频增强)、读取/写入本地存储的 STUN/TURN 服务器。
章节来源
- [main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383)
- [sendvideo.js:1-54](file://client/public/bidirectional/js/sendvideo.js#L1-L54)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [config.js:1-39](file://client/public/js/config.js#L1-L39)
- [icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104)
## 架构总览
下图展示了从用户操作到媒体流双向传输的整体架构与数据流。
```mermaid
sequenceDiagram
participant U as "用户"
participant UI as "双向页面(main.js)"
participant SV as "媒体处理(sendvideo.js)"
participant RS as "连接管理(renderstreaming.js)"
participant P as "Peer 封装(peer.js)"
participant SIG as "信令封装(signaling.js)"
participant WS as "WebSocket 信令(backend)"
U->>UI : "启动视频/设置连接"
UI->>SV : "startLocalVideo()"
SV-->>UI : "本地媒体流可用"
UI->>SIG : "创建信令实例(HTTP/WebSocket)"
UI->>RS : "start()/createConnection()"
RS->>P : "_preparePeerConnection()"
P-->>RS : "onNegotiationneeded -> 发送 offer"
RS->>SIG : "sendOffer()"
SIG->>WS : "转发 offer"
WS-->>SIG : "广播/定向转发"
SIG-->>RS : "on offer"
RS->>P : "onGotDescription(offer)"
P-->>RS : "setLocalDescription -> answer"
RS->>SIG : "sendAnswer()"
SIG->>WS : "转发 answer"
WS-->>SIG : "广播/定向转发"
SIG-->>RS : "on answer"
RS->>P : "onGotDescription(answer)"
P-->>RS : "稳定状态"
RS-->>UI : "onTrackEvent -> 添加远端轨道"
UI->>SV : "addRemoteTrack()"
SV-->>UI : "远端视频播放"
```
图表来源
- [main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184)
- [sendvideo.js:44-52](file://client/public/bidirectional/js/sendvideo.js#L44-L52)
- [renderstreaming.js:191-250](file://client/src/renderstreaming.js#L191-L250)
- [peer.js:57-82](file://client/src/peer.js#L57-L82)
- [signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [websocket.ts:44-115](file://src/websocket.ts#L44-L115)
## 详细组件分析
### 主控制器双向页面逻辑main.js
- 功能职责
- 设备与分辨率选择:枚举媒体设备,填充视频/音频选择框;支持预设分辨率与自定义分辨率。
- 本地媒体启动:根据选择的设备与分辨率调用媒体采集,设置本地视频元素并自动播放。
- 连接建立:根据配置选择 HTTP 或 WebSocket 信令,创建 RenderStreaming 实例,注册连接事件回调,添加本地轨道,设置编解码器偏好,启动统计信息展示。
- 断开连接:清理统计信息、停止渲染、删除连接、恢复 UI 状态。
- 编解码器偏好:在支持的浏览器上动态设置视频编解码器偏好,影响远端接收质量。
- 统计信息:周期性获取 WebRTC 统计并格式化显示,包含本地/远端分辨率与关键指标。
- 关键流程
- 启动本地视频:禁用输入控件,计算分辨率,调用媒体采集,启用设置按钮。
- 设置连接:创建信令实例,准备 RenderStreaming注册 onConnect/onDisconnect/onTrackEvent 回调,添加本地轨道,设置编解码器,启动统计。
- 断开连接:清除统计,删除连接,停止渲染,恢复 UI。
- 最佳实践
- 使用统一的 RTC 配置与媒体约束,避免分辨率不匹配导致的性能问题。
- 在支持的浏览器上启用编解码器偏好,提升兼容性与质量。
- 注意统计信息的周期性更新与清理,防止内存泄漏。
章节来源
- [main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184)
- [main.js:220-241](file://client/public/bidirectional/js/main.js#L220-L241)
- [main.js:289-303](file://client/public/bidirectional/js/main.js#L289-L303)
- [main.js:337-367](file://client/public/bidirectional/js/main.js#L337-L367)
### 媒体处理SendVideosendvideo.js
- 功能职责
- 本地媒体采集:根据设备 ID 与分辨率约束获取媒体流,设置本地视频元素并播放。
- 本地轨道获取:返回本地媒体流中的轨道集合,供连接管理添加到 PeerConnection。
- 远端轨道添加:创建或复用 MediaStream将远端轨道加入驱动远端视频播放。
- 关键流程
- startLocalVideo构造约束调用 getUserMedia设置本地视频源并播放。
- getLocalTracks从本地流获取轨道。
- addRemoteTrack将远端轨道加入本地流。
- 最佳实践
- 在调用 getUserMedia 前检查权限与设备可用性。
- 使用 MediaStreamTrack 的 addTrack/removeTrack 精确管理轨道生命周期。
章节来源
- [sendvideo.js:15-35](file://client/public/bidirectional/js/sendvideo.js#L15-L35)
- [sendvideo.js:40-52](file://client/public/bidirectional/js/sendvideo.js#L40-L52)
### 信令封装HTTP 与 WebSocketsignaling.js
- 功能职责
- HTTP 信令:基于轮询的信令实现,维护 Session-Id支持连接创建/删除、offer/answer/candidate 发送与接收,以及自定义消息。
- WebSocket 信令:基于 WebSocket 的实时信令实现,支持连接/断开、offer/answer/candidate、参与者加入/离开、广播消息等。
- 关键流程
- HTTPstart() 循环创建会话loopGetAll() 轮询消息并分发事件createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate。
- WebSocketonopen/onmessage/onclose 处理消息类型映射分发事件createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate/sendMessage。
- 最佳实践
- 在 HTTP 模式下合理设置轮询间隔,避免频繁请求。
- WebSocket 模式下注意连接状态与重连策略,确保消息可靠传递。
章节来源
- [signaling.js:30-97](file://client/src/signaling.js#L30-L97)
- [signaling.js:152-292](file://client/src/signaling.js#L152-L292)
### 连接管理RenderStreamingrenderstreaming.js
- 功能职责
- 统一封装信令事件与 Peer 生命周期,支持单 Peerparticipant与多 Peerhost场景。
- 路由 offer/answer/candidate 到对应 Peer处理 onTrack 事件,提供统计数据查询与消息广播。
- 关键流程
- _onConnect根据角色创建 Peer触发 onConnect。
- _onOffer/_onAnswer/_onIceCandidate根据是否为 host 与 participantId 路由到对应 Peer。
- _preparePeerConnection创建 Peer 并绑定事件,桥接信令与 Peer。
- getStats/addTrack/addTransceiver/createDataChannel按角色与 participantId 路由到具体 Peer。
- 最佳实践
- host 端应为每个参与者维护独立 Peer确保消息隔离。
- 在断开或失败状态下及时清理 Peer避免资源泄露。
章节来源
- [renderstreaming.js:40-70](file://client/src/renderstreaming.js#L40-L70)
- [renderstreaming.js:191-250](file://client/src/renderstreaming.js#L191-L250)
- [renderstreaming.js:252-290](file://client/src/renderstreaming.js#L252-L290)
### Peer 封装RTCPeerConnection 管理peer.js
- 功能职责
- 封装 RTCPeerConnection 的创建、SDP 描述交换、ICE 候选收集与注入、数据通道创建与事件派发。
- 提供防闪烁glare处理避免同时进行 offer/answer 导致的状态冲突。
- 关键流程
- _onNegotiationneededsetLocalDescription 生成 offer派发 sendoffer 事件。
- onGotDescription根据描述类型设置远端描述必要时生成 answer 并派发 sendanswer。
- onIceCandidate收集候选并通过事件派发 sendcandidate。
- loopResendOffer定期重发未确认的 offer保证协商完成。
- 最佳实践
- 正确处理 signalingState 与 iceConnectionState及时响应失败事件。
- 在多参与者场景下,确保 participantId 与 Peer 的一一对应。
章节来源
- [peer.js:57-82](file://client/src/peer.js#L57-L82)
- [peer.js:132-173](file://client/src/peer.js#L132-L173)
- [peer.js:175-186](file://client/src/peer.js#L175-L186)
### 配置与 ICERTC 配置与 STUN/TURNconfig.js, icesettings.js
- 功能职责
- getServerConfig获取服务器配置是否使用 WebSocket、启动模式等
- getRTCConfiguration设置 sdpSemantics、ICE 服务器、媒体约束与音频增强选项。
- icesettings.js管理本地存储的 STUN/TURN 服务器列表,支持增删改查与默认值。
- 最佳实践
- 在公网部署时配置可靠的 TURN 服务器,提升连通性。
- 根据网络环境调整 ICE 服务器列表与轮询间隔。
章节来源
- [config.js:3-7](file://client/public/js/config.js#L3-L7)
- [config.js:9-38](file://client/public/js/config.js#L9-L38)
- [icesettings.js:94-104](file://client/public/js/icesettings.js#L94-L104)
### 后端WebSocket 信令服务器websocket.ts
- 功能职责
- 接收并处理 WebSocket 消息,根据类型分发到处理器,支持 connect/disconnect/offer/answer/candidate/ping/pong/broadcast/on-message 等。
- 维护连接池与广播机制,支持心跳检测与消息路由。
- 关键流程
- connection 事件:添加新连接到处理器。
- onmessage解析消息类型并调用相应处理器。
- onclose移除关闭连接。
- 最佳实践
- 对未知消息类型进行安全处理,避免异常传播。
- 实现心跳与超时检测,维持长连接稳定性。
章节来源
- [websocket.ts:27-39](file://src/websocket.ts#L27-L39)
- [websocket.ts:44-115](file://src/websocket.ts#L44-L115)
### 入口与参数服务器启动index.ts
- 功能职责
- 解析命令行参数支持端口、HTTPS、信令类型、通信模式与日志级别。
- 根据参数启动 HTTP/HTTPS 服务器,初始化 WebSocket 信令服务。
- 关键流程
- 参数解析:读取 PORT/SECURE/KEYFILE/CERTFILE/TYPE/MODE/LOGGING。
- 服务器启动:根据 secure 决定 HTTP/HTTPS监听端口并输出访问地址。
- 信令类型:仅支持 websocket 或 http其他值将被修正为 websocket。
- 最佳实践
- 生产环境建议启用 HTTPS 并配置有效的证书文件。
- 根据部署环境选择合适的通信模式public/private
章节来源
- [index.ts:14-44](file://src/index.ts#L14-L44)
- [index.ts:52-91](file://src/index.ts#L52-L91)
## 依赖关系分析
```mermaid
classDiagram
class SendVideo {
+startLocalVideo(videoSource, audioSource, width, height)
+getLocalTracks()
+addRemoteTrack(track)
}
class Signaling {
+start()
+stop()
+createConnection(connectionId)
+deleteConnection(connectionId)
+sendOffer(connectionId, sdp)
+sendAnswer(connectionId, sdp)
+sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex)
+sendMessage(connectionId, message)
}
class WebSocketSignaling {
+start()
+stop()
+createConnection(connectionId)
+deleteConnection(connectionId)
+sendOffer(connectionId, sdp, participantId)
+sendAnswer(connectionId, sdp, participantId)
+sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex, participantId)
+sendMessage(connectionId, message)
}
class Peer {
+ontrack
+ondatachannel
+onicecandidate
+onnegotiationneeded
+onsignalingstatechange
+oniceconnectionstatechange
+onicegatheringstatechange
+onGotDescription(connectionId, description)
+onGotCandidate(connectionId, candidate)
+getTransceivers(connectionId)
+addTrack(connectionId, track)
+addTransceiver(connectionId, trackOrKind, init)
+createDataChannel(connectionId, label)
+getStats(connectionId)
+close()
}
class RenderStreaming {
+start()
+stop()
+createConnection(connectionId)
+deleteConnection()
+addTrack(track, participantId)
+addTransceiver(trackOrKind, init, participantId)
+createDataChannel(label, participantId)
+getStats(participantId)
+sendMessage(message)
+getTransceivers(participantId)
}
SendVideo --> RenderStreaming : "提供本地轨道"
RenderStreaming --> Signaling : "使用"
RenderStreaming --> Peer : "封装"
Signaling <|-- WebSocketSignaling : "继承"
```
图表来源
- [sendvideo.js:3-53](file://client/public/bidirectional/js/sendvideo.js#L3-L53)
- [signaling.js:3-150](file://client/src/signaling.js#L3-L150)
- [signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [peer.js:3-188](file://client/src/peer.js#L3-L188)
- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
## 性能考虑
- 分辨率与编解码器
- 合理选择分辨率,避免过高分辨率导致带宽与 CPU 压力过大。
- 在支持的浏览器上设置编解码器偏好,提升解码效率与画质一致性。
- ICE 服务器
- 在公网部署时配置可靠的 STUN/TURN 服务器,减少打洞失败与延迟。
- 统计信息
- 定期但不过度地获取 WebRTC 统计,避免频繁 IO 影响性能。
- 信令类型
- WebSocket 信令相比 HTTP 轮询具有更低的延迟与更少的请求开销,推荐在生产环境使用。
## 故障排除指南
- 浏览器不支持编解码器偏好
- 现象:无法设置编解码器偏好,界面提示当前浏览器不支持。
- 处理:忽略偏好设置,使用默认编解码器;或升级浏览器版本。
- 公共模式限制
- 现象:公共模式下页面显示警告,示例不可用。
- 处理:切换到私有模式或使用支持的部署方式。
- 本地媒体无法启动
- 现象:点击“启动视频”无反应或报错。
- 处理:检查摄像头/麦克风权限、设备 ID 是否正确、分辨率是否受支持。
- 连接无法建立
- 现象:点击“设置连接”后无进展或很快断开。
- 处理检查信令服务器是否在线、WebSocket 是否连通、ICE 服务器配置是否正确。
- 远端无画面
- 现象:本地有画面但远端无画面。
- 处理:确认已添加本地轨道到 PeerConnection、远端轨道已加入本地流、统计信息显示远端轨道已接收。
- 服务器启动失败
- 现象:启动脚本报错或无法访问。
- 处理检查端口占用、HTTPS 证书文件是否存在、命令行参数是否正确。
章节来源
- [main.js:99-105](file://client/public/bidirectional/js/main.js#L99-L105)
- [main.js:309-328](file://client/public/bidirectional/js/main.js#L309-L328)
- [index.ts:78-82](file://src/index.ts#L78-L82)
- [run.bat:8-16](file://run.bat#L8-L16)
## 结论
本双向通信示例通过清晰的模块划分与事件驱动设计,实现了从本地媒体采集到远端播放的完整链路。前端以 main.js 为核心协调各模块,后端以 WebSocket 信令服务器提供可靠的消息转发。通过合理的配置与最佳实践,可在不同网络环境下获得稳定的双向音视频体验。
## 附录
### 使用步骤
- 启动服务器
- 构建并启动后端服务,支持 HTTPS 与多种运行参数。
- 示例脚本提供了构建与启动命令,也可直接运行 TypeScript 源码进行开发调试。
- 打开示例页面
- 在浏览器中访问服务器提供的地址,进入双向示例页面。
- 页面会自动枚举可用的摄像头与麦克风,并提供分辨率与编解码器选择。
- 建立连接
- 输入连接 ID可随机生成点击“启动视频”采集本地媒体。
- 点击“设置连接”,系统将创建信令连接并开始协商。
- 成功后本地与远端视频将分别显示,统计信息会周期性更新。
- 进行双向通信
- 在双方都完成连接后,即可实现视频与音频的双向传输。
- 可通过挂断按钮结束连接,清理资源并恢复 UI 状态。
章节来源
- [package.json:9-12](file://package.json#L9-L12)
- [run.bat:8-16](file://run.bat#L8-L16)
- [index.html:76-79](file://client/public/bidirectional/index.html#L76-L79)
- [main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184)
### 常见配置选项
- 服务器参数
- 端口:-p/--port默认 80 或环境变量 PORT。
- HTTPS-s/--secure默认启用需提供 keyfile 与 certfile。
- 信令类型:-t/--typewebsocket 或 http默认 websocket。
- 通信模式:-m/--modepublic 或 private默认 public。
- 日志级别:-l/--loggingcombined/dev/short/tiny/none默认 dev。
- RTC 配置
- sdpSemantics统一计划unified-plan
- ICE 服务器STUN/TURN可通过本地存储配置。
- 音频增强:回声消除、噪声抑制、自动增益、高通滤波、打字噪声检测等。
- 编解码器偏好
- 在支持的浏览器上可选择特定视频编解码器,提升兼容性与质量。
章节来源
- [index.ts:20-41](file://src/index.ts#L20-L41)
- [config.js:9-38](file://client/public/js/config.js#L9-L38)
- [icesettings.js:94-104](file://client/public/js/icesettings.js#L94-L104)
- [main.js:189-213](file://client/public/bidirectional/js/main.js#L189-L213)
### 代码示例路径
- 双向页面主逻辑:[main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184)
- 媒体处理类:[sendvideo.js:15-35](file://client/public/bidirectional/js/sendvideo.js#L15-L35)
- 信令封装HTTP/WebSocket[signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- 连接管理:[renderstreaming.js:191-250](file://client/src/renderstreaming.js#L191-L250)
- Peer 封装:[peer.js:57-82](file://client/src/peer.js#L57-L82)
- 服务器启动与参数:[index.ts:52-91](file://src/index.ts#L52-L91)

View File

@@ -0,0 +1,399 @@
# 多播放示例
<cite>
**本文引用的文件**
- [client/src/renderstreaming.js](file://client/src/renderstreaming.js)
- [client/src/signaling.js](file://client/src/signaling.js)
- [client/src/peer.js](file://client/src/peer.js)
- [client/public/multiplay/js/main.js](file://client/public/multiplay/js/main.js)
- [src/index.ts](file://src/index.ts)
- [src/server.ts](file://src/server.ts)
- [src/websocket.ts](file://src/websocket.ts)
- [src/class/websockethandler.ts](file://src/class/websockethandler.ts)
- [src/signaling.ts](file://src/signaling.ts)
- [src/class/httphandler.ts](file://src/class/httphandler.ts)
- [package.json](file://package.json)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本示例演示如何在浏览器端与服务器之间构建多播式媒体分发通道,使多个客户端能够同时接收来自同一信源的相同媒体流。该实现以 WebRTC 为核心,结合 WebSocket 或 HTTP 轮询信令通道,支持:
- 多客户端同时加入同一连接组
- 服务器侧按连接组进行消息路由与广播
- 客户端侧根据角色(主机/参与者)分别管理独立的 PeerConnection
- 基于数据通道的广播消息与输入事件传递
本指南聚焦于多播场景下的连接管理、媒体流复制与同步机制,并给出在 Unity Render Streaming 的 Multiplay 场景中的使用方法、集成步骤、性能优化与故障处理建议。
## 项目结构
该仓库采用前后端分离结构:
- 前端示例位于 client/public/multiplay包含多客户端多播播放器与输入通道
- 前端核心逻辑位于 client/src封装了信令、WebRTC Peer 管理与渲染
- 服务端位于 src提供 HTTP 服务与 WebSocket 信令服务,以及信令路由与广播
```mermaid
graph TB
subgraph "前端"
FE_Main["multiplay/main.js"]
FE_Render["renderstreaming.js"]
FE_Signaling["signaling.js"]
FE_Peer["peer.js"]
end
subgraph "服务端"
BE_Index["index.ts"]
BE_Server["server.ts"]
BE_WS["websocket.ts"]
BE_WS_Handler["class/websockethandler.ts"]
BE_HTTP_Router["signaling.ts"]
BE_HTTP_Handler["class/httphandler.ts"]
end
FE_Main --> FE_Render
FE_Render --> FE_Signaling
FE_Render --> FE_Peer
FE_Signaling --> BE_Index
BE_Index --> BE_Server
BE_Server --> BE_WS
BE_WS --> BE_WS_Handler
BE_Server --> BE_HTTP_Router
BE_HTTP_Router --> BE_HTTP_Handler
```
**图表来源**
- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204)
- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [src/index.ts:1-109](file://src/index.ts#L1-L109)
- [src/server.ts:1-90](file://src/server.ts#L1-L90)
- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118)
- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800)
**章节来源**
- [src/server.ts:14-90](file://src/server.ts#L14-L90)
- [src/index.ts:52-109](file://src/index.ts#L52-L109)
## 核心组件
- 多播渲染层RenderStreaming
- 负责根据角色(主机/参与者)管理多个 PeerConnection路由信令与媒体事件
- 提供创建/删除连接、创建数据通道、添加轨道、获取统计等能力
- 信令层Signaling/ WebSocketSignaling
- 支持 HTTP 轮询与 WebSocket 两种信令协议
- 负责连接生命周期管理、offer/answer/candidate 的收发与广播
- Peer 层Peer
- 封装 RTCPeerConnection处理 SDP 协商、ICE 候选、数据通道与统计
- 服务器信令处理器WebSocket/HTTP
- 维护连接组(主机 + 多个参与者),实现消息路由与广播
- 支持私有模式(一对一)与公共模式(一对多)
**章节来源**
- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292)
- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188)
- [src/class/websockethandler.ts:145-260](file://src/class/websockethandler.ts#L145-L260)
- [src/class/httphandler.ts:739-800](file://src/class/httphandler.ts#L739-L800)
## 架构总览
多播架构的关键在于“连接组”与“角色路由”。服务器为每个 connectionId 维护一个连接组,首个加入者作为主机,后续加入者作为参与者。主机与参与者之间的信令消息按角色进行定向转发;广播消息则在组内进行全量分发。
```mermaid
sequenceDiagram
participant C1 as "客户端1主机"
participant C2 as "客户端2参与者"
participant Cn as "客户端n参与者"
participant WS as "WebSocket服务器"
participant H as "连接组处理器"
C1->>WS : "connect(connectionId)"
WS->>H : "onConnect(connectionId)"
H-->>C1 : "connect(role=host, participantId=p1)"
C2->>WS : "connect(connectionId)"
WS->>H : "onConnect(connectionId)"
H-->>C2 : "connect(role=participant, participantId=p2)"
H-->>C1 : "participant-joined(p2)"
C2->>WS : "offer(sdp, from=p2)"
WS->>H : "onOffer(data, from=p2)"
H-->>C1 : "offer(sdp, from=p2, participantId=p2)"
C1->>WS : "answer(sdp, from=p1)"
WS->>H : "onAnswer(data, from=p1)"
H-->>C2 : "answer(sdp, from=p1, participantId=p2)"
C1->>WS : "candidate(...)"
C2->>WS : "candidate(...)"
WS->>H : "onCandidate(...)"
H-->>C2 : "candidate(..., participantId=p2)"
H-->>C1 : "candidate(...)"
```
**图表来源**
- [src/websocket.ts:44-115](file://src/websocket.ts#L44-L115)
- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338)
**章节来源**
- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118)
- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338)
## 详细组件分析
### 多客户端连接管理与媒体流复制
- 连接组与角色
- 首个连接者为主机,后续连接者为参与者
- 服务器为每个 connectionId 维护 host 与 participants 集合
- 多 Peer 管理
- 主机端为每个参与者维护独立的 Peer 实例,确保多路媒体复制与独立统计
- 参与者端仅维护单一 Peer避免不必要的连接开销
- 媒体复制与同步
- 服务器对 offer/answer/candidate 进行定向转发或广播
- 客户端在收到 offer 时创建/复用对应参与者的 Peer 并设置 SDP随后自动生成 answer 并回传
- ICE 候选同样按目标参与者路由,保证连接建立的正确性
```mermaid
flowchart TD
Start(["开始收到offer"]) --> IsHost{"是否为主机?"}
IsHost --> |是| GetPeer["获取/创建目标参与者的Peer"]
IsHost --> |否| CreatePeer["创建或复用单一Peer"]
GetPeer --> SetSDP["设置远程SDPoffer"]
CreatePeer --> SetSDP
SetSDP --> GenAnswer["生成本地SDPanswer"]
GenAnswer --> SendAnswer["发送answer给目标方"]
SendAnswer --> End(["结束"])
```
**图表来源**
- [client/src/renderstreaming.js:72-130](file://client/src/renderstreaming.js#L72-L130)
- [client/src/peer.js:132-173](file://client/src/peer.js#L132-L173)
**章节来源**
- [client/src/renderstreaming.js:72-130](file://client/src/renderstreaming.js#L72-L130)
- [client/src/peer.js:132-173](file://client/src/peer.js#L132-L173)
### 服务器端多播路由与广播
- 连接组维护
- 使用 Map<connectionId, { host, participants }> 维护连接组
- 通过 participantId 标识参与者,实现按参与者定向转发
- 消息路由
- offer/answer/candidate主机发给指定参与者参与者发给主机
- broadcast服务器向组内所有成员广播消息
- 断线与清理
- 主机断线时,向所有参与者广播断线通知并清理连接组
- 参与者断线时,通知主机并从组内移除
```mermaid
flowchart TD
A["收到消息"] --> Type{"消息类型?"}
Type --> |offer/answer/candidate| Route["按角色与participantId路由"]
Type --> |broadcast| Broad["向组内广播"]
Route --> Send["发送到目标WS"]
Broad --> Send
Send --> Done["完成"]
```
**图表来源**
- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338)
- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402)
**章节来源**
- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206)
- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338)
- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402)
### 客户端渲染与输入通道Multiplay 示例)
- 页面初始化
- 读取服务器配置,决定使用 WebSocket 或 HTTP 信令
- 创建 RenderStreaming 实例,注册连接、断开、轨道事件回调
- 数据通道
- 创建名为 "multiplay" 的数据通道用于广播消息
- 打开后随机生成标签并向服务器广播
- 媒体播放
- 通过 onTrackEvent 回调将远端轨道接入播放器
```mermaid
sequenceDiagram
participant UI as "页面"
participant RS as "RenderStreaming"
participant Sig as "Signaling"
participant PC as "Peer"
participant VP as "VideoPlayer"
UI->>RS : "start()"
RS->>Sig : "start()"
UI->>RS : "createConnection()"
RS->>Sig : "createConnection()"
Sig-->>RS : "connect(role, participantId)"
RS->>PC : "创建/复用Peer"
RS-->>VP : "onTrackEvent -> addTrack()"
UI->>RS : "createDataChannel('multiplay')"
RS-->>UI : "onopen -> 发送标签"
```
**图表来源**
- [client/public/multiplay/js/main.js:82-125](file://client/public/multiplay/js/main.js#L82-L125)
- [client/src/renderstreaming.js:182-189](file://client/src/renderstreaming.js#L182-L189)
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
**章节来源**
- [client/public/multiplay/js/main.js:82-125](file://client/public/multiplay/js/main.js#L82-L125)
- [client/src/renderstreaming.js:182-189](file://client/src/renderstreaming.js#L182-L189)
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
### 服务器启动与信令服务
- 启动流程
- 解析命令行参数,创建 Express 应用
- 根据安全与信令类型启动 HTTP/HTTPS 与 WebSocket 服务
- 静态资源与 API
- 提供前端静态页面与模块
- 暴露 /config、/signaling 路由
- WebSocket 信令
- 监听连接、消息、关闭事件
- 分派到连接组处理器进行路由与广播
```mermaid
flowchart TD
Start(["启动"]) --> Parse["解析参数"]
Parse --> CreateApp["创建Express应用"]
CreateApp --> HTTPS{"启用HTTPS"}
HTTPS --> |是| HTTPSrv["启动HTTPS服务器"]
HTTPS --> |否| HTTPsrv["启动HTTP服务器"]
HTTPSrv --> WS{"信令类型?"}
HTTPsrv --> WS
WS --> |websocket| WSSrv["启动WebSocket信令"]
WS --> |http| End(["完成"])
```
**图表来源**
- [src/index.ts:52-109](file://src/index.ts#L52-L109)
- [src/server.ts:14-42](file://src/server.ts#L14-L42)
- [src/websocket.ts:15-40](file://src/websocket.ts#L15-L40)
**章节来源**
- [src/index.ts:52-109](file://src/index.ts#L52-L109)
- [src/server.ts:14-42](file://src/server.ts#L14-L42)
- [src/websocket.ts:15-40](file://src/websocket.ts#L15-L40)
## 依赖关系分析
- 前端模块
- multiplay/main.js 依赖 renderstreaming.js 与 signaling.js
- renderstreaming.js 依赖 peer.js 与 signaling.js
- peer.js 依赖浏览器 WebRTC API
- 服务端模块
- index.ts 作为入口,创建 server.ts 并根据配置启动 WebSocket 信令
- server.ts 注册 /signaling 路由,路由到 HTTP 信令处理器
- websocket.ts 接收 WebSocket 消息,路由到连接组处理器
```mermaid
graph LR
MJS["multiplay/main.js"] --> RS["renderstreaming.js"]
RS --> SIG["signaling.js"]
RS --> PEER["peer.js"]
IDX["index.ts"] --> SRV["server.ts"]
SRV --> WSC["websocket.ts"]
SRV --> HTTR["signaling.ts"]
HTTR --> HTH["class/httphandler.ts"]
WSC --> WSH["class/websockethandler.ts"]
```
**图表来源**
- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204)
- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [src/index.ts:1-109](file://src/index.ts#L1-L109)
- [src/server.ts:1-90](file://src/server.ts#L1-L90)
- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800)
- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479)
**章节来源**
- [package.json:14-27](file://package.json#L14-L27)
## 性能考量
- 信令协议选择
- WebSocket 适合低延迟、高并发的多播场景HTTP 轮询开销较大但兼容性更好
- 连接组规模
- 主机端为每个参与者维护独立 Peer注意内存与 CPU 开销随参与者数量增长
- 媒体编解码与带宽
- 使用 setCodecPreferences 指定编解码器,降低不必要转码成本
- 合理设置传输参数(如 maxBitrate避免上行拥塞
- 统计与监控
- 定期获取 Peer 统计,观察丢包、抖动与编码速率
- 负载均衡与扩展
- 多实例部署时,需确保同一 connectionId 的消息在同一实例内处理
- 使用外部存储(如 Redis共享连接组状态实现水平扩展
[本节为通用指导,无需列出具体文件来源]
## 故障排查指南
- 无法建立连接
- 检查服务器日志与 WebSocket/HTTP 信令是否正常
- 确认客户端使用的信令类型与服务器配置一致
- offer/answer 循环异常
- 查看客户端 Peer 日志,确认 SDP 设置顺序与状态机是否正确
- 检查服务器 onOffer/onAnswer 路由是否按角色与 participantId 正确转发
- ICE 候选无法到达
- 确认候选消息是否按目标参与者路由
- 检查 NAT/防火墙与 STUN/TURN 配置
- 广播消息未达
- 确认 broadcast 消息的目标 connectionId 与连接组是否存在
- 检查客户端 on-message 事件是否正确接收
**章节来源**
- [client/src/peer.js:132-173](file://client/src/peer.js#L132-L173)
- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338)
- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402)
## 结论
本多播示例通过“连接组 + 角色路由 + 广播”的架构,实现了多客户端同时接收相同媒体流的广播式分发。前端以 RenderStreaming 为核心,配合 WebSocket/HTTP 信令与独立 Peer 管理,确保了多路媒体复制与同步;后端通过连接组处理器实现高效的消息路由与广播。结合合理的性能优化与故障排查策略,可在生产环境中稳定运行。
[本节为总结性内容,无需列出具体文件来源]
## 附录
### 在 Unity Render Streaming 的 Multiplay 场景中使用
- 场景定位
- Multiplay 示例展示了多客户端同时观看同一渲染流的典型用法
- 关键步骤
- 启动服务器并确认 /config 返回的 useWebSocket 与 startupMode
- 在页面中点击“开始播放”,初始化 RenderStreaming 并创建连接
- 通过 "multiplay" 数据通道发送标签等广播消息
- 注意事项
- 私有模式下,页面提示不适用;请使用公共模式或调整前端逻辑
- 若浏览器不支持 setCodecPreferences需降级处理或提示用户
**章节来源**
- [client/public/multiplay/js/main.js:47-125](file://client/public/multiplay/js/main.js#L47-L125)
- [src/server.ts:25-29](file://src/server.ts#L25-L29)
### 服务器配置与部署
- 启动参数
- 端口、HTTPS 证书、信令类型websocket/http、通信模式public/private、日志级别
- 部署建议
- 使用 HTTPS 以满足现代浏览器对媒体权限的要求
- 将静态资源与模块路径正确暴露,确保前端示例可访问
- 对外暴露 /config、/signaling 路由,以便前端动态获取配置
**章节来源**
- [src/index.ts:20-41](file://src/index.ts#L20-L41)
- [src/server.ts:25-29](file://src/server.ts#L25-L29)
- [src/server.ts:26-28](file://src/server.ts#L26-L28)
### 客户端连接与负载均衡考虑
- 客户端连接
- 通过 /config 动态获取信令类型与模式
- 使用 createConnection 与 start 建立信令通道
- 负载均衡
- 多实例部署时,确保同一 connectionId 的消息在同一实例内处理
- 使用外部存储共享连接组状态,实现跨实例一致性
**章节来源**
- [client/public/multiplay/js/main.js:47-95](file://client/public/multiplay/js/main.js#L47-L95)
- [src/class/websockethandler.ts:145-168](file://src/class/websockethandler.ts#L145-L168)

View File

@@ -0,0 +1,414 @@
# 客户端示例
<cite>
**本文档引用的文件**
- [client/public/index.html](file://client/public/index.html)
- [client/public/bidirectional/index.html](file://client/public/bidirectional/index.html)
- [client/public/bidirectional/js/main.js](file://client/public/bidirectional/js/main.js)
- [client/public/bidirectional/js/sendvideo.js](file://client/public/bidirectional/js/sendvideo.js)
- [client/public/receiver/index.html](file://client/public/receiver/index.html)
- [client/public/receiver/js/main.js](file://client/public/receiver/js/main.js)
- [client/public/multiplay/index.html](file://client/public/multiplay/index.html)
- [client/public/multiplay/js/main.js](file://client/public/multiplay/js/main.js)
- [client/public/onebyone/index.html](file://client/public/onebyone/index.html)
- [client/public/onebyone/main.js](file://client/public/onebyone/main.js)
- [client/public/onebyone/store.js](file://client/public/onebyone/store.js)
- [client/public/onebyone/renderer.js](file://client/public/onebyone/renderer.js)
- [client/public/onebyone/chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [client/public/onebyone/utils.js](file://client/public/onebyone/utils.js)
- [client/public/videoplayer/index.html](file://client/public/videoplayer/index.html)
- [client/public/videoplayer/js/main.js](file://client/public/videoplayer/js/main.js)
- [client/public/videoplayer/js/video-player.js](file://client/public/videoplayer/js/video-player.js)
- [client/public/videoplayer/js/register-events.js](file://client/public/videoplayer/js/register-events.js)
- [client/public/js/config.js](file://client/public/js/config.js)
- [client/public/js/icesettings.js](file://client/public/js/icesettings.js)
- [client/public/js/stats.js](file://client/public/js/stats.js)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
- [client/src/sender.js](file://client/src/sender.js)
- [client/src/signaling.js](file://client/src/signaling.js)
- [client/src/peer.js](file://client/src/peer.js)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本仓库提供一套基于 WebRTC 的客户端示例系统,涵盖以下典型场景:
- 双向视频通信:本地采集音视频,同时接收远端流,适合需要双向互动的场景。
- 单向视频接收(广播):仅接收来自服务器的渲染流,适合“观看者”场景。
- 多客户端播放Guest 模式):多个客户端可共同观看同一渲染流。
- 视频播放器控制WebBrowserInput在浏览器中接收 Unity 渲染的摄像头画面,并通过数据通道将输入事件回传给服务器。
这些示例统一采用模块化架构,通过信令层协调连接建立、媒体轨道处理与 UI 渲染,便于集成到自有项目中作为参考或基线。
## 项目结构
客户端示例主要分为两部分:
- public 目录各示例页面及其资源HTML/CSS/JS每个示例独立运行。
- src 目录:通用的输入遥测、信令与 Peer 连接封装,供示例复用。
```mermaid
graph TB
subgraph "示例页面"
BI["双向示例<br/>bidirectional/index.html"]
RCV["接收示例<br/>receiver/index.html"]
MP["多播放示例<br/>multiplay/index.html"]
VP["播放器示例<br/>videoplayer/index.html"]
OB1["一对一通话示例<br/>onebyone/index.html"]
end
subgraph "公共脚本"
CFG["配置与ICE<br/>js/config.js, js/icesettings.js"]
STATS["统计信息<br/>js/stats.js"]
VID["视频播放器抽象<br/>js/videoplayer.js"]
end
subgraph "核心模块"
SIG["信令层<br/>src/signaling.js"]
PEER["Peer连接封装<br/>src/peer.js"]
SENDER["输入遥测发送器<br/>src/sender.js"]
end
BI --- CFG
RCV --- CFG
MP --- CFG
VP --- CFG
OB1 --- CFG
BI --- SIG
RCV --- SIG
MP --- SIG
VP --- SIG
OB1 --- SIG
BI --- PEER
RCV --- PEER
MP --- PEER
VP --- PEER
OB1 --- PEER
VP --- SENDER
VP --- VID
```
图表来源
- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84)
- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54)
- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54)
- [client/public/videoplayer/index.html:1-38](file://client/public/videoplayer/index.html#L1-L38)
- [client/public/onebyone/index.html:1-615](file://client/public/onebyone/index.html#L1-L615)
- [client/public/js/config.js](file://client/public/js/config.js)
- [client/public/js/icesettings.js](file://client/public/js/icesettings.js)
- [client/public/js/stats.js](file://client/public/js/stats.js)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209)
章节来源
- [client/public/index.html:1-78](file://client/public/index.html#L1-L78)
## 核心组件
- 配置与 ICE 管理负责读取服务器配置、选择信令协议HTTP 或 WebSocket、管理 STUN/TURN 列表。
- 信令层:封装 HTTP 与 WebSocket 两种信令方式,统一发布/订阅事件,屏蔽连接细节。
- Peer 封装:对 RTCPeerConnection 的封装,处理 offer/answer、ICE 候选、轨道事件与统计。
- 输入遥测:将鼠标、键盘、手柄、触摸等输入事件序列化并通过数据通道发送至服务器。
- 视频播放器抽象:统一视频元素创建、缩放、全屏、输入事件注册等行为。
章节来源
- [client/public/js/config.js](file://client/public/js/config.js)
- [client/public/js/icesettings.js](file://client/public/js/icesettings.js)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
## 架构总览
所有示例均遵循相同的控制流:初始化配置 → 选择信令 → 建立连接 → 添加媒体轨道 → 渲染远端流 → 统计与错误处理。
```mermaid
sequenceDiagram
participant UI as "示例页面"
participant CFG as "配置/ICE"
participant SIG as "信令层(HTTP/WebSocket)"
participant PEER as "Peer封装"
participant REM as "远端Peer"
UI->>CFG : 读取服务器配置/ICE服务器
UI->>SIG : start()/连接信令
UI->>PEER : start()/createConnection(id)
PEER->>SIG : 发布offer/answer/candidate
SIG-->>PEER : 下发offer/answer/candidate
PEER->>PEER : setLocal/RemoteDescription
PEER->>PEER : addIceCandidate
PEER-->>UI : onTrack/onDataChannel
UI->>UI : 渲染远端流/注册输入事件
```
图表来源
- [client/public/bidirectional/js/main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184)
- [client/public/receiver/js/main.js:75-88](file://client/public/receiver/js/main.js#L75-L88)
- [client/public/multiplay/js/main.js:82-95](file://client/public/multiplay/js/main.js#L82-L95)
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
- [client/src/peer.js:57-82](file://client/src/peer.js#L57-L82)
## 详细组件分析
### 双向视频通信Bidirectional
- 功能要点
- 本地采集音视频,选择设备与分辨率,支持自定义宽高。
- 建立 WebRTC 连接,添加本地轨道为 sendonly接收远端轨道并渲染。
- 支持编解码器偏好设置,按秒输出统计信息,显示本地/远端分辨率。
- 提示Public 模式不工作,需在 Private 模式下运行。
- 关键流程
- 设备枚举与分辨率选择
- 连接建立与轨道添加
- 编解码器偏好设置
- 统计信息轮询与展示
- UI 交互
- 开始采集 → 设置连接 → 挂断清理
- 编解码器选择禁用/启用
- 本地/远端视频统计显示
```mermaid
sequenceDiagram
participant UI as "双向页面"
participant SV as "SendVideo"
participant RS as "RenderStreaming"
participant SIG as "信令(HTTP/WebSocket)"
participant PC as "Peer封装"
UI->>SV : startLocalVideo(设备, 分辨率)
UI->>RS : start()/createConnection(id)
RS->>PC : onConnect回调
PC->>PC : addTransceiver(本地轨道, direction='sendonly')
PC->>SIG : 发布offer/answer/candidate
SIG-->>PC : 下发offer/answer/candidate
PC-->>UI : onTrack(远端轨道)
UI->>SV : addRemoteTrack(渲染)
UI->>PC : setCodecPreferences(可选)
UI->>UI : 每秒统计展示
```
图表来源
- [client/public/bidirectional/js/main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184)
- [client/public/bidirectional/js/sendvideo.js](file://client/public/bidirectional/js/sendvideo.js)
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [client/src/peer.js:108-130](file://client/src/peer.js#L108-L130)
章节来源
- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84)
- [client/public/bidirectional/js/main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383)
### 单向视频接收Receiver
- 功能要点
- 点击播放按钮后创建视频播放器,建立连接并接收渲染流。
- 支持编解码器偏好设置,提供光标锁定到播放器区域的选项。
- 断开连接时清理播放器与统计信息,恢复播放按钮。
- 关键流程
- 点击播放 → 创建播放器 → 建立连接 → onTrack 添加轨道 → 输入通道设置
- 断开时清理并重置 UI
```mermaid
sequenceDiagram
participant UI as "接收页面"
participant VP as "VideoPlayer"
participant RS as "RenderStreaming"
participant SIG as "信令(HTTP/WebSocket)"
UI->>VP : createPlayer(容器, 锁定选项)
UI->>RS : start()/createConnection()
RS->>SIG : 发布/订阅信令
SIG-->>RS : offer/answer/candidate
RS-->>UI : onTrack -> VP.addTrack
UI->>VP : setupInput(数据通道)
UI->>UI : 统计展示/断开清理
```
图表来源
- [client/public/receiver/js/main.js:67-108](file://client/public/receiver/js/main.js#L67-L108)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
章节来源
- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54)
- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186)
### 多客户端播放Multiplay
- 功能要点
- 与接收示例类似,但额外创建名为 "multiplay" 的数据通道,用于多客户端标签同步等场景。
- 打开通道后延时发送一条包含类型与参数的消息,演示数据通道的使用。
- 关键流程
- 建立连接 → 创建 "input" 与 "multiplay" 数据通道 → onOpenMultiplayChannel 发送消息
```mermaid
sequenceDiagram
participant UI as "多播放页面"
participant VP as "VideoPlayer"
participant RS as "RenderStreaming"
participant DC as "数据通道(input/multiplay)"
UI->>VP : createPlayer(容器, 锁定选项)
UI->>RS : start()/createConnection()
RS-->>UI : onConnect
UI->>DC : createDataChannel("input")
UI->>DC : createDataChannel("multiplay")
DC-->>UI : onopen
UI->>DC : send({type, argument})
UI->>UI : 统计展示/断开清理
```
图表来源
- [client/public/multiplay/js/main.js:97-125](file://client/public/multiplay/js/main.js#L97-L125)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
- [client/src/signaling.js:243-290](file://client/src/signaling.js#L243-L290)
章节来源
- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54)
- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204)
### 视频播放器控制WebBrowserInput
- 功能要点
- 点击播放后创建主视频与缩略图元素,注册游戏手柄、键盘、鼠标事件。
- 通过数据通道将点击事件与输入事件发送至服务器,实现“在浏览器中操作 Unity 摄像头”的效果。
- 支持全屏切换与自动尺寸调整。
- 关键流程
- 点击播放 → 创建元素 → setupConnection → 注册事件 → 发送输入事件
```mermaid
sequenceDiagram
participant UI as "播放器页面"
participant VP as "VideoPlayer"
participant SE as "输入遥测(sender)"
participant SIG as "信令(HTTP/WebSocket)"
UI->>VP : setupConnection(使用WebSocket?)
UI->>SE : registerGamepad/Keyboard/Mouse
SE->>SIG : 通过数据通道发送事件
UI->>UI : 全屏切换/尺寸调整
```
图表来源
- [client/public/videoplayer/js/main.js:49-142](file://client/public/videoplayer/js/main.js#L49-L142)
- [client/public/videoplayer/js/video-player.js](file://client/public/videoplayer/js/video-player.js)
- [client/public/videoplayer/js/register-events.js](file://client/public/videoplayer/js/register-events.js)
- [client/src/sender.js:14-209](file://client/src/sender.js#L14-L209)
章节来源
- [client/public/videoplayer/index.html:1-38](file://client/public/videoplayer/index.html#L1-L38)
- [client/public/videoplayer/js/main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157)
### 一对一视频通话One-by-one
- 功能要点
- 使用模块化架构store 管理状态renderer 负责 UI 渲染chatmessage 管理聊天utils 提供工具函数。
- 支持媒体状态切换(音频/视频/录制)、分辨率切换、侧边栏与通知、通话请求弹窗等。
- 通过 API 与 WebSocket 事件驱动 UI 更新与业务流程。
- 关键流程
- 页面加载 → 读取本地连接ID → joinCall → setUp → 绑定事件 → 渲染头部标题
```mermaid
sequenceDiagram
participant Page as "一对一页面"
participant Store as "状态管理(store)"
participant Renderer as "UI渲染(renderer)"
participant Chat as "聊天(chatmessage)"
participant Utils as "工具(utils)"
Page->>Store : joinCall(connectionId)
Page->>Store : setUp(connectionId)
Page->>Renderer : renderHeaderTitle()
Page->>Page : 绑定DOM事件/快捷键
Page->>Chat : bindMessageEvents()
Page->>Utils : showNotification(...)
```
图表来源
- [client/public/onebyone/main.js:179-212](file://client/public/onebyone/main.js#L179-L212)
- [client/public/onebyone/store.js](file://client/public/onebyone/store.js)
- [client/public/onebyone/renderer.js](file://client/public/onebyone/renderer.js)
- [client/public/onebyone/chatmessage.js](file://client/public/onebyone/chatmessage.js)
- [client/public/onebyone/utils.js](file://client/public/onebyone/utils.js)
章节来源
- [client/public/onebyone/index.html:1-615](file://client/public/onebyone/index.html#L1-L615)
- [client/public/onebyone/main.js:1-216](file://client/public/onebyone/main.js#L1-L216)
## 依赖关系分析
- 示例页面依赖公共配置与 ICE 设置以及核心模块信令、Peer、输入遥测、视频播放器抽象
- 一对一通话示例采用分层模块store/renderer/chat/utils降低耦合度提升可维护性。
- 信令层同时支持 HTTP 与 WebSocket通过统一事件接口屏蔽差异。
```mermaid
graph LR
BI["双向示例"] --> CFG["配置/ICE"]
RCV["接收示例"] --> CFG
MP["多播放示例"] --> CFG
VP["播放器示例"] --> CFG
OB1["一对一示例"] --> CFG
BI --> SIG["信令(HTTP/WebSocket)"]
RCV --> SIG
MP --> SIG
VP --> SIG
OB1 --> SIG
BI --> PEER["Peer封装"]
RCV --> PEER
MP --> PEER
VP --> PEER
OB1 --> PEER
VP --> SENDER["输入遥测(sender)"]
VP --> VID["视频播放器抽象"]
```
图表来源
- [client/public/js/config.js](file://client/public/js/config.js)
- [client/public/js/icesettings.js](file://client/public/js/icesettings.js)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
章节来源
- [client/public/js/config.js](file://client/public/js/config.js)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209)
## 性能考量
- 分辨率与编解码器
- 示例提供多种预设分辨率,支持自定义分辨率;可通过编解码器偏好减少带宽占用。
- 建议根据网络状况动态调整分辨率与编解码器,避免过高码率导致卡顿。
- 统计信息
- 每秒轮询统计信息,可用于监控丢包、延迟与分辨率变化,便于前端提示与自动调节。
- 输入事件
- 输入遥测通过数据通道发送,建议合并事件与节流发送,降低信令压力。
- 全屏与缩放
- 全屏切换与窗口 resize 时及时调整视频尺寸,避免重绘开销过大。
## 故障排查指南
- Public/Private 模式限制
- 双向示例在 Public 模式下不工作;接收/多播放示例在 Private 模式下不工作。请根据示例页面警告提示切换模式。
- 信令协议选择
- 若使用 WebSocket 信令,请确保服务端已正确配置;若使用 HTTP 信令,请确认会话创建与轮询接口可用。
- ICE 服务器
- 若无法建立连接,检查 STUN/TURN 服务器配置与网络策略;可在页面 ICE 配置区添加/移除/重置服务器。
- 编解码器不支持
- 部分浏览器不支持设置编解码器偏好;若出现相关提示,请降级处理或更换浏览器。
- 断线与重连
- ICE 失败会触发断开事件;示例会在断开时清理资源并提示消息。请检查网络稳定性与防火墙设置。
章节来源
- [client/public/bidirectional/js/main.js:99-105](file://client/public/bidirectional/js/main.js#L99-L105)
- [client/public/receiver/js/main.js:48-54](file://client/public/receiver/js/main.js#L48-L54)
- [client/public/multiplay/js/main.js:55-61](file://client/public/multiplay/js/main.js#L55-L61)
- [client/public/js/icesettings.js](file://client/public/js/icesettings.js)
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
## 结论
该客户端示例系统提供了从基础接收、双向通信到复杂输入遥测与 UI 管理的完整参考。其模块化设计与统一的信令抽象使得开发者可以快速将示例中的关键模式如连接建立、媒体轨道处理、数据通道输入、UI 渲染与状态管理)集成到自有项目中,并根据业务需求扩展功能。
## 附录
- 快速开始
- 在浏览器中打开示例页面根据页面提示选择模式Public/Private
- 如需使用 TURN请在 ICE 配置区添加 STUN/TURN 服务器。
- 对于需要输入控制的场景,确保数据通道可用且已注册相应事件。
- 集成建议
- 将公共配置与 ICE 设置抽取为独立模块,便于跨示例共享。
- 将信令层抽象为统一接口,支持 HTTP 与 WebSocket 两种实现。
- 将 Peer 封装与视频播放器抽象作为通用组件,按需组合到不同示例中。
- 对于复杂 UI如一对一通话采用分层模块store/renderer/chat/utils组织代码提升可维护性。

View File

@@ -0,0 +1,348 @@
# 接收端示例
<cite>
**本文引用的文件**
- [client/public/receiver/js/main.js](file://client/public/receiver/js/main.js)
- [client/public/receiver/index.html](file://client/public/receiver/index.html)
- [client/public/receiver/css/style.css](file://client/public/receiver/css/style.css)
- [client/src/renderstreaming.js](file://client/src/renderstreaming.js)
- [client/src/peer.js](file://client/src/peer.js)
- [client/src/signaling.js](file://client/src/signaling.js)
- [client/public/js/config.js](file://client/public/js/config.js)
- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js)
- [client/public/js/stats.js](file://client/public/js/stats.js)
- [client/public/js/icesettings.js](file://client/public/js/icesettings.js)
- [package.json](file://package.json)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本指南面向使用 Unity Render Streaming 的接收端示例,帮助您在浏览器中完成以下目标:
- 连接到服务器,建立 WebRTC 媒体会话
- 接收并解码视频/音频流
- 将媒体轨道渲染到页面中的视频元素
- 处理输入通道(鼠标/键盘/触摸/手柄)以实现远程输入遥测
- 实时查看统计信息,辅助调试与性能评估
- 提供性能优化建议与兼容性注意事项
- 给出典型使用场景与配置示例
## 项目结构
接收端示例位于 client/public/receiver 目录,前端采用模块化 JavaScript配合自定义的 RenderStreaming、Peer、Signaling 模块实现 WebRTC 信令与媒体处理。
```mermaid
graph TB
subgraph "接收端页面"
HTML["index.html"]
CSS["css/style.css"]
JSMain["receiver/js/main.js"]
JSConfig["public/js/config.js"]
JSStats["public/js/stats.js"]
JSVideo["public/js/videoplayer.js"]
end
subgraph "核心模块"
RRS["src/renderstreaming.js"]
PEER["src/peer.js"]
SIG["src/signaling.js"]
end
HTML --> JSMain
HTML --> CSS
JSMain --> JSConfig
JSMain --> JSStats
JSMain --> JSVideo
JSMain --> RRS
RRS --> PEER
RRS --> SIG
```
图表来源
- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54)
- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186)
- [client/public/receiver/css/style.css:1-43](file://client/public/receiver/css/style.css#L1-L43)
- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
章节来源
- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54)
- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186)
- [client/public/receiver/css/style.css:1-43](file://client/public/receiver/css/style.css#L1-L43)
- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
## 核心组件
- 页面与入口
- index.html页面骨架、样式与脚本加载入口
- receiver/js/main.js应用主流程负责初始化、信令选择、RTCPeerConnection 配置、媒体轨道接入与统计展示
- 媒体播放器
- public/js/videoplayer.js封装视频元素创建、全屏切换、输入通道绑定与媒体轨道追加
- 信令层
- src/signaling.jsHTTP 与 WebSocket 两种信令实现负责连接生命周期、offer/answer/candidate 转发
- 会话与媒体
- src/renderstreaming.js高层封装管理连接、多 peer、事件路由、统计查询、数据通道创建
- src/peer.js底层 RTCPeerConnection 管理包含协商、ICE、统计查询、轨道与数据通道操作
- 配置与工具
- public/js/config.js读取服务器配置、生成 RTC 配置(含 STUN/TURN、音频增强
- public/js/stats.js从 RTCStatsReport 生成可读字符串数组
- public/js/icesettings.jsSTUN/TURN 配置持久化与读取
章节来源
- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54)
- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186)
- [client/public/js/videoplayer.js:1-213](file://client/public/js/videoplayer.js#L1-L213)
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
- [client/public/js/config.js:1-39](file://client/public/js/config.js#L1-L39)
- [client/public/js/stats.js:1-91](file://client/public/js/stats.js#L1-L91)
- [client/public/js/icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104)
## 架构总览
下图展示了从用户点击“开始”到媒体渲染的关键交互路径,以及输入通道的建立流程。
```mermaid
sequenceDiagram
participant U as "用户"
participant Page as "页面(main.js)"
participant RS as "RenderStreaming"
participant Sig as "Signaling(HTTP/WebSocket)"
participant P as "Peer(RTCPeerConnection)"
participant VP as "VideoPlayer"
U->>Page : 点击“开始”
Page->>VP : 创建视频容器与播放器
Page->>Sig : 选择信令类型并创建连接
Page->>RS : 初始化并启动
RS->>P : 创建/准备 Peer
P-->>RS : 触发 onnegotiationneeded
RS->>Sig : 发送 offer
Sig-->>RS : 下行 offer
RS->>P : 设置远端 offer 并生成 answer
RS->>Sig : 发送 answer
Sig-->>RS : 下行 answer
P-->>RS : 触发 ontrack
RS-->>Page : 分发 track 事件
Page->>VP : 追加轨道到视频元素
Page->>VP : 建立输入数据通道并绑定
Page->>Page : 定时获取统计并展示
```
图表来源
- [client/public/receiver/js/main.js:67-108](file://client/public/receiver/js/main.js#L67-L108)
- [client/src/renderstreaming.js:182-250](file://client/src/renderstreaming.js#L182-L250)
- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173)
- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149)
- [client/public/js/videoplayer.js:19-41](file://client/public/js/videoplayer.js#L19-L41)
## 详细组件分析
### 页面与入口main.js
- 初始化与页面控制
- 读取服务器配置,决定是否使用 WebSocket 信令
- 显示警告(如私有模式限制)、编解码器选择下拉框、播放按钮
- 点击播放后创建视频播放器并初始化 RenderStreaming
- RTCPeerConnection 配置
- 通过 config.js 获取 RTC 配置,包含 sdpSemantics、iceServers、媒体约束与音频增强参数
- 事件与生命周期
- onConnect创建输入数据通道绑定 VideoPlayer 输入遥测
- onDisconnect清理统计、重置 UI、重建播放器与编解码器选择
- onTrackEvent将轨道追加到视频元素
- onGotOffer根据浏览器能力设置编解码器偏好
- 统计展示
- 定时调用 RenderStreaming.getStats使用 stats.js 生成可读字符串并显示
章节来源
- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186)
- [client/public/js/config.js:1-39](file://client/public/js/config.js#L1-L39)
- [client/public/js/stats.js:1-91](file://client/public/js/stats.js#L1-L91)
### 媒体播放器VideoPlayer
- 视频元素与布局
- 创建 video 元素,设置 playsInline、空 MediaStream监听元数据加载完成自动播放
- 提供全屏按钮与全屏状态切换逻辑,支持指针锁定(可选)
- 轨道与尺寸
- addTrack向 video.srcObject 追加媒体轨道
- resizeVideo计算缩放与偏移适配容器尺寸
- 输入遥测
- setupInput创建 Sender/InputRemoting订阅 RTCDataChannel打开后开始发送输入事件
- 支持鼠标、键盘、触摸屏、手柄输入的转发
章节来源
- [client/public/js/videoplayer.js:1-213](file://client/public/js/videoplayer.js#L1-L213)
### 信令层SignalingHTTP 与 WebSocket
- HTTP 信令
- start轮询获取消息队列分发 connect/disconnect/offer/answer/candidate/on-message
- createConnection/deleteConnection创建/删除连接
- sendOffer/sendAnswer/sendCandidate/sendMessage发送 SDP 与消息
- WebSocket 信令
- onopen/onmessage/onclose解析消息类型并分发事件
- createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate/sendMessage与 HTTP 版本对应
章节来源
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
### 会话与媒体RenderStreaming
- 生命周期与角色
- 支持 Host/Participant 双端模式Participant 端默认单 peerHost 端按参与者维护多 peer
- 事件路由
- 将 Peer 的 track/adddatachannel/offer/answer/candidate 等事件上抛,并携带 participantId
- 关键方法
- createConnection/start/stop连接生命周期管理
- getStats/createDataChannel/addTrack/addTransceiver/getTransceivers媒体与统计接口
- 与 Signaling 事件绑定,完成 offer/answer/candidate 的收发
章节来源
- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
### 底层会话PeerRTCPeerConnection
- 协商与 ICE
- onnegotiationneeded生成本地 offer 并通过事件上报
- onGotDescription设置远端描述若为 offer 则生成 answer 并上报
- onIceCandidate上报候选
- 状态与错误
- iceConnectionState=failed 时触发 disconnect 事件
- 工具方法
- getTransceivers/addTrack/addTransceiver/createDataChannel/getStats/close媒体与统计操作
章节来源
- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188)
### 配置与工具
- config.js
- getServerConfig读取服务器配置含 useWebSocket
- getRTCConfiguration统一计划 SDP、ICE 服务器、媒体约束与音频增强参数
- icesettings.js
- STUN/TURN 服务器的增删改查与本地存储
- stats.js
- 从 inbound/outbound RTP 统计生成可读字符串,包含编解码器、分辨率、帧率、比特率等
章节来源
- [client/public/js/config.js:1-39](file://client/public/js/config.js#L1-L39)
- [client/public/js/icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104)
- [client/public/js/stats.js:1-91](file://client/public/js/stats.js#L1-L91)
## 依赖关系分析
- 模块耦合
- main.js 依赖 renderstreaming.js、videoplayer.js、config.js、stats.js
- renderstreaming.js 依赖 peer.js 与 signaling.js
- peer.js 仅依赖 logger.js内部日志
- 外部依赖
- 浏览器原生 WebRTC APIRTCPeerConnection、MediaStream、RTCIceServer 等)
- 第三方 polyfill/适配库(页面引入了 webrtc-adapter
```mermaid
graph LR
MAIN["main.js"] --> CFG["config.js"]
MAIN --> STATS["stats.js"]
MAIN --> VP["videoplayer.js"]
MAIN --> RRS["renderstreaming.js"]
RRS --> PEER["peer.js"]
RRS --> SIG["signaling.js"]
VP --> INPUT["inputremoting.js(sender.js)"]
```
图表来源
- [client/public/receiver/js/main.js:1-10](file://client/public/receiver/js/main.js#L1-L10)
- [client/src/renderstreaming.js:1-30](file://client/src/renderstreaming.js#L1-L30)
- [client/src/peer.js:1-10](file://client/src/peer.js#L1-L10)
- [client/src/signaling.js:1-10](file://client/src/signaling.js#L1-L10)
- [client/public/js/videoplayer.js:1-3](file://client/public/js/videoplayer.js#L1-L3)
章节来源
- [client/public/receiver/js/main.js:1-10](file://client/public/receiver/js/main.js#L1-L10)
- [client/src/renderstreaming.js:1-30](file://client/src/renderstreaming.js#L1-L30)
- [client/src/peer.js:1-10](file://client/src/peer.js#L1-L10)
- [client/src/signaling.js:1-10](file://client/src/signaling.js#L1-L10)
- [client/public/js/videoplayer.js:1-3](file://client/public/js/videoplayer.js#L1-L3)
## 性能考虑
- 编解码器选择
- 若浏览器支持 setCodecPreferences可在 offer 生成前设置偏好,减少不必要解码开销
- 统计监控
- 使用 stats.js 输出的比特率、分辨率、帧率等指标,结合网络状况动态调整
- 媒体约束与音频增强
- 合理配置音频回声消除、降噪、自动增益等参数,降低 CPU 占用与提升音质
- 渲染与布局
- 使用 videoplayer.js 的 resizeVideo 计算缩放,避免不必要的重排
- 信令选择
- WebSocket 信令延迟更低适合低延迟场景HTTP 轮询更通用,兼容性更好
[本节为通用指导,无需列出具体文件来源]
## 故障排查指南
- 私有模式限制
- 页面会在私有模式下提示警告,导致部分功能不可用
- 无法播放或黑屏
- 检查 onConnect 是否触发、track 事件是否到达、VideoPlayer 是否成功追加轨道
- 确认浏览器是否支持所选编解码器,必要时切换默认或手动选择
- 无声音
- 确认音频媒体轨道已加入,检查音频增强参数与浏览器静音策略
- 输入无响应
- 确认数据通道已创建并打开VideoPlayer.setupInput 是否被调用
- 连接失败或频繁断开
- 查看 ICE 候选是否正常收发STUN/TURN 配置是否正确
- 关注 Peer 的 iceConnectionState 与 signalingState 变化
章节来源
- [client/public/receiver/js/main.js:48-54](file://client/public/receiver/js/main.js#L48-L54)
- [client/public/js/videoplayer.js:194-212](file://client/public/js/videoplayer.js#L194-L212)
- [client/src/peer.js:43-48](file://client/src/peer.js#L43-L48)
- [client/public/js/icesettings.js:94-104](file://client/public/js/icesettings.js#L94-L104)
## 结论
该接收端示例通过清晰的模块划分与事件驱动设计,实现了从信令、协商、媒体轨道接入到播放器渲染与输入遥测的完整链路。借助统计模块与编解码器偏好设置,开发者可以快速定位问题并优化体验。建议在生产环境中优先使用 WebSocket 信令、合理配置 ICE 服务器与音频增强,并持续监控统计指标以保障质量。
[本节为总结性内容,无需列出具体文件来源]
## 附录
### 使用步骤(从零到运行)
- 启动服务端
- 使用 package.json 中的脚本启动服务端,默认端口与证书参数可参考脚本配置
- 打开接收端页面
- 在浏览器中访问接收端页面,点击“开始”
- 选择信令方式
- 若服务器返回 useWebSocket=true则使用 WebSocket 信令;否则使用 HTTP 信令
- 选择编解码器(可选)
- 若浏览器支持 setCodecPreferences可在下拉框中选择偏好编解码器
- 观察统计与播放
- 页面会定时显示统计信息;视频元素加载完成后自动播放
- 输入遥测(可选)
- 勾选“锁定光标到播放器”,进入全屏后可进行指针锁定与输入转发
章节来源
- [package.json:9-10](file://package.json#L9-L10)
- [client/public/receiver/js/main.js:40-88](file://client/public/receiver/js/main.js#L40-L88)
- [client/public/js/config.js:3-7](file://client/public/js/config.js#L3-L7)
- [client/public/js/stats.js:7-91](file://client/public/js/stats.js#L7-L91)
### 关键流程图:编解码器偏好设置
```mermaid
flowchart TD
Start(["收到 Offer"]) --> CheckSupport{"浏览器支持<br/>setCodecPreferences?"}
CheckSupport --> |否| Skip["跳过设置偏好"]
CheckSupport --> |是| ReadOptions["读取下拉框选中项"]
ReadOptions --> Parse["解析 mimeType 与 sdpFmtpLine"]
Parse --> Find["在 RTCRtpSender.getCapabilities 中查找匹配编解码器"]
Find --> Found{"找到匹配项?"}
Found --> |否| Skip
Found --> |是| GetTransceivers["获取视频接收端点列表"]
GetTransceivers --> Apply["对每个视频接收端点调用 setCodecPreferences"]
Apply --> End(["完成"])
Skip --> End
```
图表来源
- [client/public/receiver/js/main.js:110-131](file://client/public/receiver/js/main.js#L110-L131)
- [client/src/renderstreaming.js:284-290](file://client/src/renderstreaming.js#L284-L290)

View File

@@ -0,0 +1,412 @@
# 视频播放器示例
<cite>
**本文档引用的文件**
- [video-player.js](file://client/public/videoplayer/js/video-player.js)
- [gamepadEvents.js](file://client/public/videoplayer/js/gamepadEvents.js)
- [register-events.js](file://client/public/videoplayer/js/register-events.js)
- [main.js](file://client/public/videoplayer/js/main.js)
- [index.html](file://client/public/videoplayer/index.html)
- [peer.js](file://client/src/peer.js)
- [signaling.js](file://client/src/signaling.js)
- [renderstreaming.js](file://client/src/renderstreaming.js)
- [sender.js](file://client/src/sender.js)
- [inputdevice.js](file://client/src/inputdevice.js)
- [inputremoting.js](file://client/src/inputremoting.js)
- [logger.js](file://client/src/logger.js)
- [keymap.js](file://client/src/keymap.js)
- [mousebutton.js](file://client/src/mousebutton.js)
- [gamepadbutton.js](file://client/src/gamepadbutton.js)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考虑](#性能考虑)
8. [故障排除指南](#故障排除指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本示例演示了如何在浏览器中接收 Unity 渲染的相机图像,并通过浏览器端操作 Unity 中的相机。系统采用 WebRTC 进行媒体流传输,同时通过数据通道发送用户输入事件(键盘、鼠标、触摸、游戏手柄)。播放器支持主画中画双路视频切换、缩放与坐标转换、以及多种输入设备的实时控制。
## 项目结构
视频播放器示例位于 Public 模式下的 videoplayer 子目录,主要由以下层次组成:
- 视频播放层:负责建立 WebRTC 连接、接收媒体轨道、管理数据通道、处理来自 Unity 的控制消息。
- 事件注册层:封装各类输入事件的监听与打包,统一通过数据通道发送至 Unity。
- WebRTC 基础层:封装 PeerConnection、信令协议、ICE 候选等底层细节。
- 输入设备抽象层:定义输入设备、状态与事件的数据格式,便于序列化与跨平台传输。
```mermaid
graph TB
subgraph "浏览器前端"
VP["VideoPlayer<br/>视频播放器"]
RE["register-events.js<br/>事件注册"]
GP["gamepadEvents.js<br/>游戏手柄事件"]
PEER["Peer<br/>RTCPeerConnection封装"]
SIG["Signaling/WebSocketSignaling<br/>信令"]
end
subgraph "Unity 后端"
RS["RenderStreaming<br/>渲染流封装"]
SENDER["Sender<br/>输入发送器"]
INPUTDEV["InputDevice/IInputState<br/>输入设备与状态"]
end
VP --> PEER
VP --> SIG
RE --> VP
GP --> RE
PEER --> SIG
RS --> PEER
SENDER --> RS
INPUTDEV --> SENDER
```
**图表来源**
- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247)
- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308)
- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147)
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [sender.js:1-209](file://client/src/sender.js#L1-L209)
- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719)
**章节来源**
- [index.html:1-38](file://client/public/videoplayer/index.html#L1-L38)
- [main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157)
## 核心组件
- VideoPlayer负责创建 RTCPeerConnection、订阅媒体轨道、管理数据通道、解析来自 Unity 的控制消息(如切换视频源)。
- register-events.js集中注册键盘、鼠标、触摸、游戏手柄事件将事件打包为二进制消息并通过数据通道发送。
- gamepadEvents.js对原生游戏手柄 API 进行封装,生成统一的自定义事件(按钮按下/抬起/持续按住、轴值变化),并维护设备状态。
- Peer封装 RTCPeerConnection 生命周期、SDP 协商、ICE 候选处理与断线检测。
- Signaling/WebSocketSignalingHTTP 轮询或 WebSocket 实现的信令通道,承载 offer/answer/candidate/on-message 等信令。
- RenderStreaming/Sender/InputDevice输入遥测链路的完整实现定义输入设备、状态与事件的内存布局支持鼠标、键盘、触摸、游戏手柄。
**章节来源**
- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247)
- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308)
- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147)
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [sender.js:1-209](file://client/src/sender.js#L1-L209)
- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719)
## 架构总览
系统采用“浏览器播放器 + 信令 + WebRTC + 数据通道”的架构。浏览器端通过 Signaling 与服务器建立连接,协商 SDP 并交换 ICE 候选;媒体轨道通过 RTCPeerConnection 接收数据通道用于传输输入事件。Unity 侧通过 RenderStreaming/Sender 将输入事件投递到远程渲染环境,同时可向浏览器发送控制消息(如切换视频源)。
```mermaid
sequenceDiagram
participant Browser as "浏览器"
participant VP as "VideoPlayer"
participant PEER as "Peer"
participant SIG as "Signaling/WebSocketSignaling"
participant Unity as "Unity"
Browser->>VP : 初始化并调用 setupConnection()
VP->>SIG : start() 启动信令
VP->>PEER : 创建 RTCPeerConnection
PEER->>SIG : 发送 offer
SIG-->>PEER : 下发 offer
PEER->>SIG : 发送 answer
SIG-->>PEER : 下发 answer
PEER->>SIG : 发送 ICE 候选
SIG-->>PEER : 下发 ICE 候选
PEER-->>VP : 触发 ontrack 获取媒体轨道
VP->>VP : 切换主/副视频轨道
Unity-->>VP : 数据通道下发控制消息
VP->>VP : 解析消息并执行操作
```
**图表来源**
- [video-player.js:49-155](file://client/public/videoplayer/js/video-player.js#L49-L155)
- [peer.js:57-173](file://client/src/peer.js#L57-L173)
- [signaling.js:30-149](file://client/src/signaling.js#L30-L149)
## 详细组件分析
### VideoPlayer 组件分析
VideoPlayer 是播放器的核心,负责:
- 建立 RTCPeerConnection 并绑定事件trackevent、sendoffer、sendanswer、sendcandidate
- 订阅媒体轨道,维护主/副视频轨道列表,支持动态切换。
- 创建数据通道,接收来自 Unity 的控制消息并执行相应操作(如切换视频源)。
- 提供尺寸与坐标转换能力,确保输入事件映射到正确的视频区域。
```mermaid
classDiagram
class VideoPlayer {
+constructor(elements)
+setupConnection(useWebSocket)
+resizeVideo()
+switchVideo(indexVideoTrack)
+replaceTrack(stream, newTrack)
+sendMsg(msg)
+stop()
+ondisconnect
+videoWidth
+videoHeight
+videoOriginX
+videoOriginY
+videoScale
}
class Peer {
+addEventListener(event, handler)
+createDataChannel(connectionId, label)
+onGotDescription(connectionId, description)
+onGotCandidate(connectionId, candidate)
+close()
}
class Signaling {
+start()
+sendOffer(connectionId, sdp)
+sendAnswer(connectionId, sdp)
+sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex)
+addEventListener(event, handler)
+stop()
}
VideoPlayer --> Peer : "使用"
VideoPlayer --> Signaling : "使用"
```
**图表来源**
- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247)
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
**章节来源**
- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247)
### register-events.js 事件注册与处理
该模块负责:
- 键盘事件:按键按下/抬起,映射键码并发送二进制消息。
- 鼠标事件:点击/移动/滚轮,计算相对于视频的坐标并发送。
- 触摸事件:多点触控,记录每个触点的阶段、坐标、压力等信息。
- 游戏手柄事件:统一的按钮与轴事件,生成自定义事件并转发给 VideoPlayer。
```mermaid
flowchart TD
Start(["开始"]) --> Detect["检测输入事件类型"]
Detect --> |键盘| BuildKey["构建键盘消息<br/>类型/方向/重复/键码/字符"]
Detect --> |鼠标| BuildMouse["构建鼠标消息<br/>坐标/按钮"]
Detect --> |触摸| BuildTouch["构建触摸消息<br/>触点数量/阶段/坐标/压力"]
Detect --> |游戏手柄| BuildGP["构建手柄消息<br/>按钮/轴值"]
BuildKey --> Send["通过数据通道发送"]
BuildMouse --> Send
BuildTouch --> Send
BuildGP --> Send
Send --> End(["结束"])
```
**图表来源**
- [register-events.js:120-281](file://client/public/videoplayer/js/register-events.js#L120-L281)
**章节来源**
- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308)
### gamepadEvents.js 游戏手柄事件处理
该模块对原生游戏手柄 API 进行封装:
- 维护设备连接时间戳与前一帧状态,避免重复触发。
- 定时循环(约 60fps扫描按钮与轴值变化生成统一的自定义事件。
- 支持不同浏览器差异(如 getGamepads 的兼容性)。
```mermaid
sequenceDiagram
participant Browser as "浏览器"
participant GPH as "gamepadEvents.js"
participant RE as "register-events.js"
participant VP as "VideoPlayer"
Browser->>GPH : 设备连接/断开
GPH->>GPH : 记录连接时间戳/初始化状态
loop 60fps
GPH->>GPH : 扫描按钮/轴值变化
alt 按钮状态变化
GPH-->>RE : 触发 gamepadButtonDown/gamepadButtonUp
else 按钮持续按住
GPH-->>RE : 触发 gamepadButtonPressed
end
alt 轴值变化
GPH-->>RE : 触发 gamepadAxis
end
end
RE->>VP : sendMsg(二进制消息)
```
**图表来源**
- [gamepadEvents.js:68-94](file://client/public/videoplayer/js/gamepadEvents.js#L68-L94)
- [register-events.js:42-101](file://client/public/videoplayer/js/register-events.js#L42-L101)
- [video-player.js:215-233](file://client/public/videoplayer/js/video-player.js#L215-L233)
**章节来源**
- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147)
- [register-events.js:42-101](file://client/public/videoplayer/js/register-events.js#L42-L101)
### 输入设备抽象与序列化
输入设备层定义了统一的状态与事件格式,便于跨平台传输:
- InputDevice抽象基类维护设备描述与当前状态。
- IInputState抽象状态接口提供 buffer 与 format 字段。
- 具体设备状态MouseState、KeyboardState、TouchscreenState、GamepadState。
- 事件封装StateEvent、TextEvent支持二进制序列化。
```mermaid
classDiagram
class InputDevice {
+name
+layout
+deviceId
+usages
+description
+updateState(state)
+queueEvent(event)
+currentState
}
class IInputState {
+buffer
+format
}
class MouseState
class KeyboardState
class TouchscreenState
class GamepadState
class StateEvent
class TextEvent
InputDevice <|-- Mouse
InputDevice <|-- Keyboard
InputDevice <|-- Touchscreen
InputDevice <|-- Gamepad
IInputState <|-- MouseState
IInputState <|-- KeyboardState
IInputState <|-- TouchscreenState
IInputState <|-- GamepadState
StateEvent --> IInputState : "包含状态"
TextEvent --> InputEvent : "包含基础事件"
```
**图表来源**
- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719)
**章节来源**
- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719)
### 信令与 WebRTC 协商
- Signaling基于 HTTP 的轮询信令,支持 offer/answer/candidate/on-message。
- WebSocketSignaling基于 WebSocket 的实时信令,支持广播与参与者事件。
- Peer封装 RTCPeerConnection自动处理 SDP 协商、ICE 候选、断线检测与重发 offer。
```mermaid
sequenceDiagram
participant A as "Peer A"
participant SIG as "Signaling"
participant B as "Peer B"
A->>SIG : 发送 offer
SIG-->>B : 下发 offer
B->>B : 设置远端 offer
B->>SIG : 发送 answer
SIG-->>A : 下发 answer
A->>A : 设置远端 answer
A->>SIG : 发送 ICE 候选
B->>SIG : 发送 ICE 候选
SIG-->>A : 下发 ICE 候选
SIG-->>B : 下发 ICE 候选
```
**图表来源**
- [peer.js:57-173](file://client/src/peer.js#L57-L173)
- [signaling.js:117-146](file://client/src/signaling.js#L117-L146)
**章节来源**
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
## 依赖关系分析
- VideoPlayer 依赖 Peer 与 Signaling用于媒体与信令。
- register-events 依赖 gamepadEvents 与 keymap用于事件注册与键码映射。
- main.js 作为入口,协调 VideoPlayer 与事件注册模块。
- 输入遥测链路由 Sender -> RenderStreaming -> Peer -> Signaling最终到达 Unity。
```mermaid
graph LR
MAIN["main.js"] --> VP["video-player.js"]
MAIN --> REG["register-events.js"]
REG --> GPE["gamepadEvents.js"]
VP --> PEER["peer.js"]
VP --> SIG["signaling.js"]
SENDER["sender.js"] --> RS["renderstreaming.js"]
RS --> PEER
INPUTDEV["inputdevice.js"] --> SENDER
```
**图表来源**
- [main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157)
- [video-player.js:1-247](file://client/public/videoplayer/js/video-player.js#L1-L247)
- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308)
- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147)
- [peer.js:1-188](file://client/src/peer.js#L1-L188)
- [signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [sender.js:1-209](file://client/src/sender.js#L1-L209)
- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317)
- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719)
**章节来源**
- [main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157)
## 性能考虑
- 事件采样频率:游戏手柄事件以约 60fps 轮询,建议根据设备刷新率调整间隔,避免过度 CPU 占用。
- 坐标转换VideoPlayer 提供视频缩放与偏移计算,确保输入事件映射准确,减少误触。
- 数据通道:仅在通道打开时发送消息,避免在关闭或连接中发送导致异常。
- 媒体轨道切换:使用 replaceTrack 替换视频轨道,避免频繁创建/销毁媒体流带来的抖动。
## 故障排除指南
- 无法建立 WebRTC 连接
- 检查信令是否正常启动与停止。
- 确认 offer/answer 协商过程无异常ICE 候选是否正确交换。
- 查看断线事件与日志输出。
- 数据通道不可用
- 确认数据通道已创建且处于 open 状态。
- 检查 sendMsg 的状态分支,避免在 closing/closed 状态发送。
- 输入事件未生效
- 确认事件监听已注册,且消息格式与 Unity 期望一致。
- 检查坐标转换参数videoScale、videoOriginX/Y是否正确。
- 游戏手柄无响应
- 确认浏览器支持 getGamepads 或 webkitGetGamepads。
- 检查设备连接时间戳与状态缓存是否正确初始化。
**章节来源**
- [video-player.js:128-154](file://client/public/videoplayer/js/video-player.js#L128-L154)
- [register-events.js:237-263](file://client/public/videoplayer/js/register-events.js#L237-L263)
- [gamepadEvents.js:112-146](file://client/public/videoplayer/js/gamepadEvents.js#L112-L146)
- [logger.js:1-30](file://client/src/logger.js#L1-L30)
## 结论
该视频播放器示例完整展示了从浏览器接收 Unity 渲染视频、通过数据通道发送用户输入、以及在 Unity 中进行相机控制的端到端流程。其模块化设计使得事件注册、输入抽象与 WebRTC 协商清晰分离,便于扩展与维护。通过合理的坐标转换与事件采样策略,系统在多设备环境下具备良好的交互体验。
## 附录
### 使用指南
- Public 模式配置
- 通过配置接口获取服务器设置,决定使用 HTTP 轮询信令还是 WebSocket 信令。
- 若启动模式为私有模式,页面会显示警告提示。
- 启动播放器
- 点击播放按钮后,页面动态创建主视频与缩略视频元素。
- 初始化 VideoPlayer 并建立 WebRTC 连接。
- 控制 Unity 中的相机
- 通过数据通道发送控制消息(如切换视频源)。
- 可通过按钮事件向 Unity 发送按钮点击消息。
- 输入设备使用
- 键盘:按键按下/抬起事件会被打包发送。
- 鼠标:点击/移动/滚轮事件,坐标经视频缩放与偏移转换。
- 触摸:多点触控,记录触点阶段、坐标、压力等。
- 游戏手柄:按钮与轴值变化统一事件,支持不同浏览器差异。
- 设备适配
- 坐标系适配VideoPlayer 提供缩放与偏移计算,确保事件映射到正确区域。
- 全屏模式:支持全屏切换,改变按钮可见性与布局。
- 断线处理:断线时清理 DOM、停止播放器并重新显示播放按钮。
**章节来源**
- [main.js:23-157](file://client/public/videoplayer/js/main.js#L23-L157)
- [video-player.js:49-247](file://client/public/videoplayer/js/video-player.js#L49-L247)
- [register-events.js:120-308](file://client/public/videoplayer/js/register-events.js#L120-L308)
- [gamepadEvents.js:112-147](file://client/public/videoplayer/js/gamepadEvents.js#L112-L147)