15 KiB
15 KiB
输入事件同步
**本文引用的文件** - [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)目录
引言
本技术文档聚焦于输入事件同步机制,围绕以下目标展开:时间同步、延迟补偿、状态一致性保证;事件队列管理、时间戳处理与网络传输优化;缓冲策略、丢包处理与重排序;以及在多设备间实现高精度时间同步与输入一致性。文档基于仓库中的客户端输入遥测与信令通道实现,结合服务端 WebSocket 信令处理,系统性阐述从事件采集、编码、传输到渲染的一体化流程。
项目结构
该项目采用前后端分离的结构:
- 客户端包含输入设备抽象、事件打包、信令交互与 WebRTC 数据通道发送等模块;
- 服务端提供基于 WebSocket 的信令通道,负责连接管理与消息转发。
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
- client/src/inputremoting.js:63-169
- client/src/sender.js:14-188
- client/src/pointercorrect.js:6-124
- client/src/signaling.js:3-292
- client/src/peer.js:3-188
- src/websocket.ts:6-118
章节来源
核心组件
- 输入设备与状态模型:鼠标、键盘、触摸屏、手柄的状态与事件定义,统一以二进制帧格式封装,便于跨网络传输与解码。
- 本地输入管理器:负责事件采集、时间戳注入、初始布局与设备信息广播、订阅者分发。
- 发送器:将 DOM 事件转换为输入状态,生成状态事件与文本事件,并通过观察者模式推送至数据通道或信令通道。
- 指针矫正器:根据视频显示比例与留边(letterbox)计算屏幕坐标到视频坐标的映射,确保跨设备一致的输入位置。
- 信令层:支持 HTTP 轮询与 WebSocket 两种模式,用于连接建立、SDP 交换与自定义消息广播。
- WebRTC Peer:封装 ICE、Offer/Answer、数据通道创建与状态机,保障媒体与控制面协同。
章节来源
- client/src/inputdevice.js:129-178
- client/src/inputremoting.js:63-169
- client/src/sender.js:14-188
- client/src/pointercorrect.js:6-124
- client/src/signaling.js:3-292
- client/src/peer.js:3-188
架构总览
下图展示了从用户输入到远端渲染的关键路径:DOM 事件经由发送器转换为状态事件,通过 RTC 数据通道或信令通道发送,服务端 WebSocket 信令进行路由,最终到达对端渲染端。
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
- client/src/inputremoting.js:138-168
- client/src/signaling.js:140-146
- src/websocket.ts:44-114
详细组件分析
时间同步与时间戳处理
- 启动时间注入:本地输入管理器在开始发送时记录启动时间,后续所有事件携带相对时间戳,确保跨设备时间基准一致。
- 事件时间戳:输入事件与状态事件均包含时间字段,用于对端按时间顺序重建状态。
- 坐标映射与时间耦合:指针矫正器在事件生成时使用当前相对时间,避免因时间漂移导致的错位。
flowchart TD
Start(["开始发送"]) --> SetStart["记录启动时间"]
SetStart --> OnEvent["捕获输入事件"]
OnEvent --> InjectTime["注入相对时间戳"]
InjectTime --> BuildState["构建状态事件/文本事件"]
BuildState --> Send["发送至观察者/信令通道"]
Send --> End(["完成一次事件发送"])
图表来源
章节来源
事件队列管理与去抖动
- 事件聚合:触摸事件在一次 TouchEvent 中可能包含多个 changedTouches,发送器逐个拆分并生成对应状态事件,保证每个触点独立同步。
- 文本事件与按键事件分离:按键事件区分 keydown/keyup 与重复按下(repeat),仅在非重复场景生成状态事件,避免冗余。
- 去抖动策略:通过事件类型判断与状态缓存(如键盘按键位图、触摸起始时间与起点),减少无效更新。
flowchart TD
TStart(["TouchEvent 到达"]) --> Split["拆分 changedTouches"]
Split --> ForEach["遍历每个触点"]
ForEach --> MapPos["指针矫正器映射坐标"]
MapPos --> BuildTouchState["构建 TouchState"]
BuildTouchState --> Emit["生成状态事件并派发"]
Emit --> TEnd(["结束"])
图表来源
章节来源
状态一致性保证机制
- 设备描述与布局广播:首次连接时发送设备列表与布局信息,确保对端具备相同的输入模型。
- 设备变更通知:新增/移除/使用变化等设备状态变更通过专用消息通知,对端据此调整输入映射。
- 状态事件结构:状态事件包含基础输入事件头、状态格式与状态数据,对端按格式解析并应用,保证状态一致性。
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
- client/src/inputdevice.js:662-718
- client/src/inputdevice.js:180-193
章节来源
- client/src/inputremoting.js:108-136
- client/src/inputremoting.js:143-162
- client/src/inputdevice.js:662-718
网络传输优化与消息封装
- 自定义消息头:消息包含参与者 ID、类型与长度,数据部分为二进制帧,紧凑且跨语言友好。
- 二进制帧格式:输入事件与状态事件均以 DataView/TypedArray 写入,保证小端序与定长字段,降低解析开销。
- 信令通道:支持 HTTP 轮询与 WebSocket,前者适合受限环境,后者提供低延迟与广播能力。
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
- client/src/inputremoting.js:234-256
- client/src/inputremoting.js:258-277
- client/src/inputremoting.js:279-291
章节来源
缓冲策略、丢包处理与重排序
- 缓冲策略:事件以“所见即所得”的方式即时生成并发送,避免大缓冲导致的时延累积;触摸事件按触点拆分,降低单帧体积。
- 丢包处理:当前实现未显式实现重传/确认机制;建议在数据通道上启用可靠传输或在应用层引入序列号与请求重发。
- 重排序:事件按时间戳顺序应用,若出现乱序,可在对端按时间戳队列进行重排与延迟投递,确保状态一致性。
章节来源
跨设备输入一致性保证
- 指针矫正:根据视频宽高与元素显示区域计算 letterbox,将屏幕坐标映射到视频坐标,消除不同设备显示差异。
- 设备描述:通过设备消息告知对端设备布局与变体,避免按键映射不一致。
- 时间基准:统一使用相对时间戳,结合启动时间校准,减少网络抖动带来的累计误差。
章节来源
依赖关系分析
- 客户端模块内聚度高:输入设备抽象与状态事件解耦,发送器仅负责事件到状态的转换与派发。
- 信令与传输解耦:信令层负责连接与消息路由,数据通道负责高吞吐事件传输,职责清晰。
- 服务端依赖:WebSocket 信令服务集中处理连接生命周期与消息分发,简化客户端复杂度。
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
- client/src/sender.js:14-188
- client/src/inputdevice.js:40-89
- client/src/pointercorrect.js:6-124
- client/src/signaling.js:152-292
- src/websocket.ts:6-118
章节来源
性能考量
- 二进制帧优先:事件以 TypedArray 写入,减少 JSON 序列化成本。
- 小步快跑:事件即时发送,避免批量堆积;触摸事件拆分,降低单帧大小。
- 显示适配:指针矫正一次性计算,避免每帧重复昂贵运算。
- 传输层选择:在低延迟要求下优先使用 WebSocket 信令与 RTC 数据通道可靠传输。
故障排查指南
- 事件未到达对端:检查 RTC 数据通道状态与 readyState,确认观察者是否正确注册。
- 事件乱序或错位:核对时间戳注入逻辑与对端按时间戳重排策略;检查指针矫正参数(视频宽高、元素尺寸)。
- 设备映射异常:确认设备消息已发送且对端已更新设备布局;核对按键映射表与手柄按钮位图写入。
- 信令不通:验证 WebSocket 信令服务运行状态与消息类型路由;检查 HTTP 轮询会话 ID 与轮询间隔。
章节来源
- client/src/sender.js:202-208
- client/src/signaling.js:152-292
- client/test/inputremoting.test.js:35-47
- client/test/inputdevice.test.js:123-144
结论
该输入事件同步方案通过“事件即时生成 + 二进制帧封装 + 相对时间戳 + 指针矫正 + 信令路由”的组合,在保证低延迟的同时实现了跨设备的一致性与可扩展性。为进一步提升鲁棒性,建议在应用层引入序列号、确认与重传机制,并对触摸与键盘事件实施更细粒度的去抖与合并策略。
附录
- 测试用例覆盖了消息创建与事件缓冲的基本行为,可作为集成测试基线。
- 建议在生产环境中增加心跳、超时与重连策略,以应对网络波动。
章节来源