472 lines
18 KiB
Markdown
472 lines
18 KiB
Markdown
|
|
# 输入设备控制
|
|||
|
|
|
|||
|
|
<cite>
|
|||
|
|
**本文引用的文件**
|
|||
|
|
- [inputremoting.js](file://client/src/inputremoting.js)
|
|||
|
|
- [inputdevice.js](file://client/src/inputdevice.js)
|
|||
|
|
- [sender.js](file://client/src/sender.js)
|
|||
|
|
- [gamepadhandler.js](file://client/src/gamepadhandler.js)
|
|||
|
|
- [pointercorrect.js](file://client/src/pointercorrect.js)
|
|||
|
|
- [memoryhelper.js](file://client/src/memoryhelper.js)
|
|||
|
|
- [keymap.js](file://client/src/keymap.js)
|
|||
|
|
- [mousebutton.js](file://client/src/mousebutton.js)
|
|||
|
|
- [touchflags.js](file://client/src/touchflags.js)
|
|||
|
|
- [touchphase.js](file://client/src/touchphase.js)
|
|||
|
|
- [charnumber.js](file://client/src/charnumber.js)
|
|||
|
|
- [gamepadbutton.js](file://client/src/gamepadbutton.js)
|
|||
|
|
- [renderstreaming.js](file://client/src/renderstreaming.js)
|
|||
|
|
- [peer.js](file://client/src/peer.js)
|
|||
|
|
- [signaling.js](file://client/src/signaling.js)
|
|||
|
|
- [logger.js](file://client/src/logger.js)
|
|||
|
|
</cite>
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
1. [简介](#简介)
|
|||
|
|
2. [项目结构](#项目结构)
|
|||
|
|
3. [核心组件](#核心组件)
|
|||
|
|
4. [架构总览](#架构总览)
|
|||
|
|
5. [详细组件分析](#详细组件分析)
|
|||
|
|
6. [依赖关系分析](#依赖关系分析)
|
|||
|
|
7. [性能考量](#性能考量)
|
|||
|
|
8. [故障排查指南](#故障排查指南)
|
|||
|
|
9. [结论](#结论)
|
|||
|
|
10. [附录](#附录)
|
|||
|
|
|
|||
|
|
## 简介
|
|||
|
|
本技术文档围绕“输入设备远程控制”能力,系统性阐述客户端侧输入设备抽象层与事件转发机制。文档覆盖鼠标、键盘、触摸屏、游戏手柄四类输入的事件捕获、状态转换与序列化打包;解释统一的输入事件与状态事件格式、时间戳与坐标系映射策略;并给出跨设备输入同步的关键点(时间戳、事件队列与延迟补偿思路)。文中所有技术细节均基于仓库现有源码进行归纳总结。
|
|||
|
|
|
|||
|
|
## 项目结构
|
|||
|
|
客户端输入子系统位于 client/src 目录,主要由以下模块组成:
|
|||
|
|
- 输入设备抽象与事件模型:inputdevice.js
|
|||
|
|
- 输入采集与事件分发:sender.js
|
|||
|
|
- 远程转发与消息封装:inputremoting.js
|
|||
|
|
- 游戏手柄轮询与事件:gamepadhandler.js
|
|||
|
|
- 坐标映射与视频适配:pointercorrect.js
|
|||
|
|
- 内存位操作工具:memoryhelper.js
|
|||
|
|
- 键盘键位映射与字符编码:keymap.js、charnumber.js
|
|||
|
|
- 按钮位定义:mousebutton.js、gamepadbutton.js
|
|||
|
|
- 触摸状态与阶段:touchphase.js、touchflags.js
|
|||
|
|
- WebRTC 信令与数据通道:renderstreaming.js、peer.js、signaling.js
|
|||
|
|
- 日志工具:logger.js
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
subgraph "输入采集层"
|
|||
|
|
S["Sender<br/>事件采集与分发"]
|
|||
|
|
GP["GamepadHandler<br/>手柄轮询"]
|
|||
|
|
PC["PointerCorrector<br/>坐标映射"]
|
|||
|
|
end
|
|||
|
|
subgraph "输入抽象层"
|
|||
|
|
ID["InputDevice/IInputState<br/>设备与状态模型"]
|
|||
|
|
IE["InputEvent/StateEvent/TextEvent<br/>事件与状态封装"]
|
|||
|
|
end
|
|||
|
|
subgraph "远程转发层"
|
|||
|
|
IR["InputRemoting<br/>事件订阅与发送"]
|
|||
|
|
MSG["Message/NewEventsMsg/NewDeviceMsg<br/>消息封装"]
|
|||
|
|
end
|
|||
|
|
subgraph "传输层"
|
|||
|
|
RS["RenderStreaming<br/>信令与Peer管理"]
|
|||
|
|
PEER["Peer<br/>RTCPeerConnection封装"]
|
|||
|
|
SIG["Signaling/WebSocketSignaling<br/>信令通道"]
|
|||
|
|
end
|
|||
|
|
S --> ID
|
|||
|
|
S --> PC
|
|||
|
|
S --> GP
|
|||
|
|
ID --> IE
|
|||
|
|
IR --> MSG
|
|||
|
|
MSG --> RS
|
|||
|
|
RS --> PEER
|
|||
|
|
RS --> SIG
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
|
|||
|
|
- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719)
|
|||
|
|
- [inputremoting.js:63-300](file://client/src/inputremoting.js#L63-L300)
|
|||
|
|
- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
|
|||
|
|
- [peer.js:3-188](file://client/src/peer.js#L3-L188)
|
|||
|
|
- [signaling.js:3-292](file://client/src/signaling.js#L3-L292)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
|
|||
|
|
- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719)
|
|||
|
|
- [inputremoting.js:63-300](file://client/src/inputremoting.js#L63-L300)
|
|||
|
|
- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
|
|||
|
|
- [peer.js:3-188](file://client/src/peer.js#L3-L188)
|
|||
|
|
- [signaling.js:3-292](file://client/src/signaling.js#L3-L292)
|
|||
|
|
|
|||
|
|
## 核心组件
|
|||
|
|
- 输入设备抽象层
|
|||
|
|
- 设备基类与具体设备:InputDevice、Mouse、Keyboard、Touchscreen、Gamepad
|
|||
|
|
- 状态抽象与序列化:IInputState 及其派生 MouseState、KeyboardState、TouchscreenState、GamepadState
|
|||
|
|
- 事件模型:InputEvent、StateEvent、TextEvent
|
|||
|
|
- 输入采集与分发
|
|||
|
|
- Sender:注册各类 DOM 事件,调用设备 queueEvent,生成 StateEvent/TextEvent 并通过自定义事件广播
|
|||
|
|
- PointerCorrector:将页面坐标映射到视频区域坐标,适配黑边(Letterbox)
|
|||
|
|
- 远程转发与消息封装
|
|||
|
|
- InputRemoting:订阅本地事件,打包为 Message,通过观察者(如 RTC 数据通道)发送
|
|||
|
|
- Message/NewEventsMsg/NewDeviceMsg:二进制消息结构与序列化
|
|||
|
|
- 手柄支持
|
|||
|
|
- GamepadHandler:周期扫描并派发 gamepadupdated 事件
|
|||
|
|
- 工具与常量
|
|||
|
|
- MemoryHelper:按位写入布尔状态
|
|||
|
|
- Keymap/CharNumber:键盘键位与字符编码映射
|
|||
|
|
- MouseButton/GamepadButton:按钮位定义
|
|||
|
|
- TouchPhase/TouchFlags:触摸阶段与标志位
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719)
|
|||
|
|
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
|
|||
|
|
- [inputremoting.js:63-300](file://client/src/inputremoting.js#L63-L300)
|
|||
|
|
- [gamepadhandler.js:1-45](file://client/src/gamepadhandler.js#L1-L45)
|
|||
|
|
- [pointercorrect.js:6-125](file://client/src/pointercorrect.js#L6-L125)
|
|||
|
|
- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28)
|
|||
|
|
- [keymap.js:1-120](file://client/src/keymap.js#L1-L120)
|
|||
|
|
- [charnumber.js:1-110](file://client/src/charnumber.js#L1-L110)
|
|||
|
|
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)
|
|||
|
|
- [gamepadbutton.js:1-26](file://client/src/gamepadbutton.js#L1-L26)
|
|||
|
|
- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9)
|
|||
|
|
- [touchflags.js:1-8](file://client/src/touchflags.js#L1-L8)
|
|||
|
|
|
|||
|
|
## 架构总览
|
|||
|
|
下图展示了从用户输入到远端重放的完整链路:DOM 事件经 Sender 转换为设备状态,再封装为 StateEvent/TextEvent,通过 InputRemoting 序列化为 Message,经 WebRTC 数据通道发送至对端。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant U as "用户"
|
|||
|
|
participant DOM as "DOM事件"
|
|||
|
|
participant S as "Sender"
|
|||
|
|
participant DEV as "设备/状态"
|
|||
|
|
participant IR as "InputRemoting"
|
|||
|
|
participant MSG as "Message/NewEventsMsg"
|
|||
|
|
participant RS as "RenderStreaming"
|
|||
|
|
participant PEER as "Peer"
|
|||
|
|
participant DC as "RTC数据通道"
|
|||
|
|
U->>DOM : 鼠标/键盘/触摸/手柄事件
|
|||
|
|
DOM->>S : 事件回调
|
|||
|
|
S->>DEV : queueEvent(...) / 更新currentState
|
|||
|
|
S->>IR : 触发自定义事件(event)
|
|||
|
|
IR->>MSG : 创建NewEventsMsg/TextEvent
|
|||
|
|
MSG->>RS : 发送消息
|
|||
|
|
RS->>PEER : 选择Peer/数据通道
|
|||
|
|
PEER->>DC : send(Message.buffer)
|
|||
|
|
DC-->>PEER : 传输完成
|
|||
|
|
PEER-->>RS : 对端接收
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [sender.js:121-182](file://client/src/sender.js#L121-L182)
|
|||
|
|
- [inputremoting.js:73-168](file://client/src/inputremoting.js#L73-L168)
|
|||
|
|
- [renderstreaming.js:260-266](file://client/src/renderstreaming.js#L260-L266)
|
|||
|
|
- [peer.js:116-122](file://client/src/peer.js#L116-L122)
|
|||
|
|
|
|||
|
|
## 详细组件分析
|
|||
|
|
|
|||
|
|
### 输入设备抽象层与事件模型
|
|||
|
|
- 设备与状态
|
|||
|
|
- InputDevice:持有设备元信息与当前状态,提供 queueEvent 接口
|
|||
|
|
- IInputState:定义 buffer/format 抽象,各设备状态实现序列化
|
|||
|
|
- 具体设备:Mouse、Keyboard、Touchscreen、Gamepad 分别在 queueEvent 中更新 currentState
|
|||
|
|
- 事件模型
|
|||
|
|
- InputEvent:统一头部字段(type、size、deviceId、time),作为所有事件的基类
|
|||
|
|
- StateEvent:在 InputEvent 基础上携带 stateFormat 与 stateData,承载完整输入状态
|
|||
|
|
- TextEvent:在 InputEvent 基础上携带字符编码,用于文本输入场景
|
|||
|
|
- 数据结构与格式
|
|||
|
|
- 四字节标识(FourCC)用于区分不同状态格式(如 MOUSE、KEYS、TOUC、TSRC、GPAD、TEXT、STAT)
|
|||
|
|
- 各状态结构严格定义字段偏移与大小,确保跨端一致解析
|
|||
|
|
|
|||
|
|
```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 InputEvent {
|
|||
|
|
+type
|
|||
|
|
+sizeInBytes
|
|||
|
|
+deviceId
|
|||
|
|
+time
|
|||
|
|
+eventId
|
|||
|
|
+buffer
|
|||
|
|
}
|
|||
|
|
class StateEvent {
|
|||
|
|
+baseEvent : InputEvent
|
|||
|
|
+stateFormat
|
|||
|
|
+stateData
|
|||
|
|
+buffer
|
|||
|
|
}
|
|||
|
|
class TextEvent {
|
|||
|
|
+baseEvent : InputEvent
|
|||
|
|
+character
|
|||
|
|
+buffer
|
|||
|
|
}
|
|||
|
|
InputDevice --> IInputState : "持有currentState"
|
|||
|
|
IInputState <|-- MouseState
|
|||
|
|
IInputState <|-- KeyboardState
|
|||
|
|
IInputState <|-- TouchscreenState
|
|||
|
|
IInputState <|-- GamepadState
|
|||
|
|
InputEvent <|-- StateEvent
|
|||
|
|
InputEvent <|-- TextEvent
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719)
|
|||
|
|
|
|||
|
|
### 输入采集与坐标映射(Sender 与 PointerCorrector)
|
|||
|
|
- 事件采集
|
|||
|
|
- Sender 注册鼠标、键盘、触摸、手柄事件监听,调用对应设备的 queueEvent
|
|||
|
|
- 鼠标移动位置经 PointerCorrector 映射到视频区域坐标后,再生成 StateEvent
|
|||
|
|
- 触摸事件逐个触点复制并映射坐标后分别生成 StateEvent
|
|||
|
|
- 坐标映射
|
|||
|
|
- PointerCorrector 计算 letterbox 类型与尺寸,将页面坐标转换为视频画布坐标
|
|||
|
|
- 支持动态宽高变化与元素尺寸观测
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
Start(["事件到达"]) --> Type{"事件类型?"}
|
|||
|
|
Type --> |鼠标| Map["PointerCorrector.map(pos)"]
|
|||
|
|
Type --> |触摸| Copy["遍历触点并复制状态"]
|
|||
|
|
Map --> Gen["生成StateEvent并广播"]
|
|||
|
|
Copy --> Map2["逐点映射坐标"]
|
|||
|
|
Map2 --> Gen
|
|||
|
|
Type --> |键盘| KGen["生成StateEvent/TextEvent并广播"]
|
|||
|
|
Type --> |手柄| GGen["生成StateEvent并广播"]
|
|||
|
|
Gen --> End(["完成"])
|
|||
|
|
KGen --> End
|
|||
|
|
GGen --> End
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [sender.js:121-182](file://client/src/sender.js#L121-L182)
|
|||
|
|
- [pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [sender.js:121-182](file://client/src/sender.js#L121-L182)
|
|||
|
|
- [pointercorrect.js:6-125](file://client/src/pointercorrect.js#L6-L125)
|
|||
|
|
|
|||
|
|
### 远程转发与消息封装(InputRemoting 与 Message)
|
|||
|
|
- 事件订阅与初始消息
|
|||
|
|
- InputRemoting 订阅 LocalInputManager 的事件与设备变更事件
|
|||
|
|
- 启动时发送设备列表与布局初始消息
|
|||
|
|
- 消息类型与封装
|
|||
|
|
- Message 统一头部:participant_id、type、length、data
|
|||
|
|
- NewEventsMsg/TextEvent/StateEvent 将事件与状态序列化为二进制
|
|||
|
|
- NewDeviceMsg/RemoveDeviceMsg/ChangeUsageMsg 提供设备生命周期消息
|
|||
|
|
- 发送路径
|
|||
|
|
- InputRemoting 将消息分发给观察者(如 RTC 数据通道)
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant L as "LocalInputManager"
|
|||
|
|
participant IR as "InputRemoting"
|
|||
|
|
participant OBS as "观察者(如RTC数据通道)"
|
|||
|
|
participant M as "Message/NewEventsMsg"
|
|||
|
|
L-->>IR : 触发'event'事件
|
|||
|
|
IR->>M : 创建NewEventsMsg/TextEvent
|
|||
|
|
IR->>OBS : onNext(Message)
|
|||
|
|
OBS-->>IR : 发送完成
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [inputremoting.js:73-168](file://client/src/inputremoting.js#L73-L168)
|
|||
|
|
- [inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232)
|
|||
|
|
- [inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [inputremoting.js:63-300](file://client/src/inputremoting.js#L63-L300)
|
|||
|
|
|
|||
|
|
### 游戏手柄事件处理(GamepadHandler)
|
|||
|
|
- 轮询与事件
|
|||
|
|
- GamepadHandler 使用 requestAnimationFrame 周期扫描已连接手柄
|
|||
|
|
- 对每个控制器派发自定义 gamepadupdated 事件,Sender 作为监听器接收并转发
|
|||
|
|
- 按钮与摇杆
|
|||
|
|
- GamepadState 将按钮 pressed/value 与摇杆 axes 转换为位掩码与浮点值
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant GH as "GamepadHandler"
|
|||
|
|
participant W as "window"
|
|||
|
|
participant S as "Sender"
|
|||
|
|
participant DEV as "Gamepad设备"
|
|||
|
|
GH->>GH : _scanGamepad()
|
|||
|
|
GH->>W : requestAnimationFrame(_updateStatus)
|
|||
|
|
GH->>S : dispatchEvent('gamepadupdated')
|
|||
|
|
S->>DEV : queueEvent(event)
|
|||
|
|
S->>IR : 生成StateEvent并发送
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [gamepadhandler.js:22-42](file://client/src/gamepadhandler.js#L22-L42)
|
|||
|
|
- [sender.js:152-168](file://client/src/sender.js#L152-L168)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [gamepadhandler.js:1-45](file://client/src/gamepadhandler.js#L1-L45)
|
|||
|
|
- [sender.js:68-85](file://client/src/sender.js#L68-L85)
|
|||
|
|
|
|||
|
|
### 键盘与字符事件映射
|
|||
|
|
- 键位映射
|
|||
|
|
- Keymap 将 KeyboardEvent.code 映射到内部键索引
|
|||
|
|
- MemoryHelper.writeSingleBit 将按键状态写入位数组
|
|||
|
|
- 文本事件
|
|||
|
|
- CharNumber 将 KeyboardEvent.key 映射为字符编码
|
|||
|
|
- TextEvent 在 InputEvent 基础上附加字符编码
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [keymap.js:1-120](file://client/src/keymap.js#L1-L120)
|
|||
|
|
- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20)
|
|||
|
|
- [charnumber.js:1-110](file://client/src/charnumber.js#L1-L110)
|
|||
|
|
- [inputdevice.js:620-660](file://client/src/inputdevice.js#L620-L660)
|
|||
|
|
|
|||
|
|
### 触摸事件与多指支持
|
|||
|
|
- 触摸阶段与标志
|
|||
|
|
- TouchPhase 定义Began/Moved/Ended/Canceled/Stationary
|
|||
|
|
- TouchFlags 定义 IndirectTouch、PrimaryTouch、Tap 等标志
|
|||
|
|
- 多触点状态
|
|||
|
|
- TouchscreenState 将 TouchEvent.changedTouches 转换为 TouchState 列表
|
|||
|
|
- 为每个触点分配唯一 touchId,缓存前一帧状态以计算 delta
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9)
|
|||
|
|
- [touchflags.js:1-8](file://client/src/touchflags.js#L1-L8)
|
|||
|
|
- [inputdevice.js:324-538](file://client/src/inputdevice.js#L324-L538)
|
|||
|
|
|
|||
|
|
### WebRTC 信令与数据通道(传输层)
|
|||
|
|
- 信令
|
|||
|
|
- Signaling/WebSocketSignaling 负责 offer/answer/candidate/on-message 传递
|
|||
|
|
- RenderStreaming 管理多参与方连接与 Peer 生命周期
|
|||
|
|
- 数据通道
|
|||
|
|
- Peer 封装 RTCPeerConnection,暴露 addDataChannel/createDataChannel
|
|||
|
|
- Sender 通过 RenderStreaming.createDataChannel 获取数据通道,发送 Message.buffer
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant RS as "RenderStreaming"
|
|||
|
|
participant PEER as "Peer"
|
|||
|
|
participant DC as "RTC数据通道"
|
|||
|
|
participant IR as "InputRemoting"
|
|||
|
|
participant MSG as "Message"
|
|||
|
|
RS->>PEER : createDataChannel(label)
|
|||
|
|
PEER-->>RS : 返回数据通道
|
|||
|
|
IR->>MSG : 序列化消息
|
|||
|
|
IR->>DC : send(Message.buffer)
|
|||
|
|
DC-->>IR : 发送成功
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [renderstreaming.js:260-266](file://client/src/renderstreaming.js#L260-L266)
|
|||
|
|
- [peer.js:116-122](file://client/src/peer.js#L116-L122)
|
|||
|
|
- [inputremoting.js:164-168](file://client/src/inputremoting.js#L164-L168)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
|
|||
|
|
- [peer.js:3-188](file://client/src/peer.js#L3-L188)
|
|||
|
|
- [signaling.js:3-292](file://client/src/signaling.js#L3-L292)
|
|||
|
|
|
|||
|
|
## 依赖关系分析
|
|||
|
|
- 组件耦合
|
|||
|
|
- Sender 依赖 InputDevice/IInputState、PointerCorrector、GamepadHandler
|
|||
|
|
- InputRemoting 依赖 Message/NewEventsMsg/NewDeviceMsg 以及观察者接口
|
|||
|
|
- RenderStreaming/Peer/Signaling 提供传输基础设施
|
|||
|
|
- 关键依赖链
|
|||
|
|
- DOM 事件 → Sender → 设备状态 → InputRemoting → Message → 数据通道 → 对端
|
|||
|
|
- 潜在循环依赖
|
|||
|
|
- 当前模块间为单向依赖,未见循环导入
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
DOM["DOM事件"] --> S["Sender"]
|
|||
|
|
S --> ID["InputDevice/IInputState"]
|
|||
|
|
S --> PC["PointerCorrector"]
|
|||
|
|
S --> GH["GamepadHandler"]
|
|||
|
|
ID --> IE["InputEvent/StateEvent/TextEvent"]
|
|||
|
|
IE --> IR["InputRemoting"]
|
|||
|
|
IR --> MSG["Message/NewEventsMsg"]
|
|||
|
|
MSG --> RS["RenderStreaming"]
|
|||
|
|
RS --> PEER["Peer"]
|
|||
|
|
RS --> SIG["Signaling"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
|
|||
|
|
- [inputremoting.js:63-300](file://client/src/inputremoting.js#L63-L300)
|
|||
|
|
- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
|
|||
|
|
- [inputremoting.js:63-300](file://client/src/inputremoting.js#L63-L300)
|
|||
|
|
- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317)
|
|||
|
|
|
|||
|
|
## 性能考量
|
|||
|
|
- 事件采样与批处理
|
|||
|
|
- 建议在 Sender 层对高频事件(如 mousemove、touchmove)进行节流/去抖,降低序列化与网络负载
|
|||
|
|
- 序列化开销
|
|||
|
|
- 使用 DataView/ArrayBuffer 直接写入,避免 JSON 序列化带来的额外开销
|
|||
|
|
- 状态压缩
|
|||
|
|
- 键盘状态采用位数组存储,减少带宽占用
|
|||
|
|
- 坐标映射
|
|||
|
|
- PointerCorrector 在每次事件时执行,建议缓存元素尺寸并在 resize 时更新
|
|||
|
|
- 传输可靠性
|
|||
|
|
- 数据通道为有序可靠传输,适合输入事件;若需低延迟可考虑自定义协议或 UDP 通道(需服务端配合)
|
|||
|
|
|
|||
|
|
## 故障排查指南
|
|||
|
|
- 事件未发送
|
|||
|
|
- 检查 InputRemoting 是否已 startSending,是否正确订阅 LocalInputManager 的事件
|
|||
|
|
- 确认观察者(数据通道)处于 open 状态
|
|||
|
|
- 坐标异常
|
|||
|
|
- 确认 PointerCorrector 的 videoWidth/videoHeight 与 HTMLVideoElement 的实际尺寸一致
|
|||
|
|
- 检查 letterbox 计算逻辑与 contentRect 更新
|
|||
|
|
- 键盘状态不同步
|
|||
|
|
- 确保 Keymap 中包含对应 KeyboardEvent.code
|
|||
|
|
- 检查 MemoryHelper.writeSingleBit 的位偏移与状态合并逻辑
|
|||
|
|
- 触摸多指错乱
|
|||
|
|
- 确认 TouchState.incrementTouchId 与 prevTouches 缓存逻辑
|
|||
|
|
- 检查 TouchPhase 转换与 tapCount 标记
|
|||
|
|
- 手柄无响应
|
|||
|
|
- 确认 GamepadHandler 正在派发 gamepadupdated 事件
|
|||
|
|
- 检查 GamepadState 按钮位与摇杆轴值映射
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [inputremoting.js:73-98](file://client/src/inputremoting.js#L73-L98)
|
|||
|
|
- [sender.js:114-182](file://client/src/sender.js#L114-L182)
|
|||
|
|
- [pointercorrect.js:71-124](file://client/src/pointercorrect.js#L71-L124)
|
|||
|
|
- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20)
|
|||
|
|
- [keymap.js:1-120](file://client/src/keymap.js#L1-L120)
|
|||
|
|
- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9)
|
|||
|
|
- [touchflags.js:1-8](file://client/src/touchflags.js#L1-L8)
|
|||
|
|
- [gamepadhandler.js:22-42](file://client/src/gamepadhandler.js#L22-L42)
|
|||
|
|
- [inputdevice.js:540-618](file://client/src/inputdevice.js#L540-L618)
|
|||
|
|
|
|||
|
|
## 结论
|
|||
|
|
本系统通过清晰的输入抽象层与严格的二进制事件格式,实现了鼠标、键盘、触摸与手柄的统一采集、转换与远程转发。Sender 负责事件采集与坐标映射,InputRemoting 负责消息封装与分发,RenderStreaming/Peer/Signaling 提供可靠的传输通道。通过时间戳与位掩码等设计,系统具备良好的跨设备同步基础。后续可在事件节流、状态压缩与延迟补偿方面进一步优化,以满足更高实时性需求。
|
|||
|
|
|
|||
|
|
## 附录
|
|||
|
|
- 时间戳与事件队列
|
|||
|
|
- Sender 使用 timeSinceStartup 作为事件时间戳,保证相对时间一致性
|
|||
|
|
- 建议对端按接收时间与发送时间差进行延迟补偿
|
|||
|
|
- 设备生命周期
|
|||
|
|
- InputRemoting 支持设备新增/移除/使用变更消息,可用于对端设备同步
|
|||
|
|
- 按键与按钮位定义
|
|||
|
|
- MouseButton/GamepadButton 提供统一位索引,便于跨平台一致映射
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [sender.js:39-41](file://client/src/sender.js#L39-L41)
|
|||
|
|
- [inputremoting.js:108-136](file://client/src/inputremoting.js#L108-L136)
|
|||
|
|
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)
|
|||
|
|
- [gamepadbutton.js:1-26](file://client/src/gamepadbutton.js#L1-L26)
|