Files
video_socket-server/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/输入设备控制.md
2026-05-16 13:24:02 +08:00

18 KiB
Raw Permalink Blame History

输入设备控制

**本文引用的文件** - [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)

目录

  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
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

图表来源

章节来源

核心组件

  • 输入设备抽象层
    • 设备基类与具体设备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触摸阶段与标志位

章节来源

架构总览

下图展示了从用户输入到远端重放的完整链路DOM 事件经 Sender 转换为设备状态,再封装为 StateEvent/TextEvent通过 InputRemoting 序列化为 Message经 WebRTC 数据通道发送至对端。

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 : 对端接收

图表来源

详细组件分析

输入设备抽象层与事件模型

  • 设备与状态
    • 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
    • 各状态结构严格定义字段偏移与大小,确保跨端一致解析
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

图表来源

章节来源

输入采集与坐标映射Sender 与 PointerCorrector

  • 事件采集
    • Sender 注册鼠标、键盘、触摸、手柄事件监听,调用对应设备的 queueEvent
    • 鼠标移动位置经 PointerCorrector 映射到视频区域坐标后,再生成 StateEvent
    • 触摸事件逐个触点复制并映射坐标后分别生成 StateEvent
  • 坐标映射
    • PointerCorrector 计算 letterbox 类型与尺寸,将页面坐标转换为视频画布坐标
    • 支持动态宽高变化与元素尺寸观测
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

图表来源

章节来源

远程转发与消息封装InputRemoting 与 Message

  • 事件订阅与初始消息
    • InputRemoting 订阅 LocalInputManager 的事件与设备变更事件
    • 启动时发送设备列表与布局初始消息
  • 消息类型与封装
    • Message 统一头部participant_id、type、length、data
    • NewEventsMsg/TextEvent/StateEvent 将事件与状态序列化为二进制
    • NewDeviceMsg/RemoveDeviceMsg/ChangeUsageMsg 提供设备生命周期消息
  • 发送路径
    • InputRemoting 将消息分发给观察者(如 RTC 数据通道)
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 : 发送完成

图表来源

章节来源

游戏手柄事件处理GamepadHandler

  • 轮询与事件
    • GamepadHandler 使用 requestAnimationFrame 周期扫描已连接手柄
    • 对每个控制器派发自定义 gamepadupdated 事件Sender 作为监听器接收并转发
  • 按钮与摇杆
    • GamepadState 将按钮 pressed/value 与摇杆 axes 转换为位掩码与浮点值
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并发送

图表来源

章节来源

键盘与字符事件映射

  • 键位映射
    • Keymap 将 KeyboardEvent.code 映射到内部键索引
    • MemoryHelper.writeSingleBit 将按键状态写入位数组
  • 文本事件
    • CharNumber 将 KeyboardEvent.key 映射为字符编码
    • TextEvent 在 InputEvent 基础上附加字符编码

章节来源

触摸事件与多指支持

  • 触摸阶段与标志
    • TouchPhase 定义Began/Moved/Ended/Canceled/Stationary
    • TouchFlags 定义 IndirectTouch、PrimaryTouch、Tap 等标志
  • 多触点状态
    • TouchscreenState 将 TouchEvent.changedTouches 转换为 TouchState 列表
    • 为每个触点分配唯一 touchId缓存前一帧状态以计算 delta

章节来源

WebRTC 信令与数据通道(传输层)

  • 信令
    • Signaling/WebSocketSignaling 负责 offer/answer/candidate/on-message 传递
    • RenderStreaming 管理多参与方连接与 Peer 生命周期
  • 数据通道
    • Peer 封装 RTCPeerConnection暴露 addDataChannel/createDataChannel
    • Sender 通过 RenderStreaming.createDataChannel 获取数据通道,发送 Message.buffer
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 : 发送成功

图表来源

章节来源

依赖关系分析

  • 组件耦合
    • Sender 依赖 InputDevice/IInputState、PointerCorrector、GamepadHandler
    • InputRemoting 依赖 Message/NewEventsMsg/NewDeviceMsg 以及观察者接口
    • RenderStreaming/Peer/Signaling 提供传输基础设施
  • 关键依赖链
    • DOM 事件 → Sender → 设备状态 → InputRemoting → Message → 数据通道 → 对端
  • 潜在循环依赖
    • 当前模块间为单向依赖,未见循环导入
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 层对高频事件(如 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 按钮位与摇杆轴值映射

章节来源

结论

本系统通过清晰的输入抽象层与严格的二进制事件格式实现了鼠标、键盘、触摸与手柄的统一采集、转换与远程转发。Sender 负责事件采集与坐标映射InputRemoting 负责消息封装与分发RenderStreaming/Peer/Signaling 提供可靠的传输通道。通过时间戳与位掩码等设计,系统具备良好的跨设备同步基础。后续可在事件节流、状态压缩与延迟补偿方面进一步优化,以满足更高实时性需求。

附录

  • 时间戳与事件队列
    • Sender 使用 timeSinceStartup 作为事件时间戳,保证相对时间一致性
    • 建议对端按接收时间与发送时间差进行延迟补偿
  • 设备生命周期
    • InputRemoting 支持设备新增/移除/使用变更消息,可用于对端设备同步
  • 按键与按钮位定义
    • MouseButton/GamepadButton 提供统一位索引,便于跨平台一致映射

章节来源