# 视频播放器示例 **本文档引用的文件** - [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) ## 目录 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
视频播放器"] RE["register-events.js
事件注册"] GP["gamepadEvents.js
游戏手柄事件"] PEER["Peer
RTCPeerConnection封装"] SIG["Signaling/WebSocketSignaling
信令"] end subgraph "Unity 后端" RS["RenderStreaming
渲染流封装"] SENDER["Sender
输入发送器"] INPUTDEV["InputDevice/IInputState
输入设备与状态"] 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["构建键盘消息
类型/方向/重复/键码/字符"] Detect --> |鼠标| BuildMouse["构建鼠标消息
坐标/按钮"] Detect --> |触摸| BuildTouch["构建触摸消息
触点数量/阶段/坐标/压力"] Detect --> |游戏手柄| BuildGP["构建手柄消息
按钮/轴值"] 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)