# 输入事件同步

<cite>
**本文引用的文件**
- [src/index.ts](file://src/index.ts)
- [src/websocket.ts](file://src/websocket.ts)
- [client/src/inputremoting.js](file://client/src/inputremoting.js)
- [client/src/sender.js](file://client/src/sender.js)
- [client/src/inputdevice.js](file://client/src/inputdevice.js)
- [client/src/memoryhelper.js](file://client/src/memoryhelper.js)
- [client/src/pointercorrect.js](file://client/src/pointercorrect.js)
- [client/src/signaling.js](file://client/src/signaling.js)
- [client/src/peer.js](file://client/src/peer.js)
- [client/test/inputremoting.test.js](file://client/test/inputremoting.test.js)
- [client/test/inputdevice.test.js](file://client/test/inputdevice.test.js)
</cite>

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

## 引言
本技术文档聚焦于输入事件同步机制，围绕以下目标展开：时间同步、延迟补偿、状态一致性保证；事件队列管理、时间戳处理与网络传输优化；缓冲策略、丢包处理与重排序；以及在多设备间实现高精度时间同步与输入一致性。文档基于仓库中的客户端输入遥测与信令通道实现，结合服务端 WebSocket 信令处理，系统性阐述从事件采集、编码、传输到渲染的一体化流程。

## 项目结构
该项目采用前后端分离的结构：
- 客户端包含输入设备抽象、事件打包、信令交互与 WebRTC 数据通道发送等模块；
- 服务端提供基于 WebSocket 的信令通道，负责连接管理与消息转发。

```mermaid
graph TB
subgraph "客户端"
A["输入设备抽象<br/>inputdevice.js"]
B["本地输入管理器<br/>inputremoting.js"]
C["发送器与事件队列<br/>sender.js"]
D["指针坐标矫正<br/>pointercorrect.js"]
E["信令(HTTP/WebSocket)<br/>signaling.js"]
F["WebRTC Peer 连接<br/>peer.js"]
end
subgraph "服务端"
G["WebSocket 信令服务<br/>websocket.ts"]
end
A --> B
B --> C
C --> F
C --> E
F --> G
```

图表来源
- [client/src/inputdevice.js:40-89](file://client/src/inputdevice.js#L40-L89)
- [client/src/inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [client/src/sender.js:14-188](file://client/src/sender.js#L14-L188)
- [client/src/pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124)
- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292)
- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188)
- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118)

章节来源
- [src/index.ts:13-109](file://src/index.ts#L13-L109)
- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118)

## 核心组件
- 输入设备与状态模型：鼠标、键盘、触摸屏、手柄的状态与事件定义，统一以二进制帧格式封装，便于跨网络传输与解码。
- 本地输入管理器：负责事件采集、时间戳注入、初始布局与设备信息广播、订阅者分发。
- 发送器：将 DOM 事件转换为输入状态，生成状态事件与文本事件，并通过观察者模式推送至数据通道或信令通道。
- 指针矫正器：根据视频显示比例与留边（letterbox）计算屏幕坐标到视频坐标的映射，确保跨设备一致的输入位置。
- 信令层：支持 HTTP 轮询与 WebSocket 两种模式，用于连接建立、SDP 交换与自定义消息广播。
- WebRTC Peer：封装 ICE、Offer/Answer、数据通道创建与状态机，保障媒体与控制面协同。

章节来源
- [client/src/inputdevice.js:129-178](file://client/src/inputdevice.js#L129-L178)
- [client/src/inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [client/src/sender.js:14-188](file://client/src/sender.js#L14-L188)
- [client/src/pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124)
- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292)
- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188)

## 架构总览
下图展示了从用户输入到远端渲染的关键路径：DOM 事件经由发送器转换为状态事件，通过 RTC 数据通道或信令通道发送，服务端 WebSocket 信令进行路由，最终到达对端渲染端。

```mermaid
sequenceDiagram
participant U as "用户"
participant DOM as "DOM 事件"
participant S as "Sender"
participant IR as "InputRemoting"
participant DC as "RTC 数据通道"
participant WS as "WebSocket 信令"
participant P as "Peer(对端)"
participant R as "渲染端"
U->>DOM : 触发输入事件
DOM->>S : 鼠标/键盘/触摸/手柄事件
S->>S : 生成状态事件与文本事件
S->>IR : 分发事件
IR->>DC : 通过观察者发送二进制消息
IR->>WS : 通过信令通道发送自定义消息
WS-->>P : 转发消息
P->>P : 解析并应用状态
P->>R : 渲染一致状态
```

图表来源
- [client/src/sender.js:170-182](file://client/src/sender.js#L170-L182)
- [client/src/inputremoting.js:138-168](file://client/src/inputremoting.js#L138-L168)
- [client/src/signaling.js:140-146](file://client/src/signaling.js#L140-L146)
- [src/websocket.ts:44-114](file://src/websocket.ts#L44-L114)

## 详细组件分析

### 时间同步与时间戳处理
- 启动时间注入：本地输入管理器在开始发送时记录启动时间，后续所有事件携带相对时间戳，确保跨设备时间基准一致。
- 事件时间戳：输入事件与状态事件均包含时间字段，用于对端按时间顺序重建状态。
- 坐标映射与时间耦合：指针矫正器在事件生成时使用当前相对时间，避免因时间漂移导致的错位。

```mermaid
flowchart TD
Start(["开始发送"]) --> SetStart["记录启动时间"]
SetStart --> OnEvent["捕获输入事件"]
OnEvent --> InjectTime["注入相对时间戳"]
InjectTime --> BuildState["构建状态事件/文本事件"]
BuildState --> Send["发送至观察者/信令通道"]
Send --> End(["完成一次事件发送"])
```

图表来源
- [client/src/inputremoting.js:73-91](file://client/src/inputremoting.js#L73-L91)
- [client/src/sender.js:170-182](file://client/src/sender.js#L170-L182)
- [client/src/pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)

章节来源
- [client/src/inputremoting.js:32-48](file://client/src/inputremoting.js#L32-L48)
- [client/src/sender.js:170-182](file://client/src/sender.js#L170-L182)
- [client/src/inputdevice.js:149-163](file://client/src/inputdevice.js#L149-L163)

### 事件队列管理与去抖动
- 事件聚合：触摸事件在一次 TouchEvent 中可能包含多个 changedTouches，发送器逐个拆分并生成对应状态事件，保证每个触点独立同步。
- 文本事件与按键事件分离：按键事件区分 keydown/keyup 与重复按下（repeat），仅在非重复场景生成状态事件，避免冗余。
- 去抖动策略：通过事件类型判断与状态缓存（如键盘按键位图、触摸起始时间与起点），减少无效更新。

```mermaid
flowchart TD
TStart(["TouchEvent 到达"]) --> Split["拆分 changedTouches"]
Split --> ForEach["遍历每个触点"]
ForEach --> MapPos["指针矫正器映射坐标"]
MapPos --> BuildTouchState["构建 TouchState"]
BuildTouchState --> Emit["生成状态事件并派发"]
Emit --> TEnd(["结束"])
```

图表来源
- [client/src/sender.js:144-151](file://client/src/sender.js#L144-L151)
- [client/src/inputdevice.js:478-517](file://client/src/inputdevice.js#L478-L517)
- [client/src/pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40)

章节来源
- [client/src/sender.js:130-143](file://client/src/sender.js#L130-L143)
- [client/src/inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538)

### 状态一致性保证机制
- 设备描述与布局广播：首次连接时发送设备列表与布局信息，确保对端具备相同的输入模型。
- 设备变更通知：新增/移除/使用变化等设备状态变更通过专用消息通知，对端据此调整输入映射。
- 状态事件结构：状态事件包含基础输入事件头、状态格式与状态数据，对端按格式解析并应用，保证状态一致性。

```mermaid
classDiagram
class InputEvent {
+number type
+number sizeInBytes
+number deviceId
+number time
+number eventId
+buffer()
}
class StateEvent {
+InputEvent baseEvent
+number stateFormat
+ArrayBuffer stateData
+buffer()
}
class IInputState {
+buffer()
+format
}
class MouseState
class KeyboardState
class TouchscreenState
class GamepadState
StateEvent --> InputEvent : "组合"
IInputState <|-- MouseState
IInputState <|-- KeyboardState
IInputState <|-- TouchscreenState
IInputState <|-- GamepadState
```

图表来源
- [client/src/inputdevice.js:129-178](file://client/src/inputdevice.js#L129-L178)
- [client/src/inputdevice.js:662-718](file://client/src/inputdevice.js#L662-L718)
- [client/src/inputdevice.js:180-193](file://client/src/inputdevice.js#L180-L193)

章节来源
- [client/src/inputremoting.js:108-136](file://client/src/inputremoting.js#L108-L136)
- [client/src/inputremoting.js:143-162](file://client/src/inputremoting.js#L143-L162)
- [client/src/inputdevice.js:662-718](file://client/src/inputdevice.js#L662-L718)

### 网络传输优化与消息封装
- 自定义消息头：消息包含参与者 ID、类型与长度，数据部分为二进制帧，紧凑且跨语言友好。
- 二进制帧格式：输入事件与状态事件均以 DataView/TypedArray 写入，保证小端序与定长字段，降低解析开销。
- 信令通道：支持 HTTP 轮询与 WebSocket，前者适合受限环境，后者提供低延迟与广播能力。

```mermaid
classDiagram
class Message {
+number participant_id
+number type
+number length
+ArrayBuffer data
+buffer()
}
class NewDeviceMsg {
+create(device)
}
class NewEventsMsg {
+create(event)
+createStateEvent(device)
}
class RemoveDeviceMsg {
+create(device)
}
Message <.. NewDeviceMsg
Message <.. NewEventsMsg
Message <.. RemoveDeviceMsg
```

图表来源
- [client/src/inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232)
- [client/src/inputremoting.js:234-256](file://client/src/inputremoting.js#L234-L256)
- [client/src/inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277)
- [client/src/inputremoting.js:279-291](file://client/src/inputremoting.js#L279-L291)

章节来源
- [client/src/inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232)
- [client/src/signaling.js:140-146](file://client/src/signaling.js#L140-L146)
- [src/websocket.ts:44-114](file://src/websocket.ts#L44-L114)

### 缓冲策略、丢包处理与重排序
- 缓冲策略：事件以“所见即所得”的方式即时生成并发送，避免大缓冲导致的时延累积；触摸事件按触点拆分，降低单帧体积。
- 丢包处理：当前实现未显式实现重传/确认机制；建议在数据通道上启用可靠传输或在应用层引入序列号与请求重发。
- 重排序：事件按时间戳顺序应用，若出现乱序，可在对端按时间戳队列进行重排与延迟投递，确保状态一致性。

章节来源
- [client/src/sender.js:170-182](file://client/src/sender.js#L170-L182)
- [client/src/inputdevice.js:149-163](file://client/src/inputdevice.js#L149-L163)

### 跨设备输入一致性保证
- 指针矫正：根据视频宽高与元素显示区域计算 letterbox，将屏幕坐标映射到视频坐标，消除不同设备显示差异。
- 设备描述：通过设备消息告知对端设备布局与变体，避免按键映射不一致。
- 时间基准：统一使用相对时间戳，结合启动时间校准，减少网络抖动带来的累计误差。

章节来源
- [client/src/pointercorrect.js:78-119](file://client/src/pointercorrect.js#L78-L119)
- [client/src/inputremoting.js:117-136](file://client/src/inputremoting.js#L117-L136)

## 依赖关系分析
- 客户端模块内聚度高：输入设备抽象与状态事件解耦，发送器仅负责事件到状态的转换与派发。
- 信令与传输解耦：信令层负责连接与消息路由，数据通道负责高吞吐事件传输，职责清晰。
- 服务端依赖：WebSocket 信令服务集中处理连接生命周期与消息分发，简化客户端复杂度。

```mermaid
graph LR
IR["InputRemoting"] --> S["Sender"]
S --> ID["InputDevice/IInputState"]
S --> PC["PointerCorrector"]
S --> OBS["Observer(RTC 数据通道)"]
IR --> WS["WebSocket 信令"]
WS --> H["WebSocket 处理器"]
```

图表来源
- [client/src/inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169)
- [client/src/sender.js:14-188](file://client/src/sender.js#L14-L188)
- [client/src/inputdevice.js:40-89](file://client/src/inputdevice.js#L40-L89)
- [client/src/pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124)
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118)

章节来源
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118)

## 性能考量
- 二进制帧优先：事件以 TypedArray 写入，减少 JSON 序列化成本。
- 小步快跑：事件即时发送，避免批量堆积；触摸事件拆分，降低单帧大小。
- 显示适配：指针矫正一次性计算，避免每帧重复昂贵运算。
- 传输层选择：在低延迟要求下优先使用 WebSocket 信令与 RTC 数据通道可靠传输。

## 故障排查指南
- 事件未到达对端：检查 RTC 数据通道状态与 readyState，确认观察者是否正确注册。
- 事件乱序或错位：核对时间戳注入逻辑与对端按时间戳重排策略；检查指针矫正参数（视频宽高、元素尺寸）。
- 设备映射异常：确认设备消息已发送且对端已更新设备布局；核对按键映射表与手柄按钮位图写入。
- 信令不通：验证 WebSocket 信令服务运行状态与消息类型路由；检查 HTTP 轮询会话 ID 与轮询间隔。

章节来源
- [client/src/sender.js:202-208](file://client/src/sender.js#L202-L208)
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [client/test/inputremoting.test.js:35-47](file://client/test/inputremoting.test.js#L35-L47)
- [client/test/inputdevice.test.js:123-144](file://client/test/inputdevice.test.js#L123-L144)

## 结论
该输入事件同步方案通过“事件即时生成 + 二进制帧封装 + 相对时间戳 + 指针矫正 + 信令路由”的组合，在保证低延迟的同时实现了跨设备的一致性与可扩展性。为进一步提升鲁棒性，建议在应用层引入序列号、确认与重传机制，并对触摸与键盘事件实施更细粒度的去抖与合并策略。

## 附录
- 测试用例覆盖了消息创建与事件缓冲的基本行为，可作为集成测试基线。
- 建议在生产环境中增加心跳、超时与重连策略，以应对网络波动。

章节来源
- [client/test/inputremoting.test.js:50-132](file://client/test/inputremoting.test.js#L50-L132)
- [client/test/inputdevice.test.js:16-173](file://client/test/inputdevice.test.js#L16-L173)