Files
video_socket-server/.qoder/repowiki/zh/content/客户端示例/多播放示例.md

399 lines
17 KiB
Markdown
Raw Permalink Normal View History

2026-05-16 13:24:02 +08:00
# 多播放示例
<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)