# 鼠标输入处理

<cite>
**本文档引用的文件**
- [inputremoting.js](file://client/src/inputremoting.js)
- [inputdevice.js](file://client/src/inputdevice.js)
- [mousebutton.js](file://client/src/mousebutton.js)
- [pointercorrect.js](file://client/src/pointercorrect.js)
- [sender.js](file://client/src/sender.js)
- [memoryhelper.js](file://client/src/memoryhelper.js)
- [register-events.js](file://client/public/videoplayer/js/register-events.js)
- [main.js（视频播放器）](file://client/public/videoplayer/js/main.js)
</cite>

## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)

## 简介
本文件聚焦于鼠标输入处理的技术文档，涵盖鼠标事件的捕获、转换与转发机制，包括鼠标移动、点击、滚轮等事件类型。文档详细说明鼠标坐标系统、按钮状态映射与事件数据结构，并给出时间戳同步、坐标变换与延迟补偿策略。同时提供具体代码路径示例，展示如何捕获鼠标事件、序列化为二进制消息并通过网络传输，在远端设备上进行重放。

## 项目结构
该仓库包含客户端与服务端两部分。与鼠标输入直接相关的核心代码位于 client/src 与 client/public/videoplayer/js 目录中：
- 输入设备建模与事件序列化：client/src/inputdevice.js
- 输入远程转发与消息封装：client/src/inputremoting.js
- 鼠标按钮常量定义：client/src/mousebutton.js
- 坐标矫正与视频适配：client/src/pointercorrect.js
- 本地事件监听与状态队列：client/src/sender.js
- 内存位写入工具：client/src/memoryhelper.js
- 前端事件注册与消息发送（另一套实现）：client/public/videoplayer/js/register-events.js
- 视频播放器入口与事件注册：client/public/videoplayer/js/main.js

```mermaid
graph TB
subgraph "客户端"
A["事件监听<br/>sender.js"]
B["输入设备模型<br/>inputdevice.js"]
C["远程转发与消息封装<br/>inputremoting.js"]
D["坐标矫正<br/>pointercorrect.js"]
E["内存位写入<br/>memoryhelper.js"]
F["鼠标按钮常量<br/>mousebutton.js"]
G["前端事件注册(替代实现)<br/>register-events.js"]
end
A --> B
A --> D
A --> C
B --> E
B --> F
G --> |"注册鼠标/滚轮/触摸事件"| A
```

图表来源
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
- [inputdevice.js:91-272](file://client/src/inputdevice.js#L91-L272)
- [inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124)
- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28)
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)
- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281)

章节来源
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
- [inputdevice.js:91-272](file://client/src/inputdevice.js#L91-L272)
- [inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124)
- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28)
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)
- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281)

## 核心组件
- LocalInputManager：本地输入管理基类，负责事件分发与时间基准。
- Sender：具体实现，注册鼠标/键盘/手柄/触摸事件，构建输入设备状态，派发 StateEvent/TextEvent。
- Mouse/MouseState：鼠标设备与状态，包含位置、相对位移、滚轮、按钮位图等字段。
- InputRemoting：将本地事件序列化为消息并转发给订阅者（如 RTC 数据通道）。
- Message/NewEventsMsg/NewDeviceMsg：消息封装与序列化。
- PointerCorrector：将页面坐标映射到视频画布坐标，处理留边距与缩放。
- MemoryHelper：按位写入布尔状态，用于按钮位图。
- MouseButton：按钮枚举。

章节来源
- [inputremoting.js:9-49](file://client/src/inputremoting.js#L9-L49)
- [inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [inputdevice.js:91-272](file://client/src/inputdevice.js#L91-L272)
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
- [pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124)
- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28)
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)

## 架构总览
鼠标事件从 DOM 事件开始，经由 Sender 注册监听，转换为 MouseState，再包装为 StateEvent，通过 InputRemoting 序列化为 Message 并发送至远端。

```mermaid
sequenceDiagram
participant U as "用户"
participant DOM as "DOM事件"
participant S as "Sender"
participant M as "Mouse/MouseState"
participant PC as "PointerCorrector"
participant IR as "InputRemoting"
participant MSG as "Message/NewEventsMsg"
participant NET as "网络/数据通道"
U->>DOM : "鼠标移动/点击/滚轮"
DOM->>S : "事件回调"
S->>M : "queueEvent(event)"
M-->>S : "currentState"
S->>PC : "map(position)"
PC-->>S : "矫正后坐标"
S->>IR : "派发 StateEvent"
IR->>MSG : "NewEventsMsg.create(stateEvent)"
MSG-->>IR : "Message"
IR->>NET : "发送 buffer"
```

图表来源
- [sender.js:121-129](file://client/src/sender.js#L121-L129)
- [inputdevice.js:91-98](file://client/src/inputdevice.js#L91-L98)
- [inputdevice.js:195-272](file://client/src/inputdevice.js#L195-L272)
- [pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)
- [inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141)
- [inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277)

## 详细组件分析

### 鼠标事件捕获与转换
- 事件监听：在视频元素上注册 click/mousedown/mouseup/mousemove/wheel 回调。
- 状态构建：将 MouseEvent/WheelEvent 转换为 MouseState，记录绝对坐标、相对位移、滚轮增量与按钮位图。
- 按钮映射：使用位图存储左右中及其他扩展按钮状态，通过 MemoryHelper.writeSingleBit 写入。
- 坐标矫正：使用 PointerCorrector 将页面坐标(clientX/Y)映射到视频画布坐标，考虑留边距与缩放。

```mermaid
flowchart TD
Start(["事件到达"]) --> Type{"事件类型"}
Type --> |mousemove| BuildMove["构建 MouseState<br/>position/delta/buttons"]
Type --> |click/mousedown/mouseup| BuildBtn["构建 MouseState<br/>position/buttons"]
Type --> |wheel| BuildWheel["构建 MouseState<br/>scroll"]
BuildMove --> BtnMap["按钮位图写入"]
BuildBtn --> BtnMap
BuildWheel --> BtnMap
BtnMap --> Correct["PointerCorrector.map(position)"]
Correct --> Queue["派发 StateEvent"]
Queue --> End(["完成"])
```

图表来源
- [sender.js:121-129](file://client/src/sender.js#L121-L129)
- [inputdevice.js:195-272](file://client/src/inputdevice.js#L195-L272)
- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20)
- [pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)

章节来源
- [sender.js:31-49](file://client/src/sender.js#L31-L49)
- [sender.js:121-129](file://client/src/sender.js#L121-L129)
- [inputdevice.js:91-98](file://client/src/inputdevice.js#L91-L98)
- [inputdevice.js:195-272](file://client/src/inputdevice.js#L195-L272)
- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20)
- [pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)

### 鼠标坐标系统与按钮状态
- 坐标系统
  - 绝对坐标：clientX/clientY 来源于 DOM 事件。
  - 相对位移：movementX/movementY 由浏览器提供；Y 方向取负以匹配常见坐标系。
  - 留边距与缩放：通过 PointerCorrector 的 contentRect 与 video 尺寸计算映射。
- 按钮状态
  - 使用 16 字节位图存储多按钮状态，左/右/中/前进/后退分别对应不同位。
  - 通过 MemoryHelper.writeSingleBit 写入位图。

章节来源
- [inputdevice.js:222-244](file://client/src/inputdevice.js#L222-L244)
- [inputdevice.js:249-264](file://client/src/inputdevice.js#L249-L264)
- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20)
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)

### 事件数据结构与时序
- InputEvent：统一事件头，包含类型、大小、设备 ID、时间戳。
- StateEvent：封装 IInputState，携带状态格式与状态数据。
- Message：封装 participant_id、type、length 与 data，作为网络传输单元。
- 时间戳：LocalInputManager 提供基于秒的时间基准，Sender 在派发时使用 timeSinceStartup。

```mermaid
classDiagram
class InputEvent {
+number type
+number sizeInBytes
+number deviceId
+number time
+buffer
}
class IInputState {
+buffer
+format
}
class MouseState {
+float32 position[2]
+float32 delta[2]
+float32 scroll[2]
+uint16 buttons
+uint16 displayIndex
+uint16 clickCount
+buffer
+format
}
class StateEvent {
+baseEvent : InputEvent
+stateFormat : number
+stateData : ArrayBuffer
+buffer
}
class Message {
+number participant_id
+number type
+number length
+ArrayBuffer data
+buffer
}
MouseState ..|> IInputState
StateEvent --> InputEvent : "组合"
Message --> StateEvent : "承载"
```

图表来源
- [inputdevice.js:129-178](file://client/src/inputdevice.js#L129-L178)
- [inputdevice.js:180-193](file://client/src/inputdevice.js#L180-L193)
- [inputdevice.js:195-272](file://client/src/inputdevice.js#L195-L272)
- [inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232)

章节来源
- [inputdevice.js:129-178](file://client/src/inputdevice.js#L129-L178)
- [inputdevice.js:180-193](file://client/src/inputdevice.js#L180-L193)
- [inputdevice.js:195-272](file://client/src/inputdevice.js#L195-L272)
- [inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232)

### 网络传输与远端重放
- 发送侧
  - InputRemoting 订阅 LocalInputManager 的事件，将 StateEvent 包装为 NewEventsMsg，再封装为 Message。
  - 通过订阅者（如 RTC 数据通道）发送二进制 buffer。
- 接收侧
  - 远端需解析 Message，识别事件类型与状态格式，重建 MouseState 并应用到目标设备或渲染层。
  - 由于未在仓库中发现接收与重放的具体实现，建议遵循以下原则：
    - 使用与发送端一致的格式标识与字段顺序。
    - 使用时间戳进行本地重放缓冲与插值（见“性能考量”）。

```mermaid
sequenceDiagram
participant L as "本地 Sender"
participant IR as "InputRemoting"
participant CH as "数据通道"
participant R as "远端接收器"
L->>IR : "StateEvent"
IR->>IR : "NewEventsMsg.create()"
IR->>CH : "Message.buffer"
CH-->>R : "二进制数据"
R->>R : "解析格式/重建状态"
R-->>R : "重放到目标设备"
```

图表来源
- [inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141)
- [inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277)
- [sender.js:170-182](file://client/src/sender.js#L170-L182)

章节来源
- [inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232)
- [sender.js:170-182](file://client/src/sender.js#L170-L182)

### 坐标变换与延迟补偿策略
- 坐标变换
  - 将 clientX/Y 减去元素边界矩形偏移，反转 Y 轴，减去留边距，再按内容矩形与视频尺寸比例映射。
  - 通过 PointerCorrector 的 contentRect 动态计算留边类型（水平/垂直）与尺寸。
- 延迟补偿
  - 使用 timeSinceStartup 作为事件时间戳，远端可据此进行缓冲与插值，减少抖动。
  - 若网络存在抖动，可在接收端维护一个环形缓冲队列，按时间戳排序后重放。

章节来源
- [pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)
- [pointercorrect.js:78-119](file://client/src/pointercorrect.js#L78-L119)
- [sender.js:39-41](file://client/src/sender.js#L39-L41)
- [sender.js:170-172](file://client/src/sender.js#L170-L172)

### 另一套前端事件注册实现（对比参考）
- register-events.js 提供了另一种前端事件注册方式，同样处理鼠标、滚轮与触摸事件，并将坐标与按钮状态打包为二进制消息发送。
- 该实现与 sender.js 的差异在于：
  - 事件监听对象不同（视频元素 vs 文档/窗口）。
  - 坐标映射逻辑略有差异（考虑 videoOriginX/Y 与 videoScale）。
  - 未使用 InputRemoting/Message 体系，而是直接构造 DataView 发送。

章节来源
- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281)
- [main.js（视频播放器）:132-142](file://client/public/videoplayer/js/main.js#L132-L142)

## 依赖关系分析
- Sender 依赖 Mouse/MouseState、PointerCorrector、LocalInputManager、StateEvent。
- MouseState 依赖 MemoryHelper 与 MouseButton。
- InputRemoting 依赖 Message/NewEventsMsg/RemoveDeviceMsg/ChangeUsageMsg 与订阅者。
- register-events.js 与 main.js 形成独立的事件注册与发送链路。

```mermaid
graph LR
Sender["sender.js"] --> Mouse["inputdevice.js: Mouse/MouseState"]
Sender --> PC["pointercorrect.js"]
Sender --> LIM["inputremoting.js: LocalInputManager"]
Sender --> SE["inputdevice.js: StateEvent"]
Mouse --> MH["memoryhelper.js"]
Mouse --> MB["mousebutton.js"]
IR["inputremoting.js"] --> MSG["inputremoting.js: Message/NewEventsMsg"]
RE["register-events.js"] --> |"事件注册/发送"| Sender
```

图表来源
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
- [inputdevice.js:91-272](file://client/src/inputdevice.js#L91-L272)
- [inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28)
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)
- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281)

章节来源
- [sender.js:14-188](file://client/src/sender.js#L14-L188)
- [inputdevice.js:91-272](file://client/src/inputdevice.js#L91-L272)
- [inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28)
- [mousebutton.js:1-7](file://client/src/mousebutton.js#L1-L7)
- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281)

## 性能考量
- 事件采样与节流
  - 对高频 mousemove 可采用节流/降采样策略，避免带宽与 CPU 压力。
- 时间戳与缓冲
  - 使用 timeSinceStartup 作为单调递增时间戳，远端按时间戳排序与重放。
  - 建议在接收端维护环形缓冲，按目标帧率进行插值。
- 坐标计算优化
  - 将 contentRect 缓存并在 resize 时更新，避免重复计算。
- 位图写入
  - 使用 MemoryHelper.writeSingleBit 批量写入按钮状态，减少内存拷贝。

## 故障排查指南
- 事件未触发
  - 检查事件监听是否绑定到正确的元素（视频元素）。
  - 确认 PointerCorrector 的 videoWidth/videoHeight 已正确初始化。
- 坐标错位
  - 核对 contentRect 与留边距计算，确认 videoScale 与 originX/Y 设置。
- 按钮状态异常
  - 检查按钮位图写入逻辑，确认 MouseButton 枚举与位索引一致。
- 网络发送失败
  - 确认订阅者 readyState 为 open，Message.buffer 构造无误。

章节来源
- [sender.js:114-120](file://client/src/sender.js#L114-L120)
- [pointercorrect.js:71-76](file://client/src/pointercorrect.js#L71-L76)
- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20)
- [inputremoting.js:202-208](file://client/src/inputremoting.js#L202-L208)

## 结论
本项目提供了完整的鼠标输入捕获、转换与转发链路：DOM 事件 → MouseState → StateEvent → Message → 网络传输。通过 PointerCorrector 实现跨设备坐标一致性，MemoryHelper 提供高效的按钮位图写入。若需在远端重放，应严格遵循格式标识与字段顺序，并结合时间戳进行缓冲与插值，以获得流畅体验。

## 附录
- 代码路径示例（不展示具体代码内容）
  - 鼠标事件监听与状态派发：[sender.js:121-129](file://client/src/sender.js#L121-L129)
  - MouseState 构建与位图写入：[inputdevice.js:222-244](file://client/src/inputdevice.js#L222-L244)
  - 坐标矫正：[pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)
  - 事件序列化为 Message：[inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277)
  - 前端事件注册（对比实现）：[register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281)