Files

412 lines
17 KiB
Markdown
Raw Permalink Normal View History

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