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

412 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>
**本文档引用的文件**
- [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)