修改
This commit is contained in:
437
.qoder/repowiki/zh/content/客户端示例/一对一通信示例.md
Normal file
437
.qoder/repowiki/zh/content/客户端示例/一对一通信示例.md
Normal 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.js:ICE 服务器配置持久化与读取
|
||||
|
||||
```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 响应状态变化更新 UI;WebRTC 通过 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 API(getUserMedia、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)
|
||||
444
.qoder/repowiki/zh/content/客户端示例/双向通信示例.md
Normal file
444
.qoder/repowiki/zh/content/客户端示例/双向通信示例.md
Normal 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)
|
||||
|
||||
### 媒体处理:SendVideo(sendvideo.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 与 WebSocket(signaling.js)
|
||||
- 功能职责
|
||||
- HTTP 信令:基于轮询的信令实现,维护 Session-Id,支持连接创建/删除、offer/answer/candidate 发送与接收,以及自定义消息。
|
||||
- WebSocket 信令:基于 WebSocket 的实时信令实现,支持连接/断开、offer/answer/candidate、参与者加入/离开、广播消息等。
|
||||
- 关键流程
|
||||
- HTTP:start() 循环创建会话,loopGetAll() 轮询消息并分发事件;createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate。
|
||||
- WebSocket:onopen/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)
|
||||
|
||||
### 连接管理:RenderStreaming(renderstreaming.js)
|
||||
- 功能职责
|
||||
- 统一封装信令事件与 Peer 生命周期,支持单 Peer(participant)与多 Peer(host)场景。
|
||||
- 路由 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 导致的状态冲突。
|
||||
- 关键流程
|
||||
- _onNegotiationneeded:setLocalDescription 生成 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)
|
||||
|
||||
### 配置与 ICE:RTC 配置与 STUN/TURN(config.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/--type,websocket 或 http,默认 websocket。
|
||||
- 通信模式:-m/--mode,public 或 private,默认 public。
|
||||
- 日志级别:-l/--logging,combined/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)
|
||||
399
.qoder/repowiki/zh/content/客户端示例/多播放示例.md
Normal file
399
.qoder/repowiki/zh/content/客户端示例/多播放示例.md
Normal 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)
|
||||
414
.qoder/repowiki/zh/content/客户端示例/客户端示例.md
Normal file
414
.qoder/repowiki/zh/content/客户端示例/客户端示例.md
Normal 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)组织代码,提升可维护性。
|
||||
348
.qoder/repowiki/zh/content/客户端示例/接收端示例.md
Normal file
348
.qoder/repowiki/zh/content/客户端示例/接收端示例.md
Normal 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.js:HTTP 与 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.js:STUN/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)
|
||||
|
||||
### 信令层:Signaling(HTTP 与 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 端默认单 peer,Host 端按参与者维护多 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)
|
||||
|
||||
### 底层会话:Peer(RTCPeerConnection)
|
||||
- 协商与 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 API(RTCPeerConnection、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)
|
||||
412
.qoder/repowiki/zh/content/客户端示例/视频播放器示例.md
Normal file
412
.qoder/repowiki/zh/content/客户端示例/视频播放器示例.md
Normal 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/WebSocketSignaling:HTTP 轮询或 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)
|
||||
Reference in New Issue
Block a user