Files
2026-05-16 13:24:02 +08:00

399 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多播放示例
<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)