diff --git a/.qoder/repowiki/zh/content/API 参考.md b/.qoder/repowiki/zh/content/API 参考.md new file mode 100644 index 0000000..a2bd211 --- /dev/null +++ b/.qoder/repowiki/zh/content/API 参考.md @@ -0,0 +1,393 @@ +# API 参考 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/src/peer.js](file://client/src/peer.js) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) +- [test/httphandler.test.ts](file://test/httphandler.test.ts) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件为视频流服务器的完整 API 参考,覆盖以下内容: +- WebSocket 信令 API:连接建立、消息格式、事件类型与实时交互模式 +- HTTP 轮询 API:端点规范、请求/响应格式、状态码与错误处理 +- Swagger 自动化文档:生成与访问方式 +- 信令消息数据结构:Offer、Answer、Candidate 等字段定义与使用场景 +- 错误代码与处理指南 +- API 使用示例与集成模板 + +## 项目结构 +后端采用 Express 提供 HTTP 服务与静态资源,WebSocket 用于实时信令;HTTP 轮询模式通过 /signaling 路由提供信令存取能力。客户端提供基于 WebSocket 与 HTTP 轮询两种信令实现。 + +```mermaid +graph TB +subgraph "服务端" +A["Express 应用
src/server.ts"] +B["WebSocket 信令
src/websocket.ts"] +C["HTTP 轮询信令
src/signaling.ts → src/class/httphandler.ts"] +D["数据模型
src/class/offer.ts / answer.ts / candidate.ts"] +E["Swagger 文档
src/swagger.ts"] +end +subgraph "客户端" +F["信令封装(HTTP)
client/src/signaling.js"] +G["信令封装(WebSocket)
client/src/signaling.js"] +H["Peer 连接控制
client/src/peer.js"] +end +A --> B +A --> C +A --> E +B --> D +C --> D +F --> A +G --> B +H --> F +H --> G +``` + +图表来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +章节来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) + +## 核心组件 +- 服务启动与配置:命令行参数解析、HTTPS/HTTP 选择、日志级别、信令协议类型与通信模式 +- Express 服务:CORS、JSON/URL 编码、静态资源、/config、/signaling 路由挂载、Swagger 文档 +- WebSocket 信令:连接生命周期、消息分发、广播、心跳与超时处理 +- HTTP 轮询信令:会话管理、连接管理、Offer/Answer/Candidate 存取、房间与用户信息查询 +- 数据模型:Offer、Answer、Candidate 结构体 +- 客户端封装:HTTP 与 WebSocket 两类信令客户端,Peer 连接控制 + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +## 架构总览 +系统支持两种信令模式: +- WebSocket:低延迟、全双工、事件驱动 +- HTTP 轮询:兼容性好、部署简单、适合受限网络环境 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant Server as "Express 服务" +participant WS as "WebSocket 信令" +participant HTTP as "HTTP 轮询信令" +Client->>Server : GET /config +Server-->>Client : {useWebSocket, startupMode, logging} +alt WebSocket 模式 +Client->>WS : 建立 ws/wss 连接 +WS-->>Client : "connect"含 role/polite/participantId +Client->>WS : "offer"/"answer"/"candidate" +WS-->>Client : 广播/定向转发对应信令 +else HTTP 模式 +Client->>HTTP : PUT "" 创建会话返回 sessionId +Client->>HTTP : PUT "/connection" 创建连接 +loop 轮询 +Client->>HTTP : GET "" /offer /answer /candidate +HTTP-->>Client : 返回增量信令 +end +Client->>HTTP : POST "/offer" /answer /candidate +Client->>HTTP : DELETE "" 关闭会话 +end +``` + +图表来源 +- [src/server.ts:25-41](file://src/server.ts#L25-L41) +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/httphandler.ts:664-696](file://src/class/httphandler.ts#L664-L696) +- [src/class/httphandler.ts:698-828](file://src/class/httphandler.ts#L698-L828) +- [src/class/httphandler.ts:830-998](file://src/class/httphandler.ts#L830-L998) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) + +## 详细组件分析 + +### WebSocket 信令 API +- 连接建立 + - 客户端通过 ws/wss 连接到服务端 + - 服务端在连接事件中注册处理器,维护连接集合与连接组 +- 消息格式与事件类型 + - connect/disconnect:建立/断开连接,包含 connectionId、polite、role、participantId + - offer/answer/candidate:信令三元组,包含 from、to、data(含 sdp 或 candidate/sdpMid/sdpMLineIndex)、participantId + - ping/pong:心跳机制 + - broadcast/on-message:广播消息与自定义消息 + - participant-joined/participant-left:多参与者的房间模式事件 +- 实时交互模式 + - 公共模式:同一房间内广播 + - 私有模式:host 与特定 participant 或 host 与其他 participants 之间定向转发 +- 心跳与超时 + - 服务端周期发送 ping,客户端回 pong;若长时间无活动则断开 + +```mermaid +sequenceDiagram +participant C as "客户端" +participant S as "WebSocket 信令" +participant H as "处理器(websockethandler)" +C->>S : "connect" {connectionId} +S->>H : onConnect(ws, connectionId) +H-->>C : "connect" {role, polite, participantId} +C->>S : "offer"/"answer"/"candidate" +S->>H : onOffer/onAnswer/onCandidate +H-->>C : 广播/定向转发对应信令 +C->>S : "ping" +S-->>C : "pong" +S->>H : 更新 lastActivity +C->>S : "disconnect" {connectionId} +S->>H : onDisconnect +H-->>C : "disconnect" +``` + +图表来源 +- [src/websocket.ts:44-115](file://src/websocket.ts#L44-L115) +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +章节来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +### HTTP 轮询信令 API +- 会话与连接管理 + - PUT "":创建会话,返回 sessionId + - DELETE "":删除会话 + - PUT "/connection":创建连接,返回 connectionId、polite、type、datetime + - DELETE "/connection":删除连接 +- 信令存取 + - GET "":获取所有信令(connect/disconnect/offer/answer/candidate),按 datetime 排序 + - GET "/offer":获取 offer 列表(可按 fromtime 过滤) + - GET "/answer":获取 answer 列表(可按 fromtime 过滤) + - GET "/candidate":获取 candidate 列表(可按 fromtime 过滤) + - GET "/connection":获取连接列表 + - GET "/connection-ids":获取所有活跃连接 ID + - POST "/offer":提交 offer + - POST "/answer":提交 answer + - POST "/candidate":提交 candidate +- 认证与安全 + - 所有受保护路由需携带请求头 "session-id" +- 房间与用户信息 + - GET "/rooms":获取房间与用户信息(含 sessionId、connected) + +```mermaid +flowchart TD +Start(["开始"]) --> CreateSession["PUT '' 创建会话"] +CreateSession --> CreateConn["PUT '/connection' 创建连接"] +CreateConn --> PollLoop["轮询 GET ''/offer/answer/candidate"] +PollLoop --> PostOffer["POST '/offer' 提交 offer"] +PollLoop --> PostAnswer["POST '/answer' 提交 answer"] +PollLoop --> PostCandidate["POST '/candidate' 提交 candidate"] +PostOffer --> PollLoop +PostAnswer --> PollLoop +PostCandidate --> PollLoop +PollLoop --> DeleteConn["DELETE '/connection' 删除连接"] +DeleteConn --> DeleteSession["DELETE '' 删除会话"] +DeleteSession --> End(["结束"]) +``` + +图表来源 +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:664-696](file://src/class/httphandler.ts#L664-L696) +- [src/class/httphandler.ts:698-828](file://src/class/httphandler.ts#L698-L828) +- [src/class/httphandler.ts:830-998](file://src/class/httphandler.ts#L830-L998) +- [src/class/httphandler.ts:1041-1076](file://src/class/httphandler.ts#L1041-L1076) + +章节来源 +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) + +### 信令消息数据结构 +- Offer + - 字段:sdp、datetime、polite + - 场景:发起端创建本地 offer 并通过信令通道发送给对端 +- Answer + - 字段:sdp、datetime + - 场景:应答端收到 offer 后创建 answer 并回传 +- Candidate + - 字段:candidate、sdpMLineIndex、sdpMid、datetime + - 场景:ICE 候选者收集阶段持续发送,帮助建立 P2P 连接 + +```mermaid +classDiagram +class Offer { ++string sdp ++number datetime ++boolean polite +} +class Answer { ++string sdp ++number datetime +} +class Candidate { ++string candidate ++number sdpMLineIndex ++string sdpMid ++number datetime +} +``` + +图表来源 +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +章节来源 +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +### Swagger 自动生成的 API 文档 +- 生成与访问 + - 初始化 Swagger,自动扫描 /src/class/httphandler.ts 与 /src/signaling.ts 中的注释 + - 访问地址:/api-docs(根据运行协议与端口动态生成) +- 安全方案 + - sessionAuth:通过请求头 "session-id" 进行认证 + +章节来源 +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +### API 使用示例与集成模板 +- 客户端封装 + - HTTP 信令封装:Signaling 类,提供 start/stop、createConnection/deleteConnection、sendOffer/sendAnswer/sendCandidate、getAll 等方法 + - WebSocket 信令封装:WebSocketSignaling 类,提供 createConnection/sendOffer/sendAnswer/sendCandidate/sendMessage 等方法 + - Peer 连接控制:Peer 类,封装 RTCPeerConnection、ICE 候选者、状态变化等 +- 示例流程 + - HTTP 模式:创建会话 → 创建连接 → 轮询获取信令 → 发送 offer/answer/candidate → 删除连接/会话 + - WebSocket 模式:建立连接 → 发送 connect → 发送 offer/answer/candidate → 接收对端信令 → 断开连接 + +章节来源 +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +## 依赖关系分析 +- 服务端依赖 + - Express、ws、swagger-jsdoc、swagger-ui-express、uuid、cors、morgan、multer +- 关键依赖链 + - src/index.ts → src/server.ts → src/signaling.ts → src/class/httphandler.ts + - src/index.ts → src/websocket.ts → src/class/websockethandler.ts + - src/swagger.ts → src/class/httphandler.ts 与 src/signaling.ts + +```mermaid +graph LR +pkg["package.json 依赖声明"] --> exp["express"] +pkg --> wsdep["ws"] +pkg --> swagger["swagger-jsdoc / swagger-ui-express"] +pkg --> uuiddep["uuid"] +pkg --> corsdep["cors"] +pkg --> morgandep["morgan"] +pkg --> multerdep["multer"] +idx["src/index.ts"] --> srv["src/server.ts"] +idx --> wsc["src/websocket.ts"] +srv --> sig["src/signaling.ts"] +sig --> hth["src/class/httphandler.ts"] +wsc --> wsh["src/class/websockethandler.ts"] +srv --> swg["src/swagger.ts"] +swg --> hth +swg --> sig +``` + +图表来源 +- [package.json:14-46](file://package.json#L14-L46) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +章节来源 +- [package.json:14-46](file://package.json#L14-L46) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) + +## 性能考量 +- WebSocket + - 低延迟、高吞吐,适合高频信令与实时交互 + - 心跳检测与超时断开避免僵尸连接占用资源 +- HTTP 轮询 + - 增量查询 fromtime 参数减少无效传输 + - 会话超时清理机制降低内存占用 +- 服务器配置 + - 日志级别可调,生产环境建议调整以减少 I/O 开销 + - HTTPS 证书启用时建议使用强密码套件与现代 TLS 版本 + +## 故障排查指南 +- 常见错误与处理 + - 404 会话不存在:检查 session-id 是否正确传递 + - 400 参数缺失:确保请求体包含 connectionId + - 400 连接 ID 已被使用(私有模式):更换唯一 connectionId + - 会话超时:定期轮询或保持 WebSocket 连接活跃 +- 测试参考 + - WebSocket 单测验证公共/私有模式下的消息转发与断开行为 + - HTTP 单测验证会话创建/删除、连接管理、信令存取与超时清理 + +章节来源 +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [test/httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) + +## 结论 +本项目提供了完整的 WebRTC 信令解决方案,支持 WebSocket 与 HTTP 轮询两种模式,满足不同部署与网络环境需求。通过清晰的 API 规范、完善的 Swagger 文档与客户端封装,开发者可快速集成并稳定运行信令服务。 + +## 附录 +- 端点一览(HTTP 轮询) + - GET /signaling/connection-ids:获取所有活跃连接 ID + - GET /signaling/connection:获取连接列表 + - GET /signaling/offer:获取 offer 列表(可选 fromtime) + - GET /signaling/answer:获取 answer 列表(可选 fromtime) + - GET /signaling/candidate:获取 candidate 列表(可选 fromtime) + - GET /signaling:获取所有信令(可选 fromtime) + - PUT /signaling:创建会话(返回 sessionId) + - DELETE /signaling:删除会话 + - PUT /signaling/connection:创建连接 + - DELETE /signaling/connection:删除连接 + - POST /signaling/offer:提交 offer + - POST /signaling/answer:提交 answer + - POST /signaling/candidate:提交 candidate + - GET /signaling/rooms:获取房间与用户信息 +- 客户端集成要点 + - HTTP 模式:使用 Signaling 类管理会话与轮询 + - WebSocket 模式:使用 WebSocketSignaling 类进行实时信令 + - Peer 控制:使用 Peer 类处理 RTCPeerConnection 生命周期与 ICE 候选者 \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/PeerConnection 管理.md b/.qoder/repowiki/zh/content/WebRTC 实现/PeerConnection 管理.md new file mode 100644 index 0000000..587f985 --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/PeerConnection 管理.md @@ -0,0 +1,399 @@ +# PeerConnection 管理 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [client/src/peer.js](file://client/src/peer.js) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/test/peerconnection.test.js](file://client/test/peerconnection.test.js) +- [client/test/peerconnectionmock.js](file://client/test/peerconnectionmock.js) +- [client/src/logger.js](file://client/src/logger.js) +- [client/test/testutils.js](file://client/test/testutils.js) +- [client/public/bidirectional/js/main.js](file://client/public/bidirectional/js/main.js) +- [client/public/onebyone/main.js](file://client/public/onebyone/main.js) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本技术文档聚焦于 PeerConnection 管理模块,系统阐述 RTCPeerConnection 的创建与配置、连接标识符与极性判断、SDP 协商流程(Offer/Answer 模型)、ICE 候选收集与处理、连接生命周期管理(建立、维护、关闭),以及常见状态变化与错误处理策略。文档同时提供基于仓库实际代码的图示与路径引用,帮助读者快速定位实现细节。 + +## 项目结构 +该项目采用前后端分离的模块化组织方式: +- 服务端:基于 Express 与 WebSocket 的信令服务器,负责连接组管理、消息路由与广播。 +- 客户端:封装 RTCPeerConnection 的管理类,负责 SDP 协商、ICE 候选处理、事件分发与状态监控。 +- 测试:包含单元测试与模拟器,覆盖协商流程、候选处理与状态机行为。 + +```mermaid +graph TB +subgraph "客户端" +P["Peer
RTCPeerConnection 管理"] +S["Signaling
HTTP/WebSocket 信令"] +L["Logger
日志工具"] +end +subgraph "服务端" +WS["WSSignaling
WebSocket 信令服务器"] +WH["WebSocketHandler
连接组/消息路由"] +DTO["Offer/Answer/Candidate DTO"] +end +P -- "事件: sendoffer/sendanswer/sendcandidate" --> S +S -- "HTTP 轮询" --> P +S -- "WebSocket" --> P +P -- "RTCPeerConnection 事件" --> P +P -- "ontrack/ondatachannel" --> P +P -- "ICE 候选" --> S +S -- "offer/answer/candidate" --> WS +WS --> WH +WH --> DTO +``` + +**图表来源** +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +**章节来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +## 核心组件 +- Peer(客户端) + - 负责创建 RTCPeerConnection、监听 onnegotiationneeded/onicecandidate 等事件、封装 SDP 设置与候选处理、维护协商状态标志位、周期性重发 Offer。 +- Signaling(客户端) + - 提供 HTTP 与 WebSocket 两种信令通道,封装 create/delete connection、发送 offer/answer/candidate 与 on-message。 +- WSSignaling(服务端) + - 基于 ws 的 WebSocket 服务器,接收并分发 offer/answer/candidate/broadcast/on-message 等消息。 +- WebSocketHandler(服务端) + - 实现连接组管理(host/participants)、消息路由、广播与参与者变更通知。 +- DTO(服务端) + - Offer/Answer/Candidate 数据模型,承载 SDP 与 ICE 候选元数据。 + +**章节来源** +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +## 架构总览 +下面的序列图展示了典型的双向视频通话建立流程:客户端通过信令创建连接、协商 Offer/Answer、交换 ICE 候选并建立连接。 + +```mermaid +sequenceDiagram +participant C as "客户端 Peer" +participant Sig as "信令层(HTTP/WebSocket)" +participant WS as "WebSocket 服务器" +participant H as "WebSocketHandler" +participant R as "远端 Peer" +C->>Sig : "createConnection(connectionId)" +Sig-->>C : "connect(polite, role, participantId)" +Note right of C : "根据 role 决定极性(polite)" +C->>C : "ontrack/onicecandidate 注册" +C->>C : "addTrack/addTransceiver/createDataChannel" +C->>C : "setLocalDescription() 触发 negotiationneeded" +C-->>Sig : "sendoffer({connectionId, sdp})" +Sig->>WS : "转发 offer" +WS->>H : "onOffer" +H-->>R : "转发 offer(按模式/目标 participantId)" +R->>R : "setRemoteDescription(offer)" +R->>R : "setLocalDescription(answer)" +R-->>Sig : "sendanswer({connectionId, sdp})" +Sig->>WS : "转发 answer" +WS->>H : "onAnswer" +H-->>C : "转发 answer" +loop "ICE 收集循环" +C-->>Sig : "sendcandidate({connectionId, candidate, sdpMid, sdpMLineIndex})" +Sig->>WS : "转发 candidate" +WS->>H : "onCandidate" +H-->>R : "转发 candidate" +R->>R : "addIceCandidate(candidate)" +end +C->>C : "iceConnectionState 变化监控" +alt "失败" +C-->>C : "dispatchEvent(disconnect)" +end +``` + +**图表来源** +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:208-338](file://src/class/websockethandler.ts#L208-L338) + +## 详细组件分析 + +### Peer 组件(RTCPeerConnection 管理) +- 关键职责 + - 创建 RTCPeerConnection 并注册 ontrack/ondatachannel/onicecandidate/onnegotiationneeded/ice/信号状态等事件。 + - 维护协商状态标志:makingOffer/waitingAnswer/ignoreOffer/srdAnswerPending,避免竞态。 + - 周期性重发 Offer,保证对端及时收到未达的 SDP。 + - 处理 onGotDescription(Offer/Answer)与 onGotCandidate,执行 setRemoteDescription/setLocalDescription/addIceCandidate。 + - 暴露 addTrack/addTransceiver/createDataChannel/getTransceivers/getStats 等操作。 +- 极性判断与角色 + - 极性(polite)在客户端创建连接时由服务端返回;服务端在私有模式下区分 host 与 participant,从而决定 polite 标记。 +- 状态监控 + - iceConnectionState=failed 时派发 disconnect 事件;signalingState/iceGatheringState 变化被记录与观察。 + +```mermaid +classDiagram +class Peer { ++connectionId : string ++polite : boolean ++pc : RTCPeerConnection ++makingOffer : boolean ++waitingAnswer : boolean ++ignoreOffer : boolean ++srdAnswerPending : boolean ++constructor(connectionId, polite, config, resendIntervalMsec) ++addTrack(connectionId, track) ++addTransceiver(connectionId, trackOrKind, init) ++createDataChannel(connectionId, label) ++getTransceivers(connectionId) ++getStats(connectionId) ++onGotDescription(connectionId, description) ++onGotCandidate(connectionId, candidate) ++close() +} +``` + +**图表来源** +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +**章节来源** +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/test/peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [client/test/peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) + +### SDP 协商流程(Offer/Answer 模型) +- Offer/Answer 模式 + - 客户端在 negotiationneeded 时 setLocalDescription 生成 Offer;若收到对端 Offer,则 setRemoteDescription 后 setLocalDescription 生成 Answer。 +- 竞态条件处理 + - 使用 makingOffer/waitingAnswer/ignoreOffer/srdAnswerPending 标志,避免同时进行多个协商或忽略冲突的 Offer。 + - impolite 端在非稳定状态下收到 Offer 且自身正在制作 Offer 时,会标记 ignoreOffer 并丢弃,防止“光竞争”。 +- 事件驱动 + - Peer 在发送 Offer/Answer 时派发 sendoffer/sendanswer 事件,供信令层转发。 + - 对端收到 Answer 后,Peer 派发 negotiated 事件,表示协商完成。 + +```mermaid +flowchart TD +Start(["开始: negotiationneeded"]) --> SetLD["setLocalDescription() 生成 Offer"] +SetLD --> WaitAnswer["waitingAnswer = true"] +WaitAnswer --> SendOffer["派发 sendoffer 事件"] +SendOffer --> RecvOffer{"收到对端 Offer?"} +RecvOffer --> |是| SetRD["setRemoteDescription(Offer)"] +SetRD --> GenAnswer["setLocalDescription() 生成 Answer"] +GenAnswer --> SendAnswer["派发 sendanswer 事件"] +SendAnswer --> RecvAnswer{"收到对端 Answer?"} +RecvAnswer --> |是| DispatchNegotiated["派发 negotiated 事件"] +RecvAnswer --> |否| LoopOffer["周期性重发 Offer"] +LoopOffer --> RecvAnswer +RecvOffer --> |否| End(["结束"]) +DispatchNegotiated --> End +``` + +**图表来源** +- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) + +**章节来源** +- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) +- [client/test/peerconnection.test.js:106-188](file://client/test/peerconnection.test.js#L106-L188) + +### ICE 候选收集与处理 +- 候选收集 + - Peer 注册 onicecandidate,当本地 ICE 收集到候选或完成时,派发 sendcandidate 事件。 +- 候选处理 + - Peer 在收到对端候选后调用 addIceCandidate;若尚未设置远端描述则抛出异常,需等待 Answer 或 Offer 完成后再处理。 +- 状态监控 + - iceGatheringState/iceConnectionState 变化被记录,iceConnectionState=failed 触发断开事件。 + +```mermaid +sequenceDiagram +participant P as "本地 Peer" +participant Sig as "信令" +participant R as "远端 Peer" +P->>P : "onicecandidate 收集候选" +P-->>Sig : "sendcandidate({connectionId, candidate, sdpMid, sdpMLineIndex})" +Sig-->>R : "转发 candidate" +R->>R : "addIceCandidate(candidate)" +alt "未设置远端描述" +R->>R : "忽略或报错(等待 Answer/Offer)" +end +``` + +**图表来源** +- [client/src/peer.js:29-35](file://client/src/peer.js#L29-L35) +- [client/src/peer.js:175-186](file://client/src/peer.js#L175-L186) +- [src/class/websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) + +**章节来源** +- [client/src/peer.js:29-35](file://client/src/peer.js#L29-L35) +- [client/src/peer.js:175-186](file://client/src/peer.js#L175-L186) +- [client/test/peerconnection.test.js:222-249](file://client/test/peerconnection.test.js#L222-L249) + +### 连接生命周期管理 +- 建立 + - 客户端创建连接(HTTP 或 WebSocket),服务端返回 connect(含 polite/role/participantId)。 + - 客户端添加媒体轨道/数据通道,触发 negotiationneeded,发送 Offer。 +- 维护 + - 循环重发 Offer(默认间隔可配置),持续发送候选,监控 ICE 状态。 +- 关闭 + - 客户端调用 close() 关闭 RTCPeerConnection;服务端在连接断开时清理组并通知其他成员。 + +```mermaid +stateDiagram-v2 +[*] --> 已连接 +已连接 --> 协商中 : "negotiationneeded" +协商中 --> 已连接 : "negotiated/Answer" +已连接 --> 断开 : "iceConnectionState=failed/disconnect" +断开 --> 已连接 : "重新建立" +``` + +**图表来源** +- [client/src/peer.js:43-48](file://client/src/peer.js#L43-L48) +- [client/src/peer.js:84-90](file://client/src/peer.js#L84-L90) +- [src/class/websockethandler.ts:175-206](file://src/class/websockethandler.ts#L175-L206) + +**章节来源** +- [client/src/peer.js:84-90](file://client/src/peer.js#L84-L90) +- [src/class/websockethandler.ts:175-206](file://src/class/websockethandler.ts#L175-L206) + +### 服务端 WebSocket 信令与连接组 +- WSSignaling + - 监听 connection/message 事件,解析消息类型并交由 WebSocketHandler 处理。 +- WebSocketHandler + - 私有模式下维护 connectionGroup(host + participants),按角色与 participantId 路由消息。 + - 公共模式下广播 offer/answer/candidate。 + - 提供广播、参与者加入/离开通知、心跳(可选)等能力。 + +```mermaid +graph TB +WS["WSSignaling"] --> WH["WebSocketHandler"] +WH --> CG["connectionGroup
host + participants"] +WH --> DTO["Offer/Answer/Candidate DTO"] +WS --> |offer/answer/candidate| WH +WH --> |按角色/目标转发| 远端["远端客户端"] +``` + +**图表来源** +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +**章节来源** +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +## 依赖关系分析 +- 客户端依赖 + - Peer 依赖 RTCPeerConnection 事件模型与浏览器 WebRTC API。 + - Signaling 依赖 fetch 或 WebSocket 与服务端交互。 + - Logger 提供统一日志输出。 +- 服务端依赖 + - Express 提供 HTTP 服务;ws 提供 WebSocket 服务。 + - WebSocketHandler 作为业务处理器,依赖 DTO 模型。 + +```mermaid +graph LR +Peer["client/src/peer.js"] --> RTCPeerConnection["浏览器 WebRTC API"] +Signaling["client/src/signaling.js"] --> HTTP["fetch"] +Signaling --> WS["WebSocket"] +WSServer["src/websocket.ts"] --> WSImpl["ws 库"] +WSHandler["src/class/websockethandler.ts"] --> DTO["Offer/Answer/Candidate"] +``` + +**图表来源** +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +**章节来源** +- [package.json:14-46](file://package.json#L14-L46) + +## 性能考量 +- 候选收集与传输 + - 廀候选收集完成后发送 null 候选,避免重复传输;建议在高延迟网络下适当降低重发 Offer 的频率。 +- 协商状态机 + - 使用标志位严格控制并发协商,减少不必要的 SDP 交换与往返。 +- 信令模式 + - 公共模式广播可能带来额外带宽压力;私有模式按目标路由更高效。 +- 统计与监控 + - 客户端可定期获取 getStats 并渲染分辨率等指标,辅助性能优化。 + +[本节为通用指导,不直接分析具体文件] + +## 故障排查指南 +- 常见问题与定位 + - ICE 失败:检查 iceConnectionState=failed 事件与网络连通性;确认 STUN/TURN 配置。 + - 候选无法添加:确认已设置远端描述;查看 onGotCandidate 的异常分支。 + - Offer/Answer 竞态:关注 ignoreOffer 标志与 waitingAnswer;必要时调整重发间隔。 + - 日志启用:通过 Logger 工具开启调试日志,观察事件与状态变化。 +- 单元测试参考 + - 测试覆盖了 addTrack/addTransceiver/createDataChannel 触发协商、重发 Offer、候选处理、状态事件等场景,可据此验证实现。 + +**章节来源** +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) +- [client/test/peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [client/test/peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) + +## 结论 +本模块通过客户端 Peer 与服务端 WebSocketHandler 的协作,实现了可靠的 RTCPeerConnection 生命周期管理与信令路由。其设计重点在于: +- 明确的极性与角色划分,保障 Offer/Answer 的一致性; +- 严格的协商状态机与标志位,消除竞态; +- 完整的 ICE 候选收集与处理流程; +- 可扩展的私有/公共模式与广播能力。 + +这些特性共同确保了在复杂网络环境下的稳定连接与良好的用户体验。 + +[本节为总结性内容,不直接分析具体文件] + +## 附录 + +### 关键流程与事件路径 +- Offer/Answer 协商 + - [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) + - [client/test/peerconnection.test.js:124-208](file://client/test/peerconnection.test.js#L124-L208) +- ICE 候选 + - [client/src/peer.js:29-35](file://client/src/peer.js#L29-L35) + - [client/src/peer.js:175-186](file://client/src/peer.js#L175-L186) + - [client/test/peerconnection.test.js:222-249](file://client/test/peerconnection.test.js#L222-L249) +- 信令与连接组 + - [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + - [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) + - [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +### 示例集成点 +- 双向视频示例 + - [client/public/bidirectional/js/main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184) +- 一对一示例 + - [client/public/onebyone/main.js:179-212](file://client/public/onebyone/main.js#L179-L212) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/信令客户端.md b/.qoder/repowiki/zh/content/WebRTC 实现/信令客户端.md new file mode 100644 index 0000000..62ee77a --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/信令客户端.md @@ -0,0 +1,377 @@ +# 信令客户端 + + +**本文引用的文件** +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/test/signaling.test.js](file://client/test/signaling.test.js) +- [client/test/mocksignaling.js](file://client/test/mocksignaling.js) +- [client/src/logger.js](file://client/src/logger.js) +- [src/signaling.ts](file://src/signaling.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [src/server.ts](file://src/server.ts) +- [src/index.ts](file://src/index.ts) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件系统性地阐述信令客户端模块的实现机制与协议设计,覆盖以下主题: +- 与服务器的连接建立、消息收发与轮询拉取流程 +- 信令协议的消息格式、事件类型与状态同步机制 +- WebSocket 与 HTTP 轮询两种信令模式的实现差异与适用场景 +- 客户端状态管理(连接状态监控、重连与错误处理) +- 实战示例(以“代码片段路径”形式给出,避免直接粘贴源码) + +## 项目结构 +前端信令客户端位于 client/src/signaling.js,提供两类信令适配器: +- HTTP 轮询适配器:基于 fetch 的 GET/PUT/POST/DELETE 接口,周期性拉取消息 +- WebSocket 适配器:基于原生 WebSocket,实时推送消息 + +后端信令服务位于 src/signaling.ts 与 src/class/httphandler.ts(HTTP)及 src/websocket.ts 与 src/class/websockethandler.ts(WebSocket),二者均通过统一的事件模型驱动前端。 + +```mermaid +graph TB +subgraph "前端" +FE_HTTP["HTTP 信令客户端
client/src/signaling.js"] +FE_WS["WebSocket 信令客户端
client/src/signaling.js"] +FE_LOGGER["日志工具
client/src/logger.js"] +end +subgraph "后端" +BE_ROUTER["HTTP 路由
src/signaling.ts"] +BE_HTTP_HANDLER["HTTP 处理器
src/class/httphandler.ts"] +BE_WS_SERVER["WebSocket 服务器
src/websocket.ts"] +BE_WS_HANDLER["WebSocket 处理器
src/class/websockethandler.ts"] +end +FE_HTTP --> BE_ROUTER +FE_WS --> BE_WS_SERVER +BE_ROUTER --> BE_HTTP_HANDLER +BE_WS_SERVER --> BE_WS_HANDLER +FE_HTTP --- FE_LOGGER +FE_WS --- FE_LOGGER +``` + +**图表来源** +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +**章节来源** +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) + +## 核心组件 +- HTTP 信令客户端(Signaling 类) + - 负责会话创建/删除、连接建立/断开、offer/answer/candidate/on-message 的发送与轮询接收 + - 通过自定义事件分发消息(connect/disconnect/offer/answer/candidate/on-message) +- WebSocket 信令客户端(WebSocketSignaling 类) + - 负责 WebSocket 连接生命周期、消息发送与事件分发 + - 支持 participant-joined/participant-left/broadcast 等扩展事件 +- 日志工具(Logger) + - 提供启用/禁用与多种级别输出,便于调试 + +**章节来源** +- [client/src/signaling.js:3-150](file://client/src/signaling.js#L3-L150) +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +## 架构总览 +信令客户端与服务端通过两种模式协作: +- HTTP 轮询:客户端周期性 GET /signaling 拉取消息;服务端按会话聚合 offer/answer/candidate/connect/disconnect 并按时间戳排序返回 +- WebSocket:客户端直连 WebSocket,服务端按连接组(host/participants)进行消息路由与广播 + +```mermaid +sequenceDiagram +participant C as "信令客户端" +participant S as "HTTP 服务端" +participant H as "HTTP 处理器" +C->>S : PUT /signaling (创建会话) +S->>H : createSession() +H-->>S : { sessionId } +S-->>C : { sessionId } +C->>S : PUT /signaling/connection (建立连接) +S->>H : createConnection() +H-->>S : { connectionId, polite } +S-->>C : { connect ... } +loop 轮询 +C->>S : GET /signaling?fromtime=... +S->>H : getAll(fromtime) +H-->>S : { messages[], datetime } +S-->>C : { messages[], datetime } +C-->>C : 分发事件 (offer/answer/candidate/disconnect) +end +C->>S : DELETE /signaling (删除会话) +S->>H : deleteSession() +H-->>S : 200 +S-->>C : 200 +``` + +**图表来源** +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [src/signaling.ts:15-18](file://src/signaling.ts#L15-L18) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) + +**章节来源** +- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) + +## 详细组件分析 + +### HTTP 信令客户端(Signaling 类) +- 会话管理 + - start():若未获取 sessionId,循环 PUT /signaling 创建会话,成功后进入轮询 + - stop():DELETE /signaling 清理会话并重置 sessionId +- 连接管理 + - createConnection()/deleteConnection():PUT/DELETE /signaling/connection +- 媒体协商 + - sendOffer()/sendAnswer()/sendCandidate():POST /signaling/offer|answer|candidate +- 通用消息 + - sendMessage():POST /signaling/on-message +- 消息拉取 + - loopGetAll():GET /signaling?fromtime=lastTime,解析 messages 数组并分发事件 +- 事件分发 + - disconnect/offer/answer/candidate/on-message 等事件通过 CustomEvent 派发 + +```mermaid +classDiagram +class Signaling { ++running boolean ++interval number ++sessionId string ++headers() object ++url(method, parameter) string ++start() void ++stop() void ++createConnection(connectionId) Promise ++deleteConnection(connectionId) Promise ++sendOffer(connectionId, sdp) Promise ++sendAnswer(connectionId, sdp) Promise ++sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) Promise ++sendMessage(connectionId, message) Promise ++getAll(fromTime) Promise +-loopGetAll() void +} +``` + +**图表来源** +- [client/src/signaling.js:3-150](file://client/src/signaling.js#L3-L150) + +**章节来源** +- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) + +### WebSocket 信令客户端(WebSocketSignaling 类) +- 连接建立 + - 构造函数根据协议选择 ws/wss,监听 open/close/message + - start():等待 isWsOpen 为真 + - stop():关闭连接并等待 isWsOpen 为假 +- 消息发送 + - createConnection()/deleteConnection() + - sendOffer()/sendAnswer()/sendCandidate() + - sendMessage():封装 type:"on-message" 数据 +- 消息接收与事件分发 + - onmessage:解析 JSON,按 type 分派 connect/disconnect/offer/answer/candidate/on-message/participant-joined/participant-left/broadcast + +```mermaid +sequenceDiagram +participant C as "WebSocket 客户端" +participant WS as "WebSocket 服务端" +participant WH as "WebSocket 处理器" +C->>WS : CONNECT ws : //host +WS-->>C : onopen +C->>WS : {"type" : "connect","connectionId" : id} +WS->>WH : onConnect(ws, id) +WH-->>WS : 广播/通知 +WS-->>C : {"type" : "connect", ...} +C->>WS : {"type" : "offer","from" : id,"data" : {"sdp",...}} +WS->>WH : onOffer(ws, data) +WH-->>WS : 路由/广播 +WS-->>C : {"type" : "offer", ...} +C->>WS : {"type" : "on-message", "data" : {...}} +WS->>WH : onMessage(ws, data) +WH-->>WS : 转发 +WS-->>C : {"type" : "on-message", ...} +C->>WS : CLOSE +WS-->>C : onclose +``` + +**图表来源** +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +**章节来源** +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +### 信令协议与消息格式 +- HTTP 模式 + - 会话:PUT /signaling -> { sessionId } + - 连接:PUT /signaling/connection -> { connectionId, polite } + - 消息:GET /signaling?fromtime=... -> { messages[], datetime } + - 消息类型:connect/disconnect/offer/answer/candidate/on-message + - offer/answer 字段:connectionId, sdp, polite(可选) + - candidate 字段:connectionId, candidate, sdpMLineIndex, sdpMid +- WebSocket 模式 + - 连接:{"type":"connect","connectionId":id} + - 信令:{"type":"offer|answer|candidate","from":id,"data":{...},"participantId":...} + - 普通消息:{"type":"on-message","data":{...}} + - 扩展事件:participant-joined/participant-left/broadcast + +```mermaid +flowchart TD +A["HTTP 消息聚合"] --> B["按时间戳排序"] +B --> C["分发事件:offer/answer/candidate/disconnect"] +D["WebSocket 消息路由"] --> E["按连接组(host/participants)转发"] +E --> F["广播/定向转发"] +``` + +**图表来源** +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) +- [src/class/websockethandler.ts:97-109](file://src/class/websockethandler.ts#L97-L109) + +**章节来源** +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) + +### 状态管理与重连策略 +- HTTP 轮询 + - start() 中若 sessionId 为空则持续尝试创建会话,间隔由 interval 控制 + - loopGetAll() 中每次拉取后更新 lastTimeRequest,避免重复消费 + - stop() 显式清理会话 +- WebSocket + - 构造函数监听 open/close,start() 等待连接就绪 + - 建议在 onclose 中实现指数退避重连(可在上层封装) +- 错误处理 + - on-message 解析失败时记录错误日志 + - HTTP 模式下 404/参数缺失等错误需上层捕获并提示 + +**章节来源** +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [client/src/signaling.js:152-241](file://client/src/signaling.js#L152-L241) +- [client/src/logger.js:27-29](file://client/src/logger.js#L27-L29) + +### 代码示例(以路径引用代替代码) +- 如何监听并处理 on-message 事件 + - [client/src/signaling.js:74-83](file://client/src/signaling.js#L74-L83) +- 如何发送 offer/answer/candidate + - [client/src/signaling.js:117-138](file://client/src/signaling.js#L117-L138) + - [client/src/signaling.js:255-279](file://client/src/signaling.js#L255-L279) +- 如何启动/停止 HTTP 信令客户端 + - [client/src/signaling.js:30-47](file://client/src/signaling.js#L30-L47) + - [client/src/signaling.js:93-97](file://client/src/signaling.js#L93-L97) +- 如何启动/停止 WebSocket 信令客户端 + - [client/src/signaling.js:230-241](file://client/src/signaling.js#L230-L241) + - [client/src/signaling.js:236-241](file://client/src/signaling.js#L236-L241) +- 如何实现消息队列(建议思路) + - 使用内存队列暂存未处理的 on-message,按顺序消费并回调处理函数 + - 参考事件分发位置:[client/src/signaling.js:74-83](file://client/src/signaling.js#L74-L83) +- 如何调试信令通信 + - 启用日志:[client/src/logger.js:3-9](file://client/src/logger.js#L3-L9) + - 观察网络请求与响应:[client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) + +**章节来源** +- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) +- [client/src/logger.js:3-9](file://client/src/logger.js#L3-L9) + +## 依赖关系分析 +- 前端依赖 + - client/src/signaling.js 依赖 client/src/logger.js + - 测试依赖 client/test/signaling.test.js 与 client/test/mocksignaling.js +- 后端依赖 + - src/server.ts 注册 /signaling 路由并挂载 HTTP 与 WebSocket 信令 + - src/signaling.ts 定义路由与鉴权中间件(会话校验) + - HTTP 侧:src/class/httphandler.ts 负责会话/连接/消息持久化与聚合 + - WebSocket 侧:src/websocket.ts 与 src/class/websockethandler.ts 负责连接组与消息路由 + +```mermaid +graph LR +P["package.json"] --> FE["client/src/signaling.js"] +P --> BE_S["src/server.ts"] +BE_S --> BE_R["src/signaling.ts"] +BE_R --> BE_H["src/class/httphandler.ts"] +BE_S --> BE_W["src/websocket.ts"] +BE_W --> BE_WH["src/class/websockethandler.ts"] +FE --> LOG["client/src/logger.js"] +``` + +**图表来源** +- [package.json:1-60](file://package.json#L1-L60) +- [src/server.ts:14-29](file://src/server.ts#L14-L29) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120) +- [src/websocket.ts:1-21](file://src/websocket.ts#L1-L21) +- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66) + +**章节来源** +- [package.json:1-60](file://package.json#L1-L60) +- [src/server.ts:14-29](file://src/server.ts#L14-L29) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) + +## 性能考量 +- HTTP 轮询 + - 通过 fromtime 参数避免重复拉取,提升效率 + - 合理设置 interval,避免过于频繁导致带宽与 CPU 压力 +- WebSocket + - 服务端支持心跳检测(注释中可见),可结合客户端 ping/pong 保持长连稳定 + - 连接组广播需控制消息规模,避免风暴 +- 会话超时 + - HTTP 处理器定期清理长时间无请求的会话,降低资源占用 + +**章节来源** +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +## 故障排查指南 +- 无法获取 sessionId + - 确认 start() 已调用且网络可达 + - 查看控制台日志与网络面板 + - 参考:[client/src/signaling.js:30-47](file://client/src/signaling.js#L30-L47) +- on-message 解析失败 + - 客户端会记录错误日志,检查消息格式与编码 + - 参考:[client/src/signaling.js:74-83](file://client/src/signaling.js#L74-L83) +- WebSocket 连接异常 + - 检查协议(ws/wss)、主机与端口配置 + - 关注 onclose 回调,必要时实现重连 + - 参考:[client/src/signaling.js:169-176](file://client/src/signaling.js#L169-L176) +- 私有模式下未收到对端 offer/answer + - 确认双方已建立相同 connectionId 的连接 + - 参考测试用例对私有模式行为的断言 + - 参考:[client/test/signaling.test.js:214-434](file://client/test/signaling.test.js#L214-L434) + +**章节来源** +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [client/src/signaling.js:152-241](file://client/src/signaling.js#L152-L241) +- [client/test/signaling.test.js:214-434](file://client/test/signaling.test.js#L214-L434) + +## 结论 +本信令客户端模块提供了统一的事件接口,兼容 HTTP 轮询与 WebSocket 两种模式。HTTP 模式适合受限网络环境,WebSocket 模式具备更低延迟与更丰富的连接组能力。通过清晰的事件分发与日志工具,开发者可以快速集成并调试信令流程。 + +## 附录 +- 数据模型(Offer/Answer/Candidate) + - Offer:sdp、datetime、polite + - Answer:sdp、datetime + - Candidate:candidate、sdpMLineIndex、sdpMid、datetime + - 参考: + - [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) + - [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) + - [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/实用工具模块.md b/.qoder/repowiki/zh/content/WebRTC 实现/实用工具模块.md new file mode 100644 index 0000000..d2f01c3 --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/实用工具模块.md @@ -0,0 +1,337 @@ +# 实用工具模块 + + +**本文引用的文件** +- [logger.js](file://client/src/logger.js) +- [log.ts](file://src/log.ts) +- [memoryhelper.js](file://client/src/memoryhelper.js) +- [charnumber.js](file://client/src/charnumber.js) +- [inputdevice.js](file://client/src/inputdevice.js) +- [pointercorrect.js](file://client/src/pointercorrect.js) +- [pointercorrect.test.js](file://client/test/pointercorrect.test.js) +- [memoryhelper.test.js](file://client/test/memoryhelper.test.js) +- [inputdevice.test.js](file://client/test/inputdevice.test.js) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件系统性地文档化了“实用工具模块”,涵盖以下方面: +- 日志系统:前端简易开关式日志与后端结构化日志(含级别、时间戳、前缀格式化)。 +- 内存辅助工具:按位写入字节缓冲,支持位级布尔操作与整型大小常量。 +- 字符数字转换工具:按键键值映射与字符编码处理,支撑文本事件生成。 +- 指针校正工具:坐标变换、黑边填充(Letterbox)补偿、屏幕适配与输入精度优化。 + +文档同时提供可直接定位到源码位置的路径指引,便于在实际项目中快速集成与验证。 + +## 项目结构 +实用工具模块位于客户端与服务端两处: +- 客户端工具位于 client/src,包含日志开关、内存辅助、字符映射、输入设备状态序列化、指针校正等。 +- 服务端日志位于 src/log.ts,提供结构化日志级别与格式化输出。 + +```mermaid +graph TB +subgraph "客户端(client/src)" +LJS["logger.js
前端简易日志"] +MJS["memoryhelper.js
内存辅助(位写入)"] +CJS["charnumber.js
字符数字映射"] +IDJS["inputdevice.js
输入设备/状态/事件"] +PCJS["pointercorrect.js
指针校正"] +end +subgraph "服务端(src)" +LTS["log.ts
结构化日志"] +end +IDJS --> MJS +IDJS --> CJS +PCJS --> |"使用元素尺寸/布局"| 浏览器DOM["浏览器DOM/HTMLVideoElement"] +LJS -. 可独立使用 .-> 浏览器控制台 +LTS -. 输出到 .-> Node控制台 +``` + +图表来源 +- [logger.js:1-30](file://client/src/logger.js#L1-L30) +- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [pointercorrect.js:1-125](file://client/src/pointercorrect.js#L1-L125) +- [log.ts:1-51](file://src/log.ts#L1-L51) + +章节来源 +- [logger.js:1-30](file://client/src/logger.js#L1-L30) +- [log.ts:1-51](file://src/log.ts#L1-L51) + +## 核心组件 +- 前端日志工具(logger.js) + - 提供启用/禁用与多级别输出函数,基于全局开关控制是否打印至浏览器控制台。 + - 适合开发调试阶段按需开启/关闭日志输出。 +- 后端日志工具(log.ts) + - 定义日志级别枚举与解析函数;统一格式化时间戳与级别前缀;根据级别选择对应 console 方法输出。 + - 支持通过配置设置全局日志级别,实现生产环境精细化日志控制。 +- 内存辅助工具(memoryhelper.js) + - 提供静态方法按位写入 ArrayBuffer 的指定偏移;支持置位/清位;提供整型大小常量。 + - 在输入设备状态序列化中用于紧凑存储布尔标志位。 +- 字符数字转换工具(charnumber.js) + - 提供按键键名到数值的映射表,用于文本事件生成与键盘状态记录。 +- 指针校正工具(pointercorrect.js) + - 将浏览器坐标系映射到视频坐标系,处理黑边(Letterbox)补偿与元素内内容矩形计算。 + - 支持动态重置视频宽高与元素尺寸,适配不同播放场景。 + +章节来源 +- [logger.js:1-30](file://client/src/logger.js#L1-L30) +- [log.ts:1-51](file://src/log.ts#L1-L51) +- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [pointercorrect.js:1-125](file://client/src/pointercorrect.js#L1-L125) + +## 架构总览 +下图展示了输入设备状态序列化与文本事件生成的整体流程,体现内存辅助与字符映射在其中的关键作用。 + +```mermaid +sequenceDiagram +participant Dev as "输入设备" +participant State as "键盘状态" +participant Mem as "内存辅助(MemoryHelper)" +participant Text as "文本事件(TextEvent)" +Dev->>State : "按键事件(KeyboardEvent)" +State->>Mem : "按位写入(keys缓冲, 键位索引, 布尔值)" +Mem-->>State : "更新后的keys缓冲" +State-->>Text : "生成文本事件(包含InputEvent头+字符编码)" +Text-->>外部 : "序列化为ArrayBuffer" +``` + +图表来源 +- [inputdevice.js:274-322](file://client/src/inputdevice.js#L274-L322) +- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20) +- [charnumber.js:3-109](file://client/src/charnumber.js#L3-L109) +- [inputdevice.js:620-660](file://client/src/inputdevice.js#L620-L660) + +## 详细组件分析 + +### 日志系统(前端与后端) +- 前端简易日志(logger.js) + - 开关控制:enable/disable 切换 isDebug 标志,后续 debug/info/log/warn/error 仅在开启时输出。 + - 使用建议:开发阶段调用 enable,发布版本调用 disable;或结合构建脚本条件编译。 +- 后端结构化日志(log.ts) + - 级别定义:none/error/warn/log/info 五级;parseLogLevel 支持字符串解析。 + - 输出格式:统一 ISO 时间戳与大写级别前缀;按级别选择 console.error/warn/info/log。 + - 控制策略:setLogLevel 设置全局级别;当级别为 none 时不输出任何内容。 + +```mermaid +flowchart TD +Start(["开始"]) --> SetLevel["设置日志级别(setLogLevel)"] +SetLevel --> Parse["解析字符串级别(parseLogLevel)"] +Parse --> LogCall["log(级别, 参数...)"] +LogCall --> Check{"级别允许输出?"} +Check --> |否| Exit["结束(不输出)"] +Check --> |是| Format["格式化时间戳与级别前缀"] +Format --> Console["按级别输出(console.*)"] +Console --> Exit +``` + +图表来源 +- [log.ts:11-24](file://src/log.ts#L11-L24) +- [log.ts:30-50](file://src/log.ts#L30-L50) + +章节来源 +- [logger.js:1-30](file://client/src/logger.js#L1-L30) +- [log.ts:1-51](file://src/log.ts#L1-L51) + +### 内存辅助工具(MemoryHelper) +- 功能概述 + - writeSingleBit:对指定 bitOffset 的位进行置位/清位,底层通过 Uint8Array 访问目标字节。 + - sizeOfInt:返回整型字节数常量,用于事件头与数据之间的边界计算。 +- 复杂度与性能 + - 单次写入为 O(1),空间开销极小;适合高频输入事件的布尔标志位压缩存储。 +- 典型用法 + - 在键盘/鼠标/手柄状态类中,使用该工具将布尔标志位紧凑写入缓冲区,减少带宽占用。 + +```mermaid +flowchart TD +S(["进入writeSingleBit"]) --> View["创建Uint8Array视图"] +View --> Index["计算字节索引(index)"] +Index --> Offset["计算位偏移(bitOffset)"] +Offset --> Load["读取原字节值(byte)"] +Load --> Mask["构造掩码(1< Op{"value为真?"} +Op --> |是| Or["按位或置位"] +Op --> |否| And["按位与清位"] +Or --> Store["写回新字节"] +And --> Store +Store --> E(["退出"]) +``` + +图表来源 +- [memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20) + +章节来源 +- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [memoryhelper.test.js:1-67](file://client/test/memoryhelper.test.js#L1-L67) + +### 字符数字转换工具(CharNumber 与 Keymap) +- CharNumber + - 将按键键名映射到数值编码,用于文本事件生成与键盘状态记录。 +- Keymap + - 将 KeyboardEvent.code 映射到内部键位索引,配合 MemoryHelper 写入 keys 缓冲。 +- 文本事件生成 + - TextEvent.create 组合 InputEvent 头与字符编码,最终序列化为 ArrayBuffer。 + +```mermaid +classDiagram +class CharNumber { ++映射表 +} +class Keymap { ++映射表 +} +class KeyboardState { ++buffer : ArrayBuffer ++format : Number +} +class TextEvent { ++create(deviceId, event, time) TextEvent ++buffer : ArrayBuffer +} +KeyboardState --> Keymap : "键位索引" +KeyboardState --> CharNumber : "字符编码" +TextEvent --> KeyboardState : "组合InputEvent头" +``` + +图表来源 +- [charnumber.js:3-109](file://client/src/charnumber.js#L3-L109) +- [inputdevice.js:274-322](file://client/src/inputdevice.js#L274-L322) +- [inputdevice.js:620-660](file://client/src/inputdevice.js#L620-L660) + +章节来源 +- [charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [inputdevice.js:274-322](file://client/src/inputdevice.js#L274-L322) +- [inputdevice.js:620-660](file://client/src/inputdevice.js#L620-L660) + +### 指针校正工具(PointerCorrector) +- 功能概述 + - 将浏览器坐标系(clientX/Y)转换为视频坐标系,考虑元素内黑边(Letterbox)补偿与视频宽高比。 + - 提供 letterBoxType、letterBoxSize、contentRect 等属性,便于精确映射。 +- 关键步骤 + - 原点归零:减去元素左上角偏移。 + - Y轴反转:浏览器坐标系向下为正,视频坐标系向上为正。 + - 黑边补偿:减去 letterbox 的 x/y 偏移。 + - 比例映射:按 contentRect 与 video 尺寸比缩放。 +- 适用场景 + - 视频播放器内点击/触摸区域映射、远程输入定位、跨屏适配。 + +```mermaid +flowchart TD +In(["输入: [clientX, clientY]"]) --> SubOrigin["减去元素左上角偏移"] +SubOrigin --> FlipY["Y轴反转(height - y)"] +FlipY --> AddLB["减去黑边偏移(x,y)"] +AddLB --> Scale["按contentRect与video尺寸比缩放"] +Scale --> Out(["输出: [videoX, videoY]"]) +``` + +图表来源 +- [pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40) +- [pointercorrect.js:78-119](file://client/src/pointercorrect.js#L78-L119) + +章节来源 +- [pointercorrect.js:1-125](file://client/src/pointercorrect.js#L1-L125) +- [pointercorrect.test.js:1-46](file://client/test/pointercorrect.test.js#L1-L46) + +## 依赖关系分析 +- 输入设备模块(inputdevice.js)依赖: + - MemoryHelper:用于将布尔标志位写入缓冲。 + - CharNumber:用于文本事件中的字符编码。 + - Keymap:用于将 KeyboardEvent.code 转换为内部键位索引。 + - 其他:MouseButton、GamepadButton、TouchPhase、TouchFlags 等常量与枚举。 +- 指针校正工具(pointercorrect.js)依赖: + - HTMLVideoElement 的 getBoundingClientRect 获取元素尺寸与偏移。 + - LetterBoxType 枚举与 contentRect 计算逻辑。 + +```mermaid +graph LR +ID["inputdevice.js"] --> MH["memoryhelper.js"] +ID --> CN["charnumber.js"] +ID --> KM["keymap.js"] +ID --> MB["mousebutton.js"] +ID --> GPB["gamepadbutton.js"] +ID --> TP["touchphase.js"] +ID --> TF["touchflags.js"] +PC["pointercorrect.js"] --> DOM["HTMLVideoElement
getBoundingClientRect()"] +``` + +图表来源 +- [inputdevice.js:1-10](file://client/src/inputdevice.js#L1-L10) +- [pointercorrect.js:12-14](file://client/src/pointercorrect.js#L12-L14) + +章节来源 +- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [pointercorrect.js:1-125](file://client/src/pointercorrect.js#L1-L125) + +## 性能考量 +- 内存辅助(MemoryHelper) + - O(1) 位写入,适合高频输入事件;注意避免频繁创建 ArrayBuffer,复用现有缓冲可降低分配开销。 +- 日志输出 + - 前端:isDebug 控制,避免在生产环境输出;建议在打包阶段移除调试日志以减少运行时开销。 + - 后端:通过 setLogLevel 限制输出级别,生产环境建议仅输出 error/warn;格式化字符串与级别前缀为常量,开销可控。 +- 指针校正 + - map 为纯数学运算,复杂度低;letterBoxType/letterBoxSize/contentRect 可缓存于实例字段,减少重复计算。 + +## 故障排查指南 +- 日志不输出 + - 前端:确认已调用 enable;检查 isDebug 是否被其他逻辑覆盖。 + - 后端:确认 setLogLevel 已正确设置;若级别为 none,则不会输出任何内容。 +- 文本事件字符异常 + - 检查 CharNumber 中是否存在目标键名映射;确保 KeyboardEvent.key 与映射一致。 +- 指针映射不准 + - 确认 HTMLVideoElement 的尺寸与样式(如 object-fit)未引入额外缩放;必要时在 PointerCorrector.reset 中传入最新尺寸。 + - 验证 letterBoxType 与 letterBoxSize 计算结果是否符合预期。 + +章节来源 +- [logger.js:1-30](file://client/src/logger.js#L1-L30) +- [log.ts:11-24](file://src/log.ts#L11-L24) +- [pointercorrect.test.js:1-46](file://client/test/pointercorrect.test.js#L1-L46) +- [memoryhelper.test.js:1-67](file://client/test/memoryhelper.test.js#L1-L67) + +## 结论 +本实用工具模块围绕“日志、内存、字符、指针”四大主题,提供了简洁高效的实现: +- 日志系统兼顾前端与后端,满足开发与生产的差异化需求; +- 内存辅助工具以位级写入为核心,支撑输入事件的紧凑序列化; +- 字符数字转换工具完善了键盘事件到内部表示的映射; +- 指针校正工具解决了视频播放场景下的坐标适配问题。 + +建议在实际项目中: +- 将日志级别纳入构建配置,按环境自动切换; +- 在输入设备状态序列化中优先使用 MemoryHelper 进行位级压缩; +- 对文本事件生成进行单元测试覆盖,保证字符映射一致性; +- 在视频播放器中使用 PointerCorrector 进行坐标映射与黑边补偿。 + +## 附录 + +### 使用示例(路径指引) +- 启用前端调试日志 + - 参考:[logger.js:3-9](file://client/src/logger.js#L3-L9) +- 设置后端日志级别 + - 参考:[log.ts:11-24](file://src/log.ts#L11-L24) +- 生成文本事件 + - 参考:[inputdevice.js:639-646](file://client/src/inputdevice.js#L639-L646) +- 按位写入布尔标志 + - 参考:[memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20) +- 指针坐标映射 + - 参考:[pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40) + +### 测试参考 +- 指针校正单元测试 + - 参考:[pointercorrect.test.js:1-46](file://client/test/pointercorrect.test.js#L1-L46) +- 内存辅助单元测试 + - 参考:[memoryhelper.test.js:1-67](file://client/test/memoryhelper.test.js#L1-L67) +- 输入设备与文本事件测试 + - 参考:[inputdevice.test.js:135-144](file://client/test/inputdevice.test.js#L135-L144) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/渲染流处理.md b/.qoder/repowiki/zh/content/WebRTC 实现/渲染流处理.md new file mode 100644 index 0000000..e9ee986 --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/渲染流处理.md @@ -0,0 +1,367 @@ +# 渲染流处理 + + +**本文引用的文件** +- [client/src/renderstreaming.js](file://client/src/renderstreaming.js) +- [client/src/peer.js](file://client/src/peer.js) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/src/logger.js](file://client/src/logger.js) +- [client/src/inputdevice.js](file://client/src/inputdevice.js) +- [client/src/inputremoting.js](file://client/src/inputremoting.js) +- [client/src/pointercorrect.js](file://client/src/pointercorrect.js) +- [client/src/gamepadhandler.js](file://client/src/gamepadhandler.js) +- [client/src/sender.js](file://client/src/sender.js) +- [client/test/renderstreaming.test.js](file://client/test/renderstreaming.test.js) +- [client/test/peerconnection.test.js](file://client/test/peerconnection.test.js) +- [client/public/onebyone/renderer.js](file://client/public/onebyone/renderer.js) +- [src/index.ts](file://src/index.ts) +- [package.json](file://package.json) + + +## 目录 +1. [引言](#引言) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 引言 +本文件聚焦“渲染流处理”模块,系统性阐述媒体流的接收、解码与显示全链路,涵盖 WebRTC 媒体流的信令与协商、轨道管理、渲染管线、性能优化与调试方法。文档面向不同技术背景读者,既提供高层架构视图,也包含代码级细节与可视化图表,帮助快速定位问题并进行针对性优化。 + +## 项目结构 +该项目由前端渲染与后端服务两部分组成: +- 前端 client:负责渲染、输入遥测、WebRTC 信令与协商、媒体轨道管理与显示。 +- 后端 src:基于 Express 的服务端入口,启动 HTTP/HTTPS 服务器并按需启用 WebSocket 信令。 + +```mermaid +graph TB +subgraph "前端 client" +RS["RenderStreaming
渲染流编排"] +PEER["Peer
RTCPeerConnection 封装"] +SIG["Signaling
HTTP/WebSocket 信令"] +RENDER["Renderer
UI 渲染器"] +INPUT["Input Remoting
输入事件打包"] +end +subgraph "后端 src" +APP["Express 应用"] +WS["WebSocket 信令服务"] +end +RS --> PEER +RS --> SIG +RENDER --> RS +INPUT --> SIG +SIG --> WS +APP --> WS +``` + +图表来源 +- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) +- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292) +- [client/public/onebyone/renderer.js:28-800](file://client/public/onebyone/renderer.js#L28-L800) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [package.json:1-60](file://package.json#L1-L60) + +## 核心组件 +- 渲染流编排(RenderStreaming) + - 负责连接生命周期管理、主机/参与方模式、多 peer 管理、事件转发与统计查询。 +- RTCPeerConnection 封装(Peer) + - 提供 SDP 协商、ICE 候选、轨道增删、统计数据查询与断连检测。 +- 信令(Signaling) + - 支持 HTTP 轮询与 WebSocket 两种模式,封装 offer/answer/candidate/on-message 等事件。 +- 输入遥测(Input Remoting) + - 将鼠标、键盘、触摸、手柄等输入事件序列化并通过数据通道传输。 +- 渲染器(Renderer) + - 将状态映射到 DOM,负责视频流显示、占位符切换、分辨率适配与网络质量指示。 + +章节来源 +- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) +- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292) +- [client/src/inputremoting.js:1-300](file://client/src/inputremoting.js#L1-L300) +- [client/public/onebyone/renderer.js:28-800](file://client/public/onebyone/renderer.js#L28-L800) + +## 架构总览 +渲染流处理采用“信令驱动 + PeerConnection 协商 + 轨道渲染”的分层架构。前端通过 Signaling 与后端建立连接,随后在 RenderStreaming 中完成主机/参与方角色下的 peer 管理与事件路由;Peer 负责 SDP 与 ICE 流程;Renderer 负责将媒体轨道映射到页面元素。 + +```mermaid +sequenceDiagram +participant UI as "页面/UI" +participant RS as "RenderStreaming" +participant SIG as "Signaling(HTTP/WebSocket)" +participant PC as "Peer(RTCPeerConnection)" +participant REN as "Renderer" +UI->>RS : 创建连接/发起协商 +RS->>SIG : createConnection() +SIG-->>RS : connect(connectId, role) +RS->>PC : _preparePeerConnection() +UI->>RS : addTrack()/createDataChannel() +RS->>PC : addTrack()/createDataChannel() +PC-->>RS : onnegotiationneeded -> setLocalDescription() +PC-->>SIG : sendoffer(sdp) +SIG-->>RS : offer +RS->>PC : onGotDescription(offer) +PC-->>PC : setRemoteDescription() +PC-->>PC : setLocalDescription() -> answer +PC-->>SIG : sendanswer(sdp) +SIG-->>RS : answer +RS->>PC : onGotDescription(answer) +PC-->>UI : ontrack -> trackevent +RS-->>REN : onTrackEvent(data) +REN-->>UI : 设置 video.srcObject / 占位符切换 +``` + +图表来源 +- [client/src/renderstreaming.js:40-130](file://client/src/renderstreaming.js#L40-L130) +- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) +- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) +- [client/public/onebyone/renderer.js:397-604](file://client/public/onebyone/renderer.js#L397-L604) + +## 详细组件分析 + +### 渲染流编排(RenderStreaming) +- 角色与连接 + - 主机(host):维护多个 participantId -> Peer 的映射,按需创建/复用 peer。 + - 参与方(participant):单一 peer,连接建立后立即协商。 +- 事件路由 + - 将底层 Peer 的 trackevent/adddatachannel/offer/answer 等事件透传给上层回调。 +- 统计与通道 + - 提供 getStats、createDataChannel、addTrack/addTransceiver 等能力,支持按 participantId 路由。 + +```mermaid +classDiagram +class RenderStreaming { ++onConnect() ++onDisconnect() ++onGotOffer() ++onGotAnswer() ++onTrackEvent() ++onAddChannel() ++onMessage() ++onParticipantLeft() ++onParticipantJoined() ++onNewPeer() ++createConnection() ++deleteConnection() ++getStats() ++createDataChannel() ++addTrack() ++addTransceiver() ++getTransceivers() ++sendMessage() ++start() ++stop() +} +class Peer { ++addEventListener() ++close() ++getTransceivers() ++addTrack() ++addTransceiver() ++createDataChannel() ++getStats() ++onGotDescription() ++onGotCandidate() +} +RenderStreaming --> Peer : "管理/路由" +``` + +图表来源 +- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) + +章节来源 +- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) + +### RTCPeerConnection 封装(Peer) +- 协商流程 + - onnegotiationneeded 自动生成 offer 并通过事件上报;收到远端 offer 后 setRemoteDescription,再 setLocalDescription 生成 answer。 +- ICE 候选 + - onicecandidate 上报候选,支持轮询重发 offer 以应对网络抖动。 +- 断连检测 + - iceConnectionState=failed 触发 disconnect 事件。 + +```mermaid +flowchart TD +Start(["开始"]) --> Negotiation["onNegotiationNeeded"] +Negotiation --> SetL["setLocalDescription() 生成 offer"] +SetL --> SendOffer["dispatchEvent('sendoffer')"] +SendOffer --> RecvOffer["收到远端 offer"] +RecvOffer --> SetR["setRemoteDescription(offer)"] +SetR --> SetL2["setLocalDescription() 生成 answer"] +SetL2 --> SendAnswer["dispatchEvent('sendanswer')"] +SendAnswer --> RecvAnswer["收到远端 answer"] +RecvAnswer --> Stable["signalingState=stable"] +Stable --> Track["ontrack -> trackevent"] +Track --> End(["结束"]) +``` + +图表来源 +- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) + +章节来源 +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) + +### 信令(Signaling) +- HTTP 轮询 + - start() 循环拉取消息队列,分发 connect/disconnect/offer/answer/candidate/on-message。 +- WebSocket + - WebSocketSignaling 通过 ws/wss 通道推送消息,支持广播与多参与方路由。 + +```mermaid +sequenceDiagram +participant RS as "RenderStreaming" +participant HTTP as "Signaling(HTTP)" +participant WS as "WebSocketSignaling" +RS->>HTTP : PUT /signaling/connection +HTTP-->>RS : connect(connectId, polite) +RS->>HTTP : POST /signaling/offer|answer|candidate +HTTP-->>RS : 轮询返回消息 +RS->>WS : createConnection/sendOffer... +WS-->>RS : offer/answer/candidate/on-message +``` + +图表来源 +- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) +- [client/src/signaling.js:230-291](file://client/src/signaling.js#L230-L291) + +章节来源 +- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292) + +### 输入遥测与渲染(Input Remoting + Renderer) +- 输入遥测 + - Sender 负责捕获鼠标/键盘/触摸/手柄事件,通过 PointerCorrector 校正坐标,打包为 StateEvent/TextEvent,经 RTCDataChannel 发送。 +- 渲染器 + - Renderer 将状态映射到 DOM,设置 video.srcObject、占位符切换、分辨率适配、网络质量指示与用户列表。 + +```mermaid +sequenceDiagram +participant UI as "页面" +participant SEN as "Sender" +participant IR as "InputRemoting" +participant SIG as "Signaling" +participant REN as "Renderer" +UI->>SEN : 鼠标/键盘/触摸/手柄事件 +SEN->>IR : StateEvent/TextEvent +IR->>SIG : RTCDataChannel 发送 +SIG-->>UI : on-message 回传可选 +UI->>REN : 状态变更 +REN-->>UI : 设置 video.srcObject / 占位符 +``` + +图表来源 +- [client/src/sender.js:14-188](file://client/src/sender.js#L14-L188) +- [client/src/inputremoting.js:63-169](file://client/src/inputremoting.js#L63-L169) +- [client/public/onebyone/renderer.js:397-604](file://client/public/onebyone/renderer.js#L397-L604) + +章节来源 +- [client/src/inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [client/src/inputremoting.js:1-300](file://client/src/inputremoting.js#L1-L300) +- [client/src/pointercorrect.js:1-125](file://client/src/pointercorrect.js#L1-L125) +- [client/src/sender.js:14-188](file://client/src/sender.js#L14-L188) +- [client/public/onebyone/renderer.js:28-800](file://client/public/onebyone/renderer.js#L28-L800) + +## 依赖关系分析 +- 前端依赖 + - RenderStreaming 依赖 Peer 与 Signaling;Peer 依赖浏览器 WebRTC API;Renderer 依赖 DOM 与媒体轨道。 +- 后端依赖 + - Express 应用启动,按配置选择 WebSocket 或 HTTP 信令;日志与选项解析来自独立模块。 + +```mermaid +graph LR +RS["RenderStreaming"] --> PEER["Peer"] +RS --> SIG["Signaling"] +PEER --> BR["浏览器 WebRTC API"] +SIG --> WS["WebSocket 服务"] +REN["Renderer"] --> DOM["DOM/VideoElement"] +APP["Express 应用"] --> WS +``` + +图表来源 +- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) +- [client/src/signaling.js:3-292](file://client/src/signaling.js#L3-L292) +- [client/public/onebyone/renderer.js:28-800](file://client/public/onebyone/renderer.js#L28-L800) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [package.json:14-46](file://package.json#L14-L46) + +## 性能考量 +- 缓冲与丢帧 + - 使用 ontrack 事件及时设置 video.srcObject,避免重复赋值导致的播放中断;监听视频轨道 resize 事件动态调整显示尺寸。 +- 延迟控制 + - 通过 WebSocket 信令降低 HTTP 轮询延迟;Peer 内部定时重发 offer,提升握手成功率。 +- 带宽与质量 + - 通过 getStats 获取 ICE/媒体统计,结合 Renderer 的网络质量指示,辅助进行自适应策略(例如降分辨率/帧率)。 +- 渲染优化 + - 使用 object-fit 与容器宽高比计算,减少重绘;仅在必要时切换占位符,避免频繁 DOM 更新。 + +章节来源 +- [client/public/onebyone/renderer.js:575-604](file://client/public/onebyone/renderer.js#L575-L604) +- [client/src/peer.js:75-82](file://client/src/peer.js#L75-L82) +- [client/src/peer.js:124-130](file://client/src/peer.js#L124-L130) + +## 故障排查指南 +- 无法建立连接 + - 检查 Signaling.start() 是否成功;确认 connect/disconnect 事件是否触发;核对 sessionId 与连接 ID。 +- 协商失败 + - 关注 Peer 的 onnegotiationneeded 与 offer/answer 流程;确认 ignoreOffer 条件与 signalingState。 +- ICE 候选异常 + - 核验 onicecandidate 事件是否上报;确认 addIceCandidate 的调用时机与错误日志。 +- 渲染无画面 + - 检查 video.srcObject 是否设置;确认 trackevent 是否到达;关注占位符切换逻辑。 +- 日志与调试 + - 使用 Logger.enable() 开启调试输出;通过 getStats 输出详细统计信息。 + +章节来源 +- [client/src/signaling.js:30-91](file://client/src/signaling.js#L30-L91) +- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) +- [client/public/onebyone/renderer.js:397-406](file://client/public/onebyone/renderer.js#L397-L406) + +## 结论 +本模块以 RenderStreaming 为核心,串联信令、协商与渲染三大环节,形成稳定高效的 WebRTC 渲染流处理框架。通过清晰的事件路由、完善的统计接口与直观的 UI 渲染器,既能满足多参与方场景,也能在复杂网络条件下保持良好的用户体验。建议在生产环境中结合 getStats 实施自适应策略,并持续优化渲染器的 DOM 更新频率与媒体轨道处理路径。 + +## 附录 + +### 渲染流配置与参数 +- 分辨率与帧率 + - 通过视频轨道的 getSettings 获取 width/height;在渲染器中根据宽高比调整显示尺寸。 +- 编码参数 + - 通过 MediaStreamConstraints 与 RTCPeerConnection 的配置对象传递;具体编码参数取决于浏览器与对端支持。 +- 数据通道 + - 使用 createDataChannel 发送输入事件;注意通道状态与 readyState。 + +章节来源 +- [client/public/onebyone/renderer.js:781-791](file://client/public/onebyone/renderer.js#L781-L791) +- [client/src/peer.js:116-122](file://client/src/peer.js#L116-L122) + +### 媒体轨道管理 +- 音频/视频分离 + - addTrack/addTransceiver 支持分别添加音频与视频轨道;Renderer 依据轨道数量决定占位符与播放行为。 +- 质量自适应 + - 结合 getStats 的比特率、丢包与 RTT 指标,动态调整轨道方向或发送参数。 + +章节来源 +- [client/src/peer.js:100-114](file://client/src/peer.js#L100-L114) +- [client/public/onebyone/renderer.js:575-604](file://client/public/onebyone/renderer.js#L575-L604) + +### 事件处理与调试示例(路径指引) +- 渲染流事件 + - [client/test/renderstreaming.test.js:78-133](file://client/test/renderstreaming.test.js#L78-L133) +- Peer 协商事件 + - [client/test/peerconnection.test.js:43-104](file://client/test/peerconnection.test.js#L43-L104) +- 输入事件打包 + - [client/src/inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141) +- 视频渲染设置 + - [client/public/onebyone/renderer.js:397-406](file://client/public/onebyone/renderer.js#L397-L406) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/触摸输入处理.md b/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/触摸输入处理.md new file mode 100644 index 0000000..6814d8b --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/触摸输入处理.md @@ -0,0 +1,385 @@ +# 触摸输入处理 + + +**本文引用的文件** +- [touchflags.js](file://client/src/touchflags.js) +- [touchphase.js](file://client/src/touchphase.js) +- [inputdevice.js](file://client/src/inputdevice.js) +- [inputremoting.js](file://client/src/inputremoting.js) +- [memoryhelper.js](file://client/src/memoryhelper.js) +- [pointercorrect.js](file://client/src/pointercorrect.js) +- [register-events.js](file://client/public/videoplayer/js/register-events.js) +- [video-player.js](file://client/public/videoplayer/js/video-player.js) +- [main.js](file://client/public/videoplayer/js/main.js) +- [inputdevice.test.js](file://client/test/inputdevice.test.js) +- [inputremoting.test.js](file://client/test/inputremoting.test.js) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件聚焦于移动端触摸输入的处理机制,涵盖触摸事件的捕获与转发、触摸点标识、多点触控支持、手势识别基础、坐标系统映射、压力感应与触摸相位管理。文档基于仓库中的触摸输入相关源码,结合测试用例与客户端示例,给出可操作的实现路径与最佳实践。 + +## 项目结构 +触摸输入处理涉及三层: +- 输入设备建模层:定义触摸状态、事件格式与序列化协议 +- 输入远程转发层:将本地输入事件打包并通过数据通道发送 +- 客户端事件绑定层:在页面元素上注册触摸事件监听并进行坐标映射 + +```mermaid +graph TB +subgraph "输入设备建模层" +A["touchphase.js
触摸相位常量"] +B["touchflags.js
触摸标志位"] +C["inputdevice.js
Touchscreen/ToucnState/TouchnscreenState
MemoryHelper"] +end +subgraph "输入远程转发层" +D["inputremoting.js
InputRemoting/NewEventsMsg/Message"] +end +subgraph "客户端事件绑定层" +E["register-events.js
注册触摸事件/坐标映射"] +F["video-player.js
计算视频区域偏移与缩放"] +G["main.js
设置元素 touch-action:none"] +end +A --> C +B --> C +C --> D +D --> E +F --> E +G --> E +``` + +**图表来源** +- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9) +- [touchflags.js:1-8](file://client/src/touchflags.js#L1-L8) +- [inputdevice.js:111-118](file://client/src/inputdevice.js#L111-L118) +- [inputremoting.js:63-91](file://client/src/inputremoting.js#L63-L91) +- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) +- [main.js:58-65](file://client/public/videoplayer/js/main.js#L58-L65) + +**章节来源** +- [inputdevice.js:111-118](file://client/src/inputdevice.js#L111-L118) +- [inputremoting.js:63-91](file://client/src/inputremoting.js#L63-L91) +- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) +- [main.js:58-65](file://client/public/videoplayer/js/main.js#L58-L65) + +## 核心组件 +- 触摸相位与标志 + - 相位:None、Began、Moved、Ended、Canceled、Stationary + - 标志:IndirectTouch、PrimaryTouch、Tap、OrphanedPrimaryTouch +- 触摸状态建模 + - TouchState:包含触摸点唯一ID、位置、增量、压力、半径、相位、点击次数、显示索引、标志、起始时间与起始位置等字段 + - TouchscreenState:聚合多个TouchState,负责将TouchEvent转换为内部状态数组 +- 输入事件序列化 + - TouchState.buffer与TouchscreenState.buffer将状态编码为二进制帧 +- 输入远程转发 + - InputRemoting监听本地输入事件,通过NewEventsMsg封装后经订阅者发送 +- 客户端事件绑定与坐标映射 + - 在播放器元素上注册touchstart/move/end/cancel事件 + - 使用PointerCorrector或VideoPlayer提供的缩放与偏移参数进行坐标映射 + +**章节来源** +- [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-453](file://client/src/inputdevice.js#L324-L453) +- [inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) +- [inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141) +- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) + +## 架构总览 +触摸事件从浏览器到远端的典型流程如下: + +```mermaid +sequenceDiagram +participant U as "用户" +participant V as "VideoPlayer
video-player.js" +participant R as "注册函数
register-events.js" +participant S as "序列化
inputdevice.js" +participant M as "远程转发
inputremoting.js" +U->>R : "触摸事件 : touchstart/move/end/cancel" +R->>R : "收集changedTouches与touches" +R->>V : "读取videoScale/videoOriginX/Y" +R->>R : "坐标映射与相位判定" +R->>S : "构造TouchState/TouchnscreenState" +S-->>R : "buffer(二进制)" +R->>M : "NewEventsMsg.create(state)" +M-->>M : "Message封装" +M-->>远端 : "发送二进制帧" +``` + +**图表来源** +- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) +- [inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) +- [inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141) + +## 详细组件分析 + +### 触摸相位与标志 +- 相位常量用于统一表示触摸生命周期各阶段,便于跨平台一致性处理 +- 标志位用于表达触摸属性(如主触摸、点击标记等),便于后续手势合成与策略判断 + +```mermaid +classDiagram +class TouchPhase { ++None ++Began ++Moved ++Ended ++Canceled ++Stationary +} +class TouchFlags { ++IndirectTouch ++PrimaryTouch ++Tap ++OrphanedPrimaryTouch +} +``` + +**图表来源** +- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9) +- [touchflags.js:1-8](file://client/src/touchflags.js#L1-L8) + +**章节来源** +- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9) +- [touchflags.js:1-8](file://client/src/touchflags.js#L1-L8) + +### 触摸状态建模与序列化 +- TouchState字段覆盖位置、增量、压力、半径、相位、起始时间与起始位置等,满足轨迹预测与手势识别需求 +- TouchscreenState聚合多点触摸,将Web TouchEvent转换为内部状态数组 +- MemoryHelper提供按位写入能力,支撑布尔型标志位的紧凑存储 + +```mermaid +classDiagram +class TouchState { ++touchId : number ++position : number[] ++delta : number[] ++pressure : number ++radius : number[] ++phaseId : number ++tapCount : number ++displayIndex : number ++flags : number ++padding : number ++startTime : number ++startPosition : number[] ++buffer() ArrayBuffer ++format() number +} +class TouchscreenState { ++touchData : TouchState[] ++buffer() ArrayBuffer ++format() number ++convertPhaseId(type) number +} +class MemoryHelper { ++writeSingleBit(buffer, bitOffset, value) void ++sizeOfInt : number +} +TouchscreenState --> TouchState : "聚合" +TouchState --> MemoryHelper : "使用" +``` + +**图表来源** +- [inputdevice.js:324-453](file://client/src/inputdevice.js#L324-L453) +- [inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) +- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) + +**章节来源** +- [inputdevice.js:324-453](file://client/src/inputdevice.js#L324-L453) +- [inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) +- [memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) + +### 客户端触摸事件绑定与坐标映射 +- 在播放器元素上注册触摸事件监听,区分Began/Moved/Ended/Canceled +- 通过VideoPlayer提供的videoScale、videoOriginX/Y与videoHeight进行坐标映射,适配letterbox与CSS布局 +- 对于Unity坐标系,需要对Y轴做翻转处理 + +```mermaid +flowchart TD +Start(["触摸事件触发"]) --> Collect["收集changedTouches与touches"] +Collect --> Phase["根据是否存在于touches判定Stationary或对应相位"] +Phase --> Map["读取videoScale/videoOriginX/Y与videoHeight"] +Map --> Scale["坐标映射: (x-origin)/scale 或 (height-(y-origin))/scale"] +Scale --> Pack["构造二进制帧: TouchState/TouchnscreenState"] +Pack --> End(["发送至InputRemoting"]) +``` + +**图表来源** +- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) + +**章节来源** +- [register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) +- [main.js:58-65](file://client/public/videoplayer/js/main.js#L58-L65) + +### 输入远程转发与消息封装 +- InputRemoting监听本地输入事件,调用NewEventsMsg.create将状态事件封装为Message +- Message包含参与者ID、消息类型与数据载荷,供订阅者发送 + +```mermaid +sequenceDiagram +participant L as "LocalInputManager" +participant IR as "InputRemoting" +participant NE as "NewEventsMsg" +participant MSG as "Message" +L->>IR : "onEvent : event" +IR->>NE : "create(state)" +NE-->>IR : "Message" +IR-->>订阅者 : "onNext(Message)" +``` + +**图表来源** +- [inputremoting.js:79-91](file://client/src/inputremoting.js#L79-L91) +- [inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141) +- [inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277) +- [inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232) + +**章节来源** +- [inputremoting.js:63-91](file://client/src/inputremoting.js#L63-L91) +- [inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141) +- [inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277) +- [inputremoting.js:184-232](file://client/src/inputremoting.js#L184-L232) + +### 坐标系统与Letterbox处理 +- PointerCorrector提供从元素坐标到视频坐标的映射,考虑letterbox偏移与缩放 +- VideoPlayer在resizeVideo时计算videoScale与videoOriginX/Y,确保触摸坐标与渲染区域一致 + +```mermaid +classDiagram +class PointerCorrector { ++map(position) number[] ++reset(w,h,elem) void ++contentRect() Object ++letterBoxType() number ++letterBoxSize() number +} +class VideoPlayer { ++resizeVideo() void ++videoScale : number ++videoOriginX : number ++videoOriginY : number +} +PointerCorrector --> VideoPlayer : "配合使用" +``` + +**图表来源** +- [pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) + +**章节来源** +- [pointercorrect.js:6-124](file://client/src/pointercorrect.js#L6-L124) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) + +### 多点触控与手势识别基础 +- TouchscreenState支持最多10个触摸点,通过TouchState.prevTouches缓存上一帧状态,便于计算delta与轨迹 +- 测试用例验证TouchscreenState能正确生成buffer,表明多点数据可被序列化传输 + +```mermaid +flowchart TD +TStart(["touchstart"]) --> Cache["缓存TouchState(prevTouches)"] +TM["touchmove"] --> Delta["计算delta与轨迹"] +TEnd(["touchend"]) --> Tap["标记点击/结束状态"] +Cache --> Delta +Delta --> TEnd +``` + +**图表来源** +- [inputdevice.js:494-516](file://client/src/inputdevice.js#L494-L516) +- [inputdevice.test.js:69-100](file://client/test/inputdevice.test.js#L69-L100) + +**章节来源** +- [inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) +- [inputdevice.test.js:69-100](file://client/test/inputdevice.test.js#L69-L100) + +### 压力感应与触摸相位管理 +- TouchState记录pressure与radius,支持压力与接触面信息 +- 相位管理由TouchscreenState.convertPhaseId统一转换,保证与TouchPhase一致 + +**章节来源** +- [inputdevice.js:324-453](file://client/src/inputdevice.js#L324-L453) +- [inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) + +### 具体代码示例路径 +- 如何处理移动端触摸事件:[register-events.js:161-281](file://client/public/videoplayer/js/register-events.js#L161-L281) +- 触摸轨迹预测与多点触控:[inputdevice.js:455-538](file://client/src/inputdevice.js#L455-L538) +- 坐标映射与letterbox处理:[pointercorrect.js:20-40](file://client/src/pointercorrect.js#L20-L40),[video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) +- 触摸相位与标志:[touchphase.js:1-9](file://client/src/touchphase.js#L1-L9),[touchflags.js:1-8](file://client/src/touchflags.js#L1-L8) + +## 依赖关系分析 +- TouchscreenState依赖TouchState与TouchPhase、TouchFlags +- TouchState依赖MemoryHelper进行位级写入 +- InputRemoting依赖NewEventsMsg与Message进行事件封装与发送 +- 客户端事件绑定依赖VideoPlayer提供的缩放与偏移参数 + +```mermaid +graph LR +TP["touchphase.js"] --> IDS["inputdevice.js"] +TF["touchflags.js"] --> IDS +MH["memoryhelper.js"] --> IDS +IDS --> IR["inputremoting.js"] +VP["video-player.js"] --> RE["register-events.js"] +IR --> RE +``` + +**图表来源** +- [inputdevice.js:9-10](file://client/src/inputdevice.js#L9-L10) +- [inputremoting.js:1-8](file://client/src/inputremoting.js#L1-L8) +- [register-events.js:1-4](file://client/public/videoplayer/js/register-events.js#L1-L4) + +**章节来源** +- [inputdevice.js:9-10](file://client/src/inputdevice.js#L9-L10) +- [inputremoting.js:1-8](file://client/src/inputremoting.js#L1-L8) +- [register-events.js:1-4](file://client/public/videoplayer/js/register-events.js#L1-L4) + +## 性能考量 +- 二进制序列化:TouchState/TouchnscreenState采用定长字段与紧凑布局,减少序列化开销 +- 缓存策略:TouchState.prevTouches缓存上一帧状态,避免重复计算与查找 +- 事件节流:在高频touchmove场景中,建议按需采样或合并更新,降低发送频率 +- 坐标映射:在resize频繁的场景,应避免在每帧都重建映射对象,复用VideoPlayer计算结果 + +## 故障排查指南 +- 触摸事件未生效 + - 检查播放器元素是否设置了touch-action:none,防止默认滚动行为干扰 + - 确认已注册touchstart/move/end/cancel事件监听 +- 坐标错位 + - 确保在resize后调用VideoPlayer.resizeVideo重新计算videoScale与videoOriginX/Y + - 检查letterbox类型与偏移是否正确 +- 压力或半径无效 + - 确认设备与浏览器支持force/radius属性;若不支持,pressure/radius可能为0 +- 手势识别异常 + - 检查TouchState.prevTouches缓存是否正确更新 + - 确认相位转换逻辑与TouchPhase一致 + +**章节来源** +- [main.js:58-65](file://client/public/videoplayer/js/main.js#L58-L65) +- [video-player.js:157-213](file://client/public/videoplayer/js/video-player.js#L157-L213) +- [inputdevice.js:494-516](file://client/src/inputdevice.js#L494-L516) +- [touchphase.js:1-9](file://client/src/touchphase.js#L1-L9) + +## 结论 +该触摸输入处理方案通过清晰的状态建模、稳定的相位与标志体系、高效的二进制序列化以及可靠的客户端坐标映射,实现了移动端触摸事件的可靠捕获与转发。结合多点触控与压力信息,可为进一步的手势识别与轨迹预测奠定基础。建议在高刷场景中引入采样与合并策略,以平衡交互流畅性与网络开销。 + +## 附录 +- 测试用例验证了TouchscreenState的buffer生成与格式正确性,可作为集成测试参考 +- 客户端示例展示了如何在播放器元素上注册触摸事件并进行坐标映射 + +**章节来源** +- [inputdevice.test.js:69-100](file://client/test/inputdevice.test.js#L69-L100) +- [inputremoting.test.js:59-106](file://client/test/inputremoting.test.js#L59-L106) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/输入设备控制.md b/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/输入设备控制.md new file mode 100644 index 0000000..2b24bdc --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/输入设备控制.md @@ -0,0 +1,472 @@ +# 输入设备控制 + + +**本文引用的文件** +- [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 + +```mermaid +graph TB +subgraph "输入采集层" +S["Sender
事件采集与分发"] +GP["GamepadHandler
手柄轮询"] +PC["PointerCorrector
坐标映射"] +end +subgraph "输入抽象层" +ID["InputDevice/IInputState
设备与状态模型"] +IE["InputEvent/StateEvent/TextEvent
事件与状态封装"] +end +subgraph "远程转发层" +IR["InputRemoting
事件订阅与发送"] +MSG["Message/NewEventsMsg/NewDeviceMsg
消息封装"] +end +subgraph "传输层" +RS["RenderStreaming
信令与Peer管理"] +PEER["Peer
RTCPeerConnection封装"] +SIG["Signaling/WebSocketSignaling
信令通道"] +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) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/键盘输入处理.md b/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/键盘输入处理.md new file mode 100644 index 0000000..bd2bf3c --- /dev/null +++ b/.qoder/repowiki/zh/content/WebRTC 实现/输入设备控制/键盘输入处理.md @@ -0,0 +1,273 @@ +# 键盘输入处理 + + +**本文档引用的文件** +- [client/src/keymap.js](file://client/src/keymap.js) +- [client/src/inputdevice.js](file://client/src/inputdevice.js) +- [client/src/inputremoting.js](file://client/src/inputremoting.js) +- [client/src/sender.js](file://client/src/sender.js) +- [client/src/memoryhelper.js](file://client/src/memoryhelper.js) +- [client/src/charnumber.js](file://client/src/charnumber.js) +- [client/test/inputdevice.test.js](file://client/test/inputdevice.test.js) +- [client/test/inputremoting.test.js](file://client/test/inputremoting.test.js) +- [client/public/onebyone/main.js](file://client/public/onebyone/main.js) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件系统性地文档化了视频流客户端中的键盘输入处理机制,覆盖按键按下、释放与重复事件的捕获与转发;键盘映射表与按键码转换;特殊键处理;键盘事件状态同步、按键组合检测与输入法支持;以及跨平台键盘布局差异、修饰键状态与跨浏览器兼容性的实践建议。通过代码级分析与可视化图示,帮助开发者在不直接阅读源码的情况下也能快速理解与扩展键盘输入子系统。 + +## 项目结构 +键盘输入处理位于前端客户端模块中,核心文件包括: +- 键盘映射与状态:keymap.js、inputdevice.js +- 输入设备抽象与事件打包:inputdevice.js、inputremoting.js +- 本地事件采集与发送:sender.js +- 辅助工具:memoryhelper.js、charnumber.js +- 测试用例:inputdevice.test.js、inputremoting.test.js +- 应用层快捷键示例:public/onebyone/main.js + +```mermaid +graph TB +subgraph "键盘输入处理模块" +KM["Keymap 映射表
keymap.js"] +ID["输入设备与状态
inputdevice.js"] +IR["输入远程转发
inputremoting.js"] +SND["本地采集与发送
sender.js"] +MH["内存位操作
memoryhelper.js"] +CN["字符编码映射
charnumber.js"] +end +subgraph "应用层" +APP["应用快捷键示例
public/onebyone/main.js"] +end +KM --> ID +MH --> ID +CN --> ID +ID --> IR +SND --> IR +APP --> SND +``` + +**图表来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/src/inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [client/src/inputremoting.js:1-300](file://client/src/inputremoting.js#L1-L300) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/src/memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [client/src/charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [client/public/onebyone/main.js:132-145](file://client/public/onebyone/main.js#L132-L145) + +**章节来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/src/inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [client/src/inputremoting.js:1-300](file://client/src/inputremoting.js#L1-L300) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/src/memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [client/src/charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [client/public/onebyone/main.js:132-145](file://client/public/onebyone/main.js#L132-L145) + +## 核心组件 +- 键盘映射表 Keymap:将 DOM KeyboardEvent.code 转换为内部键位索引,统一不同键盘布局下的按键标识。 +- 键盘状态 KeyboardState:基于位图记录每个键的按下/释放状态,支持按键重复事件的忽略与文本事件的生成。 +- 输入设备 Keyboard:封装键盘设备描述与事件队列,负责将 DOM 事件转换为内部状态。 +- 输入远程转发 InputRemoting:订阅本地事件,打包为消息并通过观察者发送。 +- 本地采集 Sender:注册文档级键盘事件监听,区分重复事件,生成状态事件与文本事件,并通过自定义事件广播。 +- 内存辅助 MemoryHelper:提供按位写入能力,用于高效更新键位状态位图。 +- 字符编码 CharNumber:将 DOM KeyboardEvent.key 映射为字符编码,用于文本事件。 + +**章节来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/src/inputdevice.js:100-322](file://client/src/inputdevice.js#L100-L322) +- [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/memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [client/src/charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) + +## 架构总览 +键盘输入从 DOM 事件开始,经由 Sender 采集,生成 KeyboardState 与可选的 TextEvent,再由 InputRemoting 打包并通过 RTC 数据通道发送至远端。 + +```mermaid +sequenceDiagram +participant DOM as "DOM 文档" +participant SND as "Sender" +participant KB as "Keyboard 设备" +participant KS as "KeyboardState" +participant IR as "InputRemoting" +participant OBS as "Observer" +DOM->>SND : "keydown/keyup 事件" +SND->>SND : "区分重复事件与文本事件" +SND->>KB : "queueEvent(KeyboardEvent)" +KB->>KS : "构造状态(按键按下/释放)" +SND->>IR : "派发 StateEvent/TextEvent 自定义事件" +IR->>IR : "NewEventsMsg.create(...) 打包" +IR-->>OBS : "onNext(Message)" +OBS-->>DOM : "RTC 数据通道发送" +``` + +**图表来源** +- [client/src/sender.js:130-143](file://client/src/sender.js#L130-L143) +- [client/src/inputdevice.js:100-109](file://client/src/inputdevice.js#L100-L109) +- [client/src/inputdevice.js:274-322](file://client/src/inputdevice.js#L274-L322) +- [client/src/inputremoting.js:138-141](file://client/src/inputremoting.js#L138-L141) +- [client/src/inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277) +- [client/src/sender.js:170-182](file://client/src/sender.js#L170-L182) + +## 详细组件分析 + +### 键盘映射表与按键码转换 +- Keymap 将标准的 KeyboardEvent.code 映射到连续整数索引,确保不同键盘布局下按键的统一识别。 +- MemoryHelper.writeSingleBit 使用位图方式更新状态,避免逐键遍历,提升性能。 +- CharNumber 提供 KeyboardEvent.key 到字符编码的映射,用于 TextEvent 的字符字段。 + +```mermaid +flowchart TD +Start(["按键事件进入"]) --> GetCode["读取 KeyboardEvent.code"] +GetCode --> LookupKM["Keymap 查找键位索引"] +LookupKM --> BitOp["MemoryHelper.writeSingleBit 更新位图"] +BitOp --> Done(["完成状态更新"]) +``` + +**图表来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/src/memoryhelper.js:7-20](file://client/src/memoryhelper.js#L7-L20) +- [client/src/inputdevice.js:287-307](file://client/src/inputdevice.js#L287-L307) + +**章节来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/src/memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [client/src/charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [client/src/inputdevice.js:274-322](file://client/src/inputdevice.js#L274-L322) + +### 键盘事件状态同步与重复事件处理 +- Sender 在 _onKeyEvent 中区分事件类型与重复标志,仅在非重复的 keydown 时生成 StateEvent,同时始终生成 TextEvent,保证文本输入与状态同步。 +- KeyboardState 根据事件类型设置对应键位的布尔值,并通过位图持久化当前状态,支持后续状态查询与同步。 + +```mermaid +flowchart TD +EvtIn(["keydown/keyup 进入"]) --> Type{"事件类型"} +Type --> |keydown| Repeat{"是否重复?"} +Repeat --> |是| SkipState["跳过 StateEvent"] +Repeat --> |否| GenState["生成 StateEvent"] +Type --> |keyup| UpState["生成 StateEvent"] +GenState --> GenText["生成 TextEvent"] +UpState --> GenText +SkipState --> End(["结束"]) +GenText --> End +``` + +**图表来源** +- [client/src/sender.js:130-143](file://client/src/sender.js#L130-L143) +- [client/src/inputdevice.js:287-307](file://client/src/inputdevice.js#L287-L307) + +**章节来源** +- [client/src/sender.js:130-143](file://client/src/sender.js#L130-L143) +- [client/src/inputdevice.js:274-322](file://client/src/inputdevice.js#L274-L322) + +### 特殊键与修饰键处理 +- 特殊键(如功能键、方向键、锁定键)通过 Keymap 统一映射,确保跨平台一致性。 +- 修饰键(Shift/Alt/Control/Meta)同样映射到固定索引,便于在位图中跟踪其状态。 +- 应用层示例展示了如何在特定上下文中拦截修饰键组合(例如 Ctrl+V),体现修饰键在用户交互中的作用。 + +**章节来源** +- [client/src/keymap.js:52-66](file://client/src/keymap.js#L52-L66) +- [client/public/onebyone/main.js:140-144](file://client/public/onebyone/main.js#L140-L144) + +### 文本事件与输入法支持 +- TextEvent 通过 CharNumber 将 KeyboardEvent.key 转换为字符编码,作为文本输入的载体。 +- 该机制独立于键盘状态位图,确保即使在某些平台或浏览器上按键重复导致状态事件被跳过,文本仍能正确传递。 + +**章节来源** +- [client/src/inputdevice.js:620-660](file://client/src/inputdevice.js#L620-L660) +- [client/src/charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) + +### 跨平台键盘布局差异与兼容性 +- 使用 KeyboardEvent.code 作为按键标识,避免因不同键盘布局导致的 key 值变化,提高跨平台一致性。 +- 对于需要根据 key 值进行语义判断的场景(如应用快捷键),可结合 KeyboardEvent.key 与修饰键状态进行综合判断。 +- 针对输入法候选窗口等复杂输入流程,建议在应用层通过监听 compositionstart/compositionupdate/compositionend 等事件进行补充处理(当前键盘子系统主要关注按键与文本事件)。 + +**章节来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/public/onebyone/main.js:132-145](file://client/public/onebyone/main.js#L132-L145) + +## 依赖关系分析 +键盘输入处理模块内部依赖清晰,职责分离明确: +- keymap.js 与 charnumber.js 为数据层,提供映射与编码。 +- inputdevice.js 为核心业务层,封装设备与状态。 +- memoryhelper.js 提供底层位操作能力。 +- sender.js 负责事件采集与分发。 +- inputremoting.js 负责消息打包与传输。 +- 测试用例验证关键行为与格式。 + +```mermaid +graph LR +KM["keymap.js"] --> ID["inputdevice.js"] +CN["charnumber.js"] --> ID +MH["memoryhelper.js"] --> ID +ID --> IR["inputremoting.js"] +SND["sender.js"] --> IR +TEST1["inputdevice.test.js"] --> ID +TEST2["inputremoting.test.js"] --> IR +``` + +**图表来源** +- [client/src/keymap.js:1-120](file://client/src/keymap.js#L1-L120) +- [client/src/charnumber.js:1-110](file://client/src/charnumber.js#L1-L110) +- [client/src/memoryhelper.js:1-28](file://client/src/memoryhelper.js#L1-L28) +- [client/src/inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [client/src/inputremoting.js:1-300](file://client/src/inputremoting.js#L1-L300) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/test/inputdevice.test.js:1-173](file://client/test/inputdevice.test.js#L1-L173) +- [client/test/inputremoting.test.js:1-132](file://client/test/inputremoting.test.js#L1-L132) + +**章节来源** +- [client/src/inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) +- [client/src/inputremoting.js:1-300](file://client/src/inputremoting.js#L1-L300) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/test/inputdevice.test.js:1-173](file://client/test/inputdevice.test.js#L1-L173) +- [client/test/inputremoting.test.js:1-132](file://client/test/inputremoting.test.js#L1-L132) + +## 性能考虑 +- 位图存储:KeyboardState 使用位图存储键位状态,空间紧凑且更新高效,适合高频事件场景。 +- 重复事件过滤:Sender 在 keydown 上跳过重复事件,减少冗余状态事件,降低带宽占用。 +- 批量发送:InputRemoting 支持批量订阅者推送,便于扩展到多路远端接收。 +- 建议:对于高密度输入场景,可进一步引入节流/去抖策略或增量状态压缩,以平衡实时性与网络开销。 + +[本节为通用性能讨论,无需具体文件引用] + +## 故障排除指南 +- 事件未触发:检查 Sender 是否正确注册文档级键盘事件监听,确认事件冒泡路径与目标元素。 +- 文本缺失:若仅发送状态事件而无文本事件,需确认是否正确生成 TextEvent(当前实现会在 keydown 时总是生成文本事件)。 +- 键位异常:核对 Keymap 中是否存在目标 code 的映射,确保跨平台一致性。 +- 修饰键误判:应用层快捷键应显式检查修饰键状态,避免在输入法候选等场景误触发。 +- 测试验证:可通过单元测试验证状态事件与文本事件的格式与内容,确保打包与解析一致。 + +**章节来源** +- [client/src/sender.js:130-143](file://client/src/sender.js#L130-L143) +- [client/src/inputdevice.js:620-660](file://client/src/inputdevice.js#L620-L660) +- [client/test/inputdevice.test.js:135-144](file://client/test/inputdevice.test.js#L135-L144) +- [client/test/inputremoting.test.js:50-57](file://client/test/inputremoting.test.js#L50-L57) + +## 结论 +该键盘输入处理子系统通过标准化的按键映射、高效的位图状态管理与清晰的事件分发链路,实现了跨平台、低延迟的键盘输入远程转发。配合应用层的修饰键与快捷键处理,能够满足视频通话等实时交互场景的需求。建议在后续迭代中增强输入法候选窗口支持与状态压缩优化,以进一步提升兼容性与性能。 + +[本节为总结性内容,无需具体文件引用] + +## 附录 +- 代码片段路径参考(用于定位实现细节) + - 键位映射与位图更新:[client/src/inputdevice.js:287-307](file://client/src/inputdevice.js#L287-L307) + - 文本事件生成:[client/src/inputdevice.js:639-646](file://client/src/inputdevice.js#L639-L646) + - 重复事件过滤与文本事件生成:[client/src/sender.js:130-143](file://client/src/sender.js#L130-L143) + - 状态事件打包:[client/src/inputremoting.js:258-277](file://client/src/inputremoting.js#L258-L277) + - 应用层修饰键快捷键示例:[client/public/onebyone/main.js:140-144](file://client/public/onebyone/main.js#L140-L144) + +[本节为参考索引,无需具体文件引用] \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/信令系统/HTTP 信令处理器.md b/.qoder/repowiki/zh/content/信令系统/HTTP 信令处理器.md new file mode 100644 index 0000000..d8683b6 --- /dev/null +++ b/.qoder/repowiki/zh/content/信令系统/HTTP 信令处理器.md @@ -0,0 +1,379 @@ +# HTTP 信令处理器 + + +**本文引用的文件** +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/server.ts](file://src/server.ts) +- [src/index.ts](file://src/index.ts) +- [client/src/signaling.js](file://client/src/signaling.js) +- [test/httphandler.test.ts](file://test/httphandler.test.ts) +- [src/log.ts](file://src/log.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/服务端接口与WebSocket消息类型.md](file://src/服务端接口与WebSocket消息类型.md) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构概览](#架构概览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件为 HTTP 信令处理器的综合技术文档,重点阐述基于 HTTP 轮询的信令工作机制与实现细节。HTTP 信令通过一组 RESTful 接口实现会话管理、消息缓存与检索、以及客户端轮询拉取等核心能力。文档同时提供与 WebSocket 信令的对比分析,涵盖适用场景、性能特点、错误处理、超时管理与重试机制,并给出实际使用示例与最佳实践建议。 + +## 项目结构 +该项目采用前后端分离的模块化组织方式,HTTP 信令相关的核心代码位于以下位置: +- 服务器端核心处理器:src/class/httphandler.ts +- 信令路由定义:src/signaling.ts +- 服务器创建与中间件配置:src/server.ts +- 应用入口与协议选择:src/index.ts +- 客户端 HTTP 信令实现:client/src/signaling.js +- 测试用例:test/httphandler.test.ts +- 日志与配置:src/log.ts、src/class/options.ts +- WebSocket 对比实现:src/websocket.ts、src/class/websockethandler.ts + +```mermaid +graph TB +subgraph "客户端" +C_Signaling["Signaling 类
client/src/signaling.js"] +end +subgraph "服务器端" +S_Index["应用入口
src/index.ts"] +S_Server["Express 服务器
src/server.ts"] +S_Router["HTTP 路由
src/signaling.ts"] +S_Handler["HTTP 处理器
src/class/httphandler.ts"] +S_WS["WebSocket 信令
src/websocket.ts"] +end +C_Signaling --> |"HTTP 轮询"| S_Router +S_Router --> S_Handler +S_Index --> S_Server +S_Server --> S_Router +S_Index --> S_WS +``` + +**图表来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:14-42](file://src/server.ts#L14-L42) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1112-1130](file://src/class/httphandler.ts#L1112-L1130) +- [client/src/signaling.js:30-91](file://client/src/signaling.js#L30-L91) + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) + +## 核心组件 +- HTTP 信令处理器:负责会话管理、消息缓存与检索、连接配对、超时清理等核心逻辑。 +- 数据模型类:Offer、Answer、Candidate,封装信令消息的数据结构。 +- HTTP 路由层:定义 /signaling 下的 REST 接口,统一鉴权中间件与会话校验。 +- 客户端轮询实现:基于 Fetch API 的轮询机制,按 fromtime 实现增量拉取。 +- 测试框架:覆盖公共/私有模式下的会话生命周期、消息传递与超时清理。 + +**章节来源** +- [src/class/httphandler.ts:31-120](file://src/class/httphandler.ts#L31-L120) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [client/src/signaling.js:30-91](file://client/src/signaling.js#L30-L91) +- [test/httphandler.test.ts:6-310](file://test/httphandler.test.ts#L6-L310) + +## 架构概览 +HTTP 信令的整体架构围绕“会话-连接-消息”三层结构展开: +- 会话(Session):通过 PUT /signaling 创建,携带 Session-Id 头进行后续请求鉴权。 +- 连接(Connection):在会话内创建/删除,用于标识对端参与者。 +- 消息(Offer/Answer/Candidate):在连接之间传递,按 fromtime 实现增量拉取。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant Router as "HTTP 路由
src/signaling.ts" +participant Handler as "HTTP 处理器
src/class/httphandler.ts" +Client->>Router : "PUT /signaling" +Router->>Handler : "createSession()" +Handler-->>Router : "{ sessionId }" +Router-->>Client : "返回 sessionId" +Client->>Router : "PUT /signaling/connection" +Router->>Handler : "createConnection()" +Handler-->>Router : "{ connectionId, polite }" +Router-->>Client : "返回连接信息" +Client->>Router : "GET /signaling?fromtime=..." +Router->>Handler : "getAll()/getOffer()/getAnswer()/getCandidate()" +Handler-->>Router : "合并后的消息数组" +Router-->>Client : "返回增量消息" +Client->>Router : "POST /signaling/offer|answer|candidate" +Router->>Handler : "postOffer()/postAnswer()/postCandidate()" +Handler-->>Router : "200 OK" +Router-->>Client : "确认接收" +``` + +**图表来源** +- [src/signaling.ts:15-22](file://src/signaling.ts#L15-L22) +- [src/class/httphandler.ts:664-675](file://src/class/httphandler.ts#L664-L675) +- [src/class/httphandler.ts:739-783](file://src/class/httphandler.ts#L739-L783) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) +- [src/class/httphandler.ts:855-886](file://src/class/httphandler.ts#L855-L886) +- [src/class/httphandler.ts:913-952](file://src/class/httphandler.ts#L913-L952) +- [src/class/httphandler.ts:985-998](file://src/class/httphandler.ts#L985-L998) + +## 详细组件分析 + +### 会话管理策略 +- 会话创建:调用 createSession() 生成唯一 sessionId,并初始化会话级映射(连接集合、Offer/Answer/Candidate 缓存、断开连接记录)。 +- 会话维护:checkSessionId() 中更新 lastRequestedTime,作为超时判断依据;轮询接口 getAll()/getOffer()/getAnswer()/getCandidate() 均先触发超时检查。 +- 会话销毁:deleteSession() 删除会话及其所有连接;_deleteConnection() 同步清理连接对、消息缓存与断开记录。 + +```mermaid +flowchart TD +Start(["开始"]) --> CreateSession["创建会话
createSession()"] +CreateSession --> InitMaps["初始化会话映射"] +InitMaps --> Maintain["维护会话
更新 lastRequestedTime"] +Maintain --> Poll["轮询接口
getAll/getOffer/getAnswer/getCandidate"] +Poll --> TimeoutCheck{"是否超时?"} +TimeoutCheck --> |是| DeleteSession["删除会话
deleteSession()"] +TimeoutCheck --> |否| Continue["继续处理"] +DeleteSession --> End(["结束"]) +Continue --> End +``` + +**图表来源** +- [src/class/httphandler.ts:664-675](file://src/class/httphandler.ts#L664-L675) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/httphandler.ts:697-696](file://src/class/httphandler.ts#L697-L696) +- [src/class/httphandler.ts:153-194](file://src/class/httphandler.ts#L153-L194) + +**章节来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/httphandler.ts:697-696](file://src/class/httphandler.ts#L697-L696) +- [src/class/httphandler.ts:153-194](file://src/class/httphandler.ts#L153-L194) + +### 消息缓存机制 +- Offer 缓存:以 sessionId -> connectionId -> Offer 映射存储,支持按 fromtime 过滤与跨会话(公共模式)广播。 +- Answer 缓存:以 sessionId -> connectionId -> Answer 映射存储,公共模式下向配对会话广播。 +- Candidate 缓存:以 sessionId -> connectionId -> Candidate[] 数组存储,按 fromtime 过滤;Answer 到来时同步更新对应 Candidate 的时间戳。 +- 断开连接记录:以 sessionId -> Disconnection[] 数组存储,支持按 fromtime 过滤。 + +```mermaid +classDiagram +class Offer { ++string sdp ++number datetime ++boolean polite +} +class Answer { ++string sdp ++number datetime +} +class Candidate { ++string candidate ++number sdpMLineIndex ++string sdpMid ++number datetime +} +class Disconnection { ++string id ++number datetime +} +class HTTPHandler { ++Map~string,Set~string~~ clients ++Map~string,number~ lastRequestedTime ++Map~string,Map~string,Offer~~ offers ++Map~string,Map~string,Answer~~ answers ++Map~string,Map~string,Candidate[]~~ candidates ++Map~string,Disconnection[]~~ disconnections ++Map~string,[string,string]~ connectionPair +} +HTTPHandler --> Offer : "缓存" +HTTPHandler --> Answer : "缓存" +HTTPHandler --> Candidate : "缓存" +HTTPHandler --> Disconnection : "记录" +``` + +**图表来源** +- [src/class/httphandler.ts:42-84](file://src/class/httphandler.ts#L42-L84) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +**章节来源** +- [src/class/httphandler.ts:58-84](file://src/class/httphandler.ts#L58-L84) +- [src/class/httphandler.ts:268-356](file://src/class/httphandler.ts#L268-L356) +- [src/class/httphandler.ts:941-951](file://src/class/httphandler.ts#L941-L951) + +### 轮询接口设计与实现 +- GET /signaling:合并连接、断开、Offer、Answer、Candidate 消息,按 datetime 升序返回;支持 fromtime 过滤。 +- GET /signaling/offer|answer|candidate:分别返回对应类型消息,支持 fromtime 过滤。 +- GET /signaling/connection:返回当前会话的连接列表。 +- PUT /signaling:创建会话,返回 sessionId。 +- DELETE /signaling:删除会话。 +- PUT|DELETE /signaling/connection:创建/删除连接。 +- POST /signaling/offer|answer|candidate:提交信令消息。 + +客户端轮询流程: +- 首次启动:PUT /signaling 获取 sessionId,随后循环调用 GET /signaling 并解析消息类型分派事件。 +- 增量拉取:使用上次响应中的 datetime 作为 fromtime,避免重复接收历史消息。 + +```mermaid +sequenceDiagram +participant Client as "客户端
client/src/signaling.js" +participant Server as "HTTP 服务器
src/server.ts" +participant Router as "路由
src/signaling.ts" +participant Handler as "处理器
src/class/httphandler.ts" +Client->>Server : "PUT /signaling" +Server->>Router : "转发请求" +Router->>Handler : "createSession()" +Handler-->>Router : "{ sessionId }" +Router-->>Client : "返回 sessionId" +loop 轮询 +Client->>Server : "GET /signaling?fromtime=..." +Server->>Router : "转发请求" +Router->>Handler : "getAll()" +Handler-->>Router : "messages + datetime" +Router-->>Client : "返回增量消息" +end +``` + +**图表来源** +- [src/server.ts:25-26](file://src/server.ts#L25-L26) +- [src/signaling.ts:15-16](file://src/signaling.ts#L15-L16) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) +- [client/src/signaling.js:30-91](file://client/src/signaling.js#L30-L91) + +**章节来源** +- [src/signaling.ts:9-22](file://src/signaling.ts#L9-L22) +- [src/class/httphandler.ts:398-407](file://src/class/httphandler.ts#L398-L407) +- [src/class/httphandler.ts:440-447](file://src/class/httphandler.ts#L440-L447) +- [src/class/httphandler.ts:492-501](file://src/class/httphandler.ts#L492-L501) +- [src/class/httphandler.ts:549-558](file://src/class/httphandler.ts#L549-L558) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) +- [client/src/signaling.js:147-149](file://client/src/signaling.js#L147-L149) + +### 与 WebSocket 信令的对比分析 +- 适用场景 + - HTTP 轮询:适合受限网络环境(NAT/防火墙)、代理服务器较多、无法建立长连接的场景。 + - WebSocket:适合实时性要求高、连接稳定、浏览器/移动端原生支持良好的场景。 +- 性能特点 + - HTTP 轮询:每次请求均为独立 TCP 连接,存在额外握手开销;但可利用 HTTP 缓存与代理优化。 + - WebSocket:单连接复用,无握手开销,延迟更低,带宽利用率更高。 +- 一致性与复杂度 + - HTTP 轮询:需自行实现 fromtime 增量拉取与会话超时管理。 + - WebSocket:内置连接状态管理,消息有序到达,无需显式超时处理。 + +**章节来源** +- [src/服务端接口与WebSocket消息类型.md:533-542](file://src/服务端接口与WebSocket消息类型.md#L533-L542) +- [src/websocket.ts:15-39](file://src/websocket.ts#L15-L39) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) + +### 错误处理、超时管理与重试机制 +- 会话超时:默认 10 秒未请求则自动清理会话;通过 _checkForTimedOutSessions() 触发。 +- 连接冲突:私有模式下同一 connectionId 仅允许一次配对,重复使用返回 400。 +- 请求校验:checkSessionId() 未找到会话返回 404;缺少必要字段返回 400。 +- 客户端重试:客户端轮询间隔默认 1 秒,若未获取到 sessionId 会持续重试直至成功。 + +```mermaid +flowchart TD +A["收到请求"] --> B{"会话存在?"} +B --> |否| E["返回 404"] +B --> |是| C["更新 lastRequestedTime"] +C --> D{"是否超时?"} +D --> |是| F["清理会话并返回空消息"] +D --> |否| G["继续处理请求"] +``` + +**图表来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [test/httphandler.test.ts:29-33](file://test/httphandler.test.ts#L29-L33) + +**章节来源** +- [src/class/httphandler.ts:31-32](file://src/class/httphandler.ts#L31-L32) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [test/httphandler.test.ts:29-33](file://test/httphandler.test.ts#L29-L33) +- [test/httphandler.test.ts:363-379](file://test/httphandler.test.ts#L363-L379) + +### 实际使用示例与最佳实践 +- 客户端使用步骤 + - 初始化 Signaling 实例,设置轮询间隔。 + - 调用 start() 自动创建会话并进入轮询循环。 + - 监听 disconnect/offer/answer/candidate 事件处理信令。 + - 使用 createConnection()/deleteConnection() 管理连接。 + - 使用 sendOffer()/sendAnswer()/sendCandidate() 发送信令。 +- 最佳实践 + - 合理设置轮询间隔(如 1-2 秒),平衡实时性与资源消耗。 + - 使用 fromtime 实现增量拉取,避免重复处理历史消息。 + - 在私有模式下确保 connectionId 的唯一性,避免 400 错误。 + - 在受限网络环境下优先选择 HTTP 轮询,否则推荐 WebSocket。 + +**章节来源** +- [client/src/signaling.js:30-91](file://client/src/signaling.js#L30-L91) +- [client/src/signaling.js:99-138](file://client/src/signaling.js#L99-L138) +- [test/httphandler.test.ts:35-153](file://test/httphandler.test.ts#L35-L153) + +## 依赖关系分析 +- 服务器启动与协议选择:index.ts 根据配置决定启用 HTTP 轮询还是 WebSocket。 +- 服务器中间件:server.ts 配置 CORS、JSON 解析、静态资源与 Swagger 文档。 +- 路由与处理器:signaling.ts 注册 HTTP 路由并挂载会话校验中间件,具体逻辑由 httphandler.ts 实现。 +- 客户端依赖:client/src/signaling.js 通过 Fetch API 与服务器交互。 + +```mermaid +graph LR +Index["src/index.ts"] --> Server["src/server.ts"] +Server --> Router["src/signaling.ts"] +Router --> Handler["src/class/httphandler.ts"] +Client["client/src/signaling.js"] --> Router +``` + +**图表来源** +- [src/index.ts:75-88](file://src/index.ts#L75-L88) +- [src/server.ts:25-41](file://src/server.ts#L25-L41) +- [src/signaling.ts:9-22](file://src/signaling.ts#L9-L22) + +**章节来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) + +## 性能考虑 +- 轮询频率:过高的轮询频率会增加服务器负载与网络开销;建议根据业务场景调整至 1-3 秒。 +- fromtime 增量拉取:减少不必要的数据传输,降低带宽占用。 +- 超时清理:及时释放闲置会话资源,避免内存泄漏。 +- 缓存策略:合理控制消息缓存大小,避免长时间运行导致内存增长。 + +## 故障排除指南 +- 404 会话不存在:确认客户端已成功创建会话并正确携带 Session-Id 头。 +- 400 连接 ID 冲突:私有模式下同一 connectionId 已被使用,需更换或释放。 +- 超时被清理:若长时间无请求,会话会被自动清理;可通过定期轮询维持会话活性。 +- 增量消息缺失:检查 fromtime 参数是否正确传递,避免重复消费历史消息。 + +**章节来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:153-194](file://src/class/httphandler.ts#L153-L194) +- [test/httphandler.test.ts:29-33](file://test/httphandler.test.ts#L29-L33) +- [test/httphandler.test.ts:363-379](file://test/httphandler.test.ts#L363-L379) + +## 结论 +HTTP 信令处理器通过简洁的 REST 接口实现了完整的会话管理与消息缓存能力,结合客户端轮询机制满足了在受限网络环境下的信令需求。其与 WebSocket 的对比显示了在不同场景下的取舍:WebSocket 更适合高实时性与高并发场景,而 HTTP 轮询则具备更好的兼容性与部署灵活性。配合 fromtime 增量拉取、超时清理与错误处理机制,HTTP 信令可在多数实际应用中稳定运行。 + +## 附录 +- 配置项说明:secure、port、keyfile、certfile、type(websocket/http)、mode(public/private)、logging。 +- 日志级别:none/error/warn/log/info,默认 info。 + +**章节来源** +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/index.ts:20-41](file://src/index.ts#L20-L41) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/信令系统/WebSocket 信令处理器.md b/.qoder/repowiki/zh/content/信令系统/WebSocket 信令处理器.md new file mode 100644 index 0000000..6d7c8c8 --- /dev/null +++ b/.qoder/repowiki/zh/content/信令系统/WebSocket 信令处理器.md @@ -0,0 +1,404 @@ +# WebSocket 信令处理器 + + +**本文引用的文件** +- [websocket.ts](file://src/websocket.ts) +- [websockethandler.ts](file://src/class/websockethandler.ts) +- [offer.ts](file://src/class/offer.ts) +- [answer.ts](file://src/class/answer.ts) +- [candidate.ts](file://src/class/candidate.ts) +- [index.ts](file://src/index.ts) +- [server.ts](file://src/server.ts) +- [signaling.ts](file://src/signaling.ts) +- [httphandler.ts](file://src/class/httphandler.ts) +- [log.ts](file://src/log.ts) +- [options.ts](file://src/class/options.ts) +- [package.json](file://package.json) +- [websockethandler.test.ts](file://test/websockethandler.test.ts) +- [README.md](file://client/public/onebyone/README.md) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件面向 WebSocket 信令处理器的技术文档,聚焦于 WebSocket 连接管理、连接组管理(主机与参与者)、Offer/Answer 协商流程、ICE 候选者传输、私有/公共模式差异、心跳与超时处理以及连接恢复策略。文档同时提供流程图与调试建议,帮助开发者快速理解与扩展系统。 + +## 项目结构 +- 服务端入口与启动:命令行参数解析、HTTPS/HTTP 服务创建、WebSocket 信令服务器启动。 +- WebSocket 信令层:连接接入、消息分发、心跳与超时、广播与点对点路由。 +- 数据模型:Offer/Answer/Candidate 的封装,便于序列化与传输。 +- HTTP 信令层(对比参考):提供轮询式信令的实现思路与数据持久化方案。 +- 日志与配置:统一日志级别与输出格式;运行参数与模式配置。 + +```mermaid +graph TB +subgraph "服务端" +A["入口: index.ts
解析参数/启动服务"] +B["HTTP 服务: server.ts
静态资源/路由注册"] +C["WebSocket 信令: websocket.ts
连接接入/消息分发"] +D["处理器: websockethandler.ts
连接组/路由/心跳"] +E["数据模型: offer.ts/answer.ts/candidate.ts"] +F["HTTP 信令: signaling.ts/httphandler.ts
轮询式信令(对比)"] +end +A --> B +B --> C +C --> D +D --> E +B --> F +``` + +图表来源 +- [index.ts:52-91](file://src/index.ts#L52-L91) +- [server.ts:14-42](file://src/server.ts#L14-L42) +- [websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +章节来源 +- [index.ts:13-109](file://src/index.ts#L13-L109) +- [server.ts:14-90](file://src/server.ts#L14-L90) +- [websocket.ts:6-117](file://src/websocket.ts#L6-L117) + +## 核心组件 +- WebSocket 信令服务器:负责连接接入、消息解析与分发、心跳与超时处理。 +- 连接组管理器:维护每个连接组的主机与参与者集合,实现双向路由与广播。 +- 数据模型封装:Offer/Answer/Candidate 对象,承载 SDP 与 ICE 候选者元数据。 +- 日志系统:统一日志级别与输出格式,便于调试与监控。 +- HTTP 信令(对比):提供轮询式信令的数据持久化与查询能力,便于非 WebSocket 场景。 + +章节来源 +- [websockethandler.ts:10-479](file://src/class/websockethandler.ts#L10-L479) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [log.ts:1-51](file://src/log.ts#L1-L51) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +## 架构总览 +WebSocket 信令处理器采用“连接接入 -> 消息分发 -> 组内路由”的三层结构。连接接入由 websocket.ts 完成,消息分发与路由由 websockethandler.ts 实现,数据模型由 offer.ts/answer.ts/candidate.ts 提供。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant WS as "WebSocket 服务器
websocket.ts" +participant Handler as "处理器
websockethandler.ts" +Client->>WS : "建立连接" +WS->>Handler : "add(ws)" +WS->>Client : "connect 消息(含 role/polite/participantId)" +Client->>WS : "offer/answer/candidate/ping/broadcast 等" +WS->>Handler : "根据 type 分派到 onOffer/onAnswer/onCandidate/onBroadcast" +Handler->>Handler : "根据 isPrivate/连接组/角色路由" +Handler-->>Client : "转发对应消息(可能携带 participantId)" +``` + +图表来源 +- [websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [websockethandler.ts:63-479](file://src/class/websockethandler.ts#L63-L479) + +## 详细组件分析 + +### 连接管理与状态维护 +- 连接添加:在连接事件中调用处理器的 add,为新连接创建空的连接 ID 集合。 +- 连接移除:在关闭事件中调用处理器的 remove,清理连接组并广播断开消息。 +- 心跳与超时:处理器内置心跳检测(注释掉的 AddHeartbeat/RemoveHeartbeat),通过 ping/pong 维护 lastActivity,超时触发断开。 + +```mermaid +flowchart TD +Start(["连接事件"]) --> Add["add(ws)
创建连接ID集合"] +Add --> Role["onConnect(ws, connectionId)
分配角色/生成participantId"] +Role --> Group{"是否已有连接组?"} +Group --> |否| Host["创建连接组(host=ws)"] +Group --> |是| Join["加入现有连接组(participants)"] +Host --> Notify["通知客户端 connect(含 role/polite/participantId)"] +Join --> Notify +Notify --> Msg["等待消息: offer/answer/candidate/ping/broadcast"] +Msg --> Ping{"收到 ping?"} +Ping --> |是| Pong["发送 pong 并更新 lastActivity"] +Ping --> |否| Route["按角色与模式路由消息"] +Route --> Close{"连接关闭/超时?"} +Close --> |是| Remove["remove(ws)
清理并广播断开"] +Close --> |否| Msg +``` + +图表来源 +- [websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [websockethandler.ts:72-206](file://src/class/websockethandler.ts#L72-L206) +- [websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +章节来源 +- [websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [websockethandler.ts:72-206](file://src/class/websockethandler.ts#L72-L206) +- [websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +### 连接组管理算法(主机与参与者) +- 角色分配:首次 onConnect 的客户端作为 host,后续为 participants;私有模式下首次连接的 polite=false,其余为 true。 +- 组内通信规则: + - host 发送的 offer/answer/candidate 转发给所有 participants。 + - participants 发送的 offer/answer/candidate 转发给 host。 + - 支持按 participantId 精确路由(私有模式下 host 可定向发送给特定 participant)。 +- 断开处理:host 离开时广播断开并删除连接组;participant 离开时通知 host 并从组中移除。 + +```mermaid +classDiagram +class ConnectionGroup { ++host : WebSocket ++participants : Set~WebSocket~ +} +class Handler { ++connectionGroup : Map~string, ConnectionGroup~ ++isPrivate : boolean ++onConnect(ws, connectionId) ++onDisconnect(ws, connectionId) ++onOffer(ws, message) ++onAnswer(ws, message) ++onCandidate(ws, message) ++broadcastToGroup(connectionId, senderWs, message) +} +Handler --> ConnectionGroup : "维护/查询" +``` + +图表来源 +- [websockethandler.ts:27-37](file://src/class/websockethandler.ts#L27-L37) +- [websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) + +章节来源 +- [websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) + +### Offer/Answer 协商流程(WebSocket 实现) +- SDP 交换:处理器将客户端发送的 SDP 封装为 Offer/Answer 对象,附加时间戳与 polite 标记。 +- 路由规则: + - 私有模式:host 可按 participantId 精确发送 offer/answer;否则广播给所有 participants。 + - 公共模式:若连接组不存在则创建,随后向所有其他客户端广播 offer。 +- 参与者角色:participant 发送的 offer/answer 自动路由至 host,携带发送者的 participantId 以便 host 识别来源。 + +```mermaid +sequenceDiagram +participant P1 as "参与者1" +participant P2 as "参与者2" +participant Host as "主机" +participant Handler as "处理器" +P1->>Handler : "onOffer({connectionId,sdp})" +Handler->>Handler : "封装为 Offer 对象" +alt 私有模式 +Handler->>P2 : "按 participantId 转发 offer" +Handler->>Host : "转发 offer(含 participantId)" +else 公共模式 +Handler->>P2 : "广播 offer" +end +Host->>Handler : "onAnswer({connectionId,sdp,participantId?})" +Handler->>P2 : "按 participantId 转发 answer 或广播" +``` + +图表来源 +- [websockethandler.ts:214-301](file://src/class/websockethandler.ts#L214-L301) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) + +章节来源 +- [websockethandler.ts:214-301](file://src/class/websockethandler.ts#L214-L301) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) + +### ICE 候选者传输(Candidate) +- 传输机制:与 Offer/Answer 类似,处理器将候选者封装为 Candidate 对象,携带 sdpMid、sdpMLineIndex 与时间戳。 +- 路由逻辑: + - 私有模式:host 可按 participantId 精确发送;否则广播。 + - 公共模式:当前实现未覆盖公共模式下的 Candidate 转发(保留注释的实现位置)。 +- 处理器提供按组广播与按角色路由的能力,便于扩展公共模式下的 Candidate 转发。 + +```mermaid +flowchart TD +A["收到 candidate 消息"] --> B["封装 Candidate 对象"] +B --> C{"私有模式?"} +C --> |是| D["按 participantId 路由或广播"] +C --> |否| E["当前未实现公共模式转发(保留扩展位)"] +D --> F["发送到目标客户端"] +E --> F +``` + +图表来源 +- [websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +章节来源 +- [websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +### 私有模式 vs 公共模式 +- 私有模式(private): + - 连接组隔离:每个 connectionId 对应唯一连接组,host 与 participants 明确划分。 + - 精确路由:支持按 participantId 精确发送 offer/answer/candidate。 + - 断开影响:host 离开导致房间解散并广播断开;participant 离开仅通知 host。 +- 公共模式(public): + - 广播为主:offer 会广播给所有其他客户端;answer/candidate 当前未实现广播。 + - 角色默认为 participant:首次连接的客户端被标记为 participant(polite=true)。 + +```mermaid +graph LR +Private["私有模式"] --> P1["连接组隔离"] +Private --> P2["按 participantId 精确路由"] +Private --> P3["host 离开即解散房间"] +Public["公共模式"] --> U1["offer 广播给其他客户端"] +Public --> U2["answer/candidate 当前未广播(保留扩展)"] +Public --> U3["默认 participant 角色"] +``` + +图表来源 +- [websockethandler.ts:150-260](file://src/class/websockethandler.ts#L150-L260) +- [websockethandler.ts:315-338](file://src/class/websockethandler.ts#L315-L338) + +章节来源 +- [websockethandler.ts:150-260](file://src/class/websockethandler.ts#L150-L260) +- [websockethandler.ts:315-338](file://src/class/websockethandler.ts#L315-L338) + +### 心跳检测、超时与连接恢复 +- 心跳机制:处理器预留心跳检测(注释掉),通过 ping/pong 维护 lastActivity,定时检查超时并触发断开。 +- 超时策略:当前实现注释掉,未在运行时启用;建议在生产环境启用以提升鲁棒性。 +- 连接恢复:客户端需重新发起 connect 流程,处理器会重新分配角色并重建连接组。 + +```mermaid +flowchart TD +S["开始计时"] --> T["发送 ping"] +T --> R{"收到 pong?"} +R --> |是| U["更新 lastActivity"] +R --> |否| O{"超时(>阈值)?"} +O --> |是| X["执行断开(onDisconnect)"] +O --> |否| T +``` + +图表来源 +- [websocket.ts:95-100](file://src/websocket.ts#L95-L100) +- [websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +章节来源 +- [websocket.ts:95-100](file://src/websocket.ts#L95-L100) +- [websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +### 广播与消息路由 +- 组内广播:处理器提供 broadcastToGroup,host 向所有 participants 转发,participants 向 host 转发。 +- 全局广播:onBroadcast 支持向指定连接组或全局广播消息。 +- 聊天消息:onMessage 将 host 的消息广播给 participants,participant 的消息转发给 host 并同步给其他 participants。 + +章节来源 +- [websockethandler.ts:97-109](file://src/class/websockethandler.ts#L97-L109) +- [websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) +- [websockethandler.ts:448-473](file://src/class/websockethandler.ts#L448-L473) + +### 与 HTTP 信令的对比(概念性说明) +- HTTP 信令通过轮询获取 offer/answer/candidate,适合无法使用 WebSocket 的场景。 +- 数据持久化:HTTP 处理器将信令消息存储在内存映射中,支持按会话与时间过滤查询。 +- 适用场景:弱网环境、代理限制、或需要与传统系统集成的场景。 + +章节来源 +- [signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +## 依赖关系分析 +- 入口依赖:index.ts 依赖 server.ts 创建 HTTP 服务,再由 server.ts 注册 /signaling 路由与静态资源。 +- WebSocket 依赖:websocket.ts 依赖 websockethandler.ts 进行连接与消息处理;依赖 log.ts 输出日志。 +- 数据模型依赖:websockethandler.ts 依赖 offer.ts/answer.ts/candidate.ts 封装 SDP 与候选者。 +- 配置依赖:index.ts 与 server.ts 读取 options.ts 的运行参数;package.json 提供依赖与脚本。 + +```mermaid +graph TB +Index["index.ts"] --> Server["server.ts"] +Server --> WS["websocket.ts"] +WS --> Handler["websockethandler.ts"] +Handler --> ModelO["offer.ts"] +Handler --> ModelA["answer.ts"] +Handler --> ModelC["candidate.ts"] +WS --> Log["log.ts"] +Index --> Opt["options.ts"] +Server --> Signaling["signaling.ts"] +Signaling --> HttpH["httphandler.ts"] +``` + +图表来源 +- [index.ts:52-91](file://src/index.ts#L52-L91) +- [server.ts:14-42](file://src/server.ts#L14-L42) +- [websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [log.ts:1-51](file://src/log.ts#L1-L51) +- [options.ts:1-10](file://src/class/options.ts#L1-L10) +- [signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +章节来源 +- [index.ts:52-91](file://src/index.ts#L52-L91) +- [server.ts:14-42](file://src/server.ts#L14-L42) +- [websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +## 性能考量 +- 内存占用:连接组与消息对象均使用 Map/Set 存储,复杂度与连接数线性相关。 +- 路由效率:广播与按角色路由均为 O(n) 遍历,适合中小规模并发。 +- 建议优化: + - 使用连接池与连接复用,避免频繁创建/销毁。 + - 对广播消息进行去重与合并,降低网络负载。 + - 引入心跳与超时(启用注释的实现)以及时回收无效连接。 + +## 故障排查指南 +- 连接无法建立: + - 检查服务端启动参数与证书配置;确认端口与协议(HTTP/HTTPS)正确。 + - 查看日志输出,定位 onConnect/添加连接阶段的问题。 +- 消息未到达: + - 确认连接组存在且角色正确;核对 participantId 与 connectionId。 + - 检查私有/公共模式下的路由分支是否符合预期。 +- 心跳与超时: + - 若启用心跳,确认 ping/pong 循环正常;检查 lastActivity 更新。 + - 未启用心跳时,长时间无活动可能导致连接被断开。 +- 单元测试参考: + - 使用测试用例验证公有/私有模式下的连接、Offer/Answer、Candidate 路由行为。 + +章节来源 +- [log.ts:30-50](file://src/log.ts#L30-L50) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +## 结论 +WebSocket 信令处理器通过清晰的连接组模型与角色路由,实现了灵活的多方通信能力。私有模式强调精确路由与房间隔离,公共模式强调广播与易用性。结合心跳与超时机制,系统具备较好的稳定性与可维护性。未来可在公共模式下完善 Candidate 转发与心跳启用,进一步提升可用性与健壮性。 + +## 附录 + +### WebSocket 信令流程示例(私有模式) +- 步骤 1:客户端 A 连接,成为 host(polite=false)。 +- 步骤 2:客户端 B 连接,成为 participant(polite=true)。 +- 步骤 3:B 发送 offer,处理器转发给 A。 +- 步骤 4:A 回答 answer,处理器按 participantId 转发给 B。 +- 步骤 5:双方交换 ICE 候选者,处理器按 participantId 转发。 +- 步骤 6:任一方断开,处理器广播断开并清理连接组。 + +章节来源 +- [websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [websockethandler.ts:214-301](file://src/class/websockethandler.ts#L214-L301) +- [websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) + +### 调试技巧 +- 启用详细日志:通过日志级别控制输出,定位连接、路由与广播问题。 +- 使用测试工具:参考单元测试用例,模拟公有/私有模式下的行为。 +- 关注客户端文档:OneByOne 客户端 README 描述了 WebSocket 事件与心跳机制,有助于理解客户端侧的配合。 + +章节来源 +- [log.ts:15-24](file://src/log.ts#L15-L24) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [README.md:126-136](file://client/public/onebyone/README.md#L126-L136) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/信令系统/信令消息对象.md b/.qoder/repowiki/zh/content/信令系统/信令消息对象.md new file mode 100644 index 0000000..c2e3c14 --- /dev/null +++ b/.qoder/repowiki/zh/content/信令系统/信令消息对象.md @@ -0,0 +1,396 @@ +# 信令消息对象 + + +**本文引用的文件** +- [offer.ts](file://src/class/offer.ts) +- [answer.ts](file://src/class/answer.ts) +- [candidate.ts](file://src/class/candidate.ts) +- [httphandler.ts](file://src/class/httphandler.ts) +- [websockethandler.ts](file://src/class/websockethandler.ts) +- [signaling.ts](file://src/signaling.ts) +- [websocket.ts](file://src/websocket.ts) +- [signaling.js](file://client/src/signaling.js) +- [signaling.test.js](file://client/test/signaling.test.js) +- [服务端接口与WebSocket消息类型.md](file://src/服务端接口与WebSocket消息类型.md) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件系统性地文档化了 WebRTC 信令消息对象 Offer、Answer 和 Candidate 的数据结构、字段定义与实现细节。重点覆盖: +- Offer 类的 SDP 描述封装、时间戳管理与礼貌协商标志(polite)语义 +- Answer 类的应答消息构建与 SDP 交换流程 +- Candidate 类的 ICE 候选者序列化与传输机制 +- 消息对象的创建、验证与序列化方法 +- JSON 结构规范与字段含义 +- 实际消息交换示例与调试技巧 +- 不同通信模式(公共/私有)下的使用差异 + +## 项目结构 +该项目采用前后端分离的 TypeScript/JavaScript 架构,核心信令逻辑位于服务端的 HTTP 与 WebSocket 处理器中,前端提供基于 HTTP 轮询与 WebSocket 的信令客户端。 + +```mermaid +graph TB +subgraph "客户端" +FE_HTTP["HTTP 客户端
signaling.js"] +FE_WS["WebSocket 客户端
signaling.js"] +end +subgraph "服务端" +HTTP["HTTP 路由
signaling.ts"] +WS["WebSocket 服务器
websocket.ts"] +HHTTP["HTTP 处理器
httphandler.ts"] +HWS["WebSocket 处理器
websockethandler.ts"] +MODELS["消息模型
offer.ts / answer.ts / candidate.ts"] +end +FE_HTTP --> HTTP +FE_WS --> WS +HTTP --> HHTTP +WS --> HWS +HHTTP --> MODELS +HWS --> MODELS +``` + +图表来源 +- [signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120) +- [websockethandler.ts:1-120](file://src/class/websockethandler.ts#L1-L120) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +章节来源 +- [signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120) +- [websockethandler.ts:1-120](file://src/class/websockethandler.ts#L1-L120) +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +## 核心组件 +- Offer:封装 SDP 描述、时间戳与礼貌协商标志 +- Answer:封装 SDP 描述与时间戳 +- Candidate:封装 ICE 候选者、SDP 媒体行索引与媒体 ID、时间戳 + +这些类作为纯数据载体,被 HTTP 与 WebSocket 处理器在消息收发时构造与使用。 + +章节来源 +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +## 架构总览 +HTTP 与 WebSocket 两条路径共同承载信令消息的收发与存储,处理器负责: +- 会话与连接管理 +- 消息持久化与检索 +- 消息路由与广播 +- 通信模式(公共/私有)下的差异化行为 + +```mermaid +sequenceDiagram +participant C as "客户端(HTTP)" +participant R as "HTTP 路由(signaling.ts)" +participant H as "HTTP 处理器(httphandler.ts)" +participant S as "存储(Maps)" +participant WS as "WebSocket 服务器(websocket.ts)" +C->>R : POST /signaling/offer +R->>H : postOffer(...) +H->>S : 存储 Offer 对象 +H-->>C : 200 OK +C->>R : GET /signaling/offer?fromtime=... +R->>H : getOffer(...) +H->>S : 查询并过滤 +H-->>C : {offers : [{connectionId,sdp,polite,type,datetime}]} +Note over WS,H : WebSocket 路由与处理器同样处理 offer/answer/candidate +``` + +图表来源 +- [signaling.ts:20-22](file://src/signaling.ts#L20-L22) +- [httphandler.ts:268-318](file://src/class/httphandler.ts#L268-L318) +- [websocket.ts:44-114](file://src/websocket.ts#L44-L114) +- [websockethandler.ts:214-260](file://src/class/websockethandler.ts#L214-L260) + +## 详细组件分析 + +### Offer 类分析 +Offer 类用于封装 WebRTC Offer 信令消息的核心数据,包含 SDP 描述、时间戳与礼貌协商标志。 + +- 字段定义 + - sdp: string —— SDP 描述字符串 + - datetime: number —— 消息创建时间戳(毫秒) + - polite: boolean —— 礼貌协商标志,用于避免并发 offer 冲突 + +- 构造与使用 + - HTTP 处理器在收到 offer 后,使用 sdp、当前时间与默认 polite 构造 Offer 对象并持久化 + - WebSocket 处理器在收到 offer 后,同样构造 Offer 对象并进行路由 + +- 礼貌协商(Polite)语义 + - 在私有模式下,host 的 polite=false,participant 的 polite=true + - 在公共模式下,polite 字段通常为 false,由服务端统一构造 + +```mermaid +classDiagram +class Offer { ++string sdp ++number datetime ++boolean polite ++constructor(sdp, datetime, polite) +} +``` + +图表来源 +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [websockethandler.ts:214-247](file://src/class/websockethandler.ts#L214-L247) +- [httphandler.ts:268-318](file://src/class/httphandler.ts#L268-L318) + +章节来源 +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [websockethandler.ts:214-247](file://src/class/websockethandler.ts#L214-L247) +- [httphandler.ts:268-318](file://src/class/httphandler.ts#L268-L318) + +### Answer 类分析 +Answer 类用于封装 WebRTC Answer 信令消息的核心数据,包含 SDP 描述与时间戳。 + +- 字段定义 + - sdp: string —— SDP 描述字符串 + - datetime: number —— 消息创建时间戳(毫秒) + +- 构造与使用 + - HTTP 处理器在收到 answer 后,使用 sdp、当前时间构造 Answer 对象并持久化 + - WebSocket 处理器在收到 answer 后,同样构造 Answer 对象并进行路由 + +```mermaid +classDiagram +class Answer { ++string sdp ++number datetime ++constructor(sdp, datetime) +} +``` + +图表来源 +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [websockethandler.ts:268-301](file://src/class/websockethandler.ts#L268-L301) +- [httphandler.ts:300-318](file://src/class/httphandler.ts#L300-L318) + +章节来源 +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [websockethandler.ts:268-301](file://src/class/websockethandler.ts#L268-L301) +- [httphandler.ts:300-318](file://src/class/httphandler.ts#L300-L318) + +### Candidate 类分析 +Candidate 类用于封装 ICE 候选者消息,包含候选者字符串、SDP 媒体行索引与媒体 ID、时间戳。 + +- 字段定义 + - candidate: string —— ICE 候选者描述 + - sdpMLineIndex: number —— SDP 媒体行索引 + - sdpMid: string —— SDP 媒体 ID + - datetime: number —— 消息创建时间戳(毫秒) + +- 构造与使用 + - HTTP 处理器在收到 candidate 后,使用 candidate、sdpMLineIndex、sdpMid、当前时间构造 Candidate 对象并持久化 + - WebSocket 处理器在收到 candidate 后,同样构造 Candidate 对象并进行路由 + +```mermaid +classDiagram +class Candidate { ++string candidate ++number sdpMLineIndex ++string sdpMid ++number datetime ++constructor(candidate, sdpMLineIndex, sdpMid, datetime) +} +``` + +图表来源 +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) +- [httphandler.ts:320-356](file://src/class/httphandler.ts#L320-L356) + +章节来源 +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) +- [httphandler.ts:320-356](file://src/class/httphandler.ts#L320-L356) + +### 消息对象的创建、验证与序列化 + +- 创建 + - HTTP:客户端通过 POST /signaling/offer、/answer、/candidate 发送 JSON,服务端在处理器中解析并构造对应消息对象 + - WebSocket:客户端通过发送包含 type 与 data 的 JSON,服务端在 WebSocket 处理器中解析并构造对应消息对象 + +- 验证 + - HTTP:处理器对必要字段进行校验(如 connectionId、sdp、candidate 等),并在缺失时返回错误 + - WebSocket:处理器对消息结构进行校验,确保 from、to、data 字段存在 + +- 序列化 + - HTTP:消息对象被转换为 JSON 并返回给客户端 + - WebSocket:消息对象被包装为 { type, from, to, data, participantId } 形式发送 + +章节来源 +- [httphandler.ts:398-558](file://src/class/httphandler.ts#L398-L558) +- [websockethandler.ts:192-200](file://src/class/websockethandler.ts#L192-L200) +- [signaling.js:117-138](file://client/src/signaling.js#L117-L138) +- [signaling.js:255-279](file://client/src/signaling.js#L255-L279) + +### JSON 结构规范与字段含义 + +- Offer + - 字段:connectionId, sdp, polite, type="offer", datetime + - 含义:连接标识、SDP 描述、礼貌协商标志、消息类型、时间戳 + +- Answer + - 字段:connectionId, sdp, type="answer", datetime + - 含义:连接标识、SDP 描述、消息类型、时间戳 + +- Candidate + - 字段:connectionId, candidate, sdpMLineIndex, sdpMid, type="candidate", datetime + - 含义:连接标识、ICE 候选者、SDP 媒体行索引、SDP 媒体 ID、消息类型、时间戳 + +- 通用字段 + - type:消息类型(connect/disconnect/offer/answer/candidate/on-message) + - datetime:消息创建时间戳(毫秒) + +章节来源 +- [服务端接口与WebSocket消息类型.md:141-234](file://src/服务端接口与WebSocket消息类型.md#L141-L234) +- [httphandler.ts:398-558](file://src/class/httphandler.ts#L398-L558) + +### 实际消息交换示例与调试技巧 + +- HTTP 模式 + - 客户端通过 PUT /signaling 创建会话,随后通过 PUT /signaling/connection 建立连接 + - 使用 GET /signaling 获取所有消息,或分别 GET /signaling/offer、/answer、/candidate 拉取增量 + - 使用 fromtime 参数实现增量拉取,避免重复处理 + +- WebSocket 模式 + - 客户端发送 { type: "connect", connectionId } 建立连接 + - 发送 { type: "offer"/"answer"/"candidate", from, data } 进行 SDP 交换 + - 监听服务端推送的 offer/answer/candidate 消息并更新本地 PeerConnection + +- 调试技巧 + - 使用浏览器开发者工具查看网络面板中的 HTTP 请求与响应 + - 在 WebSocket 面板观察消息收发,注意 participantId 与 from 字段 + - 关注服务端日志输出,定位消息路由问题 + - 使用测试用例参考消息格式与预期行为 + +章节来源 +- [signaling.js:30-150](file://client/src/signaling.js#L30-L150) +- [websocket.ts:44-114](file://src/websocket.ts#L44-L114) +- [websockethandler.ts:192-200](file://src/class/websockethandler.ts#L192-L200) +- [signaling.test.js:89-208](file://client/test/signaling.test.js#L89-L208) + +### 不同通信模式下的使用差异 + +- 公共模式(Public) + - 所有连接的客户端均可相互通信 + - offer/answer/candidate 向所有其他客户端广播 + - 适合点对点直连场景 + +- 私有模式(Private) + - 一个 connectionId 对应一个房间,包含 1 个 host 与多个 participants + - host 为第一个加入者(polite=false),participants 为后续加入者(polite=true) + - host 可单播给特定 participant 或广播给所有 participants;participant 仅能发送给 host + - host 离开时房间关闭,participants 被断开;participant 离开房间仍保留 + +章节来源 +- [服务端接口与WebSocket消息类型.md:489-505](file://src/服务端接口与WebSocket消息类型.md#L489-L505) +- [websockethandler.ts:150-167](file://src/class/websockethandler.ts#L150-L167) +- [httphandler.ts:280-289](file://src/class/httphandler.ts#L280-L289) + +## 依赖关系分析 + +```mermaid +graph LR +O["Offer 类
offer.ts"] --> HH["HTTP 处理器
httphandler.ts"] +A["Answer 类
answer.ts"] --> HH +C["Candidate 类
candidate.ts"] --> HH +HH --> S["会话/连接映射(Maps)"] +HH --> WS["WebSocket 服务器
websocket.ts"] +HH --> HWS["WebSocket 处理器
websockethandler.ts"] +HWS --> O +HWS --> A +HWS --> C +``` + +图表来源 +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [httphandler.ts:63-77](file://src/class/httphandler.ts#L63-L77) +- [websockethandler.ts:5-8](file://src/class/websockethandler.ts#L5-L8) +- [websocket.ts:1-118](file://src/websocket.ts#L1-L118) + +章节来源 +- [offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [httphandler.ts:63-77](file://src/class/httphandler.ts#L63-L77) +- [websockethandler.ts:5-8](file://src/class/websockethandler.ts#L5-L8) +- [websocket.ts:1-118](file://src/websocket.ts#L1-L118) + +## 性能考虑 +- 增量拉取:HTTP GET 支持 fromtime 参数,减少重复消息传输与解析开销 +- 会话超时:10 秒无请求自动清理会话,避免内存泄漏 +- 广播策略:公共模式下广播所有消息,私有模式下按需单播/广播,降低网络负载 +- 心跳检测:可选的心跳机制(ping/pong)用于保持连接活跃,避免被中间设备断开 + +[本节为通用建议,不直接分析具体文件] + +## 故障排查指南 +- HTTP 404:检查会话 ID 是否正确传入请求头 Session-Id +- 消息未到达:确认通信模式(公共/私有)与连接配对是否正确 +- polite 标志异常:检查连接顺序与私有模式下 host/participant 的角色分配 +- Candidate 缺失:确认 ICE 候选者是否正确序列化,sdpMLineIndex 与 sdpMid 是否匹配 +- WebSocket 断开:检查心跳机制与网络稳定性,关注服务端日志 + +章节来源 +- [httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [websocket.ts:95-100](file://src/websocket.ts#L95-L100) + +## 结论 +Offer、Answer 与 Candidate 三类消息对象构成了 WebRTC 信令的核心载体。通过 HTTP 与 WebSocket 双通道,结合公共/私有两种通信模式,系统实现了灵活高效的信令交换能力。理解各字段的语义与消息流转过程,有助于在实际开发中快速定位问题并优化性能。 + +[本节为总结性内容,不直接分析具体文件] + +## 附录 + +### API 一览(HTTP) +- GET /signaling/connection-ids +- PUT /signaling +- GET /signaling +- DELETE /signaling +- GET /signaling/connection +- PUT /signaling/connection +- DELETE /signaling/connection +- GET /signaling/offer +- POST /signaling/offer +- GET /signaling/answer +- POST /signaling/answer +- GET /signaling/candidate +- POST /signaling/candidate + +章节来源 +- [服务端接口与WebSocket消息类型.md:37-234](file://src/服务端接口与WebSocket消息类型.md#L37-L234) + +### WebSocket 消息类型 +- connect/disconnect:连接生命周期 +- offer/answer/candidate:SDP 交换 +- on-message:通用消息 +- broadcast:广播消息 +- call-request:呼叫请求 +- participant-joined/participant-left:私有模式参与者变更 + +章节来源 +- [服务端接口与WebSocket消息类型.md:262-486](file://src/服务端接口与WebSocket消息类型.md#L262-L486) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/信令系统/信令路由配置.md b/.qoder/repowiki/zh/content/信令系统/信令路由配置.md new file mode 100644 index 0000000..dc39aba --- /dev/null +++ b/.qoder/repowiki/zh/content/信令系统/信令路由配置.md @@ -0,0 +1,340 @@ +# 信令路由配置 + + +**本文档引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/log.ts](file://src/log.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [package.json](file://package.json) +- [test/httphandler.test.ts](file://test/httphandler.test.ts) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件面向信令服务器的路由配置与运行机制,覆盖 WebSocket 与 HTTP 两种信令模式的路由规则、中间件与消息分发逻辑、端点配置项(端口、SSL 证书、CORS)、启动流程与配置参数、最佳实践与性能优化、故障排除与常见问题,以及实际配置示例与部署注意事项。 + +## 项目结构 +- 应用入口与启动:命令行参数解析、HTTP/HTTPS 服务器创建、WebSocket 信令服务启动。 +- 服务器配置:Express 中间件、CORS、日志、静态资源、Swagger 文档。 +- 信令路由:HTTP 路由挂载至 /signaling;WebSocket 信令服务独立于 HTTP 服务。 +- 处理器:HTTP 与 WebSocket 两类处理器分别管理会话、连接、信令消息与广播。 + +```mermaid +graph TB +A["命令行入口
src/index.ts"] --> B["Express 应用创建
src/server.ts"] +B --> C["HTTP 信令路由挂载
src/signaling.ts"] +B --> D["静态资源与上传接口
src/server.ts"] +B --> E["Swagger 文档
src/swagger.ts"] +A --> F["HTTPS/HTTP 服务器启动
src/index.ts"] +F --> G["WebSocket 信令服务
src/websocket.ts"] +G --> H["WebSocket 处理器
src/class/websockethandler.ts"] +C --> I["HTTP 处理器
src/class/httphandler.ts"] +``` + +**图表来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:63-137](file://src/class/websockethandler.ts#L63-L137) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 核心组件 +- 选项解析与启动流程:命令行参数解析、HTTPS/HTTP 选择、端口、日志级别、信令模式与通信模式。 +- Express 应用与中间件:CORS、日志、静态资源、上传接口、Swagger。 +- HTTP 信令路由:/signaling 下的 GET/PUT/DELETE/POST 路由,配合会话 ID 中间件。 +- WebSocket 信令服务:独立于 HTTP 的 ws 服务器,消息类型与广播逻辑。 +- 处理器:HTTP 处理器(会话/连接/信令存储与查询)、WebSocket 处理器(连接组、广播、心跳)。 + +**章节来源** +- [src/index.ts:16-42](file://src/index.ts#L16-L42) +- [src/server.ts:18-22](file://src/server.ts#L18-L22) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/class/websockethandler.ts:63-137](file://src/class/websockethandler.ts#L63-L137) + +## 架构总览 +信令服务器同时支持 HTTP 与 WebSocket 两种模式: +- HTTP 模式:通过 /signaling 路由进行轮询式信令交换,依赖 session-id 请求头进行会话隔离。 +- WebSocket 模式:通过独立的 ws 服务器进行实时双向通信,支持广播、1对多连接组、心跳检测与断线通知。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant HTTP as "HTTP 服务器
Express" +participant Sig as "HTTP 信令路由
/signaling" +participant Hdl as "HTTP 处理器" +participant WS as "WebSocket 服务器" +participant WSH as "WebSocket 处理器" +Client->>HTTP : "GET /config" +HTTP-->>Client : "{useWebSocket, startupMode, logging}" +Client->>HTTP : "PUT /signaling (创建会话)" +HTTP->>Sig : "路由匹配" +Sig->>Hdl : "createSession()" +Hdl-->>HTTP : "返回 sessionId" +HTTP-->>Client : "sessionId" +Note over Client,HTTP : "HTTP 轮询模式" +Client->>HTTP : "GET /signaling/offer?fromtime=..." +HTTP->>Sig : "路由匹配" +Sig->>Hdl : "getOffer()" +Hdl-->>HTTP : "返回 offer 列表" +HTTP-->>Client : "信令数据" +Note over Client,WS : "WebSocket 实时模式" +Client->>WS : "建立 ws : //... 连接" +WS->>WSH : "connection 事件" +WSH->>Client : "connect 消息" +Client->>WS : "发送 offer/answer/candidate" +WS->>WSH : "onOffer/onAnswer/onCandidate" +WSH-->>Client : "转发对应信令" +``` + +**图表来源** +- [src/server.ts:25-29](file://src/server.ts#L25-L29) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:661-675](file://src/class/httphandler.ts#L661-L675) +- [src/class/httphandler.ts:492-501](file://src/class/httphandler.ts#L492-L501) +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/websockethandler.ts:145-168](file://src/class/websockethandler.ts#L145-L168) + +## 详细组件分析 + +### HTTP 信令路由与中间件 +- 路由挂载:/signaling 下的 GET/PUT/DELETE/POST 路由,统一由 HTTP 处理器实现。 +- 会话中间件:校验请求头 session-id 是否存在,不存在则返回 404;存在则更新会话最后请求时间。 +- 会话与连接管理:创建/删除会话、创建/删除连接、查询连接列表、查询所有信令消息。 +- 信令存储与查询:Offer/Answer/Candidate 存储与按时间过滤查询;公共/私有模式下的路由差异。 +- CORS 与日志:全局启用 CORS(允许任意源),按配置输出 HTTP 访问日志。 + +```mermaid +flowchart TD +Start(["HTTP 请求进入 /signaling"]) --> CheckSession["检查 session-id 头部"] +CheckSession --> SessionExists{"会话存在?"} +SessionExists --> |否| Return404["返回 404"] +SessionExists --> |是| UpdateTime["更新会话最后请求时间"] +UpdateTime --> RouteMatch["匹配具体路由"] +RouteMatch --> Handler["调用对应 HTTP 处理器函数"] +Handler --> Store["存储/查询信令数据"] +Store --> Response["返回 JSON 响应"] +``` + +**图表来源** +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:398-407](file://src/class/httphandler.ts#L398-L407) + +**章节来源** +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:398-407](file://src/class/httphandler.ts#L398-L407) + +### WebSocket 信令服务与消息分发 +- 服务器创建:基于现有 HTTP 服务器创建 ws 服务器,独立于 HTTP。 +- 连接生命周期:连接建立、消息处理、断开清理。 +- 消息类型:connect/disconnect/offer/answer/candidate/broadcast/ping/pong/call-request/on-message。 +- 1对多模式:host 与 participants 的角色与消息路由;广播到组内成员;断线通知。 +- 心跳检测:定期 ping/pong,超时自动断开。 + +```mermaid +sequenceDiagram +participant WS as "WebSocket 服务器" +participant WSH as "WebSocket 处理器" +participant C1 as "客户端1(host)" +participant C2 as "客户端2(participant)" +WS->>WSH : "connection(ws)" +WSH->>C1 : "connect {role : host}" +WSH->>C2 : "connect {role : participant}" +C1->>WS : "offer {connectionId, sdp}" +WS->>WSH : "onOffer" +WSH-->>C2 : "转发 offer" +C2->>WS : "answer {connectionId, sdp}" +WS->>WSH : "onAnswer" +WSH-->>C1 : "转发 answer" +C1->>WS : "broadcast {message}" +WS->>WSH : "onBroadcast" +WSH-->>C2 : "广播消息" +C1->>WS : "disconnect" +WS->>WSH : "onDisconnect" +WSH-->>C2 : "通知 host 离开" +``` + +**图表来源** +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/websockethandler.ts:145-301](file://src/class/websockethandler.ts#L145-L301) +- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) + +**章节来源** +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/class/websockethandler.ts:145-301](file://src/class/websockethandler.ts#L145-L301) +- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) + +### 选项与启动流程 +- 命令行参数:端口、HTTPS 开关、证书路径、信令类型(websocket/http)、通信模式(public/private)、日志级别。 +- 服务器创建:根据 secure 选择 HTTPS 或 HTTP;读取 key/cert 文件;监听端口并打印访问地址。 +- 信令类型校验:非 websocket/http 的值将被修正为 websocket 并给出警告。 +- WebSocket 启动:当 type 为 websocket 时,创建 WSSignaling 实例并传入通信模式。 + +```mermaid +flowchart TD +Parse["解析命令行参数"] --> CreateApp["创建 Express 应用"] +CreateApp --> Secure{"secure ?"} +Secure --> |是| HTTPS["创建 HTTPS 服务器"] +Secure --> |否| HTTP["创建 HTTP 服务器"] +HTTPS --> Listen["监听端口并打印地址"] +HTTP --> Listen +Listen --> Type{"type == http ?"} +Type --> |是| LogHttp["记录使用 HTTP 轮询"] +Type --> |否| FixType["修正为 websocket 并记录警告"] +LogHttp --> Done +FixType --> Done +Done --> WS{"type == websocket ?"} +WS --> |是| StartWS["启动 WebSocket 信令服务"] +WS --> |否| End +``` + +**图表来源** +- [src/index.ts:20-42](file://src/index.ts#L20-L42) +- [src/index.ts:55-82](file://src/index.ts#L55-L82) +- [src/index.ts:87-88](file://src/index.ts#L87-L88) + +**章节来源** +- [src/index.ts:20-42](file://src/index.ts#L20-L42) +- [src/index.ts:55-82](file://src/index.ts#L55-L82) +- [src/index.ts:87-88](file://src/index.ts#L87-L88) + +### CORS 与静态资源 +- CORS:全局启用,允许任意源访问。 +- 静态资源:/ 与 /module 路径的静态文件服务。 +- 上传接口:multer 存储头像到 uploads/avatars,重命名为以 userId 命名的文件并返回访问路径。 + +**章节来源** +- [src/server.ts:22](file://src/server.ts#L22) +- [src/server.ts:27-28](file://src/server.ts#L27-L28) +- [src/server.ts:62-86](file://src/server.ts#L62-L86) + +### Swagger 文档 +- 自动扫描 HTTP 处理器与路由文件,生成 OpenAPI 文档,暴露 /api-docs。 +- 使用 session-id 作为 API Key 安全方案。 + +**章节来源** +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 依赖关系分析 + +```mermaid +graph LR +Index["src/index.ts"] --> Server["src/server.ts"] +Index --> WS["src/websocket.ts"] +Server --> Sig["src/signaling.ts"] +Sig --> Hdl["src/class/httphandler.ts"] +WS --> WSH["src/class/websockethandler.ts"] +Server --> Swagger["src/swagger.ts"] +Server --> Log["src/log.ts"] +Index --> Opt["src/class/options.ts"] +Package["package.json"] --> Server +Package --> WS +``` + +**图表来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/class/websockethandler.ts:63-137](file://src/class/websockethandler.ts#L63-L137) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/log.ts:15-24](file://src/log.ts#L15-L24) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [package.json:14-27](file://package.json#L14-L27) + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [package.json:14-27](file://package.json#L14-L27) + +## 性能考量 +- HTTP 轮询 vs WebSocket:WebSocket 适合低延迟实时通信;HTTP 轮询简单但有额外网络开销。 +- 会话超时:HTTP 处理器按 10 秒超时清理长时间无请求的会话,避免内存泄漏。 +- 广播与路由:WebSocket 广播需注意组内成员数量,避免大规模广播造成拥塞。 +- 日志级别:生产环境建议降低日志级别,减少 I/O 压力。 +- 静态资源与上传:确保上传目录存在并限制文件大小,避免磁盘压力。 + +[本节为通用指导,无需列出具体文件来源] + +## 故障排除指南 +- 会话不存在:HTTP 路由在缺少 session-id 或会话不存在时返回 404。 +- 会话超时:长时间无请求导致会话被清理,重新创建会话并获取新的 session-id。 +- 信令类型错误:非 websocket/http 的 type 将被修正为 websocket 并记录警告。 +- CORS 问题:默认允许任意源,若遇到跨域,请检查客户端与服务器同源策略。 +- 上传失败:确认 uploads/avatars 目录存在且可写,检查文件名重命名逻辑。 + +**章节来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/index.ts:78-82](file://src/index.ts#L78-L82) +- [src/server.ts:22](file://src/server.ts#L22) +- [src/server.ts:44-57](file://src/server.ts#L44-L57) + +## 结论 +本信令服务器通过 Express 提供 HTTP 轮询与 WebSocket 实时两种信令模式,结合会话与连接管理、CORS、日志与 Swagger 文档,形成完整的信令基础设施。合理配置端口、SSL 证书与通信模式,可满足不同场景需求;通过测试用例可验证 HTTP 与 WebSocket 的行为一致性。 + +[本节为总结性内容,无需列出具体文件来源] + +## 附录 + +### 信令端点配置选项 +- 端口:通过命令行参数 -p/--port 指定,默认来自环境变量 PORT。 +- SSL 证书:通过 -s/--secure 开启 HTTPS,-k/--keyfile 与 -c/--certfile 指定密钥与证书路径。 +- 信令类型:-t/--type 选择 websocket 或 http。 +- 通信模式:-m/--mode 选择 public 或 private。 +- 日志级别:-l/--logging 选择 combined/dev/short/tiny 或 none。 + +**章节来源** +- [src/index.ts:20-29](file://src/index.ts#L20-L29) +- [package.json:9-10](file://package.json#L9-L10) + +### 启动流程与配置参数 +- 启动命令示例:参考 package.json 中的 start/dev 脚本,包含端口、模式、证书等参数。 +- 服务器启动:根据 secure 决定 HTTPS/HTTP;打印访问地址;根据 type 启动 WebSocket 或记录 HTTP 轮询模式。 + +**章节来源** +- [package.json:9-10](file://package.json#L9-L10) +- [src/index.ts:55-82](file://src/index.ts#L55-L82) + +### 路由配置最佳实践 +- 优先使用 WebSocket:实时性要求高时采用 WebSocket,HTTP 轮询仅用于兼容性或受限网络。 +- 明确通信模式:public 模式下跨会话广播;private 模式下严格按连接组路由。 +- 控制日志级别:生产环境避免高频日志,必要时仅保留 warn/info。 +- CORS 与静态资源:生产环境建议限制 CORS 源,静态资源开启缓存策略。 + +[本节为通用指导,无需列出具体文件来源] + +### 实际配置示例与部署注意事项 +- 示例:使用 HTTPS,端口 8080,private 模式,WebSocket 信令。 +- 部署:确保 server.key 与 server.cert 存在;防火墙开放端口;反向代理(如 Nginx)可前置以支持 TLS 终止与负载均衡。 + +**章节来源** +- [package.json:9-10](file://package.json#L9-L10) +- [src/index.ts:55-65](file://src/index.ts#L55-L65) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/客户端示例/一对一通信示例.md b/.qoder/repowiki/zh/content/客户端示例/一对一通信示例.md new file mode 100644 index 0000000..f2acc75 --- /dev/null +++ b/.qoder/repowiki/zh/content/客户端示例/一对一通信示例.md @@ -0,0 +1,437 @@ +# 一对一通信示例 + + +**本文引用的文件** +- [models.js](file://client/public/onebyone/models.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [connect.js](file://client/public/onebyone/connect/connect.js) +- [endcall.js](file://client/public/onebyone/endcall/endcall.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [utils.js](file://client/public/onebyone/utils.js) +- [main.js](file://client/public/onebyone/main.js) +- [index.html](file://client/public/onebyone/index.html) +- [connect.html](file://client/public/onebyone/connect/connect.html) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本项目是一对一视频通话示例,基于浏览器 WebRTC 技术实现点对点(P2P)视频通话,包含连接建立、媒体协商、通话管理、聊天消息与用户界面等完整功能。本文档将深入解析代码架构,涵盖数据模型、状态管理、视图渲染、连接流程与通话结束处理,并提供扩展、定制与集成建议。 + +## 项目结构 +客户端采用模块化组织,核心目录与文件如下: +- onebyone 目录:一对一通话主界面与相关模块 + - models.js:数据模型定义 + - store.js:状态管理与 WebRTC 会话控制 + - renderer.js:视图渲染器,负责将状态映射到 DOM + - main.js:应用入口,绑定事件与初始化 + - index.html:主界面 HTML 模板 + - connect/:连接界面 + - connect.html:初始连接界面 + - connect.js:连接界面逻辑 + - endcall/:通话结束界面 + - endcall.html:结束界面 + - endcall.js:结束逻辑 + - chatmessage.js:聊天消息模块 + - utils.js:工具函数 +- public/js:通用配置与 ICE 设置 + - config.js:获取服务器配置与 RTC 配置 + - icesettings.js:ICE 服务器配置持久化与读取 + +```mermaid +graph TB +subgraph "客户端(onebyone)" +A["index.html
主界面"] +B["main.js
入口与事件绑定"] +C["store.js
状态管理/WebRTC"] +D["renderer.js
视图渲染"] +E["models.js
数据模型"] +F["chatmessage.js
聊天消息"] +G["utils.js
工具函数"] +H["connect/connect.html
连接界面"] +I["connect/connect.js
连接逻辑"] +J["endcall/endcall.js
结束逻辑"] +end +subgraph "公共配置" +K["config.js
服务器/RTC配置"] +L["icesettings.js
ICE服务器持久化"] +end +A --> B +B --> C +B --> D +B --> F +C --> D +C --> F +C --> E +C --> K +K --> L +H --> I +A --> J +``` + +图表来源 +- [index.html](file://client/public/onebyone/index.html) +- [main.js](file://client/public/onebyone/main.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [models.js](file://client/public/onebyone/models.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [utils.js](file://client/public/onebyone/utils.js) +- [connect/connect.html](file://client/public/onebyone/connect/connect.html) +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) +- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) + +章节来源 +- [index.html](file://client/public/onebyone/index.html) +- [main.js](file://client/public/onebyone/main.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [models.js](file://client/public/onebyone/models.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [utils.js](file://client/public/onebyone/utils.js) +- [connect/connect.html](file://client/public/onebyone/connect/connect.html) +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) +- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) + +## 核心组件 +- 数据模型(models.js) + - 定义通话会话、本地/远端用户、媒体状态、聊天消息等类型与模拟数据 +- 状态管理(store.js) + - 使用简单观察者模式管理应用状态,封装 WebRTC 连接、媒体轨道、编解码器偏好、编码参数、网络质量检测、音频活动检测、消息广播等 +- 视图渲染(renderer.js) + - 将状态映射到 DOM,支持多参与者网格、占位符、占位符切换、分辨率自适应、网络质量指示等 +- 连接界面(connect/connect.js) + - 提供创建/加入通话、连接 ID 列表浏览、用户设置(头像、昵称、ID)、头像上传等功能 +- 通话结束界面(endcall/endcall.js) + - 提供重新连接与离开选项 +- 聊天消息(chatmessage.js) + - 负责消息的发送、接收、显示、未读计数与侧边栏切换 +- 工具函数(utils.js) + - 时间格式化、通知、元素显示/隐藏、按钮状态切换等 +- 应用入口(main.js) + - 初始化渲染器、绑定 DOM 事件、键盘快捷键、挂断确认对话框等 + +章节来源 +- [models.js](file://client/public/onebyone/models.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) +- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [utils.js](file://client/public/onebyone/utils.js) +- [main.js](file://client/public/onebyone/main.js) + +## 架构总览 +整体采用“状态驱动”的架构:store.js 维护核心状态,renderer.js 响应状态变化更新 UI;WebRTC 通过 RenderStreaming 实例进行媒体协商与传输;聊天消息通过 WebSocket 或信令通道传递;配置通过 config.js 与 icesettings.js 提供 STUN/TURN 与媒体约束。 + +```mermaid +sequenceDiagram +participant U as "用户" +participant M as "main.js" +participant S as "store.js" +participant R as "renderer.js" +participant RS as "RenderStreaming(WebRTC)" +participant WS as "WebSocket/信令" +U->>M : 打开主界面 +M->>S : 初始化并加入通话 +S->>RS : 创建连接并启动 +RS-->>S : 连接建立回调 +S->>R : 通知 CALL_STATUS_CHANGE/ONGOING +S->>WS : 发送 user-info / media-state-changed +WS-->>S : 接收 chat-message / media-state-changed / user-info +S->>R : 通知 REMOTE_STREAM_OBTAINED / USER_LIST_UPDATE +R-->>U : 渲染远端视频/用户列表/网络质量 +``` + +图表来源 +- [main.js](file://client/public/onebyone/main.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) + +## 详细组件分析 + +### 数据模型(models.js) +- 类型定义 + - CallSession:通话会话,包含通话类型、状态、起始时间、时长、加密标志、本地/远端用户信息 + - LocalUser/RemoteUser:用户信息,含媒体状态与网络质量 + - MediaState:音频/视频/屏幕共享/录屏/说话检测状态 + - ChatMessage:消息结构,含发送者信息、内容、类型、时间戳与是否自已发送 +- 模拟数据 + - 提供 mockCallSession 与 mockMessages,便于开发调试与演示 + +章节来源 +- [models.js](file://client/public/onebyone/models.js) + +### 状态管理(store.js) +- 核心职责 + - 初始化与本地媒体流获取、媒体状态变更、WebRTC 连接建立与回调注册 + - 编解码器偏好设置、视频编码参数动态调整、分辨率切换 + - 网络质量检测、音频活动检测、统计信息输出 + - 聊天消息广播、用户信息同步、成员列表广播 + - 通话结束处理(挂断、清理、通知) +- 关键流程 + - 连接建立:_createSignalingAndRTC -> setUp -> _registerCallbacks -> _startConnection + - 媒体协商:onNewPeer/addTransceiver/setCodecPreferences/setVideoEncodingParameters + - 状态通知:notify -> renderer 渲染 + - 媒体状态变化:emitMediaStateChange -> WebSocket 广播 + - 网络质量:detectNetworkQuality -> 综合丢包率/抖动/RTT 评估 + - 音频活动:startActivityDetection -> VAD 检测 +- 设计要点 + - 观察者模式:subscribe/notify 解耦状态与 UI + - 可扩展性:编解码器与编码参数策略可按平台能力扩展 + - 容错性:轨道替换、占位符延迟通知、分辨率回退 + +```mermaid +classDiagram +class CallStateManager { ++state ++listeners ++init() ++setUp(connectionId) ++updateLocalMedia(type, value) ++hangUp() ++broadcastParticipantsList() ++setCodecPreferences(participantId) ++setVideoEncodingParameters(participantId) ++changeResolution(width, height) ++emitMediaStateChange() ++detectNetworkQuality() ++startActivityDetection(stream, options) ++showStatsMessage() +} +class UIRenderer { ++render(state, changes) ++renderRemoteStream(stream, connectionId, isHost) ++renderUserList(localUser, remoteUser, participants) +} +CallStateManager --> UIRenderer : "通知状态变化" +``` + +图表来源 +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) + +章节来源 +- [store.js](file://client/public/onebyone/store.js) + +### 视图渲染(renderer.js) +- 渲染策略 + - 基于变化类型分派渲染:INIT、DURATION_UPDATE、LOCAL_MEDIA_CHANGE、REMOTE_STREAM_OBTAINED、REMOTE_MEDIA_CHANGE、USER_LIST_UPDATE、NETWORK_CHANGE、CALL_STATUS_CHANGE、CALL_ENDED、PARTICIPANT_LEFT、RESOLUTION_CHANGED + - 支持多参与者网格(Host 端)与单路远端视频(Participant 端) + - 占位符与占位符切换:音频先到、视频后到时延迟通知,避免黑屏 + - 网络质量指示:根据远端网络质量更新头部与侧边栏 + - 分辨率自适应:监听视频轨道 resize 事件调整显示尺寸 +- 交互与事件 + - 绑定事件监听器(由 main.js 绑定),响应按钮与快捷键 + - 侧边栏开关与未读计数联动 + +章节来源 +- [renderer.js](file://client/public/onebyone/renderer.js) +- [index.html](file://client/public/onebyone/index.html) + +### 连接建立(connect/connect.js) +- 功能 + - 加入通话:校验连接 ID,保存到本地存储,跳转主界面 + - 创建通话:生成随机连接 ID,保存并跳转主界面 + - 浏览连接 ID:调用 /signaling/connection-ids 获取列表并展示 + - 用户设置:昵称、头像、用户 ID,支持头像上传(/api/upload/avatar) +- 交互 + - 输入框回车触发加入 + - 选择连接 ID 自动填充 + - 设置菜单外点击关闭 + +章节来源 +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) +- [connect/connect.html](file://client/public/onebyone/connect/connect.html) + +### 通话结束处理(endcall/endcall.js) +- 功能 + - 重新连接:跳转主界面 + - 离开:清除本地连接 ID,回到连接界面 +- 交互 + - 页面加载时显示断开连接信息(连接 ID 与时间) + +章节来源 +- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js) + +### 聊天消息(chatmessage.js) +- 功能 + - 消息状态管理:消息列表、未读计数、侧边栏开关 + - 发送消息:构造消息对象,通过 store 发送到远端 + - 接收消息:handleChatMessage -> addMessage -> 通知 UI + - 图片消息:限制大小、读取为 DataURL、发送文件消息 + - 未读通知:侧边栏关闭时累加未读,打开时清零 +- 事件绑定 + - sendMessage/handleChatSubmit/openImagePicker/handleImageUpload + +章节来源 +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) + +### 工具函数(utils.js) +- 功能 + - formatTime/formatTimestamp:时间格式化 + - generateId:生成唯一 ID + - showNotification:通知组件显示与隐藏 + - toggleElement/toggleButtonState:元素与按钮状态切换 + +章节来源 +- [utils.js](file://client/public/onebyone/utils.js) + +### 应用入口(main.js) +- 功能 + - 初始化渲染器与聊天模块 + - 绑定 DOM 事件:静音/视频切换、录屏、更多选项、分辨率切换、结束通话确认 + - 键盘快捷键:Space 静音、Ctrl+V 切换视频 + - 接收通话请求弹窗:接受/拒绝 + - 页面加载后检查连接 ID,初始化并启动 WebRTC 连接 +- 交互 + - 对话框事件绑定、更多选项菜单外点击关闭 + +章节来源 +- [main.js](file://client/public/onebyone/main.js) +- [index.html](file://client/public/onebyone/index.html) + +## 依赖关系分析 +- 模块依赖 + - main.js 依赖 store、renderer、chatmessage、utils + - store.js 依赖 models、config、icesettings、chatmessage、utils + - renderer.js 依赖 models、chatmessage、utils、store + - connect/connect.js 依赖 utils + - endcall/endcall.js 依赖 utils + - config.js 依赖 icesettings.js +- 外部依赖 + - WebRTC API(getUserMedia、RTCPeerConnection、RTCRtpTransceiver) + - WebSocket/信令通道(用于媒体状态、用户信息、聊天消息、参与者列表同步) + - 服务器接口:/config、/api/upload/avatar、/signaling/connection-ids + +```mermaid +graph LR +main_js["main.js"] --> store_js["store.js"] +main_js --> renderer_js["renderer.js"] +main_js --> chat_js["chatmessage.js"] +store_js --> models_js["models.js"] +store_js --> config_js["config.js"] +store_js --> chat_js +renderer_js --> models_js +renderer_js --> chat_js +renderer_js --> utils_js["utils.js"] +store_js --> utils_js +connect_js["connect/connect.js"] --> utils_js +endcall_js["endcall/endcall.js"] --> utils_js +config_js --> icesettings_js["icesettings.js"] +``` + +图表来源 +- [main.js](file://client/public/onebyone/main.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [models.js](file://client/public/onebyone/models.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [utils.js](file://client/public/onebyone/utils.js) +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) +- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) + +章节来源 +- [main.js](file://client/public/onebyone/main.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [models.js](file://client/public/onebyone/models.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [utils.js](file://client/public/onebyone/utils.js) +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) +- [endcall/endcall.js](file://client/public/onebyone/endcall/endcall.js) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) + +## 性能考量 +- 媒体轨道管理 + - 音频先到、视频后到时延迟通知,避免黑屏与频繁 UI 刷新 + - 替换视频轨道时保留音频轨道,减少资源开销 +- 编解码器与编码参数 + - 优先选择 AV1/VP9,回退 H264,提升压缩效率 + - 根据采集分辨率动态设置最大比特率,平衡画质与带宽 +- 网络质量检测 + - 丢包率、抖动、RTT 综合评估,定期更新网络质量指示 +- 统计信息 + - 定时输出视频/音频统计,便于诊断与优化 +- 分辨率切换 + - 使用 applyConstraints 实时调整,避免重新获取流 + +章节来源 +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) + +## 故障排查指南 +- 无法获取本地媒体流 + - 检查 getUserMedia 权限与约束;若失败,保持媒体状态关闭并通知 UI +- 远端视频黑屏 + - 确认音频轨道先到时的延迟通知逻辑;检查轨道是否正确添加到 MediaStream +- 连接状态异常 + - 检查 onConnect/onDisconnect 回调;确认信令通道正常 +- 媒体状态不同步 + - 确认 emitMediaStateChange 是否被调用;检查 onMessage 中 media-state-changed 分发 +- 网络质量指示不更新 + - 确认 startNetworkQualityDetection 是否启动;检查 detectNetworkQuality 统计字段 +- 头像上传失败 + - 检查文件类型与大小限制;确认 /api/upload/avatar 接口可用 + +章节来源 +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [connect/connect.js](file://client/public/onebyone/connect/connect.js) + +## 结论 +本示例通过清晰的模块划分与状态驱动架构,实现了稳定的一对一视频通话体验。其关键优势在于: +- 状态与 UI 解耦,易于扩展与维护 +- 完整的媒体协商与网络质量处理 +- 丰富的聊天与用户信息同步机制 +- 友好的用户界面与交互体验 + +建议在生产环境中进一步完善: +- 使用成熟的状态管理库(如 Redux/Pinia)替代简易观察者模式 +- 增强错误处理与重连机制 +- 集成更多媒体能力检测与自适应策略 +- 优化统计信息展示与告警机制 + +## 附录 + +### 开发指南:扩展与定制 +- 扩展聊天功能 + - 新增消息类型:在 ChatMessage 类型中扩展 type 字段,更新 chatmessage.js 的发送/接收逻辑 + - 文件上传:参考 handleImageUpload 的流程,扩展更多文件类型与大小限制 +- 自定义界面 + - 修改 index.html 的布局与样式,注意与 renderer.js 的 DOM 选择器保持一致 + - 新增控制按钮:在 main.js 绑定事件并在 store 中实现对应逻辑 +- 集成其他服务 + - 服务器配置:通过 /config 接口返回 useWebSocket 等配置,store.js 中据此选择 WebSocketSignaling 或 Signaling + - ICE 服务器:通过 icesettings.js 的持久化配置,支持多 STUN/TURN 服务器 +- WebRTC 优化 + - 编解码器与编码参数:根据设备能力动态调整,提升兼容性与画质 + - 分辨率与帧率:结合网络状况动态调整,保证流畅度 + +章节来源 +- [models.js](file://client/public/onebyone/models.js) +- [store.js](file://client/public/onebyone/store.js) +- [renderer.js](file://client/public/onebyone/renderer.js) +- [chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/客户端示例/双向通信示例.md b/.qoder/repowiki/zh/content/客户端示例/双向通信示例.md new file mode 100644 index 0000000..be692a4 --- /dev/null +++ b/.qoder/repowiki/zh/content/客户端示例/双向通信示例.md @@ -0,0 +1,444 @@ +# 双向通信示例 + + +**本文档引用的文件** +- [main.js](file://client/public/bidirectional/js/main.js) +- [sendvideo.js](file://client/public/bidirectional/js/sendvideo.js) +- [index.html](file://client/public/bidirectional/index.html) +- [style.css](file://client/public/bidirectional/css/style.css) +- [config.js](file://client/public/js/config.js) +- [icesettings.js](file://client/public/js/icesettings.js) +- [signaling.js](file://client/src/signaling.js) +- [peer.js](file://client/src/peer.js) +- [renderstreaming.js](file://client/src/renderstreaming.js) +- [index.ts](file://src/index.ts) +- [websocket.ts](file://src/websocket.ts) +- [options.ts](file://src/class/options.ts) +- [package.json](file://package.json) +- [run.bat](file://run.bat) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本示例演示了基于 WebRTC 的双向音视频通信,涵盖从本地媒体采集、信令交互、PeerConnection 建立到媒体流收发的完整流程。前端通过双向页面控制本地摄像头与麦克风,选择分辨率与编解码器偏好,发起连接后自动建立点对点通道,实现本地预览与远端播放的双向传输。后端提供 WebSocket 信令服务器,负责转发 offer/answer/candidate 等信令消息,并支持公共与私有通信模式。 + +## 项目结构 +该项目采用前后端分离架构: +- 前端部分位于 client/public 与 client/src,包含双向示例页面、通用配置与工具模块、WebRTC 连接管理与信令封装。 +- 后端部分位于 src,提供 Express 应用与 WebSocket 信令服务,支持 HTTPS、日志与多种运行参数。 + +```mermaid +graph TB +subgraph "前端" +A["bidirectional 页面
index.html"] +B["主控制器
main.js"] +C["媒体处理
sendvideo.js"] +D["信令封装
signaling.js"] +E["连接管理
renderstreaming.js"] +F["Peer 封装
peer.js"] +G["配置与ICE
config.js / icesettings.js"] +end +subgraph "后端" +H["入口与参数解析
index.ts"] +I["WebSocket 信令
websocket.ts"] +J["选项接口
options.ts"] +end +A --> B +B --> C +B --> D +B --> E +E --> F +D --> I +H --> I +G --> B +``` + +图表来源 +- [index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) +- [sendvideo.js:1-54](file://client/public/bidirectional/js/sendvideo.js#L1-L54) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [config.js:1-39](file://client/public/js/config.js#L1-L39) +- [icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104) +- [index.ts:1-109](file://src/index.ts#L1-L109) +- [websocket.ts:1-118](file://src/websocket.ts#L1-L118) + +章节来源 +- [index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) +- [index.ts:1-109](file://src/index.ts#L1-L109) + +## 核心组件 +- 双向页面控制器:负责 UI 控件初始化、媒体设备选择、分辨率与编解码器配置、启动本地媒体、建立 WebRTC 连接、处理统计信息与错误。 +- 媒体处理类:封装本地媒体流采集、本地/远端视频播放、本地轨道获取与远端轨道添加。 +- 信令封装:提供 HTTP 与 WebSocket 两种信令实现,统一事件接口,负责会话创建、offer/answer/candidate 发送与接收。 +- 连接管理:封装多个 Peer 的生命周期与事件路由,负责 SDP 协商、ICE 候选处理、统计数据查询与消息广播。 +- Peer 封装:封装 RTCPeerConnection 生命周期、SDP 描述交换、ICE 候选收集与注入、数据通道创建与事件派发。 +- 配置与 ICE:提供 RTC 配置(sdpSemantics、ICE 服务器、音频增强)、读取/写入本地存储的 STUN/TURN 服务器。 + +章节来源 +- [main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) +- [sendvideo.js:1-54](file://client/public/bidirectional/js/sendvideo.js#L1-L54) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [config.js:1-39](file://client/public/js/config.js#L1-L39) +- [icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104) + +## 架构总览 +下图展示了从用户操作到媒体流双向传输的整体架构与数据流。 + +```mermaid +sequenceDiagram +participant U as "用户" +participant UI as "双向页面(main.js)" +participant SV as "媒体处理(sendvideo.js)" +participant RS as "连接管理(renderstreaming.js)" +participant P as "Peer 封装(peer.js)" +participant SIG as "信令封装(signaling.js)" +participant WS as "WebSocket 信令(backend)" +U->>UI : "启动视频/设置连接" +UI->>SV : "startLocalVideo()" +SV-->>UI : "本地媒体流可用" +UI->>SIG : "创建信令实例(HTTP/WebSocket)" +UI->>RS : "start()/createConnection()" +RS->>P : "_preparePeerConnection()" +P-->>RS : "onNegotiationneeded -> 发送 offer" +RS->>SIG : "sendOffer()" +SIG->>WS : "转发 offer" +WS-->>SIG : "广播/定向转发" +SIG-->>RS : "on offer" +RS->>P : "onGotDescription(offer)" +P-->>RS : "setLocalDescription -> answer" +RS->>SIG : "sendAnswer()" +SIG->>WS : "转发 answer" +WS-->>SIG : "广播/定向转发" +SIG-->>RS : "on answer" +RS->>P : "onGotDescription(answer)" +P-->>RS : "稳定状态" +RS-->>UI : "onTrackEvent -> 添加远端轨道" +UI->>SV : "addRemoteTrack()" +SV-->>UI : "远端视频播放" +``` + +图表来源 +- [main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184) +- [sendvideo.js:44-52](file://client/public/bidirectional/js/sendvideo.js#L44-L52) +- [renderstreaming.js:191-250](file://client/src/renderstreaming.js#L191-L250) +- [peer.js:57-82](file://client/src/peer.js#L57-L82) +- [signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [websocket.ts:44-115](file://src/websocket.ts#L44-L115) + +## 详细组件分析 + +### 主控制器:双向页面逻辑(main.js) +- 功能职责 + - 设备与分辨率选择:枚举媒体设备,填充视频/音频选择框;支持预设分辨率与自定义分辨率。 + - 本地媒体启动:根据选择的设备与分辨率调用媒体采集,设置本地视频元素并自动播放。 + - 连接建立:根据配置选择 HTTP 或 WebSocket 信令,创建 RenderStreaming 实例,注册连接事件回调,添加本地轨道,设置编解码器偏好,启动统计信息展示。 + - 断开连接:清理统计信息、停止渲染、删除连接、恢复 UI 状态。 + - 编解码器偏好:在支持的浏览器上动态设置视频编解码器偏好,影响远端接收质量。 + - 统计信息:周期性获取 WebRTC 统计并格式化显示,包含本地/远端分辨率与关键指标。 +- 关键流程 + - 启动本地视频:禁用输入控件,计算分辨率,调用媒体采集,启用设置按钮。 + - 设置连接:创建信令实例,准备 RenderStreaming,注册 onConnect/onDisconnect/onTrackEvent 回调,添加本地轨道,设置编解码器,启动统计。 + - 断开连接:清除统计,删除连接,停止渲染,恢复 UI。 +- 最佳实践 + - 使用统一的 RTC 配置与媒体约束,避免分辨率不匹配导致的性能问题。 + - 在支持的浏览器上启用编解码器偏好,提升兼容性与质量。 + - 注意统计信息的周期性更新与清理,防止内存泄漏。 + +章节来源 +- [main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184) +- [main.js:220-241](file://client/public/bidirectional/js/main.js#L220-L241) +- [main.js:289-303](file://client/public/bidirectional/js/main.js#L289-L303) +- [main.js:337-367](file://client/public/bidirectional/js/main.js#L337-L367) + +### 媒体处理:SendVideo(sendvideo.js) +- 功能职责 + - 本地媒体采集:根据设备 ID 与分辨率约束获取媒体流,设置本地视频元素并播放。 + - 本地轨道获取:返回本地媒体流中的轨道集合,供连接管理添加到 PeerConnection。 + - 远端轨道添加:创建或复用 MediaStream,将远端轨道加入,驱动远端视频播放。 +- 关键流程 + - startLocalVideo:构造约束,调用 getUserMedia,设置本地视频源并播放。 + - getLocalTracks:从本地流获取轨道。 + - addRemoteTrack:将远端轨道加入本地流。 +- 最佳实践 + - 在调用 getUserMedia 前检查权限与设备可用性。 + - 使用 MediaStreamTrack 的 addTrack/removeTrack 精确管理轨道生命周期。 + +章节来源 +- [sendvideo.js:15-35](file://client/public/bidirectional/js/sendvideo.js#L15-L35) +- [sendvideo.js:40-52](file://client/public/bidirectional/js/sendvideo.js#L40-L52) + +### 信令封装:HTTP 与 WebSocket(signaling.js) +- 功能职责 + - HTTP 信令:基于轮询的信令实现,维护 Session-Id,支持连接创建/删除、offer/answer/candidate 发送与接收,以及自定义消息。 + - WebSocket 信令:基于 WebSocket 的实时信令实现,支持连接/断开、offer/answer/candidate、参与者加入/离开、广播消息等。 +- 关键流程 + - HTTP:start() 循环创建会话,loopGetAll() 轮询消息并分发事件;createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate。 + - WebSocket:onopen/onmessage/onclose 处理消息类型映射,分发事件;createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate/sendMessage。 +- 最佳实践 + - 在 HTTP 模式下合理设置轮询间隔,避免频繁请求。 + - WebSocket 模式下注意连接状态与重连策略,确保消息可靠传递。 + +章节来源 +- [signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [signaling.js:152-292](file://client/src/signaling.js#L152-L292) + +### 连接管理:RenderStreaming(renderstreaming.js) +- 功能职责 + - 统一封装信令事件与 Peer 生命周期,支持单 Peer(participant)与多 Peer(host)场景。 + - 路由 offer/answer/candidate 到对应 Peer,处理 onTrack 事件,提供统计数据查询与消息广播。 +- 关键流程 + - _onConnect:根据角色创建 Peer,触发 onConnect。 + - _onOffer/_onAnswer/_onIceCandidate:根据是否为 host 与 participantId 路由到对应 Peer。 + - _preparePeerConnection:创建 Peer 并绑定事件,桥接信令与 Peer。 + - getStats/addTrack/addTransceiver/createDataChannel:按角色与 participantId 路由到具体 Peer。 +- 最佳实践 + - host 端应为每个参与者维护独立 Peer,确保消息隔离。 + - 在断开或失败状态下及时清理 Peer,避免资源泄露。 + +章节来源 +- [renderstreaming.js:40-70](file://client/src/renderstreaming.js#L40-L70) +- [renderstreaming.js:191-250](file://client/src/renderstreaming.js#L191-L250) +- [renderstreaming.js:252-290](file://client/src/renderstreaming.js#L252-L290) + +### Peer 封装:RTCPeerConnection 管理(peer.js) +- 功能职责 + - 封装 RTCPeerConnection 的创建、SDP 描述交换、ICE 候选收集与注入、数据通道创建与事件派发。 + - 提供防闪烁(glare)处理,避免同时进行 offer/answer 导致的状态冲突。 +- 关键流程 + - _onNegotiationneeded:setLocalDescription 生成 offer,派发 sendoffer 事件。 + - onGotDescription:根据描述类型设置远端描述,必要时生成 answer 并派发 sendanswer。 + - onIceCandidate:收集候选并通过事件派发 sendcandidate。 + - loopResendOffer:定期重发未确认的 offer,保证协商完成。 +- 最佳实践 + - 正确处理 signalingState 与 iceConnectionState,及时响应失败事件。 + - 在多参与者场景下,确保 participantId 与 Peer 的一一对应。 + +章节来源 +- [peer.js:57-82](file://client/src/peer.js#L57-L82) +- [peer.js:132-173](file://client/src/peer.js#L132-L173) +- [peer.js:175-186](file://client/src/peer.js#L175-L186) + +### 配置与 ICE:RTC 配置与 STUN/TURN(config.js, icesettings.js) +- 功能职责 + - getServerConfig:获取服务器配置(是否使用 WebSocket、启动模式等)。 + - getRTCConfiguration:设置 sdpSemantics、ICE 服务器、媒体约束与音频增强选项。 + - icesettings.js:管理本地存储的 STUN/TURN 服务器列表,支持增删改查与默认值。 +- 最佳实践 + - 在公网部署时配置可靠的 TURN 服务器,提升连通性。 + - 根据网络环境调整 ICE 服务器列表与轮询间隔。 + +章节来源 +- [config.js:3-7](file://client/public/js/config.js#L3-L7) +- [config.js:9-38](file://client/public/js/config.js#L9-L38) +- [icesettings.js:94-104](file://client/public/js/icesettings.js#L94-L104) + +### 后端:WebSocket 信令服务器(websocket.ts) +- 功能职责 + - 接收并处理 WebSocket 消息,根据类型分发到处理器,支持 connect/disconnect/offer/answer/candidate/ping/pong/broadcast/on-message 等。 + - 维护连接池与广播机制,支持心跳检测与消息路由。 +- 关键流程 + - connection 事件:添加新连接到处理器。 + - onmessage:解析消息类型并调用相应处理器。 + - onclose:移除关闭连接。 +- 最佳实践 + - 对未知消息类型进行安全处理,避免异常传播。 + - 实现心跳与超时检测,维持长连接稳定性。 + +章节来源 +- [websocket.ts:27-39](file://src/websocket.ts#L27-L39) +- [websocket.ts:44-115](file://src/websocket.ts#L44-L115) + +### 入口与参数:服务器启动(index.ts) +- 功能职责 + - 解析命令行参数,支持端口、HTTPS、信令类型、通信模式与日志级别。 + - 根据参数启动 HTTP/HTTPS 服务器,初始化 WebSocket 信令服务。 +- 关键流程 + - 参数解析:读取 PORT/SECURE/KEYFILE/CERTFILE/TYPE/MODE/LOGGING。 + - 服务器启动:根据 secure 决定 HTTP/HTTPS,监听端口并输出访问地址。 + - 信令类型:仅支持 websocket 或 http,其他值将被修正为 websocket。 +- 最佳实践 + - 生产环境建议启用 HTTPS 并配置有效的证书文件。 + - 根据部署环境选择合适的通信模式(public/private)。 + +章节来源 +- [index.ts:14-44](file://src/index.ts#L14-L44) +- [index.ts:52-91](file://src/index.ts#L52-L91) + +## 依赖关系分析 + +```mermaid +classDiagram +class SendVideo { ++startLocalVideo(videoSource, audioSource, width, height) ++getLocalTracks() ++addRemoteTrack(track) +} +class Signaling { ++start() ++stop() ++createConnection(connectionId) ++deleteConnection(connectionId) ++sendOffer(connectionId, sdp) ++sendAnswer(connectionId, sdp) ++sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) ++sendMessage(connectionId, message) +} +class WebSocketSignaling { ++start() ++stop() ++createConnection(connectionId) ++deleteConnection(connectionId) ++sendOffer(connectionId, sdp, participantId) ++sendAnswer(connectionId, sdp, participantId) ++sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex, participantId) ++sendMessage(connectionId, message) +} +class Peer { ++ontrack ++ondatachannel ++onicecandidate ++onnegotiationneeded ++onsignalingstatechange ++oniceconnectionstatechange ++onicegatheringstatechange ++onGotDescription(connectionId, description) ++onGotCandidate(connectionId, candidate) ++getTransceivers(connectionId) ++addTrack(connectionId, track) ++addTransceiver(connectionId, trackOrKind, init) ++createDataChannel(connectionId, label) ++getStats(connectionId) ++close() +} +class RenderStreaming { ++start() ++stop() ++createConnection(connectionId) ++deleteConnection() ++addTrack(track, participantId) ++addTransceiver(trackOrKind, init, participantId) ++createDataChannel(label, participantId) ++getStats(participantId) ++sendMessage(message) ++getTransceivers(participantId) +} +SendVideo --> RenderStreaming : "提供本地轨道" +RenderStreaming --> Signaling : "使用" +RenderStreaming --> Peer : "封装" +Signaling <|-- WebSocketSignaling : "继承" +``` + +图表来源 +- [sendvideo.js:3-53](file://client/public/bidirectional/js/sendvideo.js#L3-L53) +- [signaling.js:3-150](file://client/src/signaling.js#L3-L150) +- [signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [peer.js:3-188](file://client/src/peer.js#L3-L188) +- [renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) + +## 性能考虑 +- 分辨率与编解码器 + - 合理选择分辨率,避免过高分辨率导致带宽与 CPU 压力过大。 + - 在支持的浏览器上设置编解码器偏好,提升解码效率与画质一致性。 +- ICE 服务器 + - 在公网部署时配置可靠的 STUN/TURN 服务器,减少打洞失败与延迟。 +- 统计信息 + - 定期但不过度地获取 WebRTC 统计,避免频繁 IO 影响性能。 +- 信令类型 + - WebSocket 信令相比 HTTP 轮询具有更低的延迟与更少的请求开销,推荐在生产环境使用。 + +## 故障排除指南 +- 浏览器不支持编解码器偏好 + - 现象:无法设置编解码器偏好,界面提示当前浏览器不支持。 + - 处理:忽略偏好设置,使用默认编解码器;或升级浏览器版本。 +- 公共模式限制 + - 现象:公共模式下页面显示警告,示例不可用。 + - 处理:切换到私有模式或使用支持的部署方式。 +- 本地媒体无法启动 + - 现象:点击“启动视频”无反应或报错。 + - 处理:检查摄像头/麦克风权限、设备 ID 是否正确、分辨率是否受支持。 +- 连接无法建立 + - 现象:点击“设置连接”后无进展或很快断开。 + - 处理:检查信令服务器是否在线、WebSocket 是否连通、ICE 服务器配置是否正确。 +- 远端无画面 + - 现象:本地有画面但远端无画面。 + - 处理:确认已添加本地轨道到 PeerConnection、远端轨道已加入本地流、统计信息显示远端轨道已接收。 +- 服务器启动失败 + - 现象:启动脚本报错或无法访问。 + - 处理:检查端口占用、HTTPS 证书文件是否存在、命令行参数是否正确。 + +章节来源 +- [main.js:99-105](file://client/public/bidirectional/js/main.js#L99-L105) +- [main.js:309-328](file://client/public/bidirectional/js/main.js#L309-L328) +- [index.ts:78-82](file://src/index.ts#L78-L82) +- [run.bat:8-16](file://run.bat#L8-L16) + +## 结论 +本双向通信示例通过清晰的模块划分与事件驱动设计,实现了从本地媒体采集到远端播放的完整链路。前端以 main.js 为核心协调各模块,后端以 WebSocket 信令服务器提供可靠的消息转发。通过合理的配置与最佳实践,可在不同网络环境下获得稳定的双向音视频体验。 + +## 附录 + +### 使用步骤 +- 启动服务器 + - 构建并启动后端服务,支持 HTTPS 与多种运行参数。 + - 示例脚本提供了构建与启动命令,也可直接运行 TypeScript 源码进行开发调试。 +- 打开示例页面 + - 在浏览器中访问服务器提供的地址,进入双向示例页面。 + - 页面会自动枚举可用的摄像头与麦克风,并提供分辨率与编解码器选择。 +- 建立连接 + - 输入连接 ID(可随机生成),点击“启动视频”采集本地媒体。 + - 点击“设置连接”,系统将创建信令连接并开始协商。 + - 成功后本地与远端视频将分别显示,统计信息会周期性更新。 +- 进行双向通信 + - 在双方都完成连接后,即可实现视频与音频的双向传输。 + - 可通过挂断按钮结束连接,清理资源并恢复 UI 状态。 + +章节来源 +- [package.json:9-12](file://package.json#L9-L12) +- [run.bat:8-16](file://run.bat#L8-L16) +- [index.html:76-79](file://client/public/bidirectional/index.html#L76-L79) +- [main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184) + +### 常见配置选项 +- 服务器参数 + - 端口:-p/--port,默认 80 或环境变量 PORT。 + - HTTPS:-s/--secure,默认启用;需提供 keyfile 与 certfile。 + - 信令类型:-t/--type,websocket 或 http,默认 websocket。 + - 通信模式:-m/--mode,public 或 private,默认 public。 + - 日志级别:-l/--logging,combined/dev/short/tiny/none,默认 dev。 +- RTC 配置 + - sdpSemantics:统一计划(unified-plan)。 + - ICE 服务器:STUN/TURN,可通过本地存储配置。 + - 音频增强:回声消除、噪声抑制、自动增益、高通滤波、打字噪声检测等。 +- 编解码器偏好 + - 在支持的浏览器上可选择特定视频编解码器,提升兼容性与质量。 + +章节来源 +- [index.ts:20-41](file://src/index.ts#L20-L41) +- [config.js:9-38](file://client/public/js/config.js#L9-L38) +- [icesettings.js:94-104](file://client/public/js/icesettings.js#L94-L104) +- [main.js:189-213](file://client/public/bidirectional/js/main.js#L189-L213) + +### 代码示例路径 +- 双向页面主逻辑:[main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184) +- 媒体处理类:[sendvideo.js:15-35](file://client/public/bidirectional/js/sendvideo.js#L15-L35) +- 信令封装(HTTP/WebSocket):[signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- 连接管理:[renderstreaming.js:191-250](file://client/src/renderstreaming.js#L191-L250) +- Peer 封装:[peer.js:57-82](file://client/src/peer.js#L57-L82) +- 服务器启动与参数:[index.ts:52-91](file://src/index.ts#L52-L91) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/客户端示例/多播放示例.md b/.qoder/repowiki/zh/content/客户端示例/多播放示例.md new file mode 100644 index 0000000..1186e42 --- /dev/null +++ b/.qoder/repowiki/zh/content/客户端示例/多播放示例.md @@ -0,0 +1,399 @@ +# 多播放示例 + + +**本文引用的文件** +- [client/src/renderstreaming.js](file://client/src/renderstreaming.js) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/src/peer.js](file://client/src/peer.js) +- [client/public/multiplay/js/main.js](file://client/public/multiplay/js/main.js) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本示例演示如何在浏览器端与服务器之间构建多播式媒体分发通道,使多个客户端能够同时接收来自同一信源的相同媒体流。该实现以 WebRTC 为核心,结合 WebSocket 或 HTTP 轮询信令通道,支持: +- 多客户端同时加入同一连接组 +- 服务器侧按连接组进行消息路由与广播 +- 客户端侧根据角色(主机/参与者)分别管理独立的 PeerConnection +- 基于数据通道的广播消息与输入事件传递 + +本指南聚焦于多播场景下的连接管理、媒体流复制与同步机制,并给出在 Unity Render Streaming 的 Multiplay 场景中的使用方法、集成步骤、性能优化与故障处理建议。 + +## 项目结构 +该仓库采用前后端分离结构: +- 前端示例位于 client/public/multiplay,包含多客户端多播播放器与输入通道 +- 前端核心逻辑位于 client/src,封装了信令、WebRTC Peer 管理与渲染 +- 服务端位于 src,提供 HTTP 服务与 WebSocket 信令服务,以及信令路由与广播 + +```mermaid +graph TB +subgraph "前端" +FE_Main["multiplay/main.js"] +FE_Render["renderstreaming.js"] +FE_Signaling["signaling.js"] +FE_Peer["peer.js"] +end +subgraph "服务端" +BE_Index["index.ts"] +BE_Server["server.ts"] +BE_WS["websocket.ts"] +BE_WS_Handler["class/websockethandler.ts"] +BE_HTTP_Router["signaling.ts"] +BE_HTTP_Handler["class/httphandler.ts"] +end +FE_Main --> FE_Render +FE_Render --> FE_Signaling +FE_Render --> FE_Peer +FE_Signaling --> BE_Index +BE_Index --> BE_Server +BE_Server --> BE_WS +BE_WS --> BE_WS_Handler +BE_Server --> BE_HTTP_Router +BE_HTTP_Router --> BE_HTTP_Handler +``` + +**图表来源** +- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204) +- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +**章节来源** +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/index.ts:52-109](file://src/index.ts#L52-L109) + +## 核心组件 +- 多播渲染层(RenderStreaming) + - 负责根据角色(主机/参与者)管理多个 PeerConnection,路由信令与媒体事件 + - 提供创建/删除连接、创建数据通道、添加轨道、获取统计等能力 +- 信令层(Signaling/ WebSocketSignaling) + - 支持 HTTP 轮询与 WebSocket 两种信令协议 + - 负责连接生命周期管理、offer/answer/candidate 的收发与广播 +- Peer 层(Peer) + - 封装 RTCPeerConnection,处理 SDP 协商、ICE 候选、数据通道与统计 +- 服务器信令处理器(WebSocket/HTTP) + - 维护连接组(主机 + 多个参与者),实现消息路由与广播 + - 支持私有模式(一对一)与公共模式(一对多) + +**章节来源** +- [client/src/renderstreaming.js:11-317](file://client/src/renderstreaming.js#L11-L317) +- [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/class/websockethandler.ts:145-260](file://src/class/websockethandler.ts#L145-L260) +- [src/class/httphandler.ts:739-800](file://src/class/httphandler.ts#L739-L800) + +## 架构总览 +多播架构的关键在于“连接组”与“角色路由”。服务器为每个 connectionId 维护一个连接组,首个加入者作为主机,后续加入者作为参与者。主机与参与者之间的信令消息按角色进行定向转发;广播消息则在组内进行全量分发。 + +```mermaid +sequenceDiagram +participant C1 as "客户端1主机" +participant C2 as "客户端2参与者" +participant Cn as "客户端n参与者" +participant WS as "WebSocket服务器" +participant H as "连接组处理器" +C1->>WS : "connect(connectionId)" +WS->>H : "onConnect(connectionId)" +H-->>C1 : "connect(role=host, participantId=p1)" +C2->>WS : "connect(connectionId)" +WS->>H : "onConnect(connectionId)" +H-->>C2 : "connect(role=participant, participantId=p2)" +H-->>C1 : "participant-joined(p2)" +C2->>WS : "offer(sdp, from=p2)" +WS->>H : "onOffer(data, from=p2)" +H-->>C1 : "offer(sdp, from=p2, participantId=p2)" +C1->>WS : "answer(sdp, from=p1)" +WS->>H : "onAnswer(data, from=p1)" +H-->>C2 : "answer(sdp, from=p1, participantId=p2)" +C1->>WS : "candidate(...)" +C2->>WS : "candidate(...)" +WS->>H : "onCandidate(...)" +H-->>C2 : "candidate(..., participantId=p2)" +H-->>C1 : "candidate(...)" +``` + +**图表来源** +- [src/websocket.ts:44-115](file://src/websocket.ts#L44-L115) +- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +**章节来源** +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +## 详细组件分析 + +### 多客户端连接管理与媒体流复制 +- 连接组与角色 + - 首个连接者为主机,后续连接者为参与者 + - 服务器为每个 connectionId 维护 host 与 participants 集合 +- 多 Peer 管理 + - 主机端为每个参与者维护独立的 Peer 实例,确保多路媒体复制与独立统计 + - 参与者端仅维护单一 Peer,避免不必要的连接开销 +- 媒体复制与同步 + - 服务器对 offer/answer/candidate 进行定向转发或广播 + - 客户端在收到 offer 时创建/复用对应参与者的 Peer 并设置 SDP,随后自动生成 answer 并回传 + - ICE 候选同样按目标参与者路由,保证连接建立的正确性 + +```mermaid +flowchart TD +Start(["开始:收到offer"]) --> IsHost{"是否为主机?"} +IsHost --> |是| GetPeer["获取/创建目标参与者的Peer"] +IsHost --> |否| CreatePeer["创建或复用单一Peer"] +GetPeer --> SetSDP["设置远程SDPoffer"] +CreatePeer --> SetSDP +SetSDP --> GenAnswer["生成本地SDPanswer"] +GenAnswer --> SendAnswer["发送answer给目标方"] +SendAnswer --> End(["结束"]) +``` + +**图表来源** +- [client/src/renderstreaming.js:72-130](file://client/src/renderstreaming.js#L72-L130) +- [client/src/peer.js:132-173](file://client/src/peer.js#L132-L173) + +**章节来源** +- [client/src/renderstreaming.js:72-130](file://client/src/renderstreaming.js#L72-L130) +- [client/src/peer.js:132-173](file://client/src/peer.js#L132-L173) + +### 服务器端多播路由与广播 +- 连接组维护 + - 使用 Map 维护连接组 + - 通过 participantId 标识参与者,实现按参与者定向转发 +- 消息路由 + - offer/answer/candidate:主机发给指定参与者,参与者发给主机 + - broadcast:服务器向组内所有成员广播消息 +- 断线与清理 + - 主机断线时,向所有参与者广播断线通知并清理连接组 + - 参与者断线时,通知主机并从组内移除 + +```mermaid +flowchart TD +A["收到消息"] --> Type{"消息类型?"} +Type --> |offer/answer/candidate| Route["按角色与participantId路由"] +Type --> |broadcast| Broad["向组内广播"] +Route --> Send["发送到目标WS"] +Broad --> Send +Send --> Done["完成"] +``` + +**图表来源** +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) + +**章节来源** +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) + +### 客户端渲染与输入通道(Multiplay 示例) +- 页面初始化 + - 读取服务器配置,决定使用 WebSocket 或 HTTP 信令 + - 创建 RenderStreaming 实例,注册连接、断开、轨道事件回调 +- 数据通道 + - 创建名为 "multiplay" 的数据通道用于广播消息 + - 打开后随机生成标签并向服务器广播 +- 媒体播放 + - 通过 onTrackEvent 回调将远端轨道接入播放器 + +```mermaid +sequenceDiagram +participant UI as "页面" +participant RS as "RenderStreaming" +participant Sig as "Signaling" +participant PC as "Peer" +participant VP as "VideoPlayer" +UI->>RS : "start()" +RS->>Sig : "start()" +UI->>RS : "createConnection()" +RS->>Sig : "createConnection()" +Sig-->>RS : "connect(role, participantId)" +RS->>PC : "创建/复用Peer" +RS-->>VP : "onTrackEvent -> addTrack()" +UI->>RS : "createDataChannel('multiplay')" +RS-->>UI : "onopen -> 发送标签" +``` + +**图表来源** +- [client/public/multiplay/js/main.js:82-125](file://client/public/multiplay/js/main.js#L82-L125) +- [client/src/renderstreaming.js:182-189](file://client/src/renderstreaming.js#L182-L189) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) + +**章节来源** +- [client/public/multiplay/js/main.js:82-125](file://client/public/multiplay/js/main.js#L82-L125) +- [client/src/renderstreaming.js:182-189](file://client/src/renderstreaming.js#L182-L189) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) + +### 服务器启动与信令服务 +- 启动流程 + - 解析命令行参数,创建 Express 应用 + - 根据安全与信令类型启动 HTTP/HTTPS 与 WebSocket 服务 +- 静态资源与 API + - 提供前端静态页面与模块 + - 暴露 /config、/signaling 路由 +- WebSocket 信令 + - 监听连接、消息、关闭事件 + - 分派到连接组处理器进行路由与广播 + +```mermaid +flowchart TD +Start(["启动"]) --> Parse["解析参数"] +Parse --> CreateApp["创建Express应用"] +CreateApp --> HTTPS{"启用HTTPS?"} +HTTPS --> |是| HTTPSrv["启动HTTPS服务器"] +HTTPS --> |否| HTTPsrv["启动HTTP服务器"] +HTTPSrv --> WS{"信令类型?"} +HTTPsrv --> WS +WS --> |websocket| WSSrv["启动WebSocket信令"] +WS --> |http| End(["完成"]) +``` + +**图表来源** +- [src/index.ts:52-109](file://src/index.ts#L52-L109) +- [src/server.ts:14-42](file://src/server.ts#L14-L42) +- [src/websocket.ts:15-40](file://src/websocket.ts#L15-L40) + +**章节来源** +- [src/index.ts:52-109](file://src/index.ts#L52-L109) +- [src/server.ts:14-42](file://src/server.ts#L14-L42) +- [src/websocket.ts:15-40](file://src/websocket.ts#L15-L40) + +## 依赖关系分析 +- 前端模块 + - multiplay/main.js 依赖 renderstreaming.js 与 signaling.js + - renderstreaming.js 依赖 peer.js 与 signaling.js + - peer.js 依赖浏览器 WebRTC API +- 服务端模块 + - index.ts 作为入口,创建 server.ts 并根据配置启动 WebSocket 信令 + - server.ts 注册 /signaling 路由,路由到 HTTP 信令处理器 + - websocket.ts 接收 WebSocket 消息,路由到连接组处理器 + +```mermaid +graph LR +MJS["multiplay/main.js"] --> RS["renderstreaming.js"] +RS --> SIG["signaling.js"] +RS --> PEER["peer.js"] +IDX["index.ts"] --> SRV["server.ts"] +SRV --> WSC["websocket.ts"] +SRV --> HTTR["signaling.ts"] +HTTR --> HTH["class/httphandler.ts"] +WSC --> WSH["class/websockethandler.ts"] +``` + +**图表来源** +- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204) +- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +**章节来源** +- [package.json:14-27](file://package.json#L14-L27) + +## 性能考量 +- 信令协议选择 + - WebSocket 适合低延迟、高并发的多播场景;HTTP 轮询开销较大但兼容性更好 +- 连接组规模 + - 主机端为每个参与者维护独立 Peer,注意内存与 CPU 开销随参与者数量增长 +- 媒体编解码与带宽 + - 使用 setCodecPreferences 指定编解码器,降低不必要转码成本 + - 合理设置传输参数(如 maxBitrate),避免上行拥塞 +- 统计与监控 + - 定期获取 Peer 统计,观察丢包、抖动与编码速率 +- 负载均衡与扩展 + - 多实例部署时,需确保同一 connectionId 的消息在同一实例内处理 + - 使用外部存储(如 Redis)共享连接组状态,实现水平扩展 + +[本节为通用指导,无需列出具体文件来源] + +## 故障排查指南 +- 无法建立连接 + - 检查服务器日志与 WebSocket/HTTP 信令是否正常 + - 确认客户端使用的信令类型与服务器配置一致 +- offer/answer 循环异常 + - 查看客户端 Peer 日志,确认 SDP 设置顺序与状态机是否正确 + - 检查服务器 onOffer/onAnswer 路由是否按角色与 participantId 正确转发 +- ICE 候选无法到达 + - 确认候选消息是否按目标参与者路由 + - 检查 NAT/防火墙与 STUN/TURN 配置 +- 广播消息未达 + - 确认 broadcast 消息的目标 connectionId 与连接组是否存在 + - 检查客户端 on-message 事件是否正确接收 + +**章节来源** +- [client/src/peer.js:132-173](file://client/src/peer.js#L132-L173) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) + +## 结论 +本多播示例通过“连接组 + 角色路由 + 广播”的架构,实现了多客户端同时接收相同媒体流的广播式分发。前端以 RenderStreaming 为核心,配合 WebSocket/HTTP 信令与独立 Peer 管理,确保了多路媒体复制与同步;后端通过连接组处理器实现高效的消息路由与广播。结合合理的性能优化与故障排查策略,可在生产环境中稳定运行。 + +[本节为总结性内容,无需列出具体文件来源] + +## 附录 + +### 在 Unity Render Streaming 的 Multiplay 场景中使用 +- 场景定位 + - Multiplay 示例展示了多客户端同时观看同一渲染流的典型用法 +- 关键步骤 + - 启动服务器并确认 /config 返回的 useWebSocket 与 startupMode + - 在页面中点击“开始播放”,初始化 RenderStreaming 并创建连接 + - 通过 "multiplay" 数据通道发送标签等广播消息 +- 注意事项 + - 私有模式下,页面提示不适用;请使用公共模式或调整前端逻辑 + - 若浏览器不支持 setCodecPreferences,需降级处理或提示用户 + +**章节来源** +- [client/public/multiplay/js/main.js:47-125](file://client/public/multiplay/js/main.js#L47-L125) +- [src/server.ts:25-29](file://src/server.ts#L25-L29) + +### 服务器配置与部署 +- 启动参数 + - 端口、HTTPS 证书、信令类型(websocket/http)、通信模式(public/private)、日志级别 +- 部署建议 + - 使用 HTTPS 以满足现代浏览器对媒体权限的要求 + - 将静态资源与模块路径正确暴露,确保前端示例可访问 + - 对外暴露 /config、/signaling 路由,以便前端动态获取配置 + +**章节来源** +- [src/index.ts:20-41](file://src/index.ts#L20-L41) +- [src/server.ts:25-29](file://src/server.ts#L25-L29) +- [src/server.ts:26-28](file://src/server.ts#L26-L28) + +### 客户端连接与负载均衡考虑 +- 客户端连接 + - 通过 /config 动态获取信令类型与模式 + - 使用 createConnection 与 start 建立信令通道 +- 负载均衡 + - 多实例部署时,确保同一 connectionId 的消息在同一实例内处理 + - 使用外部存储共享连接组状态,实现跨实例一致性 + +**章节来源** +- [client/public/multiplay/js/main.js:47-95](file://client/public/multiplay/js/main.js#L47-L95) +- [src/class/websockethandler.ts:145-168](file://src/class/websockethandler.ts#L145-L168) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/客户端示例/客户端示例.md b/.qoder/repowiki/zh/content/客户端示例/客户端示例.md new file mode 100644 index 0000000..28b85e0 --- /dev/null +++ b/.qoder/repowiki/zh/content/客户端示例/客户端示例.md @@ -0,0 +1,414 @@ +# 客户端示例 + + +**本文档引用的文件** +- [client/public/index.html](file://client/public/index.html) +- [client/public/bidirectional/index.html](file://client/public/bidirectional/index.html) +- [client/public/bidirectional/js/main.js](file://client/public/bidirectional/js/main.js) +- [client/public/bidirectional/js/sendvideo.js](file://client/public/bidirectional/js/sendvideo.js) +- [client/public/receiver/index.html](file://client/public/receiver/index.html) +- [client/public/receiver/js/main.js](file://client/public/receiver/js/main.js) +- [client/public/multiplay/index.html](file://client/public/multiplay/index.html) +- [client/public/multiplay/js/main.js](file://client/public/multiplay/js/main.js) +- [client/public/onebyone/index.html](file://client/public/onebyone/index.html) +- [client/public/onebyone/main.js](file://client/public/onebyone/main.js) +- [client/public/onebyone/store.js](file://client/public/onebyone/store.js) +- [client/public/onebyone/renderer.js](file://client/public/onebyone/renderer.js) +- [client/public/onebyone/chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [client/public/onebyone/utils.js](file://client/public/onebyone/utils.js) +- [client/public/videoplayer/index.html](file://client/public/videoplayer/index.html) +- [client/public/videoplayer/js/main.js](file://client/public/videoplayer/js/main.js) +- [client/public/videoplayer/js/video-player.js](file://client/public/videoplayer/js/video-player.js) +- [client/public/videoplayer/js/register-events.js](file://client/public/videoplayer/js/register-events.js) +- [client/public/js/config.js](file://client/public/js/config.js) +- [client/public/js/icesettings.js](file://client/public/js/icesettings.js) +- [client/public/js/stats.js](file://client/public/js/stats.js) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) +- [client/src/sender.js](file://client/src/sender.js) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/src/peer.js](file://client/src/peer.js) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本仓库提供一套基于 WebRTC 的客户端示例系统,涵盖以下典型场景: +- 双向视频通信:本地采集音视频,同时接收远端流,适合需要双向互动的场景。 +- 单向视频接收(广播):仅接收来自服务器的渲染流,适合“观看者”场景。 +- 多客户端播放(Guest 模式):多个客户端可共同观看同一渲染流。 +- 视频播放器控制(WebBrowserInput):在浏览器中接收 Unity 渲染的摄像头画面,并通过数据通道将输入事件回传给服务器。 + +这些示例统一采用模块化架构,通过信令层协调连接建立、媒体轨道处理与 UI 渲染,便于集成到自有项目中作为参考或基线。 + +## 项目结构 +客户端示例主要分为两部分: +- public 目录:各示例页面及其资源(HTML/CSS/JS),每个示例独立运行。 +- src 目录:通用的输入遥测、信令与 Peer 连接封装,供示例复用。 + +```mermaid +graph TB +subgraph "示例页面" +BI["双向示例
bidirectional/index.html"] +RCV["接收示例
receiver/index.html"] +MP["多播放示例
multiplay/index.html"] +VP["播放器示例
videoplayer/index.html"] +OB1["一对一通话示例
onebyone/index.html"] +end +subgraph "公共脚本" +CFG["配置与ICE
js/config.js, js/icesettings.js"] +STATS["统计信息
js/stats.js"] +VID["视频播放器抽象
js/videoplayer.js"] +end +subgraph "核心模块" +SIG["信令层
src/signaling.js"] +PEER["Peer连接封装
src/peer.js"] +SENDER["输入遥测发送器
src/sender.js"] +end +BI --- CFG +RCV --- CFG +MP --- CFG +VP --- CFG +OB1 --- CFG +BI --- SIG +RCV --- SIG +MP --- SIG +VP --- SIG +OB1 --- SIG +BI --- PEER +RCV --- PEER +MP --- PEER +VP --- PEER +OB1 --- PEER +VP --- SENDER +VP --- VID +``` + +图表来源 +- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54) +- [client/public/videoplayer/index.html:1-38](file://client/public/videoplayer/index.html#L1-L38) +- [client/public/onebyone/index.html:1-615](file://client/public/onebyone/index.html#L1-L615) +- [client/public/js/config.js](file://client/public/js/config.js) +- [client/public/js/icesettings.js](file://client/public/js/icesettings.js) +- [client/public/js/stats.js](file://client/public/js/stats.js) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) + +章节来源 +- [client/public/index.html:1-78](file://client/public/index.html#L1-L78) + +## 核心组件 +- 配置与 ICE 管理:负责读取服务器配置、选择信令协议(HTTP 或 WebSocket)、管理 STUN/TURN 列表。 +- 信令层:封装 HTTP 与 WebSocket 两种信令方式,统一发布/订阅事件,屏蔽连接细节。 +- Peer 封装:对 RTCPeerConnection 的封装,处理 offer/answer、ICE 候选、轨道事件与统计。 +- 输入遥测:将鼠标、键盘、手柄、触摸等输入事件序列化并通过数据通道发送至服务器。 +- 视频播放器抽象:统一视频元素创建、缩放、全屏、输入事件注册等行为。 + +章节来源 +- [client/public/js/config.js](file://client/public/js/config.js) +- [client/public/js/icesettings.js](file://client/public/js/icesettings.js) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) + +## 架构总览 +所有示例均遵循相同的控制流:初始化配置 → 选择信令 → 建立连接 → 添加媒体轨道 → 渲染远端流 → 统计与错误处理。 + +```mermaid +sequenceDiagram +participant UI as "示例页面" +participant CFG as "配置/ICE" +participant SIG as "信令层(HTTP/WebSocket)" +participant PEER as "Peer封装" +participant REM as "远端Peer" +UI->>CFG : 读取服务器配置/ICE服务器 +UI->>SIG : start()/连接信令 +UI->>PEER : start()/createConnection(id) +PEER->>SIG : 发布offer/answer/candidate +SIG-->>PEER : 下发offer/answer/candidate +PEER->>PEER : setLocal/RemoteDescription +PEER->>PEER : addIceCandidate +PEER-->>UI : onTrack/onDataChannel +UI->>UI : 渲染远端流/注册输入事件 +``` + +图表来源 +- [client/public/bidirectional/js/main.js:146-184](file://client/public/bidirectional/js/main.js#L146-L184) +- [client/public/receiver/js/main.js:75-88](file://client/public/receiver/js/main.js#L75-L88) +- [client/public/multiplay/js/main.js:82-95](file://client/public/multiplay/js/main.js#L82-L95) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [client/src/peer.js:57-82](file://client/src/peer.js#L57-L82) + +## 详细组件分析 + +### 双向视频通信(Bidirectional) +- 功能要点 + - 本地采集音视频,选择设备与分辨率,支持自定义宽高。 + - 建立 WebRTC 连接,添加本地轨道为 sendonly,接收远端轨道并渲染。 + - 支持编解码器偏好设置,按秒输出统计信息,显示本地/远端分辨率。 + - 提示:Public 模式不工作,需在 Private 模式下运行。 +- 关键流程 + - 设备枚举与分辨率选择 + - 连接建立与轨道添加 + - 编解码器偏好设置 + - 统计信息轮询与展示 +- UI 交互 + - 开始采集 → 设置连接 → 挂断清理 + - 编解码器选择禁用/启用 + - 本地/远端视频统计显示 + +```mermaid +sequenceDiagram +participant UI as "双向页面" +participant SV as "SendVideo" +participant RS as "RenderStreaming" +participant SIG as "信令(HTTP/WebSocket)" +participant PC as "Peer封装" +UI->>SV : startLocalVideo(设备, 分辨率) +UI->>RS : start()/createConnection(id) +RS->>PC : onConnect回调 +PC->>PC : addTransceiver(本地轨道, direction='sendonly') +PC->>SIG : 发布offer/answer/candidate +SIG-->>PC : 下发offer/answer/candidate +PC-->>UI : onTrack(远端轨道) +UI->>SV : addRemoteTrack(渲染) +UI->>PC : setCodecPreferences(可选) +UI->>UI : 每秒统计展示 +``` + +图表来源 +- [client/public/bidirectional/js/main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184) +- [client/public/bidirectional/js/sendvideo.js](file://client/public/bidirectional/js/sendvideo.js) +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [client/src/peer.js:108-130](file://client/src/peer.js#L108-L130) + +章节来源 +- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [client/public/bidirectional/js/main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) + +### 单向视频接收(Receiver) +- 功能要点 + - 点击播放按钮后创建视频播放器,建立连接并接收渲染流。 + - 支持编解码器偏好设置,提供光标锁定到播放器区域的选项。 + - 断开连接时清理播放器与统计信息,恢复播放按钮。 +- 关键流程 + - 点击播放 → 创建播放器 → 建立连接 → onTrack 添加轨道 → 输入通道设置 + - 断开时清理并重置 UI + +```mermaid +sequenceDiagram +participant UI as "接收页面" +participant VP as "VideoPlayer" +participant RS as "RenderStreaming" +participant SIG as "信令(HTTP/WebSocket)" +UI->>VP : createPlayer(容器, 锁定选项) +UI->>RS : start()/createConnection() +RS->>SIG : 发布/订阅信令 +SIG-->>RS : offer/answer/candidate +RS-->>UI : onTrack -> VP.addTrack +UI->>VP : setupInput(数据通道) +UI->>UI : 统计展示/断开清理 +``` + +图表来源 +- [client/public/receiver/js/main.js:67-108](file://client/public/receiver/js/main.js#L67-L108) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) + +章节来源 +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186) + +### 多客户端播放(Multiplay) +- 功能要点 + - 与接收示例类似,但额外创建名为 "multiplay" 的数据通道,用于多客户端标签同步等场景。 + - 打开通道后延时发送一条包含类型与参数的消息,演示数据通道的使用。 +- 关键流程 + - 建立连接 → 创建 "input" 与 "multiplay" 数据通道 → onOpenMultiplayChannel 发送消息 + +```mermaid +sequenceDiagram +participant UI as "多播放页面" +participant VP as "VideoPlayer" +participant RS as "RenderStreaming" +participant DC as "数据通道(input/multiplay)" +UI->>VP : createPlayer(容器, 锁定选项) +UI->>RS : start()/createConnection() +RS-->>UI : onConnect +UI->>DC : createDataChannel("input") +UI->>DC : createDataChannel("multiplay") +DC-->>UI : onopen +UI->>DC : send({type, argument}) +UI->>UI : 统计展示/断开清理 +``` + +图表来源 +- [client/public/multiplay/js/main.js:97-125](file://client/public/multiplay/js/main.js#L97-L125) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) +- [client/src/signaling.js:243-290](file://client/src/signaling.js#L243-L290) + +章节来源 +- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54) +- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204) + +### 视频播放器控制(WebBrowserInput) +- 功能要点 + - 点击播放后创建主视频与缩略图元素,注册游戏手柄、键盘、鼠标事件。 + - 通过数据通道将点击事件与输入事件发送至服务器,实现“在浏览器中操作 Unity 摄像头”的效果。 + - 支持全屏切换与自动尺寸调整。 +- 关键流程 + - 点击播放 → 创建元素 → setupConnection → 注册事件 → 发送输入事件 + +```mermaid +sequenceDiagram +participant UI as "播放器页面" +participant VP as "VideoPlayer" +participant SE as "输入遥测(sender)" +participant SIG as "信令(HTTP/WebSocket)" +UI->>VP : setupConnection(使用WebSocket?) +UI->>SE : registerGamepad/Keyboard/Mouse +SE->>SIG : 通过数据通道发送事件 +UI->>UI : 全屏切换/尺寸调整 +``` + +图表来源 +- [client/public/videoplayer/js/main.js:49-142](file://client/public/videoplayer/js/main.js#L49-L142) +- [client/public/videoplayer/js/video-player.js](file://client/public/videoplayer/js/video-player.js) +- [client/public/videoplayer/js/register-events.js](file://client/public/videoplayer/js/register-events.js) +- [client/src/sender.js:14-209](file://client/src/sender.js#L14-L209) + +章节来源 +- [client/public/videoplayer/index.html:1-38](file://client/public/videoplayer/index.html#L1-L38) +- [client/public/videoplayer/js/main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157) + +### 一对一视频通话(One-by-one) +- 功能要点 + - 使用模块化架构:store 管理状态,renderer 负责 UI 渲染,chatmessage 管理聊天,utils 提供工具函数。 + - 支持媒体状态切换(音频/视频/录制)、分辨率切换、侧边栏与通知、通话请求弹窗等。 + - 通过 API 与 WebSocket 事件驱动 UI 更新与业务流程。 +- 关键流程 + - 页面加载 → 读取本地连接ID → joinCall → setUp → 绑定事件 → 渲染头部标题 + +```mermaid +sequenceDiagram +participant Page as "一对一页面" +participant Store as "状态管理(store)" +participant Renderer as "UI渲染(renderer)" +participant Chat as "聊天(chatmessage)" +participant Utils as "工具(utils)" +Page->>Store : joinCall(connectionId) +Page->>Store : setUp(connectionId) +Page->>Renderer : renderHeaderTitle() +Page->>Page : 绑定DOM事件/快捷键 +Page->>Chat : bindMessageEvents() +Page->>Utils : showNotification(...) +``` + +图表来源 +- [client/public/onebyone/main.js:179-212](file://client/public/onebyone/main.js#L179-L212) +- [client/public/onebyone/store.js](file://client/public/onebyone/store.js) +- [client/public/onebyone/renderer.js](file://client/public/onebyone/renderer.js) +- [client/public/onebyone/chatmessage.js](file://client/public/onebyone/chatmessage.js) +- [client/public/onebyone/utils.js](file://client/public/onebyone/utils.js) + +章节来源 +- [client/public/onebyone/index.html:1-615](file://client/public/onebyone/index.html#L1-L615) +- [client/public/onebyone/main.js:1-216](file://client/public/onebyone/main.js#L1-L216) + +## 依赖关系分析 +- 示例页面依赖公共配置与 ICE 设置,以及核心模块(信令、Peer、输入遥测、视频播放器抽象)。 +- 一对一通话示例采用分层模块(store/renderer/chat/utils),降低耦合度,提升可维护性。 +- 信令层同时支持 HTTP 与 WebSocket,通过统一事件接口屏蔽差异。 + +```mermaid +graph LR +BI["双向示例"] --> CFG["配置/ICE"] +RCV["接收示例"] --> CFG +MP["多播放示例"] --> CFG +VP["播放器示例"] --> CFG +OB1["一对一示例"] --> CFG +BI --> SIG["信令(HTTP/WebSocket)"] +RCV --> SIG +MP --> SIG +VP --> SIG +OB1 --> SIG +BI --> PEER["Peer封装"] +RCV --> PEER +MP --> PEER +VP --> PEER +OB1 --> PEER +VP --> SENDER["输入遥测(sender)"] +VP --> VID["视频播放器抽象"] +``` + +图表来源 +- [client/public/js/config.js](file://client/public/js/config.js) +- [client/public/js/icesettings.js](file://client/public/js/icesettings.js) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) + +章节来源 +- [client/public/js/config.js](file://client/public/js/config.js) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) + +## 性能考量 +- 分辨率与编解码器 + - 示例提供多种预设分辨率,支持自定义分辨率;可通过编解码器偏好减少带宽占用。 + - 建议根据网络状况动态调整分辨率与编解码器,避免过高码率导致卡顿。 +- 统计信息 + - 每秒轮询统计信息,可用于监控丢包、延迟与分辨率变化,便于前端提示与自动调节。 +- 输入事件 + - 输入遥测通过数据通道发送,建议合并事件与节流发送,降低信令压力。 +- 全屏与缩放 + - 全屏切换与窗口 resize 时及时调整视频尺寸,避免重绘开销过大。 + +## 故障排查指南 +- Public/Private 模式限制 + - 双向示例在 Public 模式下不工作;接收/多播放示例在 Private 模式下不工作。请根据示例页面警告提示切换模式。 +- 信令协议选择 + - 若使用 WebSocket 信令,请确保服务端已正确配置;若使用 HTTP 信令,请确认会话创建与轮询接口可用。 +- ICE 服务器 + - 若无法建立连接,检查 STUN/TURN 服务器配置与网络策略;可在页面 ICE 配置区添加/移除/重置服务器。 +- 编解码器不支持 + - 部分浏览器不支持设置编解码器偏好;若出现相关提示,请降级处理或更换浏览器。 +- 断线与重连 + - ICE 失败会触发断开事件;示例会在断开时清理资源并提示消息。请检查网络稳定性与防火墙设置。 + +章节来源 +- [client/public/bidirectional/js/main.js:99-105](file://client/public/bidirectional/js/main.js#L99-L105) +- [client/public/receiver/js/main.js:48-54](file://client/public/receiver/js/main.js#L48-L54) +- [client/public/multiplay/js/main.js:55-61](file://client/public/multiplay/js/main.js#L55-L61) +- [client/public/js/icesettings.js](file://client/public/js/icesettings.js) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) + +## 结论 +该客户端示例系统提供了从基础接收、双向通信到复杂输入遥测与 UI 管理的完整参考。其模块化设计与统一的信令抽象,使得开发者可以快速将示例中的关键模式(如连接建立、媒体轨道处理、数据通道输入、UI 渲染与状态管理)集成到自有项目中,并根据业务需求扩展功能。 + +## 附录 +- 快速开始 + - 在浏览器中打开示例页面,根据页面提示选择模式(Public/Private)。 + - 如需使用 TURN,请在 ICE 配置区添加 STUN/TURN 服务器。 + - 对于需要输入控制的场景,确保数据通道可用且已注册相应事件。 +- 集成建议 + - 将公共配置与 ICE 设置抽取为独立模块,便于跨示例共享。 + - 将信令层抽象为统一接口,支持 HTTP 与 WebSocket 两种实现。 + - 将 Peer 封装与视频播放器抽象作为通用组件,按需组合到不同示例中。 + - 对于复杂 UI(如一对一通话),采用分层模块(store/renderer/chat/utils)组织代码,提升可维护性。 \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/客户端示例/接收端示例.md b/.qoder/repowiki/zh/content/客户端示例/接收端示例.md new file mode 100644 index 0000000..a76c318 --- /dev/null +++ b/.qoder/repowiki/zh/content/客户端示例/接收端示例.md @@ -0,0 +1,348 @@ +# 接收端示例 + + +**本文引用的文件** +- [client/public/receiver/js/main.js](file://client/public/receiver/js/main.js) +- [client/public/receiver/index.html](file://client/public/receiver/index.html) +- [client/public/receiver/css/style.css](file://client/public/receiver/css/style.css) +- [client/src/renderstreaming.js](file://client/src/renderstreaming.js) +- [client/src/peer.js](file://client/src/peer.js) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/public/js/config.js](file://client/public/js/config.js) +- [client/public/js/videoplayer.js](file://client/public/js/videoplayer.js) +- [client/public/js/stats.js](file://client/public/js/stats.js) +- [client/public/js/icesettings.js](file://client/public/js/icesettings.js) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向使用 Unity Render Streaming 的接收端示例,帮助您在浏览器中完成以下目标: +- 连接到服务器,建立 WebRTC 媒体会话 +- 接收并解码视频/音频流 +- 将媒体轨道渲染到页面中的视频元素 +- 处理输入通道(鼠标/键盘/触摸/手柄)以实现远程输入遥测 +- 实时查看统计信息,辅助调试与性能评估 +- 提供性能优化建议与兼容性注意事项 +- 给出典型使用场景与配置示例 + +## 项目结构 +接收端示例位于 client/public/receiver 目录,前端采用模块化 JavaScript,配合自定义的 RenderStreaming、Peer、Signaling 模块实现 WebRTC 信令与媒体处理。 + +```mermaid +graph TB +subgraph "接收端页面" +HTML["index.html"] +CSS["css/style.css"] +JSMain["receiver/js/main.js"] +JSConfig["public/js/config.js"] +JSStats["public/js/stats.js"] +JSVideo["public/js/videoplayer.js"] +end +subgraph "核心模块" +RRS["src/renderstreaming.js"] +PEER["src/peer.js"] +SIG["src/signaling.js"] +end +HTML --> JSMain +HTML --> CSS +JSMain --> JSConfig +JSMain --> JSStats +JSMain --> JSVideo +JSMain --> RRS +RRS --> PEER +RRS --> SIG +``` + +图表来源 +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186) +- [client/public/receiver/css/style.css:1-43](file://client/public/receiver/css/style.css#L1-L43) +- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +章节来源 +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186) +- [client/public/receiver/css/style.css:1-43](file://client/public/receiver/css/style.css#L1-L43) +- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +## 核心组件 +- 页面与入口 + - index.html:页面骨架、样式与脚本加载入口 + - receiver/js/main.js:应用主流程,负责初始化、信令选择、RTCPeerConnection 配置、媒体轨道接入与统计展示 +- 媒体播放器 + - public/js/videoplayer.js:封装视频元素创建、全屏切换、输入通道绑定与媒体轨道追加 +- 信令层 + - src/signaling.js:HTTP 与 WebSocket 两种信令实现,负责连接生命周期、offer/answer/candidate 转发 +- 会话与媒体 + - src/renderstreaming.js:高层封装,管理连接、多 peer、事件路由、统计查询、数据通道创建 + - src/peer.js:底层 RTCPeerConnection 管理,包含协商、ICE、统计查询、轨道与数据通道操作 +- 配置与工具 + - public/js/config.js:读取服务器配置、生成 RTC 配置(含 STUN/TURN、音频增强) + - public/js/stats.js:从 RTCStatsReport 生成可读字符串数组 + - public/js/icesettings.js:STUN/TURN 配置持久化与读取 + +章节来源 +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186) +- [client/public/js/videoplayer.js:1-213](file://client/public/js/videoplayer.js#L1-L213) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/public/js/config.js:1-39](file://client/public/js/config.js#L1-L39) +- [client/public/js/stats.js:1-91](file://client/public/js/stats.js#L1-L91) +- [client/public/js/icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104) + +## 架构总览 +下图展示了从用户点击“开始”到媒体渲染的关键交互路径,以及输入通道的建立流程。 + +```mermaid +sequenceDiagram +participant U as "用户" +participant Page as "页面(main.js)" +participant RS as "RenderStreaming" +participant Sig as "Signaling(HTTP/WebSocket)" +participant P as "Peer(RTCPeerConnection)" +participant VP as "VideoPlayer" +U->>Page : 点击“开始” +Page->>VP : 创建视频容器与播放器 +Page->>Sig : 选择信令类型并创建连接 +Page->>RS : 初始化并启动 +RS->>P : 创建/准备 Peer +P-->>RS : 触发 onnegotiationneeded +RS->>Sig : 发送 offer +Sig-->>RS : 下行 offer +RS->>P : 设置远端 offer 并生成 answer +RS->>Sig : 发送 answer +Sig-->>RS : 下行 answer +P-->>RS : 触发 ontrack +RS-->>Page : 分发 track 事件 +Page->>VP : 追加轨道到视频元素 +Page->>VP : 建立输入数据通道并绑定 +Page->>Page : 定时获取统计并展示 +``` + +图表来源 +- [client/public/receiver/js/main.js:67-108](file://client/public/receiver/js/main.js#L67-L108) +- [client/src/renderstreaming.js:182-250](file://client/src/renderstreaming.js#L182-L250) +- [client/src/peer.js:57-173](file://client/src/peer.js#L57-L173) +- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149) +- [client/public/js/videoplayer.js:19-41](file://client/public/js/videoplayer.js#L19-L41) + +## 详细组件分析 + +### 页面与入口:main.js +- 初始化与页面控制 + - 读取服务器配置,决定是否使用 WebSocket 信令 + - 显示警告(如私有模式限制)、编解码器选择下拉框、播放按钮 + - 点击播放后创建视频播放器并初始化 RenderStreaming +- RTCPeerConnection 配置 + - 通过 config.js 获取 RTC 配置,包含 sdpSemantics、iceServers、媒体约束与音频增强参数 +- 事件与生命周期 + - onConnect:创建输入数据通道,绑定 VideoPlayer 输入遥测 + - onDisconnect:清理统计、重置 UI、重建播放器与编解码器选择 + - onTrackEvent:将轨道追加到视频元素 + - onGotOffer:根据浏览器能力设置编解码器偏好 +- 统计展示 + - 定时调用 RenderStreaming.getStats,使用 stats.js 生成可读字符串并显示 + +章节来源 +- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186) +- [client/public/js/config.js:1-39](file://client/public/js/config.js#L1-L39) +- [client/public/js/stats.js:1-91](file://client/public/js/stats.js#L1-L91) + +### 媒体播放器:VideoPlayer +- 视频元素与布局 + - 创建 video 元素,设置 playsInline、空 MediaStream,监听元数据加载完成自动播放 + - 提供全屏按钮与全屏状态切换逻辑,支持指针锁定(可选) +- 轨道与尺寸 + - addTrack:向 video.srcObject 追加媒体轨道 + - resizeVideo:计算缩放与偏移,适配容器尺寸 +- 输入遥测 + - setupInput:创建 Sender/InputRemoting,订阅 RTCDataChannel,打开后开始发送输入事件 + - 支持鼠标、键盘、触摸屏、手柄输入的转发 + +章节来源 +- [client/public/js/videoplayer.js:1-213](file://client/public/js/videoplayer.js#L1-L213) + +### 信令层:Signaling(HTTP 与 WebSocket) +- HTTP 信令 + - start:轮询获取消息队列,分发 connect/disconnect/offer/answer/candidate/on-message + - createConnection/deleteConnection:创建/删除连接 + - sendOffer/sendAnswer/sendCandidate/sendMessage:发送 SDP 与消息 +- WebSocket 信令 + - onopen/onmessage/onclose:解析消息类型并分发事件 + - createConnection/deleteConnection/sendOffer/sendAnswer/sendCandidate/sendMessage:与 HTTP 版本对应 + +章节来源 +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +### 会话与媒体:RenderStreaming +- 生命周期与角色 + - 支持 Host/Participant 双端模式;Participant 端默认单 peer,Host 端按参与者维护多 peer +- 事件路由 + - 将 Peer 的 track/adddatachannel/offer/answer/candidate 等事件上抛,并携带 participantId +- 关键方法 + - createConnection/start/stop:连接生命周期管理 + - getStats/createDataChannel/addTrack/addTransceiver/getTransceivers:媒体与统计接口 + - 与 Signaling 事件绑定,完成 offer/answer/candidate 的收发 + +章节来源 +- [client/src/renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) + +### 底层会话:Peer(RTCPeerConnection) +- 协商与 ICE + - onnegotiationneeded:生成本地 offer 并通过事件上报 + - onGotDescription:设置远端描述,若为 offer 则生成 answer 并上报 + - onIceCandidate:上报候选 +- 状态与错误 + - iceConnectionState=failed 时触发 disconnect 事件 +- 工具方法 + - getTransceivers/addTrack/addTransceiver/createDataChannel/getStats/close:媒体与统计操作 + +章节来源 +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +### 配置与工具 +- config.js + - getServerConfig:读取服务器配置(含 useWebSocket) + - getRTCConfiguration:统一计划 SDP、ICE 服务器、媒体约束与音频增强参数 +- icesettings.js + - STUN/TURN 服务器的增删改查与本地存储 +- stats.js + - 从 inbound/outbound RTP 统计生成可读字符串,包含编解码器、分辨率、帧率、比特率等 + +章节来源 +- [client/public/js/config.js:1-39](file://client/public/js/config.js#L1-L39) +- [client/public/js/icesettings.js:1-104](file://client/public/js/icesettings.js#L1-L104) +- [client/public/js/stats.js:1-91](file://client/public/js/stats.js#L1-L91) + +## 依赖关系分析 +- 模块耦合 + - main.js 依赖 renderstreaming.js、videoplayer.js、config.js、stats.js + - renderstreaming.js 依赖 peer.js 与 signaling.js + - peer.js 仅依赖 logger.js(内部日志) +- 外部依赖 + - 浏览器原生 WebRTC API(RTCPeerConnection、MediaStream、RTCIceServer 等) + - 第三方 polyfill/适配库(页面引入了 webrtc-adapter) + +```mermaid +graph LR +MAIN["main.js"] --> CFG["config.js"] +MAIN --> STATS["stats.js"] +MAIN --> VP["videoplayer.js"] +MAIN --> RRS["renderstreaming.js"] +RRS --> PEER["peer.js"] +RRS --> SIG["signaling.js"] +VP --> INPUT["inputremoting.js(sender.js)"] +``` + +图表来源 +- [client/public/receiver/js/main.js:1-10](file://client/public/receiver/js/main.js#L1-L10) +- [client/src/renderstreaming.js:1-30](file://client/src/renderstreaming.js#L1-L30) +- [client/src/peer.js:1-10](file://client/src/peer.js#L1-L10) +- [client/src/signaling.js:1-10](file://client/src/signaling.js#L1-L10) +- [client/public/js/videoplayer.js:1-3](file://client/public/js/videoplayer.js#L1-L3) + +章节来源 +- [client/public/receiver/js/main.js:1-10](file://client/public/receiver/js/main.js#L1-L10) +- [client/src/renderstreaming.js:1-30](file://client/src/renderstreaming.js#L1-L30) +- [client/src/peer.js:1-10](file://client/src/peer.js#L1-L10) +- [client/src/signaling.js:1-10](file://client/src/signaling.js#L1-L10) +- [client/public/js/videoplayer.js:1-3](file://client/public/js/videoplayer.js#L1-L3) + +## 性能考虑 +- 编解码器选择 + - 若浏览器支持 setCodecPreferences,可在 offer 生成前设置偏好,减少不必要解码开销 +- 统计监控 + - 使用 stats.js 输出的比特率、分辨率、帧率等指标,结合网络状况动态调整 +- 媒体约束与音频增强 + - 合理配置音频回声消除、降噪、自动增益等参数,降低 CPU 占用与提升音质 +- 渲染与布局 + - 使用 videoplayer.js 的 resizeVideo 计算缩放,避免不必要的重排 +- 信令选择 + - WebSocket 信令延迟更低,适合低延迟场景;HTTP 轮询更通用,兼容性更好 + +[本节为通用指导,无需列出具体文件来源] + +## 故障排查指南 +- 私有模式限制 + - 页面会在私有模式下提示警告,导致部分功能不可用 +- 无法播放或黑屏 + - 检查 onConnect 是否触发、track 事件是否到达、VideoPlayer 是否成功追加轨道 + - 确认浏览器是否支持所选编解码器,必要时切换默认或手动选择 +- 无声音 + - 确认音频媒体轨道已加入,检查音频增强参数与浏览器静音策略 +- 输入无响应 + - 确认数据通道已创建并打开,VideoPlayer.setupInput 是否被调用 +- 连接失败或频繁断开 + - 查看 ICE 候选是否正常收发,STUN/TURN 配置是否正确 + - 关注 Peer 的 iceConnectionState 与 signalingState 变化 + +章节来源 +- [client/public/receiver/js/main.js:48-54](file://client/public/receiver/js/main.js#L48-L54) +- [client/public/js/videoplayer.js:194-212](file://client/public/js/videoplayer.js#L194-L212) +- [client/src/peer.js:43-48](file://client/src/peer.js#L43-L48) +- [client/public/js/icesettings.js:94-104](file://client/public/js/icesettings.js#L94-L104) + +## 结论 +该接收端示例通过清晰的模块划分与事件驱动设计,实现了从信令、协商、媒体轨道接入到播放器渲染与输入遥测的完整链路。借助统计模块与编解码器偏好设置,开发者可以快速定位问题并优化体验。建议在生产环境中优先使用 WebSocket 信令、合理配置 ICE 服务器与音频增强,并持续监控统计指标以保障质量。 + +[本节为总结性内容,无需列出具体文件来源] + +## 附录 + +### 使用步骤(从零到运行) +- 启动服务端 + - 使用 package.json 中的脚本启动服务端,默认端口与证书参数可参考脚本配置 +- 打开接收端页面 + - 在浏览器中访问接收端页面,点击“开始” +- 选择信令方式 + - 若服务器返回 useWebSocket=true,则使用 WebSocket 信令;否则使用 HTTP 信令 +- 选择编解码器(可选) + - 若浏览器支持 setCodecPreferences,可在下拉框中选择偏好编解码器 +- 观察统计与播放 + - 页面会定时显示统计信息;视频元素加载完成后自动播放 +- 输入遥测(可选) + - 勾选“锁定光标到播放器”,进入全屏后可进行指针锁定与输入转发 + +章节来源 +- [package.json:9-10](file://package.json#L9-L10) +- [client/public/receiver/js/main.js:40-88](file://client/public/receiver/js/main.js#L40-L88) +- [client/public/js/config.js:3-7](file://client/public/js/config.js#L3-L7) +- [client/public/js/stats.js:7-91](file://client/public/js/stats.js#L7-L91) + +### 关键流程图:编解码器偏好设置 +```mermaid +flowchart TD +Start(["收到 Offer"]) --> CheckSupport{"浏览器支持
setCodecPreferences?"} +CheckSupport --> |否| Skip["跳过设置偏好"] +CheckSupport --> |是| ReadOptions["读取下拉框选中项"] +ReadOptions --> Parse["解析 mimeType 与 sdpFmtpLine"] +Parse --> Find["在 RTCRtpSender.getCapabilities 中查找匹配编解码器"] +Find --> Found{"找到匹配项?"} +Found --> |否| Skip +Found --> |是| GetTransceivers["获取视频接收端点列表"] +GetTransceivers --> Apply["对每个视频接收端点调用 setCodecPreferences"] +Apply --> End(["完成"]) +Skip --> End +``` + +图表来源 +- [client/public/receiver/js/main.js:110-131](file://client/public/receiver/js/main.js#L110-L131) +- [client/src/renderstreaming.js:284-290](file://client/src/renderstreaming.js#L284-L290) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/客户端示例/视频播放器示例.md b/.qoder/repowiki/zh/content/客户端示例/视频播放器示例.md new file mode 100644 index 0000000..86f1731 --- /dev/null +++ b/.qoder/repowiki/zh/content/客户端示例/视频播放器示例.md @@ -0,0 +1,412 @@ +# 视频播放器示例 + + +**本文档引用的文件** +- [video-player.js](file://client/public/videoplayer/js/video-player.js) +- [gamepadEvents.js](file://client/public/videoplayer/js/gamepadEvents.js) +- [register-events.js](file://client/public/videoplayer/js/register-events.js) +- [main.js](file://client/public/videoplayer/js/main.js) +- [index.html](file://client/public/videoplayer/index.html) +- [peer.js](file://client/src/peer.js) +- [signaling.js](file://client/src/signaling.js) +- [renderstreaming.js](file://client/src/renderstreaming.js) +- [sender.js](file://client/src/sender.js) +- [inputdevice.js](file://client/src/inputdevice.js) +- [inputremoting.js](file://client/src/inputremoting.js) +- [logger.js](file://client/src/logger.js) +- [keymap.js](file://client/src/keymap.js) +- [mousebutton.js](file://client/src/mousebutton.js) +- [gamepadbutton.js](file://client/src/gamepadbutton.js) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本示例演示了如何在浏览器中接收 Unity 渲染的相机图像,并通过浏览器端操作 Unity 中的相机。系统采用 WebRTC 进行媒体流传输,同时通过数据通道发送用户输入事件(键盘、鼠标、触摸、游戏手柄)。播放器支持主画中画双路视频切换、缩放与坐标转换、以及多种输入设备的实时控制。 + +## 项目结构 +视频播放器示例位于 Public 模式下的 videoplayer 子目录,主要由以下层次组成: +- 视频播放层:负责建立 WebRTC 连接、接收媒体轨道、管理数据通道、处理来自 Unity 的控制消息。 +- 事件注册层:封装各类输入事件的监听与打包,统一通过数据通道发送至 Unity。 +- WebRTC 基础层:封装 PeerConnection、信令协议、ICE 候选等底层细节。 +- 输入设备抽象层:定义输入设备、状态与事件的数据格式,便于序列化与跨平台传输。 + +```mermaid +graph TB +subgraph "浏览器前端" +VP["VideoPlayer
视频播放器"] +RE["register-events.js
事件注册"] +GP["gamepadEvents.js
游戏手柄事件"] +PEER["Peer
RTCPeerConnection封装"] +SIG["Signaling/WebSocketSignaling
信令"] +end +subgraph "Unity 后端" +RS["RenderStreaming
渲染流封装"] +SENDER["Sender
输入发送器"] +INPUTDEV["InputDevice/IInputState
输入设备与状态"] +end +VP --> PEER +VP --> SIG +RE --> VP +GP --> RE +PEER --> SIG +RS --> PEER +SENDER --> RS +INPUTDEV --> SENDER +``` + +**图表来源** +- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247) +- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308) +- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147) +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [sender.js:1-209](file://client/src/sender.js#L1-L209) +- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) + +**章节来源** +- [index.html:1-38](file://client/public/videoplayer/index.html#L1-L38) +- [main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157) + +## 核心组件 +- VideoPlayer:负责创建 RTCPeerConnection、订阅媒体轨道、管理数据通道、解析来自 Unity 的控制消息(如切换视频源)。 +- register-events.js:集中注册键盘、鼠标、触摸、游戏手柄事件,将事件打包为二进制消息并通过数据通道发送。 +- gamepadEvents.js:对原生游戏手柄 API 进行封装,生成统一的自定义事件(按钮按下/抬起/持续按住、轴值变化),并维护设备状态。 +- Peer:封装 RTCPeerConnection 生命周期、SDP 协商、ICE 候选处理与断线检测。 +- Signaling/WebSocketSignaling:HTTP 轮询或 WebSocket 实现的信令通道,承载 offer/answer/candidate/on-message 等信令。 +- RenderStreaming/Sender/InputDevice:输入遥测链路的完整实现,定义输入设备、状态与事件的内存布局,支持鼠标、键盘、触摸、游戏手柄。 + +**章节来源** +- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247) +- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308) +- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147) +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [sender.js:1-209](file://client/src/sender.js#L1-L209) +- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) + +## 架构总览 +系统采用“浏览器播放器 + 信令 + WebRTC + 数据通道”的架构。浏览器端通过 Signaling 与服务器建立连接,协商 SDP 并交换 ICE 候选;媒体轨道通过 RTCPeerConnection 接收,数据通道用于传输输入事件。Unity 侧通过 RenderStreaming/Sender 将输入事件投递到远程渲染环境,同时可向浏览器发送控制消息(如切换视频源)。 + +```mermaid +sequenceDiagram +participant Browser as "浏览器" +participant VP as "VideoPlayer" +participant PEER as "Peer" +participant SIG as "Signaling/WebSocketSignaling" +participant Unity as "Unity" +Browser->>VP : 初始化并调用 setupConnection() +VP->>SIG : start() 启动信令 +VP->>PEER : 创建 RTCPeerConnection +PEER->>SIG : 发送 offer +SIG-->>PEER : 下发 offer +PEER->>SIG : 发送 answer +SIG-->>PEER : 下发 answer +PEER->>SIG : 发送 ICE 候选 +SIG-->>PEER : 下发 ICE 候选 +PEER-->>VP : 触发 ontrack 获取媒体轨道 +VP->>VP : 切换主/副视频轨道 +Unity-->>VP : 数据通道下发控制消息 +VP->>VP : 解析消息并执行操作 +``` + +**图表来源** +- [video-player.js:49-155](file://client/public/videoplayer/js/video-player.js#L49-L155) +- [peer.js:57-173](file://client/src/peer.js#L57-L173) +- [signaling.js:30-149](file://client/src/signaling.js#L30-L149) + +## 详细组件分析 + +### VideoPlayer 组件分析 +VideoPlayer 是播放器的核心,负责: +- 建立 RTCPeerConnection 并绑定事件(trackevent、sendoffer、sendanswer、sendcandidate)。 +- 订阅媒体轨道,维护主/副视频轨道列表,支持动态切换。 +- 创建数据通道,接收来自 Unity 的控制消息并执行相应操作(如切换视频源)。 +- 提供尺寸与坐标转换能力,确保输入事件映射到正确的视频区域。 + +```mermaid +classDiagram +class VideoPlayer { ++constructor(elements) ++setupConnection(useWebSocket) ++resizeVideo() ++switchVideo(indexVideoTrack) ++replaceTrack(stream, newTrack) ++sendMsg(msg) ++stop() ++ondisconnect ++videoWidth ++videoHeight ++videoOriginX ++videoOriginY ++videoScale +} +class Peer { ++addEventListener(event, handler) ++createDataChannel(connectionId, label) ++onGotDescription(connectionId, description) ++onGotCandidate(connectionId, candidate) ++close() +} +class Signaling { ++start() ++sendOffer(connectionId, sdp) ++sendAnswer(connectionId, sdp) ++sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) ++addEventListener(event, handler) ++stop() +} +VideoPlayer --> Peer : "使用" +VideoPlayer --> Signaling : "使用" +``` + +**图表来源** +- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247) +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +**章节来源** +- [video-player.js:18-247](file://client/public/videoplayer/js/video-player.js#L18-L247) + +### register-events.js 事件注册与处理 +该模块负责: +- 键盘事件:按键按下/抬起,映射键码并发送二进制消息。 +- 鼠标事件:点击/移动/滚轮,计算相对于视频的坐标并发送。 +- 触摸事件:多点触控,记录每个触点的阶段、坐标、压力等信息。 +- 游戏手柄事件:统一的按钮与轴事件,生成自定义事件并转发给 VideoPlayer。 + +```mermaid +flowchart TD +Start(["开始"]) --> Detect["检测输入事件类型"] +Detect --> |键盘| BuildKey["构建键盘消息
类型/方向/重复/键码/字符"] +Detect --> |鼠标| BuildMouse["构建鼠标消息
坐标/按钮"] +Detect --> |触摸| BuildTouch["构建触摸消息
触点数量/阶段/坐标/压力"] +Detect --> |游戏手柄| BuildGP["构建手柄消息
按钮/轴值"] +BuildKey --> Send["通过数据通道发送"] +BuildMouse --> Send +BuildTouch --> Send +BuildGP --> Send +Send --> End(["结束"]) +``` + +**图表来源** +- [register-events.js:120-281](file://client/public/videoplayer/js/register-events.js#L120-L281) + +**章节来源** +- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308) + +### gamepadEvents.js 游戏手柄事件处理 +该模块对原生游戏手柄 API 进行封装: +- 维护设备连接时间戳与前一帧状态,避免重复触发。 +- 定时循环(约 60fps)扫描按钮与轴值变化,生成统一的自定义事件。 +- 支持不同浏览器差异(如 getGamepads 的兼容性)。 + +```mermaid +sequenceDiagram +participant Browser as "浏览器" +participant GPH as "gamepadEvents.js" +participant RE as "register-events.js" +participant VP as "VideoPlayer" +Browser->>GPH : 设备连接/断开 +GPH->>GPH : 记录连接时间戳/初始化状态 +loop 60fps +GPH->>GPH : 扫描按钮/轴值变化 +alt 按钮状态变化 +GPH-->>RE : 触发 gamepadButtonDown/gamepadButtonUp +else 按钮持续按住 +GPH-->>RE : 触发 gamepadButtonPressed +end +alt 轴值变化 +GPH-->>RE : 触发 gamepadAxis +end +end +RE->>VP : sendMsg(二进制消息) +``` + +**图表来源** +- [gamepadEvents.js:68-94](file://client/public/videoplayer/js/gamepadEvents.js#L68-L94) +- [register-events.js:42-101](file://client/public/videoplayer/js/register-events.js#L42-L101) +- [video-player.js:215-233](file://client/public/videoplayer/js/video-player.js#L215-L233) + +**章节来源** +- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147) +- [register-events.js:42-101](file://client/public/videoplayer/js/register-events.js#L42-L101) + +### 输入设备抽象与序列化 +输入设备层定义了统一的状态与事件格式,便于跨平台传输: +- InputDevice:抽象基类,维护设备描述与当前状态。 +- IInputState:抽象状态接口,提供 buffer 与 format 字段。 +- 具体设备状态:MouseState、KeyboardState、TouchscreenState、GamepadState。 +- 事件封装:StateEvent、TextEvent,支持二进制序列化。 + +```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 StateEvent +class TextEvent +InputDevice <|-- Mouse +InputDevice <|-- Keyboard +InputDevice <|-- Touchscreen +InputDevice <|-- Gamepad +IInputState <|-- MouseState +IInputState <|-- KeyboardState +IInputState <|-- TouchscreenState +IInputState <|-- GamepadState +StateEvent --> IInputState : "包含状态" +TextEvent --> InputEvent : "包含基础事件" +``` + +**图表来源** +- [inputdevice.js:40-719](file://client/src/inputdevice.js#L40-L719) + +**章节来源** +- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) + +### 信令与 WebRTC 协商 +- Signaling:基于 HTTP 的轮询信令,支持 offer/answer/candidate/on-message。 +- WebSocketSignaling:基于 WebSocket 的实时信令,支持广播与参与者事件。 +- Peer:封装 RTCPeerConnection,自动处理 SDP 协商、ICE 候选、断线检测与重发 offer。 + +```mermaid +sequenceDiagram +participant A as "Peer A" +participant SIG as "Signaling" +participant B as "Peer B" +A->>SIG : 发送 offer +SIG-->>B : 下发 offer +B->>B : 设置远端 offer +B->>SIG : 发送 answer +SIG-->>A : 下发 answer +A->>A : 设置远端 answer +A->>SIG : 发送 ICE 候选 +B->>SIG : 发送 ICE 候选 +SIG-->>A : 下发 ICE 候选 +SIG-->>B : 下发 ICE 候选 +``` + +**图表来源** +- [peer.js:57-173](file://client/src/peer.js#L57-L173) +- [signaling.js:117-146](file://client/src/signaling.js#L117-L146) + +**章节来源** +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +## 依赖关系分析 +- VideoPlayer 依赖 Peer 与 Signaling,用于媒体与信令。 +- register-events 依赖 gamepadEvents 与 keymap,用于事件注册与键码映射。 +- main.js 作为入口,协调 VideoPlayer 与事件注册模块。 +- 输入遥测链路由 Sender -> RenderStreaming -> Peer -> Signaling,最终到达 Unity。 + +```mermaid +graph LR +MAIN["main.js"] --> VP["video-player.js"] +MAIN --> REG["register-events.js"] +REG --> GPE["gamepadEvents.js"] +VP --> PEER["peer.js"] +VP --> SIG["signaling.js"] +SENDER["sender.js"] --> RS["renderstreaming.js"] +RS --> PEER +INPUTDEV["inputdevice.js"] --> SENDER +``` + +**图表来源** +- [main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157) +- [video-player.js:1-247](file://client/public/videoplayer/js/video-player.js#L1-L247) +- [register-events.js:1-308](file://client/public/videoplayer/js/register-events.js#L1-L308) +- [gamepadEvents.js:1-147](file://client/public/videoplayer/js/gamepadEvents.js#L1-L147) +- [peer.js:1-188](file://client/src/peer.js#L1-L188) +- [signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [sender.js:1-209](file://client/src/sender.js#L1-L209) +- [renderstreaming.js:1-317](file://client/src/renderstreaming.js#L1-L317) +- [inputdevice.js:1-719](file://client/src/inputdevice.js#L1-L719) + +**章节来源** +- [main.js:1-157](file://client/public/videoplayer/js/main.js#L1-L157) + +## 性能考虑 +- 事件采样频率:游戏手柄事件以约 60fps 轮询,建议根据设备刷新率调整间隔,避免过度 CPU 占用。 +- 坐标转换:VideoPlayer 提供视频缩放与偏移计算,确保输入事件映射准确,减少误触。 +- 数据通道:仅在通道打开时发送消息,避免在关闭或连接中发送导致异常。 +- 媒体轨道切换:使用 replaceTrack 替换视频轨道,避免频繁创建/销毁媒体流带来的抖动。 + +## 故障排除指南 +- 无法建立 WebRTC 连接 + - 检查信令是否正常启动与停止。 + - 确认 offer/answer 协商过程无异常,ICE 候选是否正确交换。 + - 查看断线事件与日志输出。 +- 数据通道不可用 + - 确认数据通道已创建且处于 open 状态。 + - 检查 sendMsg 的状态分支,避免在 closing/closed 状态发送。 +- 输入事件未生效 + - 确认事件监听已注册,且消息格式与 Unity 期望一致。 + - 检查坐标转换参数(videoScale、videoOriginX/Y)是否正确。 +- 游戏手柄无响应 + - 确认浏览器支持 getGamepads 或 webkitGetGamepads。 + - 检查设备连接时间戳与状态缓存是否正确初始化。 + +**章节来源** +- [video-player.js:128-154](file://client/public/videoplayer/js/video-player.js#L128-L154) +- [register-events.js:237-263](file://client/public/videoplayer/js/register-events.js#L237-L263) +- [gamepadEvents.js:112-146](file://client/public/videoplayer/js/gamepadEvents.js#L112-L146) +- [logger.js:1-30](file://client/src/logger.js#L1-L30) + +## 结论 +该视频播放器示例完整展示了从浏览器接收 Unity 渲染视频、通过数据通道发送用户输入、以及在 Unity 中进行相机控制的端到端流程。其模块化设计使得事件注册、输入抽象与 WebRTC 协商清晰分离,便于扩展与维护。通过合理的坐标转换与事件采样策略,系统在多设备环境下具备良好的交互体验。 + +## 附录 + +### 使用指南 +- Public 模式配置 + - 通过配置接口获取服务器设置,决定使用 HTTP 轮询信令还是 WebSocket 信令。 + - 若启动模式为私有模式,页面会显示警告提示。 +- 启动播放器 + - 点击播放按钮后,页面动态创建主视频与缩略视频元素。 + - 初始化 VideoPlayer 并建立 WebRTC 连接。 +- 控制 Unity 中的相机 + - 通过数据通道发送控制消息(如切换视频源)。 + - 可通过按钮事件向 Unity 发送按钮点击消息。 +- 输入设备使用 + - 键盘:按键按下/抬起事件会被打包发送。 + - 鼠标:点击/移动/滚轮事件,坐标经视频缩放与偏移转换。 + - 触摸:多点触控,记录触点阶段、坐标、压力等。 + - 游戏手柄:按钮与轴值变化统一事件,支持不同浏览器差异。 +- 设备适配 + - 坐标系适配:VideoPlayer 提供缩放与偏移计算,确保事件映射到正确区域。 + - 全屏模式:支持全屏切换,改变按钮可见性与布局。 + - 断线处理:断线时清理 DOM、停止播放器并重新显示播放按钮。 + +**章节来源** +- [main.js:23-157](file://client/public/videoplayer/js/main.js#L23-L157) +- [video-player.js:49-247](file://client/public/videoplayer/js/video-player.js#L49-L247) +- [register-events.js:120-308](file://client/public/videoplayer/js/register-events.js#L120-L308) +- [gamepadEvents.js:112-147](file://client/public/videoplayer/js/gamepadEvents.js#L112-L147) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/开发指南/代码规范与质量.md b/.qoder/repowiki/zh/content/开发指南/代码规范与质量.md new file mode 100644 index 0000000..6f29e50 --- /dev/null +++ b/.qoder/repowiki/zh/content/开发指南/代码规范与质量.md @@ -0,0 +1,379 @@ +# 代码规范与质量 + + +**本文引用的文件** +- [.eslintrc.cjs](file://.eslintrc.cjs) +- [.eslintrc.json(客户端)](file://client/.eslintrc.json) +- [.editorconfig](file://.editorconfig) +- [package.json(服务端)](file://package.json) +- [package.json(客户端)](file://client/package.json) +- [tsconfig.json](file://tsconfig.json) +- [tsconfig.lint.json](file://tsconfig.lint.json) +- [jest.config.js](file://jest.config.js) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [client/src/peer.js](file://client/src/peer.js) +- [client/src/sender.js](file://client/src/sender.js) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) +- [client/test/inputremoting.test.js](file://client/test/inputremoting.test.js) + + +## 目录 +1. 引言 +2. 项目结构 +3. 核心组件 +4. 架构总览 +5. 详细组件分析 +6. 依赖分析 +7. 性能考虑 +8. 故障排查指南 +9. 结论 +10. 附录 + +## 引言 +本指南面向视频服务器项目的开发者与维护者,系统性阐述代码规范与质量控制策略,覆盖以下方面: +- ESLint 规则与 TypeScript/JavaScript 的差异化配置 +- 代码格式化工具与自动格式化设置(EditorConfig) +- 命名约定与变量命名最佳实践 +- 注释规范与文档编写标准 +- 代码审查流程与质量检查要点 +- 常见问题识别与修复方法 +- 自动化质量检查工具的配置与使用 + +## 项目结构 +该项目采用前后端分离的多包结构: +- 服务端(TypeScript):src、test、配置文件(tsconfig、eslint、jest) +- 客户端(前端 JS/TS):client 目录下包含公共资源、源码与测试 +- 全局脚本与工具:根目录 package.json 提供 lint、test、build 等命令 + +```mermaid +graph TB +subgraph "服务端" +TS["TypeScript 源码
src/*.ts"] +TSTest["测试
test/*.test.ts"] +ESLintCJS[".eslintrc.cjs
TypeScript 规则"] +JestCfg["jest.config.js
测试配置"] +TSConf["tsconfig.json
编译配置"] +LintTSConf["tsconfig.lint.json
ESLint 专用"] +end +subgraph "客户端" +JS["前端源码
client/src/*.js"] +JSTest["前端测试
client/test/*.test.js"] +ESLintJSON[".eslintrc.json
浏览器环境规则"] +end +RootPkg["根 package.json
脚本与依赖"] +RootPkg --> ESLintCJS +RootPkg --> JestCfg +RootPkg --> TSConf +RootPkg --> LintTSConf +TS --> ESLintCJS +TSTest --> JestCfg +JS --> ESLintJSON +JSTest --> JestCfg +``` + +图表来源 +- [.eslintrc.cjs](file://.eslintrc.cjs) +- [.eslintrc.json(客户端)](file://client/.eslintrc.json) +- [jest.config.js](file://jest.config.js) +- [tsconfig.json](file://tsconfig.json) +- [tsconfig.lint.json](file://tsconfig.lint.json) +- [package.json(服务端)](file://package.json) +- [package.json(客户端)](file://client/package.json) + +章节来源 +- [package.json(服务端):1-60](file://package.json#L1-L60) +- [package.json(客户端):1-19](file://client/package.json#L1-L19) + +## 核心组件 +- 代码检查(ESLint) + - 服务端:基于 TypeScript ESLint 插件,启用推荐规则集,解析器为 TypeScript,使用独立的 tsconfig.lint.json 以避免 sourceMap 影响性能。 + - 客户端:基于 eslint:recommended 与 jest 插件,针对浏览器与 ES6 环境,强制分号与禁止多余分号。 +- 测试框架(Jest) + - 收集覆盖率、使用 ts-jest 转换 TS/TSX,测试环境为 node;客户端测试使用 jsdom 环境。 +- 编辑器配置(EditorConfig) + - 统一缩进风格、换行符、结尾换行与尾随空白处理。 + +章节来源 +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [.eslintrc.json(客户端):1-23](file://client/.eslintrc.json#L1-L23) +- [tsconfig.lint.json:1-12](file://tsconfig.lint.json#L1-L12) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [.editorconfig:1-20](file://.editorconfig#L1-L20) + +## 架构总览 +整体质量控制链路如下: +- 开发阶段:编辑器读取 EditorConfig 进行基础格式化;保存时由 IDE 或插件执行 ESLint 校验。 +- CI/本地:通过 npm/yarn scripts 执行 lint 与 test;ESLint 针对 src 与 test;Jest 覆盖服务端与客户端测试。 +- 发布前:构建产物生成于 build 目录,确保 TypeScript 编译无误。 + +```mermaid +sequenceDiagram +participant Dev as "开发者" +participant IDE as "编辑器/IDE" +participant ESL as "ESLint" +participant Jest as "Jest" +participant TS as "TypeScript 编译器" +Dev->>IDE : 保存文件 +IDE->>ESL : 触发语法与风格检查 +ESL-->>Dev : 报告规则违规 +Dev->>Jest : 运行测试 +Jest-->>Dev : 输出测试结果与覆盖率 +Dev->>TS : 执行构建 +TS-->>Dev : 生成构建产物 +``` + +图表来源 +- [package.json(服务端):5-12](file://package.json#L5-L12) +- [jest.config.js:174-176](file://jest.config.js#L174-L176) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) + +## 详细组件分析 + +### ESLint 配置与规则 +- 服务端(TypeScript) + - 环境:node、jest + - 继承:eslint:recommended、@typescript-eslint/eslint-recommended、@typescript-eslint/recommended、jest/recommended + - 解析器:@typescript-eslint/parser + - 解析选项:module、project 指向 tsconfig.lint.json + - 关键规则: + - 禁止 var 的 require 使用 + - any 类型放宽 + - 分号:强制;多余分号:报错 +- 客户端(JavaScript) + - 环境:browser、es6、jest + - 继承:eslint:recommended、jest/recommended + - 规则:强制分号、禁止多余分号 + +章节来源 +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [.eslintrc.json(客户端):1-23](file://client/.eslintrc.json#L1-L23) + +### 代码格式化与 EditorConfig +- 统一规则: + - 缩进:空格 + - 行结束符:LF + - 缩进大小:2 + - 文件末尾插入换行 + - 字符集:UTF-8 + - 去除行尾空白 +- 适用范围:ts、js、json、html + +章节来源 +- [.editorconfig:10-20](file://.editorconfig#L10-L20) + +### TypeScript 与 JavaScript 的差异化规则 +- TypeScript + - 使用 @typescript-eslint 推荐规则,强调类型安全与结构一致性 + - 通过 tsconfig.lint.json 优化 ESLint 性能(关闭 sourceMap) +- JavaScript(客户端) + - 面向浏览器与 ES6,强调语义正确性与简洁性 + - 保留分号一致性,避免多余分号 + +章节来源 +- [.eslintrc.cjs:6-16](file://.eslintrc.cjs#L6-L16) +- [.eslintrc.json(客户端):3-22](file://client/.eslintrc.json#L3-L22) +- [tsconfig.lint.json:4-11](file://tsconfig.lint.json#L4-L11) + +### 命名约定与变量命名最佳实践 +- 类型与模块 + - 类名:帕斯卡命名(如 RenderStreaming) + - 接口/类型:帕斯卡命名(如 Options) + - 模块导出:默认导出或具名导出保持一致,避免混用 +- 变量与函数 + - 变量:小驼峰命名(如 connectionId、options) + - 函数:动词短语或小驼峰(如 getIPAddress、createServer) + - 常量:全大写蛇形(如 MAX_RETRIES) +- 文件与路径 + - 源文件:按功能分层组织(如 src/class、src/signaling) + - 测试文件:与被测模块同名或以 .test 后缀 +- 前端输入设备类 + - 类名:帕斯卡命名(如 Sender、Observer) + - 属性与方法:小驼峰(如 devices、onEvent) + +章节来源 +- [src/index.ts:13-106](file://src/index.ts#L13-L106) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) +- [client/src/sender.js:14-209](file://client/src/sender.js#L14-L209) + +### 注释规范与文档编写标准 +- 文件级注释 + - 说明模块职责、入口点与关键行为 +- 函数/方法注释 + - 参数与返回值:使用 JSDoc 风格(如 @param、@returns) + - 复杂逻辑:简述目的与边界条件 +- 类注释 + - 类的用途、构造参数、事件与生命周期 +- 前端类示例 + - 类定义处与关键方法处提供注释,描述事件派发与状态变化 + +章节来源 +- [client/src/sender.js:107-112](file://client/src/sender.js#L107-L112) +- [client/src/sender.js:191-201](file://client/src/sender.js#L191-L201) + +### 代码审查流程与质量检查要点 +- 代码检查 + - 本地:npm run lint(服务端)与 npm run lint(客户端) + - CI:集成 lint 与 test 步骤,确保通过后再合并 +- 测试覆盖率 + - Jest 启用覆盖率收集,建议设定最小阈值 +- 审查清单 + - 类型安全:TS 是否启用严格模式相关规则 + - 命名一致性:是否遵循命名约定 + - 注释完整性:关键函数/类是否具备必要注释 + - 错误处理:异常分支与日志记录 + - 性能与资源:定时任务、事件监听与清理 + +章节来源 +- [package.json(服务端):5-12](file://package.json#L5-L12) +- [package.json(客户端):5-8](file://client/package.json#L5-L8) +- [jest.config.js:20-34](file://jest.config.js#L20-L34) + +### 常见代码问题与修复方法 +- TypeScript + - any 类型滥用:优先使用具体类型或泛型 + - 导入路径不一致:统一相对路径或别名 + - 未使用的变量/私有成员:删除或标记为私有 +- JavaScript(客户端) + - 事件监听未移除:在组件销毁时解绑 + - 分号缺失:遵循 EditorConfig 与 ESLint 规则 + - 重复逻辑:抽取为函数或工具模块 +- 通用 + - 日志与错误:使用统一的日志模块,避免直接 console + - 资源清理:WebSocket、HTTP 请求、文件句柄等及时释放 + +章节来源 +- [.eslintrc.cjs:18-23](file://.eslintrc.cjs#L18-L23) +- [.eslintrc.json(客户端):19-22](file://client/.eslintrc.json#L19-L22) +- [src/server.ts:44-57](file://src/server.ts#L44-L57) + +### 自动化质量检查工具配置与使用 +- ESLint + - 服务端:npm run lint 检查 src 与 test 下的 TypeScript 文件 + - 客户端:npm run lint 检查 public 与 src、test 下的 JS 文件 +- Jest + - 服务端:npm test 运行所有测试 + - 客户端:npm test 运行前端测试(需 jsdom 环境) +- TypeScript + - 构建:npm run build 使用 tsconfig.build.json + - ESLint 专用:tsconfig.lint.json 关闭 sourceMap,提升性能 + +章节来源 +- [package.json(服务端):5-12](file://package.json#L5-L12) +- [package.json(客户端):5-8](file://client/package.json#L5-L8) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.lint.json:1-12](file://tsconfig.lint.json#L1-L12) +- [jest.config.js:174-176](file://jest.config.js#L174-L176) + +## 依赖分析 +- 工具链耦合 + - ESLint 与 TypeScript 解析器耦合,解析选项指向 tsconfig.lint.json + - Jest 与 ts-jest 耦合,支持 TS/TSX 测试文件 +- 环境差异 + - 服务端:Node 环境,Jest 测试环境为 node + - 客户端:浏览器环境,Jest 使用 jsdom 与自定义环境配置 + +```mermaid +graph LR +ESLintTS["@typescript-eslint/parser
解析 TS/TSX"] +ESLintCfg[".eslintrc.cjs
规则与插件"] +JestTS["ts-jest
转换 TS/TSX"] +JestCfg["jest.config.js
测试环境与扩展"] +TSConf["tsconfig.json
编译配置"] +LintTSConf["tsconfig.lint.json
ESLint 专用"] +ESLintCfg --> ESLintTS +ESLintCfg --> LintTSConf +JestCfg --> JestTS +JestCfg --> TSConf +``` + +图表来源 +- [.eslintrc.cjs:12-16](file://.eslintrc.cjs#L12-L16) +- [tsconfig.lint.json:4-11](file://tsconfig.lint.json#L4-L11) +- [jest.config.js:174-176](file://jest.config.js#L174-L176) +- [tsconfig.json:4-11](file://tsconfig.json#L4-L11) + +章节来源 +- [.eslintrc.cjs:12-16](file://.eslintrc.cjs#L12-L16) +- [tsconfig.lint.json:4-11](file://tsconfig.lint.json#L4-L11) +- [jest.config.js:174-176](file://jest.config.js#L174-L176) +- [tsconfig.json:4-11](file://tsconfig.json#L4-L11) + +## 性能考虑 +- ESLint 性能 + - 使用 tsconfig.lint.json 关闭 sourceMap,减少解析开销 + - 仅对 src 与 test 目标进行检查,避免 node_modules +- 构建与测试 + - 构建输出至 build 目录,便于缓存与增量编译 + - Jest 使用 v8 覆盖率提供器,平衡速度与准确性 + +章节来源 +- [tsconfig.lint.json:8](file://tsconfig.lint.json#L8) +- [jest.config.js:34](file://jest.config.js#L34) +- [package.json(服务端):6](file://package.json#L6) + +## 故障排查指南 +- ESLint 报错 + - 规则冲突:调整 .eslintrc.cjs 或 .eslintrc.json 中的 rules + - 解析失败:确认 tsconfig.lint.json 与 tsconfig.json 的 include/exclude 一致 +- Jest 测试失败 + - 环境问题:检查 jest.config.js 的 testEnvironment 与 jsdom 配置 + - 转换问题:确认 ts-jest 版本与 tsconfig.json 设置匹配 +- EditorConfig 不生效 + - 检查文件扩展名是否在 [*.{ts,js,json,html}] 范围内 + - 确认编辑器已加载 EditorConfig 插件 + +章节来源 +- [.eslintrc.cjs:18-23](file://.eslintrc.cjs#L18-L23) +- [.eslintrc.json(客户端):19-22](file://client/.eslintrc.json#L19-L22) +- [jest.config.js:140-145](file://jest.config.js#L140-L145) +- [.editorconfig:15-18](file://.editorconfig#L15-L18) + +## 结论 +本指南提供了从工具配置到编码实践的完整质量保障体系。建议团队在开发流程中坚持: +- 保存即检查:利用 EditorConfig 与 ESLint 在本地即时反馈 +- 测试驱动:以 Jest 覆盖核心逻辑,持续改进覆盖率 +- 文档与注释:关键模块与复杂函数必须具备清晰注释 +- 审查与迭代:结合审查清单与自动化检查,逐步完善规则与流程 + +## 附录 + +### 示例:WebSocket 信令处理测试流程 +```mermaid +sequenceDiagram +participant Test as "测试用例" +participant WSHandler as "WebSocket 处理器" +participant MockWS as "WebSocket 模拟" +participant Signaling as "信令服务" +Test->>MockWS : 建立连接 +Test->>WSHandler : add(客户端) +WSHandler->>Signaling : 广播 connect 消息 +Test->>WSHandler : onOffer(发送 offer) +WSHandler->>Signaling : 广播 offer 消息 +Test->>WSHandler : onAnswer(发送 answer) +WSHandler->>Signaling : 广播 answer 消息 +Test->>WSHandler : remove(断开) +WSHandler->>Signaling : 广播 disconnect 消息 +``` + +图表来源 +- [test/websockethandler.test.ts:30-99](file://test/websockethandler.test.ts#L30-L99) + +### 示例:输入远程控制测试流程 +```mermaid +sequenceDiagram +participant Test as "测试用例" +participant Sender as "Sender" +participant Remoting as "InputRemoting" +participant Observer as "Observer" +Test->>Sender : 创建实例 +Test->>Remoting : 创建实例并绑定 Sender +Test->>Observer : 创建观察者 +Test->>Remoting : subscribe(Observer) +Test->>Remoting : startSending() +Test->>Remoting : stopSending() +``` + +图表来源 +- [client/test/inputremoting.test.js:24-48](file://client/test/inputremoting.test.js#L24-L48) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/开发指南/开发指南.md b/.qoder/repowiki/zh/content/开发指南/开发指南.md new file mode 100644 index 0000000..37138cf --- /dev/null +++ b/.qoder/repowiki/zh/content/开发指南/开发指南.md @@ -0,0 +1,319 @@ +# 开发指南 + + +**本文引用的文件** +- [package.json](file://package.json) +- [client/package.json](file://client/package.json) +- [tsconfig.json](file://tsconfig.json) +- [tsconfig.lint.json](file://tsconfig.lint.json) +- [jest.config.js](file://jest.config.js) +- [client/jest.config.js](file://client/jest.config.js) +- [client/jest.setup.js](file://client/jest.setup.js) +- [.eslintrc.cjs](file://.eslintrc.cjs) +- [client/.eslintrc.json](file://client/.eslintrc.json) +- [.editorconfig](file://.editorconfig) +- [run.bat](file://run.bat) +- [.gitignore](file://.gitignore) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/log.ts](file://src/log.ts) +- [test/httphandler.test.ts](file://test/httphandler.test.ts) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本开发指南面向参与“视频流服务器”项目的开发者,目标是帮助你快速搭建本地开发环境、理解代码规范与编码标准、掌握调试与测试技巧、明确扩展开发流程、以及建立版本管理与发布流程的共识。项目采用 Node.js + TypeScript 构建,前端静态资源由服务端托管,支持 WebSocket 与 HTTP 两种信令模式,内置日志系统与 Swagger 文档。 + +## 项目结构 +项目分为服务端与客户端两部分: +- 服务端(src):Express 应用、HTTP 路由、WebSocket 信令、日志与配置等。 +- 客户端(client):静态页面与前端脚本,通过 /module 与 /uploads 等路由访问。 + +```mermaid +graph TB +subgraph "服务端(src)" +IDX["入口: index.ts"] +SRV["HTTP服务: server.ts"] +LOG["日志: log.ts"] +OPT["选项: class/options.ts"] +end +subgraph "客户端(client)" +PUB["静态资源: public/*"] +SRC["前端源码: src/*"] +end +IDX --> SRV +SRV --> PUB +SRV --> SRC +SRV --> LOG +IDX --> OPT +``` + +图表来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +章节来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +## 核心组件 +- 入口与启动:命令行参数解析、HTTPS/HTTP 启动、信令类型选择、日志输出。 +- HTTP 服务:CORS、JSON/URL 编码中间件、静态资源、Swagger 文档、头像上传接口。 +- 日志系统:可配置的日志级别与格式化输出。 +- 测试框架:Jest(服务端)、Jest + jsdom(客户端),覆盖 HTTP 与 WebSocket 信令逻辑。 + +章节来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [client/jest.config.js:1-196](file://client/jest.config.js#L1-L196) + +## 架构总览 +下图展示从浏览器到服务端的典型请求链路,包括静态资源、信令与上传接口。 + +```mermaid +sequenceDiagram +participant Browser as "浏览器" +participant Express as "Express 应用(server.ts)" +participant Signaling as "信令(HTTP/WebSocket)" +participant FS as "文件系统(头像上传)" +Browser->>Express : GET / +Express-->>Browser : 返回首页(静态资源) +Browser->>Express : GET /config +Express-->>Browser : 返回运行配置 +Browser->>Express : GET /signaling/* +Express->>Signaling : 转发至 HTTP 信令处理器 +Browser->>Express : POST /api/upload/avatar +Express->>FS : 写入头像并重命名 +FS-->>Express : 返回结果 +Express-->>Browser : JSON 响应 +``` + +图表来源 +- [src/server.ts:25-87](file://src/server.ts#L25-L87) +- [src/index.ts:75-90](file://src/index.ts#L75-L90) + +章节来源 +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) + +## 详细组件分析 + +### 启动与配置(index.ts) +- 支持命令行参数:端口、HTTPS 证书、信令类型、通信模式、日志级别。 +- 自动检测本机 IPv4 地址并输出访问地址。 +- 根据信令类型启动 HTTP 轮询或 WebSocket 信令服务。 + +```mermaid +flowchart TD +Start(["进程启动"]) --> ParseArgs["解析命令行参数"] +ParseArgs --> CreateApp["创建 Express 应用"] +CreateApp --> Mode{"是否启用 HTTPS?"} +Mode --> |是| HTTPS["读取密钥与证书并监听"] +Mode --> |否| HTTP["以 HTTP 监听"] +HTTPS --> DetectIP["检测本机 IPv4 地址"] +HTTP --> DetectIP +DetectIP --> SigType{"信令类型"} +SigType --> |websocket| WS["初始化 WebSocket 信令"] +SigType --> |http| HTTPSig["初始化 HTTP 轮询信令"] +WS --> Done(["完成"]) +HTTPSig --> Done +``` + +图表来源 +- [src/index.ts:14-105](file://src/index.ts#L14-L105) + +章节来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +### HTTP 服务与静态资源(server.ts) +- 中间件:CORS、JSON、URL 编码、日志(Morgan)。 +- 路由: + - GET /config:返回运行配置(信令类型、模式、日志级别)。 + - /signaling:转发至信令模块。 + - 静态资源:/ 与 /module。 + - 上传接口:POST /api/upload/avatar,使用 Multer 存储并按用户 ID 重命名。 + - /uploads:公开头像目录。 + +```mermaid +flowchart TD +Req["请求进入"] --> CORS["CORS 允许所有来源"] +CORS --> Body["解析 JSON/URL 编码"] +Body --> Log{"日志级别允许?"} +Log --> |是| Morgan["Morgan 访问日志"] +Log --> |否| Skip["跳过日志"] +Morgan --> Routes["分发路由"] +Skip --> Routes +Routes --> Config["GET /config"] +Routes --> Signaling["GET/POST /signaling/*"] +Routes --> Static["静态资源: / 与 /module"] +Routes --> Upload["POST /api/upload/avatar"] +Routes --> PublicUploads["GET /uploads/*"] +``` + +图表来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +章节来源 +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +### 日志系统(log.ts) +- 提供多级日志:none/error/warn/log/info。 +- 可通过字符串解析日志级别。 +- 输出包含时间戳与级别前缀。 + +章节来源 +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +### 测试与断言(Jest) +- 服务端测试:覆盖 HTTP 信令在 public/private 模式下的行为,含超时清理逻辑。 +- 客户端测试:Jest + jsdom 环境,通过 setup 注入 fetch、TextEncoder/Decoder、RTCPeerConnection 等缺失对象。 + +章节来源 +- [test/httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [client/jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) + +## 依赖关系分析 +- 语言与构建:TypeScript、ts-node、Jest、ESLint。 +- Web 框架:Express、ws(WebSocket)、Multer(文件上传)。 +- 文档:Swagger JSdoc、Swagger UI Express。 +- 日志:Morgan。 +- 工具:Commander(命令行解析)。 + +```mermaid +graph LR +Pkg["package.json 依赖"] --> TS["TypeScript 运行时"] +Pkg --> Express["Express"] +Pkg --> WS["ws"] +Pkg --> Mul["Multer"] +Pkg --> Swagger["Swagger UI Express"] +Pkg --> Morgan["Morgan"] +Pkg --> Cmdr["Commander"] +Pkg --> Jest["Jest"] +Pkg --> ESL["ESLint + TS 插件"] +``` + +图表来源 +- [package.json:14-46](file://package.json#L14-L46) + +章节来源 +- [package.json:1-60](file://package.json#L1-L60) + +## 性能考虑 +- 上传路径:头像上传使用磁盘存储,建议在生产环境挂载独立持久卷并限制文件大小与类型。 +- 日志级别:默认 info,可在高并发场景调低日志级别以减少 I/O。 +- 静态资源:Express 静态托管适合开发,生产建议使用 CDN 或反向代理缓存。 +- 信令:WebSocket 在高并发下优于 HTTP 轮询,建议优先使用 websocket 模式。 + +## 故障排查指南 +- 启动失败(HTTPS 证书):确认 server.key 与 server.cert 存在且可读;或禁用 HTTPS 并使用 HTTP。 +- 无法访问 /uploads:确保 uploads 目录存在且可读写。 +- 上传失败:检查 Multer 配置与权限,查看服务端错误日志。 +- 测试失败(jsdom 缺失 API):确保使用客户端 jest.config 的 setup 文件已加载。 +- 超时会话未清理:检查 HTTP 信令测试中的超时轮询逻辑与清理触发点。 + +章节来源 +- [src/server.ts:44-87](file://src/server.ts#L44-L87) +- [client/jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) +- [test/httphandler.test.ts:194-309](file://test/httphandler.test.ts#L194-L309) + +## 结论 +本项目提供了清晰的服务端架构与完善的测试体系,支持 WebSocket 与 HTTP 两种信令模式,并内置日志与文档能力。遵循本文的开发与测试规范,可高效地进行功能扩展与维护。 + +## 附录 + +### 开发环境搭建步骤 +- 安装 Node.js 与 npm(建议使用版本管理器以避免版本冲突)。 +- 克隆仓库后,在根目录与 client 目录分别执行安装依赖命令。 +- 准备 HTTPS 证书(可选):若启用 HTTPS,需准备 server.key 与 server.cert。 +- 使用提供的脚本启动开发服务或打包产物。 + +章节来源 +- [package.json:5-12](file://package.json#L5-L12) +- [client/package.json:5-8](file://client/package.json#L5-L8) +- [run.bat:1-17](file://run.bat#L1-L17) + +### 代码规范与编码标准 +- 统一缩进与换行:EditorConfig 规定空格缩进、LF 换行、UTF-8 字符集。 +- TypeScript 规范:ESLint 配置启用 @typescript-eslint 推荐规则,强制分号。 +- JavaScript 规范(客户端):ESLint 配置启用 jest 插件与推荐规则,强制分号。 +- 代码风格:遵循 EditorConfig 与 ESLint 规则,保持一致的排版与语法。 + +章节来源 +- [.editorconfig:10-20](file://.editorconfig#L10-L20) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [client/.eslintrc.json:1-23](file://client/.eslintrc.json#L1-L23) + +### 调试技巧与开发工具 +- 本地启动:使用开发脚本启动服务,自动监听 TypeScript 源码变化。 +- 单元测试:使用 Jest 运行服务端与客户端测试,关注覆盖率与断言。 +- 信令调试:根据运行日志确认信令类型与模式,必要时降低日志级别以便观察。 +- 上传调试:通过 /uploads 访问上传后的头像,验证重命名与路径。 + +章节来源 +- [package.json:5-12](file://package.json#L5-L12) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [client/jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [src/server.ts:62-87](file://src/server.ts#L62-L87) + +### 扩展开发机制 +- 新增 HTTP 路由:在 server.ts 中添加路由与处理逻辑,注意中间件顺序与错误处理。 +- 新增 WebSocket 事件:在 WebSocket 信令模块中新增事件处理函数,并在测试中补充断言。 +- 新增测试用例:在 test 目录下新增对应测试文件,复用现有的公共断言与模拟数据。 +- 修改现有功能:遵循最小变更原则,先写测试,再实现修复或增强。 + +章节来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [test/httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +### 版本管理、分支策略与发布流程 +- 版本号:项目使用语义化版本(package.json 中 version 字段)。 +- 分支策略:建议采用 Git Flow,主分支用于稳定版本,开发分支用于迭代。 +- 发布流程:本地构建(build)、测试(test)、打包(pack),生成可执行包或容器镜像后发布。 + +章节来源 +- [package.json:2-4](file://package.json#L2-L4) +- [package.json:5-12](file://package.json#L5-L12) + +### 贡献指南与代码审查标准 +- 提交前:运行 lint 与 test,确保通过。 +- 提交流程:提交变更到功能分支,发起 Pull Request,至少一名维护者审查。 +- 代码审查要点:功能正确性、边界条件、错误处理、日志与安全、测试覆盖、性能影响。 + +章节来源 +- [package.json:11-12](file://package.json#L11-L12) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [client/jest.config.js:1-196](file://client/jest.config.js#L1-L196) + +### 常见问题与最佳实践 +- 问题:浏览器跨域访问受限 + - 解决:服务端已启用 CORS,确保请求头与路径正确。 +- 问题:WebSocket 连接失败 + - 解决:确认信令类型为 websocket,检查服务端日志与防火墙设置。 +- 问题:头像上传失败 + - 解决:确认上传目录存在且可写,检查文件类型与大小限制。 +- 最佳实践:在生产环境使用 HTTPS、CDN 加速静态资源、合理设置日志级别与轮询间隔。 + +章节来源 +- [src/server.ts:22-24](file://src/server.ts#L22-L24) +- [src/server.ts:44-87](file://src/server.ts#L44-L87) +- [src/index.ts:55-74](file://src/index.ts#L55-L74) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/开发指南/开发环境搭建.md b/.qoder/repowiki/zh/content/开发指南/开发环境搭建.md new file mode 100644 index 0000000..abd1e38 --- /dev/null +++ b/.qoder/repowiki/zh/content/开发指南/开发环境搭建.md @@ -0,0 +1,392 @@ +# 开发环境搭建 + + +**本文引用的文件** +- [package.json](file://package.json) +- [client/package.json](file://client/package.json) +- [tsconfig.json](file://tsconfig.json) +- [tsconfig.build.json](file://tsconfig.build.json) +- [tsconfig.lint.json](file://tsconfig.lint.json) +- [jest.config.js](file://jest.config.js) +- [client/jest.config.js](file://client/jest.config.js) +- [.eslintrc.cjs](file://.eslintrc.cjs) +- [client/.eslintrc.json](file://client/.eslintrc.json) +- [run.bat](file://run.bat) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [client/jest.setup.js](file://client/jest.setup.js) +- [.gitignore](file://.gitignore) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向首次参与“视频流服务器”项目的开发者,目标是帮助你在本地快速完成开发环境搭建与运行。内容涵盖: +- Node.js 版本要求与安装步骤(含 LTS 建议) +- TypeScript 编译与构建配置说明 +- 包管理器选择建议(npm 与 yarn) +- 依赖安装流程(生产依赖与开发依赖) +- 开发脚本详解(dev、build、test 等) +- IDE 配置建议(VS Code 插件与调试) +- 常见环境问题排查与解决方案 + +## 项目结构 +该项目采用前后端分离的多包结构: +- 根包:服务端逻辑与构建、测试、打包配置 +- 客户端子包:前端示例页面与测试配置 + +```mermaid +graph TB +Root["根项目
package.json"] --> TS["TypeScript 配置
tsconfig*.json"] +Root --> JestRoot["Jest 根测试配置
jest.config.js"] +Root --> ESLintRoot[".eslintrc.cjs"] +Root --> RunBat["启动批处理
run.bat"] +Client["客户端子包
client/package.json"] --> JestClient["Jest 客户端测试配置
client/jest.config.js"] +Client --> ESLintClient["ESLint 客户端规则
client/.eslintrc.json"] +Client --> Public["静态资源
client/public/*"] +Root --> Src["服务端源码
src/*"] +Src --> IndexTS["入口与参数解析
src/index.ts"] +Src --> ServerTS["HTTP 服务与路由
src/server.ts"] +``` + +图表来源 +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [client/jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [client/.eslintrc.json:1-23](file://client/.eslintrc.json#L1-L23) +- [run.bat:1-17](file://run.bat#L1-L17) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +章节来源 +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [client/jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [client/.eslintrc.json:1-23](file://client/.eslintrc.json#L1-L23) +- [run.bat:1-17](file://run.bat#L1-L17) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +## 核心组件 +- Node.js 运行时与包管理器:用于安装依赖、执行脚本、启动服务 +- TypeScript 编译系统:将 ts/ts 文件编译为 js 并输出到 build 目录 +- 测试框架:Jest(服务端)与 Jest + jsdom(客户端) +- 代码质量工具:ESLint(TS 规则与 Jest 插件) +- 启动与打包:npm scripts 提供 dev/build/test/start/newman/pack 等命令;pkg 用于打包 + +章节来源 +- [package.json:5-13](file://package.json#L5-L13) +- [client/package.json:5-8](file://client/package.json#L5-L8) +- [tsconfig.json:4-11](file://tsconfig.json#L4-L11) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [tsconfig.lint.json:4-11](file://tsconfig.lint.json#L4-L11) +- [jest.config.js:174-176](file://jest.config.js#L174-L176) +- [client/jest.config.js:139-144](file://client/jest.config.js#L139-L144) +- [.eslintrc.cjs:6-11](file://.eslintrc.cjs#L6-L11) +- [client/.eslintrc.json:3-6](file://client/.eslintrc.json#L3-L6) + +## 架构总览 +下图展示了从开发脚本到服务启动的关键路径,以及静态资源与 API 的组织方式。 + +```mermaid +sequenceDiagram +participant Dev as "开发者" +participant NPM as "npm 脚本" +participant TS as "TypeScript 编译器" +participant Node as "Node 进程" +participant Express as "Express 应用" +participant Static as "静态资源" +participant Client as "浏览器客户端" +Dev->>NPM : 执行 "npm run dev" +NPM->>TS : 使用 ts-node 直接执行 src/index.ts +TS-->>Node : 返回可执行的 Node 入口 +Node->>Express : 初始化应用与中间件 +Express->>Static : 挂载静态目录 client/public +Express-->>Dev : 输出监听地址与模式信息 +Client->>Express : 访问 / 与 /config +Express-->>Client : 返回示例页面与配置 +``` + +图表来源 +- [package.json:10](file://package.json#L10) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +章节来源 +- [package.json:5-13](file://package.json#L5-L13) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 详细组件分析 + +### Node.js 与包管理器 +- 版本要求与 LTS 建议 + - 项目使用 TypeScript 4.8.x 与 Jest 29.x 等较新生态,建议优先选择 Node.js LTS(长期支持版本),以获得更稳定的依赖兼容性与社区支持。 + - 若需使用实验特性(如客户端测试中的 ES Module),可考虑使用当前稳定版 Node.js,但请确保团队统一版本。 +- 包管理器选择 + - 推荐使用 npm(项目未指定 yarn.lock,且 package.json 中未声明 yarn 专属配置)。 + - 如团队已有 yarn 工作流,也可继续使用,但需注意切换时清理 node_modules 并重新安装,避免锁文件冲突。 + +章节来源 +- [package.json:28-46](file://package.json#L28-L46) +- [client/package.json:9-16](file://client/package.json#L9-L16) + +### TypeScript 配置与构建 +- 编译目标与输出 + - 目标:ES5(target),模块:CommonJS(module),输出目录:build(outDir),根目录:src(rootDir)。 + - 适用于 Node.js 运行时的通用兼容性与打包工具链。 +- 构建配置 + - 标准配置:tsconfig.json + - 构建专用配置:tsconfig.build.json 继承标准配置,仅包含 src/**/*。 + - Lint 专用配置:tsconfig.lint.json,关闭 sourceMap,便于 ESLint 解析。 +- 关键编译脚本 + - build:通过 tsc -p tsconfig.build.json 执行构建。 + - dev:通过 ts-node 直接运行 src/index.ts,无需预先编译。 + - lint:通过 ESLint 对 src 与 test 下的 TS 文件进行检查。 + +```mermaid +flowchart TD +Start(["开始"]) --> LoadTS["加载 tsconfig.json"] +LoadTS --> ExtendBuild["继承 tsconfig.build.json"] +ExtendBuild --> Compile["执行 tsc 编译 src/**/*"] +Compile --> OutDir["输出至 build 目录"] +OutDir --> End(["结束"]) +``` + +图表来源 +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [tsconfig.lint.json:1-12](file://tsconfig.lint.json#L1-L12) + +章节来源 +- [tsconfig.json:4-11](file://tsconfig.json#L4-L11) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [tsconfig.lint.json:4-11](file://tsconfig.lint.json#L4-L11) +- [package.json:6-7](file://package.json#L6-L7) +- [package.json:11](file://package.json#L11) + +### 依赖安装与分类 +- 生产依赖(dependencies) + - 包括 express、ws、cors、morgan、multer、uuid、swagger-* 等,用于服务端运行期功能。 +- 开发依赖(devDependencies) + - 包括 TypeScript、ts-node、jest、ts-jest、eslint 及其插件、pkg 等,用于开发、测试与打包。 +- 客户端子包 + - 客户端 package.json 为独立的开发环境,包含 eslint、jest、jsdom 等测试相关依赖。 + +章节来源 +- [package.json:14-27](file://package.json#L14-L27) +- [package.json:28-46](file://package.json#L28-L46) +- [client/package.json:9-16](file://client/package.json#L9-L16) + +### 开发脚本详解 +- dev:使用 ts-node 直接运行 src/index.ts,支持热调试与快速迭代。 +- build:调用 tsc 按 tsconfig.build.json 编译生成 build。 +- test:运行 Jest 测试,收集覆盖率,服务端测试位于 test/*.ts。 +- start:启动已编译的服务端进程,支持 HTTPS 参数与日志级别。 +- lint:使用 ESLint 检查 TS 与测试代码。 +- pack:使用 pkg 将项目打包为可执行文件(targets 指向 node10)。 +- newman:执行 Postman 集合进行接口测试(需要 Newman)。 + +```mermaid +sequenceDiagram +participant Dev as "开发者" +participant NPM as "npm 脚本" +participant Jest as "Jest" +participant ESLint as "ESLint" +participant TSC as "TypeScript 编译器" +participant Pkg as "pkg 打包器" +Dev->>NPM : npm run test +NPM->>Jest : 执行测试与覆盖率收集 +Jest-->>Dev : 输出测试结果 +Dev->>NPM : npm run lint +NPM->>ESLint : 检查 src 与 test 下 TS 文件 +ESLint-->>Dev : 输出规则警告/错误 +Dev->>NPM : npm run build +NPM->>TSC : tsc -p tsconfig.build.json +TSC-->>Dev : 生成 build 目录 +Dev->>NPM : npm run pack +NPM->>Pkg : 打包为可执行文件 +Pkg-->>Dev : 输出二进制产物 +``` + +图表来源 +- [package.json:5-13](file://package.json#L5-L13) +- [jest.config.js:174-176](file://jest.config.js#L174-L176) +- [.eslintrc.cjs:6-11](file://.eslintrc.cjs#L6-L11) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) + +章节来源 +- [package.json:5-13](file://package.json#L5-L13) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) + +### 客户端测试与环境配置 +- 客户端 Jest 配置 + - 测试环境:jest-environment-jsdom + - 超时时间:5000ms + - setupFilesAfterEnv:引入 client/jest.setup.js,注入 fetch、TextEncoder/Decoder、RTCPeerConnection 等浏览器 API 的 polyfill。 +- 客户端 ESLint + - 推荐规则:eslint:recommended、plugin:jest/recommended + - 环境:browser、es6、jest + - 规则:分号强制与多余分号报错。 + +章节来源 +- [client/jest.config.js:139-144](file://client/jest.config.js#L139-L144) +- [client/jest.config.js:130](file://client/jest.config.js#L130) +- [client/jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) +- [client/.eslintrc.json:1-23](file://client/.eslintrc.json#L1-L23) + +### 服务端启动与静态资源 +- 启动流程 + - 通过 src/index.ts 解析命令行参数(端口、HTTPS、密钥证书、信令类型、通信模式、日志级别等),创建 Express 应用并启动 HTTP/HTTPS 服务。 + - 根据模式输出监听地址与模式信息。 +- 静态资源与 API + - 挂载 client/public 为静态目录,根路径返回示例页面。 + - 提供 /config 接口返回当前配置。 + - 提供 /signaling 路由(由 signaling 模块处理)。 + - 提供头像上传 API 与 /uploads 静态目录。 + +```mermaid +flowchart TD +A["启动 src/index.ts"] --> B["解析参数与选项"] +B --> C{"是否启用 HTTPS"} +C --> |是| D["读取 server.key 与 server.cert"] +C --> |否| E["使用 HTTP 监听"] +D --> F["创建 HTTPS 服务器"] +E --> G["创建 HTTP 服务器"] +F --> H["初始化 Express 应用"] +G --> H +H --> I["挂载静态资源 client/public"] +H --> J["注册 /config 与 /signaling 路由"] +H --> K["启动 Swagger 文档"] +H --> L["启动头像上传与 /uploads"] +``` + +图表来源 +- [src/index.ts:14-109](file://src/index.ts#L14-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +章节来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +### IDE 配置建议(VS Code) +- 推荐扩展 + - ESLint:实时语法与风格检查 + - Jest Runner:一键运行/调试单个或全部测试 + - TypeScript Importer:自动导入 TS 模块 + - Prettier:格式化(配合 ESLint 规则) + - Thunder Client 或 REST Client:HTTP 请求调试 +- 调试配置(launch.json) + - 启动服务端:使用 Node 调试器附加到 ts-node 进程,或直接调试已编译的 build/index.js。 + - 启动客户端:在浏览器中打开 client/public 下的示例页面,结合断点与网络面板调试。 +- 设置同步 + - .vscode/settings.json 与 .vscode/tasks.json、.vscode/launch.json 由 .gitignore 排除,可在本地按需添加。 + +章节来源 +- [.gitignore:175-181](file://.gitignore#L175-L181) + +## 依赖关系分析 +- 内部依赖 + - src/index.ts 依赖 src/server.ts 与 src/websocket(通过导入)。 + - src/server.ts 依赖 signaling、swagger、multer 等模块。 +- 外部依赖 + - 生产依赖:express、ws、cors、morgan、multer、uuid、swagger-*。 + - 开发依赖:TypeScript、ts-node、jest、ts-jest、eslint、pkg 等。 +- 客户端依赖 + - eslint、jest、jest-environment-jsdom、node-fetch 等。 + +```mermaid +graph LR +Index["src/index.ts"] --> Server["src/server.ts"] +Server --> Signaling["signaling 模块"] +Server --> Swagger["swagger 初始化"] +Server --> Multer["文件上传处理"] +Index --> WS["WebSocket 信令"] +RootPkg["根 package.json"] --> Deps["生产依赖"] +RootPkg --> DevDeps["开发依赖"] +ClientPkg["client/package.json"] --> ClientDeps["客户端测试依赖"] +``` + +图表来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [package.json:14-46](file://package.json#L14-L46) +- [client/package.json:9-16](file://client/package.json#L9-L16) + +章节来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [package.json:14-46](file://package.json#L14-L46) +- [client/package.json:9-16](file://client/package.json#L9-L16) + +## 性能考虑 +- 构建与缓存 + - 使用 TypeScript 编译缓存(tsbuildinfo)减少增量编译时间。 + - 在 CI 环境中复用 node_modules,避免重复下载。 +- 测试性能 + - Jest 默认并发运行测试,可通过 maxWorkers 控制(已在配置中注释)。 + - 使用覆盖率收集时关注 I/O,必要时拆分测试集。 +- 服务端性能 + - 静态资源由 Express 直接提供,建议在生产环境前移至 CDN 或反向代理。 + - 日志级别可调整为 none 以降低开销(通过 start 脚本参数)。 + +[本节为通用指导,不直接分析具体文件] + +## 故障排查指南 +- 无法找到 node_modules 或依赖缺失 + - 清理缓存并重新安装:删除 node_modules 与 lockfile 后重新安装。 + - 确认包管理器一致性(npm/yarn),避免混用导致锁文件冲突。 +- 启动失败(端口占用) + - 修改端口或释放占用端口(默认 8080)。 +- HTTPS 启动失败 + - 确认 server.key 与 server.cert 存在且可读。 + - 使用 start 脚本时传入正确的密钥与证书路径。 +- 浏览器控制台缺少 API + - 客户端测试需注入 polyfill,确认 jest.setup.js 已被加载。 +- ESLint 报错 + - 使用 tsconfig.lint.json 作为 ESLint 的 parserOptions.project,确保规则解析正确。 +- 覆盖率未生成 + - 确认 Jest 配置中的 collectCoverage 与 coverageDirectory 设置。 +- Windows 批处理异常 + - run.bat 中包含注释与非执行语句,建议仅保留必要命令(如先 build 再 start)。 + +章节来源 +- [.gitignore:35-51](file://.gitignore#L35-L51) +- [package.json:9](file://package.json#L9) +- [src/index.ts:55-66](file://src/index.ts#L55-L66) +- [client/jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) +- [.eslintrc.cjs:13-16](file://.eslintrc.cjs#L13-L16) +- [jest.config.js:20](file://jest.config.js#L20) +- [run.bat:1-7](file://run.bat#L1-L7) + +## 结论 +按照本指南完成 Node.js、TypeScript、ESLint、Jest 与包管理器的配置后,你即可顺利运行与开发该视频流服务器项目。建议在团队内统一 Node.js 版本与包管理器,遵循 ESLint 规则与测试规范,以保证代码质量与协作效率。 + +[本节为总结,不直接分析具体文件] + +## 附录 +- 快速验证清单 + - 安装 Node.js LTS(或稳定版) + - 使用 npm 安装依赖 + - 运行 npm run build 与 npm run dev + - 执行 npm run test 与 npm run lint + - 如需打包,运行 npm run pack + +[本节为补充说明,不直接分析具体文件] \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/开发指南/扩展开发.md b/.qoder/repowiki/zh/content/开发指南/扩展开发.md new file mode 100644 index 0000000..cbef093 --- /dev/null +++ b/.qoder/repowiki/zh/content/开发指南/扩展开发.md @@ -0,0 +1,433 @@ +# 扩展开发 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/src/peer.js](file://client/src/peer.js) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) +- [package.json](file://package.json) +- [client/package.json](file://client/package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖分析](#依赖分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向希望扩展视频信令服务器的开发者,系统讲解如何: +- 添加新的信令消息类型(如 Offer、Answer、Candidate 的扩展) +- 扩展 WebSocketHandler 与 HttpHandler 的消息处理机制 +- 开发自定义信令处理器,实现消息路由与处理流程 +- 扩展客户端功能(新增 WebRTC 能力与 UI 组件) +- 使用插件化扩展点设计原则与最佳实践 +- 考虑版本兼容性与向后兼容性 + +## 项目结构 +项目采用前后端分离的模块化组织: +- 服务端(Node.js + Express + WebSocket):负责信令路由、消息持久化与广播 +- 客户端(浏览器端 JS):封装 HTTP 与 WebSocket 两类信令通道,驱动 WebRTC PeerConnection + +```mermaid +graph TB +subgraph "服务端" +IDX["入口: src/index.ts"] +SRV["HTTP 服务器: src/server.ts"] +WS["WebSocket 信令: src/websocket.ts"] +WSH["WebSocket 处理器: src/class/websockethandler.ts"] +HTH["HTTP 处理器: src/class/httphandler.ts"] +SIG["HTTP 路由: src/signaling.ts"] +end +subgraph "客户端" +CL_SIG["信令封装: client/src/signaling.js"] +CL_PEER["WebRTC 对端: client/src/peer.js"] +end +IDX --> SRV +SRV --> SIG +SRV --> WS +WS --> WSH +SIG --> HTH +CL_SIG --> WS +CL_SIG --> SIG +CL_PEER --> CL_SIG +``` + +图表来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +## 核心组件 +- 信令模型类:Offer、Answer、Candidate,承载 SDP 与 ICE 候选等数据 +- WebSocket 信令:WSSignaling 负责连接生命周期与消息分发 +- WebSocket 处理器:websockethandler 提供连接组管理、广播、消息路由 +- HTTP 信令:httphandler 提供会话、连接、offer/answer/candidate 的持久化与轮询接口 +- HTTP 路由:signaling 路由器挂载在 /signaling 下,统一暴露 REST API +- 客户端封装:signaling.js 提供 HTTP 与 WebSocket 两种信令通道;peer.js 驱动 WebRTC + +章节来源 +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) + +## 架构总览 +WebSocket 与 HTTP 两条信令通道并行工作: +- WebSocket:低延迟、实时广播、支持 ping/pong 心跳 +- HTTP:轮询获取历史信令,适合弱网或受限环境 + +```mermaid +sequenceDiagram +participant C as "客户端" +participant WS as "WSSignaling" +participant WH as "WebSocket 处理器" +participant HS as "HTTP 服务器" +participant HH as "HTTP 处理器" +C->>WS : "connect/disconnect/offer/answer/candidate" +WS->>WH : "分发消息类型" +WH-->>C : "广播/单播回执" +C->>HS : "PUT/GET/POST /signaling/*" +HS->>HH : "路由到处理器" +HH-->>C : "返回历史信令/状态" +``` + +图表来源 +- [src/websocket.ts:44-115](file://src/websocket.ts#L44-L115) +- [src/class/websockethandler.ts:76-338](file://src/class/websockethandler.ts#L76-L338) +- [src/server.ts:25-42](file://src/server.ts#L25-L42) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +章节来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +## 详细组件分析 + +### 1) 新增信令消息类型(以 Offer/Answer/Candidate 为例) +- 数据模型 + - Offer:包含 sdp、datetime、polite 字段 + - Answer:包含 sdp、datetime + - Candidate:包含 candidate、sdpMLineIndex、sdpMid、datetime +- 服务端处理 + - WebSocket:在 WSSignaling 中解析消息类型并调用 websockethandler 的 onOffer/onAnswer/onCandidate + - HTTP:在 httphandler 中维护会话级的 offers/answers/candidates 映射,提供 GET/POST 接口 +- 客户端封装 + - HTTP 与 WebSocket 两端均提供发送与接收事件,便于上层业务订阅 + +```mermaid +classDiagram +class Offer { ++string sdp ++number datetime ++boolean polite +} +class Answer { ++string sdp ++number datetime +} +class Candidate { ++string candidate ++number sdpMLineIndex ++string sdpMid ++number datetime +} +class WebSocket_Handler { ++onOffer(ws,msg) ++onAnswer(ws,msg) ++onCandidate(ws,msg) +} +class HTTP_Handler { ++postOffer(req,res) ++postAnswer(req,res) ++postCandidate(req,res) ++getOffer(req,res) ++getAnswer(req,res) ++getCandidate(req,res) +} +WebSocket_Handler --> Offer : "构造/转发" +WebSocket_Handler --> Answer : "构造/转发" +WebSocket_Handler --> Candidate : "构造/转发" +HTTP_Handler --> Offer : "持久化/查询" +HTTP_Handler --> Answer : "持久化/查询" +HTTP_Handler --> Candidate : "持久化/查询" +``` + +图表来源 +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +章节来源 +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +### 2) WebSocketHandler 扩展机制 +- 连接组管理:host 与 participants 的角色区分,支持私有模式下的多对一/一对一路由 +- 广播与单播:根据消息类型与角色进行目标选择 +- 心跳与断线:定时心跳检测,超时自动断开并广播离线事件 +- 新消息类型接入步骤 + 1) 在 WSSignaling 的消息分发处增加 case 分支 + 2) 在 websockethandler 中实现对应处理函数(如 onXxx),完成路由与广播 + 3) 在客户端 signaling.js 中订阅并派发自定义事件 + +```mermaid +sequenceDiagram +participant WS as "WebSocket" +participant WSH as "websockethandler" +participant P as "参与者" +WS->>WSH : "type='xxx'" +WSH->>WSH : "匹配处理函数" +WSH->>P : "广播/单播消息" +P-->>WSH : "回执/确认" +``` + +图表来源 +- [src/websocket.ts:76-113](file://src/websocket.ts#L76-L113) +- [src/class/websockethandler.ts:97-137](file://src/class/websockethandler.ts#L97-L137) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) + +章节来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +### 3) HttpHandler 扩展机制 +- 会话与连接:通过 session-id 维持会话上下文,支持连接对(connectionPair)在私有模式下建立双向关系 +- 消息持久化:offers/answers/candidates 以会话+连接维度缓存,支持 fromtime 过滤 +- 轮询接口:提供 /signaling、/signaling/offer、/signaling/answer、/signaling/candidate 的 GET/POST +- 新消息类型接入步骤 + 1) 在 httphandler 中新增 postXxx 与 getXxx 方法,维护内部映射 + 2) 在 signaling 路由中注册对应路由 + 3) 在客户端 signaling.js 中补充发送与接收逻辑 + +```mermaid +flowchart TD +Start(["HTTP 请求进入"]) --> CheckSession["校验 session-id"] +CheckSession --> Route{"路由到哪?"} +Route --> |GET /signaling| GetAll["合并并排序历史消息"] +Route --> |GET /signaling/offer| GetOffer["按会话/时间过滤 offer"] +Route --> |GET /signaling/answer| GetAnswer["按会话/时间过滤 answer"] +Route --> |GET /signaling/candidate| GetCandidate["按会话/时间过滤 candidate"] +Route --> |POST /signaling/offer| PostOffer["写入 offer 映射"] +Route --> |POST /signaling/answer| PostAnswer["写入 answer 映射"] +Route --> |POST /signaling/candidate| PostCandidate["写入 candidate 映射"] +GetAll --> End(["返回 JSON"]) +GetOffer --> End +GetAnswer --> End +GetCandidate --> End +PostOffer --> End +PostAnswer --> End +PostCandidate --> End +``` + +图表来源 +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) + +章节来源 +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) + +### 4) 自定义信令处理器开发指南 +- 设计原则 + - 单一职责:每个处理器只负责一类消息或一类资源 + - 可组合:通过中间件/装饰器模式复用鉴权、日志、限流 + - 可替换:对外暴露一致的 API,内部可替换实现 +- 消息路由与处理流程 + - WebSocket:在 WSSignaling 的 switch 分支中新增 case,调用处理器对应方法 + - HTTP:在 signaling 路由中注册新路由,处理器中实现鉴权与数据持久化 +- 客户端集成 + - 在 signaling.js 中新增发送与接收事件,确保与服务端消息结构一致 + - 在 peer.js 或业务层订阅事件并驱动 WebRTC + +```mermaid +sequenceDiagram +participant App as "应用层" +participant WS as "WSSignaling" +participant WH as "自定义处理器" +participant HH as "自定义 HTTP 处理器" +participant CL as "客户端" +App->>WS : "发送自定义消息" +WS->>WH : "分发到自定义处理器" +WH-->>CL : "广播/回执" +App->>HH : "HTTP 请求" +HH-->>CL : "返回处理结果" +CL-->>App : "事件回调" +``` + +图表来源 +- [src/websocket.ts:76-113](file://src/websocket.ts#L76-L113) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) + +章节来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +### 5) 扩展客户端功能(WebRTC 与 UI 组件) +- 新增 WebRTC 能力 + - 在 peer.js 中扩展事件(如 onCustomMessage),并在 signaling.js 中订阅 + - 如需数据通道,参考 createDataChannel 与 ondatachannel 的使用模式 +- UI 组件 + - 基于现有 signaling.js 的事件模型,新增 DOM 事件监听与渲染逻辑 + - 保持与现有连接/断开/offer/answer/candidate 事件的兼容 + +章节来源 +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +### 6) 插件系统与扩展点设计原则 +- 扩展点 + - WebSocket 消息分发:在 WSSignaling 的 switch 中新增 case + - HTTP 路由:在 signaling 路由中新增子路由 + - 处理器内部:在 websockethandler/httphandler 中新增处理函数 +- 设计原则 + - 向后兼容:新增字段建议可选,避免破坏既有消息结构 + - 版本化:通过消息中的 version 字段或路由版本号区分 + - 强约束:严格校验必填字段,失败时返回明确错误码 + - 可观测:为新消息类型增加日志与指标埋点 + +章节来源 +- [src/websocket.ts:76-113](file://src/websocket.ts#L76-L113) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/websockethandler.ts:76-338](file://src/class/websockethandler.ts#L76-L338) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +### 7) 实际扩展示例与最佳实践 +- 示例:新增“自定义消息”类型 + - 服务端 + - 在 WSSignaling 的 switch 中新增 case:"custom" + - 在 websockethandler 中实现 onCustom,完成路由与广播 + - 在 httphandler 中新增 postCustom/getCustom,维护映射 + - 在 signaling 路由中注册 /signaling/custom + - 客户端 + - 在 signaling.js 中新增 sendCustom 与订阅 "custom" 事件 +- 最佳实践 + - 消息结构最小化:仅包含必要字段 + - 错误处理:对缺失字段与非法值返回 4xx/5xx 并记录日志 + - 幂等性:对重复消息进行去重(基于时间戳或唯一 ID) + - 性能:批量消息合并、异步处理、限流与背压 + +章节来源 +- [src/websocket.ts:76-113](file://src/websocket.ts#L76-L113) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) + +## 依赖分析 +- 服务端依赖 + - Express:HTTP 服务器与路由 + - ws:WebSocket 服务器 + - morgan:HTTP 日志 + - uuid:会话 ID 生成 +- 客户端依赖 + - 浏览器原生 fetch/WebSocket + - Jest(测试) + +```mermaid +graph LR +PKG["服务端 package.json"] --> EXP["express"] +PKG --> WS["ws"] +PKG --> MORGAN["morgan"] +PKG --> UUID["uuid"] +CLPKG["客户端 package.json"] --> JEST["jest"] +CLPKG --> ESLINT["eslint"] +``` + +图表来源 +- [package.json:14-27](file://package.json#L14-L27) +- [client/package.json:9-18](file://client/package.json#L9-L18) + +章节来源 +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +## 性能考量 +- WebSocket + - 心跳间隔与超时阈值可调,避免频繁断线 + - 大消息拆分与压缩,减少带宽占用 +- HTTP + - fromtime 参数配合增量拉取,降低响应体积 + - 合理设置轮询间隔,平衡实时性与资源消耗 +- 通用 + - 缓存热点数据(如最近 N 条 candidate) + - 异步处理耗时操作(如持久化) + +## 故障排查指南 +- WebSocket 无法连接 + - 检查 WSSignaling 的连接事件与日志 + - 确认客户端 URL 与协议(ws/wss) +- 消息未到达 + - 核对 websockethandler 的广播逻辑与角色判断 + - 确认客户端事件监听是否正确 +- HTTP 会话异常 + - 检查 session-id 请求头与会话超时清理 + - 使用 getAll 接口核对历史消息 +- 单元测试参考 + - 使用 websockethandler.test.ts 验证消息路由与广播行为 + +章节来源 +- [src/websocket.ts:27-38](file://src/websocket.ts#L27-L38) +- [src/class/websockethandler.ts:97-137](file://src/class/websockethandler.ts#L97-L137) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +## 结论 +通过明确的扩展点与清晰的处理流程,项目允许以最小改动安全地引入新的信令消息类型与客户端能力。遵循向后兼容、可观测与可测试的原则,可确保扩展的稳定性与演进速度。 + +## 附录 +- 版本与兼容性 + - 服务端与客户端版本号:3.1.0 + - 建议在新增字段时保留默认值,避免破坏旧客户端解析 + - 对于重大变更,建议引入版本字段或路由版本号 + +章节来源 +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/开发指南/调试与测试.md b/.qoder/repowiki/zh/content/开发指南/调试与测试.md new file mode 100644 index 0000000..ce4836b --- /dev/null +++ b/.qoder/repowiki/zh/content/开发指南/调试与测试.md @@ -0,0 +1,441 @@ +# 调试与测试 + + +**本文引用的文件** +- [package.json](file://package.json) +- [jest.config.js](file://jest.config.js) +- [src/index.ts](file://src/index.ts) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) +- [test/httphandler.test.ts](file://test/httphandler.test.ts) +- [client/package.json](file://client/package.json) +- [client/jest.config.js](file://client/jest.config.js) +- [client/jest.setup.js](file://client/jest.setup.js) +- [client/test/signaling.test.js](file://client/test/signaling.test.js) +- [client/test/mocksignaling.js](file://client/test/mocksignaling.js) +- [client/test/testutils.js](file://client/test/testutils.js) +- [client/test/peerconnection.test.js](file://client/test/peerconnection.test.js) +- [src/log.ts](file://src/log.ts) +- [run.bat](file://run.bat) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖分析](#依赖分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向开发者与测试工程师,系统化讲解本项目的测试体系与调试方法,覆盖以下主题: +- JUnit(在本项目中为 Jest)测试框架的配置与使用,包括单元测试与集成测试的编写规范 +- 测试环境搭建、测试数据准备与断言策略 +- WebSocket 与 HTTP 服务的调试技巧(断点设置、日志分析) +- 客户端测试:WebRTC 连接测试与信令测试 +- 模拟对象的使用:mock-socket 与 jest-websocket-mock +- 性能测试与压力测试方法 +- 测试覆盖率报告的生成与分析 + +## 项目结构 +该项目采用前后端分离的测试布局: +- 后端(Node/Express + WebSocket/HTTP 信令):位于根目录,测试文件位于 test/ 下 +- 前端(Web 客户端,含 WebRTC 与信令逻辑):位于 client/ 目录,测试文件位于 client/test/ 下 +- 根目录与 client/ 目录分别维护独立的 Jest 配置与脚本 + +```mermaid +graph TB +subgraph "后端服务" +A["src/index.ts
启动 HTTP/HTTPS 与 WebSocket 信令"] +B["test/httphandler.test.ts
HTTP 信令集成测试"] +C["test/websockethandler.test.ts
WebSocket 信令集成测试"] +end +subgraph "前端客户端" +D["client/test/signaling.test.js
信令端到端测试"] +E["client/test/mocksignaling.js
模拟信令管理器"] +F["client/test/peerconnection.test.js
WebRTC 连接单元测试"] +G["client/jest.config.js
Jest 配置JSDOM 环境"] +H["client/jest.setup.js
全局 polyfill 与 mock"] +end +A --> B +A --> C +D --> E +D --> F +G --> D +H --> D +``` + +图表来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [test/httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [client/test/signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [client/test/mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) +- [client/test/peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [client/jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [client/jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) + +章节来源 +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +## 核心组件 +- 后端入口与信令模式选择:根据命令行参数决定 HTTP 或 WebSocket 信令模式,并按模式启动对应信令服务 +- 前端信令抽象:支持 HTTP 与 WebSocket 两种信令;同时提供 MockSignaling 用于快速验证 +- WebRTC 连接层:封装 RTCPeerConnection、事件与候选处理 +- 日志系统:统一的日志级别控制,便于调试与生产输出 + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 架构总览 +下图展示从客户端发起信令到后端处理再到另一客户端接收的关键流程。 + +```mermaid +sequenceDiagram +participant ClientA as "客户端A" +participant SignalingA as "信令适配(HTTP/WebSocket/Mock)" +participant Server as "后端服务(src/index.ts)" +participant SignalingB as "信令适配(HTTP/WebSocket/Mock)" +participant ClientB as "客户端B" +ClientA->>SignalingA : "创建连接/发送Offer" +SignalingA->>Server : "POST /offer 或 WebSocket 消息" +Server-->>SignalingB : "广播/转发 Offer" +SignalingB-->>ClientB : "触发 offer 事件" +ClientB->>SignalingB : "发送 Answer/Candidate" +SignalingB->>Server : "上报 Answer/Candidate" +Server-->>SignalingA : "转发 Answer/Candidate" +SignalingA-->>ClientA : "触发 answer/candidate 事件" +``` + +图表来源 +- [client/test/signaling.test.js:67-208](file://client/test/signaling.test.js#L67-L208) +- [test/httphandler.test.ts:71-120](file://test/httphandler.test.ts#L71-L120) +- [test/websockethandler.test.ts:52-82](file://test/websockethandler.test.ts#L52-L82) +- [src/index.ts:75-88](file://src/index.ts#L75-L88) + +## 详细组件分析 + +### 后端 HTTP 信令测试(集成测试) +- 使用 @jest-mock/express 提供的 getMockReq/getMockRes 构造 Express 请求/响应对象 +- 通过 createSession/checkSessionId/createConnection/post*/getAll/delete* 等接口组合,验证公共/私有模式下的消息流转 +- 关键断言点: + - 连接创建时返回的 polite 标识 + - Offer/Answer/Candidate 的收发与过滤规则 + - 超时清理与会话删除行为 +- 超时场景通过循环调用 checkSessionId 并等待超时实现 + +```mermaid +flowchart TD +Start(["开始"]) --> CreateSess1["创建会话A"] +CreateSess1 --> CreateSess2["创建会话B"] +CreateSess2 --> ConnA["会话A 创建连接"] +ConnA --> ConnB["会话B 创建连接"] +ConnB --> Offer["会话A 发送 Offer"] +Offer --> ExpectOffer["会话B 收到 Offer"] +ExpectOffer --> Answer["会话B 发送 Answer"] +Answer --> ExpectAnswer["会话A 收到 Answer"] +ExpectAnswer --> Candidate["双方发送 Candidate"] +Candidate --> Cleanup["删除连接/会话"] +Cleanup --> End(["结束"]) +``` + +图表来源 +- [test/httphandler.test.ts:45-153](file://test/httphandler.test.ts#L45-L153) +- [test/httphandler.test.ts:194-249](file://test/httphandler.test.ts#L194-L249) + +章节来源 +- [test/httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) + +### 后端 WebSocket 信令测试(集成测试) +- 使用 jest-websocket-mock 搭建本地 WebSocket 服务器,模拟真实信令通道 +- 验证公共/私有模式下 connect/polite、offer/answer、candidate、disconnect 的消息一致性 +- 使用 toReceiveMessage/toHaveReceivedMessages 断言消息内容与顺序 + +```mermaid +sequenceDiagram +participant WS as "WS 服务器(jest-websocket-mock)" +participant Handler as "wsHandler" +participant C1 as "客户端1" +participant C2 as "客户端2" +C1->>WS : "连接" +C2->>WS : "连接" +C1->>Handler : "onConnect(connectionId1)" +Handler-->>WS : "广播 connect(polite=true)" +WS-->>C1 : "收到 connect" +C2->>Handler : "onConnect(connectionId1)" +Handler-->>WS : "广播 connect(polite=true)" +WS-->>C2 : "收到 connect" +C1->>Handler : "onOffer({connectionId1,sdp})" +Handler-->>WS : "广播 offer" +WS-->>C2 : "收到 offer" +C2->>Handler : "onAnswer(...)" +Handler-->>WS : "广播 answer" +WS-->>C1 : "收到 answer" +C1->>Handler : "onDisconnect(...)" +Handler-->>WS : "广播 disconnect" +WS-->>C1 : "收到 disconnect" +WS-->>C2 : "收到 disconnect" +``` + +图表来源 +- [test/websockethandler.test.ts:9-99](file://test/websockethandler.test.ts#L9-L99) +- [test/websockethandler.test.ts:101-190](file://test/websockethandler.test.ts#L101-L190) + +章节来源 +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +### 前端信令测试(端到端测试) +- 支持三种模式:mock、http、websocket +- 使用 jest-dev-server 启动本地服务,或使用 MockSignaling 进行纯内存测试 +- 通过自定义事件 connect/offer/answer/candidate/disconnect 驱动断言 +- 使用 waitFor/sleep 辅助异步等待与稳定测试节奏 + +```mermaid +sequenceDiagram +participant Test as "Jest 测试" +participant DevServer as "本地服务(可选)" +participant Signaling as "Signaling/WS 签名器" +participant PeerA as "Peer A" +participant PeerB as "Peer B" +Test->>DevServer : "启动(可选)" +Test->>Signaling : "start()" +Test->>PeerA : "createConnection()" +Test->>PeerB : "createConnection()" +Test->>PeerA : "sendOffer()" +PeerA-->>PeerB : "触发 offer 事件" +Test->>PeerB : "sendAnswer()" +PeerB-->>PeerA : "触发 answer 事件" +Test->>PeerB : "sendCandidate()" +PeerB-->>PeerA : "触发 candidate 事件" +Test->>PeerA : "deleteConnection()" +PeerA-->>PeerB : "触发 disconnect 事件" +``` + +图表来源 +- [client/test/signaling.test.js:23-65](file://client/test/signaling.test.js#L23-L65) +- [client/test/signaling.test.js:67-208](file://client/test/signaling.test.js#L67-L208) +- [client/test/signaling.test.js:221-264](file://client/test/signaling.test.js#L221-L264) + +章节来源 +- [client/test/signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) + +### 前端模拟信令管理器(MockSignaling) +- 公共模式:任意两个 Signaling 实例之间互相广播消息 +- 私有模式:基于 connectionId 维护对等集合,仅在双方均已打开连接时才互相广播 +- 提供延迟以模拟网络时延,便于验证事件顺序与时序 + +```mermaid +classDiagram +class MockSignaling { ++interval ++start() ++stop() ++createConnection(connectionId) ++deleteConnection(connectionId) ++sendOffer(connectionId, sdp) ++sendAnswer(connectionId, sdp) ++sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid) +} +class MockPublicSignalingManager { ++list : Set ++add(signaling) ++remove(signaling) ++openConnection(signaling, connectionId) ++closeConnection(signaling, connectionId) ++offer(owner, data) ++answer(owner, data) ++candidate(owner, data) +} +class MockPrivateSignalingManager { ++connectionIds : Map ++openConnection(signaling, connectionId) ++closeConnection(signaling, connectionId) ++offer(owner, data) ++answer(owner, data) ++candidate(owner, data) +-findList(owner, connectionId) +} +MockSignaling --> MockPublicSignalingManager : "公共模式" +MockSignaling --> MockPrivateSignalingManager : "私有模式" +``` + +图表来源 +- [client/test/mocksignaling.js:10-52](file://client/test/mocksignaling.js#L10-L52) +- [client/test/mocksignaling.js:54-113](file://client/test/mocksignaling.js#L54-L113) +- [client/test/mocksignaling.js:115-224](file://client/test/mocksignaling.js#L115-L224) + +章节来源 +- [client/test/mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) + +### WebRTC 连接单元测试 +- 验证 Peer 对象在不同场景下的事件触发与状态变化 +- 包括添加轨道/Transceiver/DataChannel 自动触发 Offer +- 在不同角色(polite/impolite)下正确触发 Answer +- 候选收集与接受的条件判断 + +```mermaid +flowchart TD +Init["初始化 Peer(config)"] --> AddTrack["添加轨道/Transceiver/DataChannel"] +AddTrack --> FireOffer["触发 sendoffer 事件"] +FireOffer --> OnOffer["收到 Offer 描述"] +OnOffer --> MaybeAnswer{"是否需要 Answer?"} +MaybeAnswer --> |是| FireAnswer["触发 sendanswer 事件"] +MaybeAnswer --> |否| Skip["不触发 Answer"] +FireAnswer --> OnAnswer["收到 Answer 描述"] +OnAnswer --> Negotiate["触发 negotiated 事件"] +AddTrack --> Candidate["收集 ICE Candidate"] +Candidate --> Accept{"已有远端描述?"} +Accept --> |是| Store["保存候选"] +Accept --> |否| Drop["丢弃候选"] +``` + +图表来源 +- [client/test/peerconnection.test.js:69-122](file://client/test/peerconnection.test.js#L69-L122) +- [client/test/peerconnection.test.js:124-188](file://client/test/peerconnection.test.js#L124-L188) +- [client/test/peerconnection.test.js:222-238](file://client/test/peerconnection.test.js#L222-L238) + +章节来源 +- [client/test/peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) + +## 依赖分析 +- 根目录测试依赖 + - jest、jest-websocket-mock、@jest-mock/express、ts-jest、typescript +- 客户端测试依赖 + - jest、jest-environment-jsdom、jest-dev-server、node-fetch、@jest/globals +- 关键配置 + - 根目录 jest.config.js 开启覆盖率收集,使用 ts-jest 转换 TS + - 客户端 jest.config.js 使用 JSDOM 环境,加载 jest.setup.js 注入 polyfill 与 WebRTC mock + +章节来源 +- [package.json:28-46](file://package.json#L28-L46) +- [client/package.json:9-18](file://client/package.json#L9-L18) +- [jest.config.js:19-34](file://jest.config.js#L19-L34) +- [client/jest.config.js:18-25](file://client/jest.config.js#L18-L25) +- [client/jest.config.js:130-131](file://client/jest.config.js#L130-L131) + +## 性能考虑 +- 单元测试应避免真实网络 I/O,优先使用 Mock 与内存态模拟 +- 集成测试中,WebSocket/HTTP 信令测试通过本地 mock 降低外部依赖 +- WebRTC 测试通过自定义事件与延迟函数控制执行节奏,避免非确定性 +- 大规模并发场景建议拆分测试套件,使用并行 worker 与独立端口运行多实例 + +## 故障排查指南 +- 启动与调试 + - 使用 run.bat 构建并启动服务,同时开启日志级别以便定位问题 + - 通过 src/log.ts 的日志级别控制,将日志提升至 info 或更高等级 +- WebSocket 调试 + - 在 jest-websocket-mock 中断言 toReceiveMessage/toHaveReceivedMessages,确保消息类型与字段匹配 + - 若出现“未收到消息”,检查信令管理器是否正确广播以及客户端监听是否生效 +- HTTP 信令调试 + - 使用 @jest-mock/express 的请求/响应对象构造,逐个接口断言状态码与 JSON 结果 + - 注意会话超时逻辑,必要时增加等待时间或调整超时阈值 +- 客户端测试 + - 若使用 jest-dev-server 启动本地服务,注意端口占用与进程回收 + - 使用 waitFor/sleep 稳定异步事件,避免竞态条件导致断言失败 +- 日志分析 + - 利用 setLogLevel 与 log 输出统一格式的时间戳与级别前缀,结合断点逐步缩小范围 + +章节来源 +- [run.bat:1-17](file://run.bat#L1-L17) +- [src/log.ts:9-24](file://src/log.ts#L9-L24) +- [client/test/signaling.test.js:23-65](file://client/test/signaling.test.js#L23-L65) +- [client/test/testutils.js:3-15](file://client/test/testutils.js#L3-L15) + +## 结论 +本项目提供了完善的测试与调试基础设施: +- 后端通过 HTTP/WebSocket 两种信令模式的集成测试,覆盖公共/私有模式的消息流转与超时清理 +- 前端通过 MockSignaling 与 jest-dev-server 支持端到端测试,结合 WebRTC 单元测试保障连接稳定性 +- Jest 配置与覆盖率收集已就绪,便于持续改进质量与回归保障 + +## 附录 + +### 测试环境搭建与运行 +- 根目录测试 + - 安装依赖后,使用 npm 脚本运行测试与覆盖率收集 +- 客户端测试 + - 在 client/ 目录下安装依赖,使用 npm 脚本运行前端测试 +- 覆盖率报告 + - 根目录与客户端目录均启用覆盖率收集,生成 coverage 报告目录 + +章节来源 +- [package.json:5-12](file://package.json#L5-L12) +- [client/package.json:5-8](file://client/package.json#L5-L8) +- [jest.config.js:25-26](file://jest.config.js#L25-L26) +- [client/jest.config.js:24-25](file://client/jest.config.js#L24-L25) + +### 测试数据准备与断言规范 +- HTTP 信令 + - 使用 getMockReq/getMockRes 构造请求体与头部,断言 res.json/res.sendStatus 的返回值 + - 通过 getAll/getOffer/getAnswer/getCandidate 验证消息队列状态 +- WebSocket 信令 + - 使用 jest-websocket-mock 的 toReceiveMessage/toHaveReceivedMessages 断言消息 +- 前端信令 + - 使用自定义事件断言,结合 waitFor/sleep 控制异步时机 +- WebRTC + - 通过事件监听与内部状态断言,验证 Offer/Answer/Candidate 的触发与接受条件 + +章节来源 +- [test/httphandler.test.ts:14-33](file://test/httphandler.test.ts#L14-L33) +- [test/websockethandler.test.ts:17-28](file://test/websockethandler.test.ts#L17-L28) +- [client/test/signaling.test.js:67-208](file://client/test/signaling.test.js#L67-L208) +- [client/test/peerconnection.test.js:69-122](file://client/test/peerconnection.test.js#L69-L122) + +### WebSocket 与 HTTP 服务调试技巧 +- 断点设置 + - 在 src/index.ts 的信令分支处设置断点,观察 mode/type 参数对行为的影响 + - 在 wsHandler/httphandler 的消息处理函数中设置断点,验证输入与输出 +- 日志分析 + - 使用 src/log.ts 的 log 函数输出关键路径,结合 setLogLevel 提升日志级别 + - 在客户端测试中,利用 waitFor/sleep 精确控制事件时序,便于定位异常 + +章节来源 +- [src/index.ts:75-88](file://src/index.ts#L75-L88) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [client/test/testutils.js:3-15](file://client/test/testutils.js#L3-L15) + +### 客户端测试:WebRTC 连接与信令 +- 连接测试 + - 通过 Peer 对象的 addTrack/addTransceiver/createDataChannel 触发 Offer + - 在不同角色下验证 Answer 的触发时机 +- 信令测试 + - 使用 MockSignaling 或 jest-dev-server 启动的服务进行端到端验证 + - 断言 connect/offer/answer/candidate/disconnect 事件的完整性 + +章节来源 +- [client/test/peerconnection.test.js:8-30](file://client/test/peerconnection.test.js#L8-L30) +- [client/test/signaling.test.js:67-208](file://client/test/signaling.test.js#L67-L208) + +### 模拟对象使用指南 +- jest-websocket-mock + - 用于 WebSocket 信令的本地服务器模拟,断言消息收发 +- @jest-mock/express + - 用于 HTTP 信令的请求/响应对象构造与断言 +- jest-dev-server + - 用于启动本地服务参与端到端测试 +- WebRTC mock + - 在 client/jest.setup.js 中注入 RTCPeerConnection/SessionDescription/IceCandidate/ResizeObserver 等 polyfill + +章节来源 +- [test/websockethandler.test.ts:1](file://test/websockethandler.test.ts#L1) +- [test/httphandler.test.ts:1](file://test/httphandler.test.ts#L1) +- [client/test/signaling.test.js:3](file://client/test/signaling.test.js#L3) +- [client/jest.setup.js:21-35](file://client/jest.setup.js#L21-L35) + +### 性能测试与压力测试方法 +- 单元测试阶段保持无外部依赖,使用 Mock 与同步断言 +- 集成测试阶段,通过多个客户端并发创建连接、发送 Offer/Answer/Candidate,统计事件完成时间 +- 使用独立端口与进程并行运行多实例,评估吞吐与延迟 +- 建议在 CI 中开启覆盖率报告,持续监控代码覆盖度 + +### 测试覆盖率报告生成与分析 +- 根目录与客户端目录均启用覆盖率收集,输出目录为 coverage +- 可在 CI 中配置覆盖率阈值,确保关键路径被覆盖 + +章节来源 +- [jest.config.js:19-26](file://jest.config.js#L19-L26) +- [client/jest.config.js:18-25](file://client/jest.config.js#L18-L25) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/开发指南/贡献指南.md b/.qoder/repowiki/zh/content/开发指南/贡献指南.md new file mode 100644 index 0000000..f6829f2 --- /dev/null +++ b/.qoder/repowiki/zh/content/开发指南/贡献指南.md @@ -0,0 +1,316 @@ +# 贡献指南 + + +**本文引用的文件** +- [package.json](file://package.json) +- [tsconfig.json](file://tsconfig.json) +- [.eslintrc.cjs](file://.eslintrc.cjs) +- [jest.config.js](file://jest.config.js) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [.gitignore](file://.gitignore) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本贡献指南面向希望参与“视频流服务器”项目的开发者,提供从 Fork、Clone、分支与提交、Pull Request 到代码审查、Issue 提交、版本发布与变更日志维护、社区行为准则与协作规范的全流程说明。同时给出新贡献者的入门建议与资源链接,帮助快速上手。 + +## 项目结构 +该项目采用前后端一体化的 Node/Express + TypeScript 后端,内置静态前端资源与测试框架,支持 WebSocket 与 HTTP 两种信令模式,具备会话管理、WebRTC 信令转发、文件上传与 Swagger 文档能力。 + +```mermaid +graph TB +subgraph "后端" +IDX["入口: src/index.ts"] +SRV["服务器: src/server.ts"] +WS["WebSocket处理器: src/class/websockethandler.ts"] +HTTP["HTTP处理器: src/class/httphandler.ts"] +end +subgraph "前端" +PUB["静态资源: client/public/**/*"] +SRC["前端模块: client/src/**/*"] +end +subgraph "工具与配置" +PKG["包脚本: package.json"] +TSC["编译配置: tsconfig.json"] +ESL["代码规范: .eslintrc.cjs"] +JST["测试配置: jest.config.js"] +GIT["忽略规则: .gitignore"] +end +IDX --> SRV +SRV --> WS +SRV --> HTTP +SRV --> PUB +SRV --> SRC +PKG --> TSC +PKG --> ESL +PKG --> JST +PKG --> GIT +``` + +**图表来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [package.json:1-60](file://package.json#L1-L60) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [.gitignore:1-187](file://.gitignore#L1-L187) + +**章节来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [package.json:1-60](file://package.json#L1-L60) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [.gitignore:1-187](file://.gitignore#L1-L187) + +## 核心组件 +- 入口与启动 + - 通过命令行参数解析与 HTTPS/HTTP 服务启动,支持 WebSocket 或 HTTP 信令模式,以及公共/私有通信模式。 +- 服务器与路由 + - Express 应用挂载 CORS、日志中间件、静态资源、Swagger 文档、上传接口与信令路由。 +- WebSocket 信令 + - 实现 1 对多(主持人/参与者)模式下的 offer/answer/candidate 转发、广播、心跳检测与断线清理。 +- HTTP 信令 + - 基于轮询的信令 API,提供会话创建、连接管理、offer/answer/candidate 获取与断线记录。 +- 测试与规范 + - Jest 测试配置与覆盖率收集;ESLint TypeScript 规则;TypeScript 编译配置。 + +**章节来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) + +## 架构总览 +后端以 Express 为核心,根据信令类型选择 WebSocket 或 HTTP 处理器;静态资源与前端模块通过 Express 提供;测试与规范工具贯穿开发流程。 + +```mermaid +graph TB +Client["浏览器/客户端"] --> Mode{"信令模式"} +Mode --> |WebSocket| WS["WebSocket处理器
websockethandler.ts"] +Mode --> |HTTP| HTTP["HTTP处理器
httphandler.ts"] +WS --> Server["Express服务器
server.ts"] +HTTP --> Server +Server --> Static["静态资源
client/public/**/*"] +Server --> Front["前端模块
client/src/**/*"] +Server --> Swagger["Swagger文档"] +Server --> Upload["头像上传API"] +``` + +**图表来源** +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +## 详细组件分析 + +### WebSocket 信令处理流程 +WebSocket 处理器支持公共/私有模式,实现连接、断开、offer/answer/candidate 转发与广播、心跳检测与断线清理。 + +```mermaid +sequenceDiagram +participant C as "客户端" +participant S as "WebSocket处理器" +participant G as "连接组" +participant H as "主持人" +participant P as "参与者" +C->>S : "connect(connectionId)" +S->>G : "创建/加入连接组" +S-->>C : "connect(角色, participantId)" +C->>S : "offer/answer/candidate" +alt 私有模式 +S->>H : "主持人接收转发" +S->>P : "参与者接收转发" +else 公共模式 +S->>G : "广播到其他客户端" +end +C->>S : "disconnect" +S->>G : "清理连接组并通知" +S-->>C : "disconnect" +``` + +**图表来源** +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:448-473](file://src/class/websockethandler.ts#L448-L473) + +**章节来源** +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +### HTTP 信令处理流程 +HTTP 处理器提供会话与连接管理、offer/answer/candidate 获取、断线记录与全量信令拉取。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant Handler as "HTTP处理器" +participant Store as "内存存储" +participant Other as "其他会话/连接" +Client->>Handler : "PUT /signaling (创建会话)" +Handler->>Store : "初始化会话映射" +Handler-->>Client : "{ sessionId }" +Client->>Handler : "PUT /signaling/connection (创建连接)" +Handler->>Store : "登记连接ID与配对" +Handler-->>Client : "{ connectionId, polite }" +Client->>Handler : "GET /signaling/offer?fromtime" +Handler->>Store : "按模式过滤并返回offer列表" +Client->>Handler : "GET /signaling (全量信令)" +Handler->>Store : "聚合连接/断线/offer/answer/candidate" +Handler-->>Client : "{ messages, datetime }" +Client->>Handler : "DELETE /signaling/connection" +Handler->>Store : "清理连接与配对" +``` + +**图表来源** +- [src/class/httphandler.ts:664-783](file://src/class/httphandler.ts#L664-L783) +- [src/class/httphandler.ts:492-641](file://src/class/httphandler.ts#L492-L641) + +**章节来源** +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +### 代码规范与测试 +- ESLint 规则启用 TypeScript 推荐规则与 Jest 推荐规则,强制分号与多余分号检查。 +- Jest 配置启用覆盖率收集、TS 转换、Node 环境与测试匹配规则。 +- TypeScript 编译配置输出至 build 目录,源码位于 src,测试位于 test。 + +**章节来源** +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) + +## 依赖关系分析 +- 后端运行时依赖:Express、WS、CORS、Morgan、Multer、Swagger。 +- 开发依赖:Jest、TS-Jest、ESLint、TypeScript、pkg 等。 +- 构建与打包:通过 TypeScript 编译与 pkg 配置,打包静态资源与前端模块。 + +```mermaid +graph LR +PKG["package.json"] --> DEPS["运行时依赖"] +PKG --> DEVDEPS["开发依赖"] +PKG --> BIN["构建与打包配置"] +BIN --> TSC["tsconfig.json"] +BIN --> ESL["eslint配置"] +BIN --> JST["jest配置"] +``` + +**图表来源** +- [package.json:1-60](file://package.json#L1-L60) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) + +**章节来源** +- [package.json:1-60](file://package.json#L1-L60) + +## 性能考量 +- WebSocket 心跳与超时:处理器内置心跳检测与超时断开逻辑,避免僵尸连接占用资源。 +- HTTP 轮询:提供 fromtime 参数过滤增量消息,降低带宽与计算压力。 +- 日志与中间件:morgan 日志级别可配置,建议在生产环境调整为合适级别。 +- 静态资源:静态目录与前端模块统一托管,确保缓存与压缩策略一致。 + +**章节来源** +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [src/server.ts:17-20](file://src/server.ts#L17-L20) + +## 故障排查指南 +- 启动失败 + - 检查证书文件路径与权限(HTTPS 模式),确认端口占用与网络接口。 +- 信令异常 + - 确认信令模式(WebSocket/HTTP)与通信模式(public/private)配置一致。 + - 查看日志输出定位连接组状态与消息转发问题。 +- 测试失败 + - 使用 Jest 覆盖率报告定位未覆盖路径,修正测试用例与模拟。 +- 规范检查 + - 运行 ESLint 并修复规则冲突,确保提交前通过静态检查。 + +**章节来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:25-42](file://src/server.ts#L25-L42) +- [jest.config.js:19-26](file://jest.config.js#L19-L26) +- [.eslintrc.cjs:18-23](file://.eslintrc.cjs#L18-L23) + +## 结论 +本指南提供了从开发环境准备到贡献流程、代码审查、Issue 规范、版本发布与变更日志维护、社区协作的完整实践路径。建议贡献者在提交前完成本地测试与规范检查,并遵循分支命名与提交信息格式,确保高质量交付。 + +## 附录 + +### 代码贡献流程 +- Fork 仓库 + - 在代码托管平台执行 Fork 操作,获得个人副本。 +- Clone 与安装 + - 克隆到本地,安装依赖并运行开发脚本。 +- 分支与提交 + - 基于主分支创建特性分支,遵循分支命名规范与提交信息格式。 +- Pull Request + - 提交 PR,填写模板信息,等待审查与合并。 + +**章节来源** +- [package.json:5-12](file://package.json#L5-L12) + +### 分支命名规范与提交信息格式 +- 分支命名 + - feat/xxx:新增功能 + - fix/xxx:缺陷修复 + - docs/xxx:文档更新 + - refactor/xxx:重构 + - test/xxx:测试相关 +- 提交信息格式 + - 类型(scope): 描述 + - 例如:feat(server): 支持自定义日志级别 + +### 代码审查标准与流程 +- 审查清单 + - 功能正确性与边界条件 + - 性能影响与资源占用 + - 日志与错误处理 + - 单元测试覆盖率 + - 规范检查通过 +- 审查流程 + - 提交 PR → 指派审查者 → 评论与修改 → 合并 + +### Issue 提交规范 +- Bug 报告 + - 环境信息、复现步骤、期望/实际结果、日志片段 +- 功能请求 + - 背景、需求描述、影响范围、优先级建议 + +### 版本发布与变更日志维护 +- 版本号 + - 遵循语义化版本:主版本.次版本.修订 +- 变更日志 + - 按版本记录新增、修复、变更与废弃项,标注影响与迁移指引 + +### 社区行为准则与协作规范 +- 尊重与包容:保持友善与专业 +- 明确沟通:使用清晰、简洁的语言 +- 合规贡献:遵守开源许可证与安全政策 + +### 新贡献者入门指导与资源链接 +- 环境准备 + - Node.js、TypeScript、Git 基础 +- 本地运行 + - 使用开发脚本启动服务,访问静态页面与 Swagger 文档 +- 学习资源 + - Express、WebSocket、WebRTC 信令基础 + - Jest 与 ESLint 使用指南 \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/快速开始.md b/.qoder/repowiki/zh/content/快速开始.md new file mode 100644 index 0000000..b494b3c --- /dev/null +++ b/.qoder/repowiki/zh/content/快速开始.md @@ -0,0 +1,391 @@ +# 快速开始 + + +**本文引用的文件** +- [package.json](file://package.json) +- [tsconfig.json](file://tsconfig.json) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [run.bat](file://run.bat) +- [client/public/index.html](file://client/public/index.html) +- [client/public/bidirectional/index.html](file://client/public/bidirectional/index.html) +- [client/public/bidirectional/js/main.js](file://client/public/bidirectional/js/main.js) +- [client/public/receiver/index.html](file://client/public/receiver/index.html) +- [client/public/receiver/js/main.js](file://client/public/receiver/js/main.js) +- [client/public/multiplay/index.html](file://client/public/multiplay/index.html) +- [client/public/multiplay/js/main.js](file://client/public/multiplay/js/main.js) +- [client/package.json](file://client/package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖分析](#依赖分析) +7. [性能考虑](#性能考虑) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本快速开始指南面向首次部署与运行 Video Socket Server 的用户,涵盖以下内容: +- 环境要求:Node.js 版本、操作系统兼容性与依赖工具 +- 安装与构建:全局安装 TypeScript 与开发工具,构建后端与打包 +- 启动服务器:命令行参数详解(端口、模式、SSL 证书等) +- 运行内置客户端示例:双向通信、视频接收、多客户端播放 +- 常见问题与故障排除:证书缺失、端口占用、浏览器限制等 +- 实际命令行示例与预期输出 + +## 项目结构 +该仓库采用“前后端同源”的组织方式:服务端为基于 Express 的 Node.js 应用,前端示例位于 client/public 与 client/src,通过静态资源与 API 提供完整的 WebRTC 示例页面。 + +```mermaid +graph TB +subgraph "服务端" +A["Express 应用
src/server.ts"] +B["入口与参数解析
src/index.ts"] +C["选项类型定义
src/class/options.ts"] +end +subgraph "客户端示例" +D["首页与导航
client/public/index.html"] +E["双向通信示例
client/public/bidirectional/index.html"] +F["接收端示例
client/public/receiver/index.html"] +G["多人播放示例
client/public/multiplay/index.html"] +end +B --> A +A --> D +D --> E +D --> F +D --> G +``` + +图表来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [client/public/index.html:1-78](file://client/public/index.html#L1-L78) +- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [client/public/index.html:1-78](file://client/public/index.html#L1-L78) + +## 核心组件 +- 服务端入口与参数解析:负责读取命令行参数、创建 HTTP/HTTPS 服务器、初始化 WebSocket 或 HTTP 信令,并打印启动日志。 +- Express 服务器:提供静态资源托管、/signaling 接口、/config 接口、Swagger 文档、头像上传 API 等。 +- 客户端示例:包含双向通信、接收端、多人播放等示例页面,均通过模块化 JS 与服务端交互。 + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +## 架构总览 +下图展示从浏览器到服务端的整体交互路径:浏览器加载示例页面,通过 /config 获取信令协议与模式,随后根据模式与协议选择 WebSocket 或 HTTP 信令通道进行连接;服务端根据模式(public/private)与协议(websocket/http)决定行为。 + +```mermaid +sequenceDiagram +participant Browser as "浏览器" +participant Static as "静态资源服务
/ (Express)" +participant Config as "/config 接口" +participant Signaling as "/signaling 接口" +participant WS as "WebSocket 信令" +Browser->>Static : 访问根路径 / +Static-->>Browser : 返回 client/public/index.html +Browser->>Config : GET /config +Config-->>Browser : 返回 useWebSocket/startupMode/logging +Browser->>Signaling : 发起信令请求HTTP 或 WebSocket +Signaling-->>Browser : 返回信令数据 +Browser->>WS : 建立 WebSocket 连接可选 +WS-->>Browser : 传输信令消息 +``` + +图表来源 +- [src/server.ts:25-27](file://src/server.ts#L25-L27) +- [src/server.ts:26](file://src/server.ts#L26) +- [src/index.ts:75-88](file://src/index.ts#L75-L88) + +章节来源 +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/index.ts:13-109](file://src/index.ts#L13-L109) + +## 详细组件分析 + +### 服务端启动与参数解析 +- 支持的命令行参数: + - -p, --port:监听端口,默认来自环境变量或 80 + - -s, --secure:启用 HTTPS(需要 server.key 与 server.cert) + - -k, --keyfile:HTTPS 私钥文件路径 + - -c, --certfile:HTTPS 证书文件路径 + - -t, --type:信令协议类型,websocket 或 http + - -m, --mode:通信模式,public 或 private + - -l, --logging:HTTP 日志格式,combined、dev、short、tiny 或 none +- 启动流程要点: + - 若启用 HTTPS,则读取指定密钥与证书文件并监听对应端口 + - 打印可用 IP 地址与协议(http/https) + - 根据 type 决定使用 WebSocket 或 HTTP 信令 + - 根据 mode 输出当前模式日志 + +```mermaid +flowchart TD +Start(["进程启动"]) --> ParseArgs["解析命令行参数"] +ParseArgs --> CreateServer{"是否启用 HTTPS?"} +CreateServer --> |是| LoadCert["读取密钥与证书"] +LoadCert --> ListenHTTPS["监听 HTTPS 端口"] +CreateServer --> |否| ListenHTTP["监听 HTTP 端口"] +ListenHTTPS --> DecideType{"信令类型?"} +ListenHTTP --> DecideType +DecideType --> |websocket| InitWS["初始化 WebSocket 信令"] +DecideType --> |http| InitHTTP["初始化 HTTP 信令"] +InitWS --> LogMode["输出模式日志"] +InitHTTP --> LogMode +LogMode --> End(["服务就绪"]) +``` + +图表来源 +- [src/index.ts:14-44](file://src/index.ts#L14-L44) +- [src/index.ts:55-91](file://src/index.ts#L55-L91) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +### Express 服务器与路由 +- 静态资源:托管 client/public 与 client/src 下的文件 +- 路由: + - GET /config:返回 useWebSocket、startupMode、logging + - /signaling:转发至信令模块 + - GET /:返回首页 index.html(若存在) + - POST /api/upload/avatar:头像上传并按用户 ID 重命名 + - /uploads:头像上传后的静态访问 +- Swagger 文档:初始化并暴露接口文档 + +章节来源 +- [src/server.ts:14-90](file://src/server.ts#L14-L90) + +### 客户端示例概览 +- 首页(client/public/index.html):列出示例入口(接收端、双向通信、多人播放),并展示服务器配置信息 +- 双向通信(bidirectional):支持本地/远端视频预览、编解码器偏好设置、统计信息展示 +- 接收端(receiver):点击播放后建立连接,接收视频流并支持输入通道 +- 多人播放(multiplay):在接收端基础上增加多玩家标签通道 + +章节来源 +- [client/public/index.html:1-78](file://client/public/index.html#L1-L78) +- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54) + +### 双向通信示例工作流 +```mermaid +sequenceDiagram +participant U as "用户" +participant UI as "示例页面
bidirectional/index.html" +participant JS as "JS 主逻辑
bidirectional/js/main.js" +participant RS as "RenderStreaming" +participant Sign as "信令" +participant Peer as "对端浏览器" +U->>UI : 选择设备/分辨率并点击“启动视频” +UI->>JS : 触发 startVideo() +JS->>RS : startLocalVideo() +U->>UI : 点击“设置连接” +UI->>JS : 触发 setUp() +JS->>Sign : 选择 WebSocket 或 HTTP 信令 +JS->>RS : start() + createConnection() +RS-->>JS : onConnect 回调 +JS->>RS : addTransceiver(本地轨道) +JS->>Peer : 发送 Offer/ICE 候选 +Peer-->>JS : 返回 Answer/ICE 候选 +JS->>UI : 显示远端轨道 +U->>UI : 点击“挂断” +UI->>JS : 触发 hangUp() +JS->>RS : deleteConnection() + stop() +``` + +图表来源 +- [client/public/bidirectional/js/main.js:112-184](file://client/public/bidirectional/js/main.js#L112-L184) +- [client/public/bidirectional/js/main.js:220-241](file://client/public/bidirectional/js/main.js#L220-L241) + +章节来源 +- [client/public/bidirectional/js/main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) + +### 接收端示例工作流 +```mermaid +sequenceDiagram +participant U as "用户" +participant UI as "示例页面
receiver/index.html" +participant JS as "JS 主逻辑
receiver/js/main.js" +participant RS as "RenderStreaming" +participant Sign as "信令" +U->>UI : 点击“播放”按钮 +UI->>JS : onClickPlayButton() +JS->>RS : start() + createConnection() +RS-->>JS : onConnect 回调 +JS->>RS : createDataChannel("input") +JS->>UI : 创建 VideoPlayer 并添加远端轨道 +U->>UI : 关闭页面/断开 +UI->>JS : beforeunload +JS->>RS : stop() +``` + +图表来源 +- [client/public/receiver/js/main.js:67-88](file://client/public/receiver/js/main.js#L67-L88) +- [client/public/receiver/js/main.js:96-108](file://client/public/receiver/js/main.js#L96-L108) + +章节来源 +- [client/public/receiver/js/main.js:1-186](file://client/public/receiver/js/main.js#L1-L186) + +### 多人播放示例工作流 +```mermaid +sequenceDiagram +participant U as "用户" +participant UI as "示例页面
multiplay/index.html" +participant JS as "JS 主逻辑
multiplay/js/main.js" +participant RS as "RenderStreaming" +participant Sign as "信令" +U->>UI : 点击“播放”按钮 +UI->>JS : onClickPlayButton() +JS->>RS : start() + createConnection() +RS-->>JS : onConnect 回调 +JS->>RS : createDataChannel("input") +JS->>RS : createDataChannel("multiplay") +JS->>JS : onOpenMultiplayChannel() 发送标签变更消息 +JS->>UI : 创建 VideoPlayer 并添加远端轨道 +U->>UI : 关闭页面/断开 +UI->>JS : beforeunload +JS->>RS : stop() +``` + +图表来源 +- [client/public/multiplay/js/main.js:74-95](file://client/public/multiplay/js/main.js#L74-L95) +- [client/public/multiplay/js/main.js:105-110](file://client/public/multiplay/js/main.js#L105-L110) + +章节来源 +- [client/public/multiplay/js/main.js:1-204](file://client/public/multiplay/js/main.js#L1-L204) + +## 依赖分析 +- 服务端依赖(部分):Express、ws(WebSocket)、cors、multer、morgan、swagger-ui-express 等 +- 开发依赖(部分):TypeScript、ts-node、Jest、ESLint、pkg 等 +- 客户端依赖(部分):Jest、ESLint、node-fetch 等 +- 构建与打包:TypeScript 编译、Jest 测试、pkg 打包 + +```mermaid +graph LR +P["package.json
服务端脚本与依赖"] --> TS["TypeScript 编译
tsconfig.json"] +P --> NPM["NPM 脚本
build/test/dev/start"] +P --> DevDeps["开发依赖
ts-node/jest/eslint/pkg"] +P --> Deps["运行时依赖
express/ws/cors/multer/morgan/swagger"] +CP["client/package.json
客户端脚本与依赖"] --> JestC["Jest 测试"] +CP --> ESLintC["ESLint 代码检查"] +``` + +图表来源 +- [package.json:14-46](file://package.json#L14-L46) +- [tsconfig.json:1-13](file://tsconfig.json#L1-13) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +章节来源 +- [package.json:1-60](file://package.json#L1-L60) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +## 性能考虑 +- 信令协议选择:WebSocket 通常具有更低延迟与更少轮询开销,适合实时场景;HTTP 轮询在受限网络环境下更稳定但会增加带宽与 CPU 开销。 +- 日志级别:合理选择 -l 参数以平衡可观测性与性能(none 最低开销)。 +- 编解码器偏好:在支持的浏览器上设置编解码器偏好可提升质量与效率,避免不必要的冗余编解码器。 +- 多人播放:多人场景下建议使用公共模式(public)并控制并发连接数量,避免带宽与 CPU 压力过大。 + +## 故障排除指南 +- 无法启动 HTTPS: + - 确认 server.key 与 server.cert 文件存在且路径正确 + - 使用 -k 与 -c 指定密钥与证书文件 +- 端口被占用: + - 更换 -p 指定的端口,或释放占用端口 +- 浏览器报错“不支持编解码器偏好”: + - 当前浏览器不支持 setCodecPreferences,示例会提示不支持;升级浏览器或移除偏好设置 +- 模式不匹配: + - 双向通信示例仅在 Private 模式有效;接收端与多人播放示例仅在 Public 模式有效 +- 无法访问示例页面: + - 确认已执行构建(npm run build),并使用 npm run start 启动服务 +- 上传头像失败: + - 检查 uploads 目录权限与磁盘空间;确认请求包含 avatar 字段 + +章节来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:62-83](file://src/server.ts#L62-L83) +- [client/public/bidirectional/js/main.js:99-105](file://client/public/bidirectional/js/main.js#L99-L105) +- [client/public/receiver/js/main.js:48-54](file://client/public/receiver/js/main.js#L48-L54) +- [client/public/multiplay/js/main.js:55-61](file://client/public/multiplay/js/main.js#L55-L61) + +## 结论 +通过本指南,您可以在本地完成 Video Socket Server 的环境准备、依赖安装、构建与启动,并成功运行内置的客户端示例。请根据实际网络与浏览器环境调整信令协议、模式与日志级别,以获得最佳体验。 + +## 附录 + +### 环境要求 +- Node.js:用于运行服务端与客户端示例 +- TypeScript:用于编译与开发(版本由 package.json 中 devDependencies 指定) +- 操作系统:跨平台(Windows/macOS/Linux),需满足 Node.js 与浏览器兼容性 + +章节来源 +- [package.json:28-46](file://package.json#L28-L46) + +### 依赖安装步骤 +- 全局安装 TypeScript 与 ts-node(如需直接运行 TypeScript 源码) +- 在项目根目录执行安装:npm install +- 在 client 目录执行安装:cd client && npm install + +章节来源 +- [package.json:28-46](file://package.json#L28-L46) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +### 构建与打包 +- 构建:npm run build(TypeScript 编译至 build 目录) +- 打包:npm run pack(使用 pkg 打包) + +章节来源 +- [package.json:5, 6, 12:5-12](file://package.json#L5-L12) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) + +### 启动服务器 +- 开发模式:npm run dev(热启动,直接运行 src/index.ts) +- 生产模式:npm run start(先构建再启动,使用 build/index.js) +- Windows 批处理:双击 run.bat(自动构建并启动) + +常用命令行参数说明(节选): +- -p, --port:监听端口(默认来自环境变量或 80) +- -s, --secure:启用 HTTPS(需要 server.key 与 server.cert) +- -k, --keyfile:HTTPS 私钥文件路径 +- -c, --certfile:HTTPS 证书文件路径 +- -t, --type:信令协议类型(websocket 或 http) +- -m, --mode:通信模式(public 或 private) +- -l, --logging:HTTP 日志格式(combined、dev、short、tiny 或 none) + +章节来源 +- [src/index.ts:20-29](file://src/index.ts#L20-L29) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) + +### 运行内置客户端示例 +- 双向通信:打开 bidirectional/index.html,选择设备与分辨率,点击“启动视频”,再点击“设置连接”,即可进行双向视频通话 +- 视频接收:打开 receiver/index.html,点击“播放”,建立连接后接收视频流 +- 多人播放:打开 multiplay/index.html,点击“播放”,建立连接后接收视频流并支持多玩家标签通道 + +章节来源 +- [client/public/bidirectional/index.html:1-84](file://client/public/bidirectional/index.html#L1-L84) +- [client/public/receiver/index.html:1-54](file://client/public/receiver/index.html#L1-L54) +- [client/public/multiplay/index.html:1-54](file://client/public/multiplay/index.html#L1-L54) + +### 实际命令行示例与预期输出 +- 构建与启动(Windows):双击 run.bat,预期输出包含 http/https 地址与模式日志 +- 开发启动:npm run dev,预期输出包含端口与协议信息 +- 生产启动:npm run start,预期输出包含端口与协议信息 + +章节来源 +- [run.bat:1-6](file://run.bat#L1-L6) +- [package.json:9-10](file://package.json#L9-L10) +- [src/index.ts:67-73](file://src/index.ts#L67-L73) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/服务器核心/HTTP 服务器配置.md b/.qoder/repowiki/zh/content/服务器核心/HTTP 服务器配置.md new file mode 100644 index 0000000..7320046 --- /dev/null +++ b/.qoder/repowiki/zh/content/服务器核心/HTTP 服务器配置.md @@ -0,0 +1,588 @@ +# HTTP 服务器配置 + + +**本文档引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [src/log.ts](file://src/log.ts) +- [package.json](file://package.json) +- [run.bat](file://run.bat) +- [server.cert](file://server.cert) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构概览](#架构概览) +5. [详细组件分析](#详细组件分析) +6. [HTTPS 服务器配置](#https-服务器配置) +7. [CORS 配置](#cors-配置) +8. [日志记录中间件](#日志记录中间件) +9. [错误处理机制](#错误处理机制) +10. [性能优化建议](#性能优化建议) +11. [安全配置最佳实践](#安全配置最佳实践) +12. [配置示例](#配置示例) +13. [故障排除指南](#故障排除指南) +14. [结论](#结论) + +## 简介 + +本项目是一个基于 Node.js 和 Express 的 WebRTC 信令服务器,提供了完整的 HTTP 和 WebSocket 服务器配置功能。该系统支持 HTTPS 加密传输、CORS 跨域资源共享、详细的日志记录、文件上传处理以及 RESTful API 接口。系统采用模块化设计,支持公共模式和私有模式两种通信方式,适用于视频流媒体应用的信令传输需求。 + +## 项目结构 + +该项目采用清晰的模块化架构,主要包含以下核心目录和文件: + +```mermaid +graph TB +subgraph "项目根目录" +A[package.json] --> B[src/] +C[client/] --> D[public/] +E[client/src/] --> F[测试文件] +G[test/] --> H[单元测试] +end +subgraph "源代码目录 (src/)" +B --> I[index.ts] +B --> J[server.ts] +B --> K[class/] +B --> L[websocket.ts] +B --> M[signaling.ts] +B --> N[swagger.ts] +B --> O[log.ts] +subgraph "class/" +K --> P[options.ts] +K --> Q[httphandler.ts] +K --> R[websockethandler.ts] +K --> S[offer.ts] +K --> T[answer.ts] +K --> U[candidate.ts] +end +end +subgraph "客户端资源" +D --> V[HTML页面] +D --> W[CSS样式] +D --> X[JavaScript文件] +E --> Y[源代码文件] +end +``` + +**图表来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +**章节来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +## 核心组件 + +### 服务器启动器 (RenderStreaming) + +`RenderStreaming` 类是整个应用程序的入口点,负责解析命令行参数、创建 Express 应用程序、配置 HTTPS 服务器以及启动 WebSocket 信令服务。 + +```mermaid +classDiagram +class RenderStreaming { ++Options options ++Application app ++Server server ++run(argv) RenderStreaming ++constructor(options) ++getIPAddress() string[] +} +class Options { ++boolean secure ++number port ++string keyfile ++string certfile ++string type ++string mode ++string logging +} +class WSSignaling { ++Server server ++WebSocketServer wss ++constructor(server, mode) +} +RenderStreaming --> Options : 使用 +RenderStreaming --> WSSignaling : 启动 +``` + +**图表来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +### Express 应用程序配置 + +Express 应用程序通过 `createServer` 函数进行配置,集成了多种中间件和路由处理功能。 + +**章节来源** +- [src/index.ts:52-109](file://src/index.ts#L52-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 架构概览 + +系统采用双服务器架构,同时支持 HTTP 和 WebSocket 协议: + +```mermaid +graph TB +subgraph "客户端层" +A[Web浏览器] +B[移动应用] +C[桌面应用] +end +subgraph "服务器层" +D[HTTP服务器] +E[HTTPS服务器] +F[WebSocket服务器] +subgraph "Express中间件" +G[Morgan日志] +H[CORS跨域] +I[JSON解析] +J[静态资源] +K[文件上传] +end +subgraph "业务逻辑层" +L[HTTP信令处理器] +M[WebSocket信令处理器] +N[会话管理器] +end +end +subgraph "外部服务" +O[WebRTC客户端] +P[API消费者] +Q[文件存储] +end +A --> D +B --> E +C --> F +D --> G +E --> G +F --> M +G --> L +H --> L +I --> L +J --> L +K --> L +L --> N +M --> N +N --> O +L --> P +K --> Q +``` + +**图表来源** +- [src/index.ts:55-88](file://src/index.ts#L55-L88) +- [src/server.ts:18-41](file://src/server.ts#L18-L41) + +## 详细组件分析 + +### HTTP 服务器配置 + +HTTP 服务器通过 Express 框架实现,配置了完整的中间件栈和路由系统。 + +#### 中间件配置流程 + +```mermaid +flowchart TD +Start([服务器启动]) --> CreateApp["创建Express应用"] +CreateApp --> ConfigLogging["配置日志中间件
morgan(config.logging)"] +ConfigLogging --> EnableCORS["启用CORS
允许所有来源"] +EnableCORS --> ParseBody["配置请求体解析
JSON和URL编码"] +ParseBody --> SetupRoutes["设置路由处理"] +SetupRoutes --> StaticFiles["配置静态资源
客户端文件"] +StaticFiles --> UploadConfig["配置文件上传
Multer中间件"] +UploadConfig --> SwaggerInit["初始化Swagger文档"] +SwaggerInit --> Ready([服务器就绪]) +``` + +**图表来源** +- [src/server.ts:18-41](file://src/server.ts#L18-L41) + +#### 路由系统架构 + +```mermaid +graph LR +subgraph "根路由 (/)" +A[GET /] --> B[返回主页] +C[GET /config] --> D[返回服务器配置] +end +subgraph "信令路由 (/signaling)" +E[子路由] --> F[HTTP信令处理器] +G[/connection] --> H[连接管理] +I[/offer] --> J[Offer消息处理] +K[/answer] --> L[Answer消息处理] +M[/candidate] --> N[Candidate消息处理] +end +subgraph "静态资源" +O[public/] --> P[HTML/CSS/JS] +Q[/module] --> R[源代码文件] +S[/uploads] --> T[用户上传文件] +end +subgraph "API文档" +U[Swagger UI] --> V[在线API文档] +end +``` + +**图表来源** +- [src/server.ts:25-41](file://src/server.ts#L25-L41) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) + +**章节来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) + +### WebSocket 服务器配置 + +WebSocket 服务器提供实时双向通信能力,支持 WebRTC 信令传输。 + +#### WebSocket 连接处理流程 + +```mermaid +sequenceDiagram +participant Client as 客户端 +participant WS as WebSocket服务器 +participant Handler as 信令处理器 +participant Session as 会话管理器 +Client->>WS : 建立WebSocket连接 +WS->>Handler : 触发connection事件 +Handler->>Session : 添加新连接 +Handler->>Client : 发送connect确认 +loop 信令消息循环 +Client->>Handler : 发送信令消息 +Handler->>Handler : 解析消息类型 +alt offer消息 +Handler->>Session : 存储Offer +Handler->>Client : 广播Offer +else answer消息 +Handler->>Session : 存储Answer +Handler->>Client : 转发Answer +else candidate消息 +Handler->>Session : 存储Candidate +Handler->>Client : 转发Candidate +end +end +Client->>WS : 断开连接 +WS->>Handler : 触发close事件 +Handler->>Session : 移除连接 +Handler->>Client : 发送断开通知 +``` + +**图表来源** +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) + +**章节来源** +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) + +### 会话管理器 + +会话管理器负责维护客户端连接状态和信令消息队列。 + +#### 会话状态管理 + +```mermaid +stateDiagram-v2 +[*] --> 会话创建 +会话创建 --> 连接等待 : 创建会话 +连接等待 --> Offer发送 : 连接建立 +Offer发送 --> Answer接收 : 发送Offer +Answer接收 --> Candidate交换 : 接收Answer +Candidate交换 --> 信令完成 : 交换Candidate +信令完成 --> 连接断开 : 通话结束 +连接断开 --> [*] +state 连接等待 { +[*] --> 等待参与者 +等待参与者 --> 等待Offer : 收到连接 +等待Offer --> Offer发送 : 收到Offer +} +state 信令完成 { +[*] --> 保持连接 +保持连接 --> Candidate交换 : 有新Candidate +Candidate交换 --> 保持连接 : Candidate同步 +} +``` + +**图表来源** +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) + +**章节来源** +- [src/class/httphandler.ts:106-120](file://src/class/httphandler.ts#L106-L120) + +## HTTPS 服务器配置 + +### SSL 证书配置 + +HTTPS 服务器通过读取 PEM 格式的密钥和证书文件来建立安全连接。 + +#### HTTPS 服务器启动流程 + +```mermaid +flowchart TD +Start([检查HTTPS配置]) --> SecureEnabled{"secure参数启用?"} +SecureEnabled --> |是| LoadCert["读取SSL证书文件"] +SecureEnabled --> |否| StartHTTP["启动HTTP服务器"] +LoadCert --> CertLoaded{"证书文件存在?"} +CertLoaded --> |是| CreateHTTPS["创建HTTPS服务器"] +CertLoaded --> |否| ErrorCert["证书文件不存在"] +CreateHTTPS --> ListenHTTPS["监听HTTPS端口"] +StartHTTP --> ListenHTTP["监听HTTP端口"] +ListenHTTPS --> LogHTTPS["记录HTTPS地址"] +ListenHTTP --> LogHTTP["记录HTTP地址"] +LogHTTPS --> Ready([服务器就绪]) +LogHTTP --> Ready +ErrorCert --> Error([启动失败]) +``` + +**图表来源** +- [src/index.ts:55-74](file://src/index.ts#L55-L74) + +### 证书文件处理 + +系统支持自定义证书文件路径配置,默认使用 `server.key` 和 `server.cert` 文件。 + +**章节来源** +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [package.json:9](file://package.json#L9) + +## CORS 配置 + +### 跨域资源共享设置 + +系统默认启用了 CORS 中间件,允许来自任何来源的跨域请求。 + +#### CORS 配置分析 + +```mermaid +graph TD +A[CORS中间件] --> B[Origin: *] +A --> C[方法: GET, POST, PUT, DELETE] +A --> D[头部: 自动处理] +A --> E[凭证: 允许] +B --> F[允许所有来源] +C --> G[支持RESTful操作] +D --> H[自动预检请求] +E --> I[支持认证请求] +F --> J[简化客户端集成] +G --> K[完整的API访问] +H --> L[减少开发复杂度] +I --> M[增强安全性] +``` + +**图表来源** +- [src/server.ts:22](file://src/server.ts#L22) + +**章节来源** +- [src/server.ts:22](file://src/server.ts#L22) + +## 日志记录中间件 + +### Morgan 日志系统 + +系统使用 Morgan 中间件提供详细的 HTTP 请求日志记录。 + +#### 日志级别配置 + +```mermaid +flowchart TD +A[日志配置] --> B[none - 禁用日志] +A --> C[combined - 详细日志] +A --> D[dev - 开发友好] +A --> E[short - 简洁日志] +A --> F[tiny - 最简日志] +B --> G[生产环境推荐] +C --> H[完整请求详情] +D --> I[开发调试] +E --> J[基本请求信息] +F --> K[最小化开销] +H --> L[适合生产监控] +I --> M[适合开发调试] +J --> N[适合快速部署] +K --> O[适合性能敏感场景] +``` + +**图表来源** +- [src/index.ts:28](file://src/index.ts#L28) + +**章节来源** +- [src/index.ts:18-20](file://src/index.ts#L18-L20) +- [src/log.ts:15-24](file://src/log.ts#L15-L24) + +## 错误处理机制 + +### 统一日志系统 + +系统实现了统一的日志记录机制,支持多种日志级别和格式化输出。 + +#### 错误处理流程 + +```mermaid +flowchart TD +A[请求到达] --> B[中间件处理] +B --> C{处理成功?} +C --> |是| D[正常响应] +C --> |否| E[捕获错误] +E --> F[记录错误日志] +F --> G[返回错误响应] +G --> H[客户端处理] +D --> I[记录成功日志] +I --> H +``` + +**图表来源** +- [src/log.ts:30-50](file://src/log.ts#L30-L50) + +**章节来源** +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 性能优化建议 + +### 服务器性能调优 + +基于现有代码结构,以下是针对该 HTTP 服务器的性能优化建议: + +#### 内存管理优化 +- 实现会话超时清理机制,避免内存泄漏 +- 使用连接池管理数据库连接 +- 实施缓存策略减少重复计算 + +#### 网络性能优化 +- 启用 HTTP/2 支持提升传输效率 +- 实现 gzip 压缩减少带宽占用 +- 配置适当的缓存头提高静态资源加载速度 + +#### 并发处理优化 +- 使用集群模式支持多核 CPU +- 实现请求队列管理防止过载 +- 优化中间件执行顺序减少不必要的处理 + +## 安全配置最佳实践 + +### HTTPS 安全配置 + +#### 证书管理 +- 使用受信任的 CA 机构签发的证书 +- 定期更新证书并配置自动续期 +- 实施证书透明度日志监控 + +#### 加密协议配置 +- 启用 TLS 1.2+ 版本 +- 配置强加密套件 +- 禁用不安全的加密算法 + +#### 安全中间件 +- 实施内容安全策略 (CSP) +- 启用 HTTP 严格传输安全 (HSTS) +- 配置跨站请求伪造 (CSRF) 保护 + +## 配置示例 + +### 基础配置示例 + +以下是一些常见的服务器配置示例: + +#### 开发环境配置 +```bash +# 启动开发服务器 +npm run dev + +# 或使用命令行参数 +node ./build/index.js -s=false -p 8080 -m public -l dev +``` + +#### 生产环境配置 +```bash +# 构建生产版本 +npm run build + +# 启动生产服务器 +npm run start + +# 或使用自定义参数 +node ./build/index.js -s=true -p 443 -m private -k ./server.key -c ./server.cert -l combined +``` + +#### 批处理脚本配置 +```batch +@echo off +pushd %~dp0 +call npm run build +call npm run start +popd +pause + +# 自定义运行参数 +node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert -l dev +``` + +**章节来源** +- [package.json:9-12](file://package.json#L9-L12) +- [run.bat:8](file://run.bat#L8) + +## 故障排除指南 + +### 常见问题诊断 + +#### HTTPS 证书问题 +- **问题**: 证书文件不存在或权限不足 +- **解决方案**: 确保证书文件路径正确,检查文件权限,验证证书格式 + +#### 端口占用问题 +- **问题**: 端口被其他进程占用 +- **解决方案**: 更换端口号,使用 `netstat` 检查端口占用情况 + +#### CORS 阻挡问题 +- **问题**: 跨域请求被浏览器阻止 +- **解决方案**: 检查 CORS 配置,确保 Origin 设置正确 + +#### 日志级别问题 +- **问题**: 日志输出过多或过少 +- **解决方案**: 调整日志级别配置,根据环境选择合适的日志格式 + +### 调试技巧 + +#### 启用详细日志 +```javascript +// 在开发环境中使用详细日志 +node ./build/index.js -l dev +``` + +#### 检查服务器状态 +```javascript +// 查看服务器配置 +curl http://localhost:8080/config +``` + +#### 测试 API 接口 +```javascript +// 测试信令接口 +curl -X GET http://localhost:8080/signaling +``` + +**章节来源** +- [src/index.ts:28](file://src/index.ts#L28) +- [src/server.ts:25](file://src/server.ts#L25) + +## 结论 + +本 HTTP 服务器配置模块提供了完整的 WebRTC 信令服务器解决方案,具有以下特点: + +### 核心优势 +- **模块化设计**: 清晰的组件分离便于维护和扩展 +- **灵活配置**: 支持多种运行模式和配置选项 +- **完整功能**: 集成 HTTP、HTTPS、WebSocket、文件上传等多种功能 +- **易于部署**: 提供构建脚本和批处理文件简化部署流程 + +### 技术特色 +- **双协议支持**: 同时支持 HTTP Polling 和 WebSocket 两种信令方式 +- **会话管理**: 完善的会话生命周期管理和超时处理机制 +- **安全考虑**: HTTPS 加密传输和 CORS 跨域配置 +- **可观测性**: 详细的日志记录和 Swagger API 文档 + +### 适用场景 +该服务器适用于视频会议、在线教育、远程协作等需要实时音视频通信的应用场景。通过合理的配置和优化,可以满足不同规模和性能要求的部署需求。 \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/服务器核心/WebSocket 服务器.md b/.qoder/repowiki/zh/content/服务器核心/WebSocket 服务器.md new file mode 100644 index 0000000..0020689 --- /dev/null +++ b/.qoder/repowiki/zh/content/服务器核心/WebSocket 服务器.md @@ -0,0 +1,305 @@ +# WebSocket 服务器 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [src/log.ts](file://src/log.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [client/src/signaling.js](file://client/src/signaling.js) +- [package.json](file://package.json) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本技术文档围绕 WebSocket 服务器模块进行深入解析,重点覆盖以下方面: +- WebSocket 服务器的创建与配置(含 HTTPS/WSS、日志与启动流程) +- 连接生命周期管理(连接建立、断开、广播、消息路由) +- WSSignaling 类的职责与实现要点(事件监听、消息分发、心跳与超时) +- WebSocketHandler 的核心能力(连接组模型、1对多/多对多路由、广播机制) +- 连接调试与监控方法(日志级别、Swagger 文档、心跳检测) +- 连接池管理、心跳检测与断线重连策略 + +## 项目结构 +后端采用 Express 提供 HTTP/HTTPS 服务与静态资源,WebSocket 信令在独立模块中运行;同时保留 HTTP 轮询信令作为备选方案。 + +```mermaid +graph TB +subgraph "服务端进程" +A["Express 应用
src/server.ts"] +B["HTTP(S) 服务器
src/index.ts"] +C["WebSocket 信令服务器
src/websocket.ts"] +D["WebSocket 处理器
src/class/websockethandler.ts"] +E["HTTP 信令路由
src/signaling.ts"] +F["Swagger 文档
src/swagger.ts"] +end +subgraph "客户端" +G["浏览器/前端页面
client/public"] +H["信令客户端封装
client/src/signaling.js"] +end +H --> |"WebSocket"| C +H --> |"HTTP 轮询"| E +B --> |"创建/绑定"| C +A --> |"挂载路由/静态资源"| G +A --> |"注册 HTTP 信令路由"| E +A --> |"初始化 Swagger"| F +``` + +图表来源 +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:14-42](file://src/server.ts#L14-L42) +- [src/websocket.ts:15-116](file://src/websocket.ts#L15-L116) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 核心组件 +- WSSignaling:WebSocket 信令服务器入口,负责监听连接、消息解析与分发至 WebSocketHandler。 +- WebSocketHandler:核心业务逻辑,维护连接组、1对多/多对多路由、广播、心跳与断线清理。 +- 数据模型:Offer、Answer、Candidate 用于封装信令数据。 +- 日志系统:统一日志级别控制与输出格式。 +- HTTP 信令路由:保留 HTTP 轮询信令通道,便于兼容与调试。 +- Swagger 文档:自动生成 API 文档,支持会话认证与信令接口说明。 + +章节来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 架构总览 +WebSocket 服务器在 HTTP(S) 之上提供实时信令通道,客户端可通过 WebSocket 或 HTTP 轮询两种方式接入。WebSocket 信令以“连接组”为核心组织多端通信,支持广播与定向转发。 + +```mermaid +sequenceDiagram +participant Client as "客户端
client/src/signaling.js" +participant WS as "WSSignaling
src/websocket.ts" +participant Handler as "WebSocketHandler
src/class/websockethandler.ts" +Client->>WS : "WebSocket 连接建立" +WS->>Handler : "add(ws)" +WS-->>Client : "connect 消息role/polite/participantId" +Client->>WS : "发送信令消息offer/answer/candidate/on-message/broadcast" +WS->>Handler : "根据消息类型分发" +Handler-->>Client : "转发/广播结果含 participantId" +Client-->>WS : "断开连接/心跳 ping/pong" +WS->>Handler : "remove(ws)/心跳检测" +``` + +图表来源 +- [src/websocket.ts:27-116](file://src/websocket.ts#L27-L116) +- [src/class/websockethandler.ts:72-137](file://src/class/websockethandler.ts#L72-L137) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) + +## 详细组件分析 + +### WSSignaling 类 +职责与行为: +- 接收 HTTP 服务器实例,创建 ws.Server 并绑定到同一端口。 +- 注册 connection 事件:为新连接调用处理器 add,并在关闭时 remove。 +- 注册 message 事件:解析 JSON 消息,按 type 分发到对应处理器(connect/disconnect/offer/answer/candidate/ping/pong/broadcast/on-message/call-request)。 +- 内置 ping/pong 心跳:收到 ping 回复 pong,收到 pong 更新 lastActivity;注释掉的心跳定时器可在需要时启用。 + +实现要点: +- 模式切换:通过 reset(mode) 设置 isPrivate,影响连接组与路由行为。 +- 消息格式:支持 participantId 字段以区分多参与者场景。 +- 日志:统一通过 log 输出接收到的消息与关键事件。 + +章节来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:63-66](file://src/class/websockethandler.ts#L63-L66) + +### WebSocketHandler 核心功能 +- 连接组模型:Map,每个连接组包含 host 与多个 participants。 +- 连接管理:add/remove 维护 clients 映射与连接组;onConnect/onDisconnect 实现 1对多/多对多角色分配。 +- 信令路由: + - offer/answer/candidate:host 与 participants 之间双向转发;私有模式下支持按 participantId 精确路由。 + - broadcast:支持全局广播与按连接组广播。 + - on-message:支持聊天消息等扩展消息的组内转发。 +- 心跳与超时:提供心跳初始化/清理函数,注释掉的定时器默认不启用;可按需开启。 +- 辅助能力:获取所有连接组 ID、判断是否 host、组内广播工具。 + +```mermaid +classDiagram +class WSSignaling { ++server ++wss ++constructor(server, mode) +} +class WebSocketHandler { ++reset(mode) ++add(ws) ++remove(ws) ++onConnect(ws, connectionId) ++onDisconnect(ws, connectionId) ++onOffer(ws, message) ++onAnswer(ws, message) ++onCandidate(ws, message) ++onBroadcast(ws, message) ++onMessage(ws, message) ++AddHeartbeat(ws, connectionId) ++RemoveHeartbeat(ws) ++isHost(ws, connectionId) ++broadcastToGroup(connectionId, senderWs, message) +} +WSSignaling --> WebSocketHandler : "消息分发" +``` + +图表来源 +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:63-479](file://src/class/websockethandler.ts#L63-L479) + +章节来源 +- [src/class/websockethandler.ts:139-206](file://src/class/websockethandler.ts#L139-L206) +- [src/class/websockethandler.ts:208-338](file://src/class/websockethandler.ts#L208-L338) +- [src/class/websockethandler.ts:340-402](file://src/class/websockethandler.ts#L340-L402) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +### 数据模型 +- Offer:封装 SDP、时间戳与是否“polite”标记。 +- Answer:封装 SDP 与时间戳。 +- Candidate:封装 ICE 候选项与 SDP 元信息及时间戳。 + +章节来源 +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +### 启动与配置 +- 命令行参数:端口、HTTPS 开关与证书、信令类型(websocket/http)、通信模式(public/private)、日志级别。 +- HTTPS:当 secure=true 时使用 server.key/server.cert 创建 https.Server。 +- 信令类型:type=websocket 时启动 WSSignaling;否则提示并回退为 websocket。 +- 日志:统一通过 log 模块输出启动信息与运行日志。 + +章节来源 +- [src/index.ts:14-109](file://src/index.ts#L14-L109) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +### HTTP 信令与 Swagger +- HTTP 信令路由:提供会话管理与信令 CRUD 接口,支持会话认证(session-id)。 +- Swagger:自动扫描 httphandler.ts 与 signaling.ts,生成 API 文档,支持会话认证与信令接口说明。 + +章节来源 +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1112-1130](file://src/class/httphandler.ts#L1112-L1130) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 依赖关系分析 +- 运行时依赖:ws(WebSocket)、express(HTTP)、morgan(日志)、swagger-ui-express(文档)、uuid(会话 ID)。 +- 构建与测试:TypeScript、Jest、ESLint、ts-jest 等。 +- 客户端:浏览器通过 WebSocket 或 HTTP 轮询与服务端交互。 + +```mermaid +graph LR +P["package.json 依赖声明"] --> WS["ws"] +P --> EXP["express"] +P --> MORGAN["morgan"] +P --> SWG["swagger-ui-express"] +P --> UUID["uuid"] +IDX["src/index.ts"] --> SRV["src/server.ts"] +IDX --> WSC["src/websocket.ts"] +WSC --> WSH["src/class/websockethandler.ts"] +SRV --> SIG["src/signaling.ts"] +SRV --> SWG +``` + +图表来源 +- [package.json:14-46](file://package.json#L14-L46) +- [src/index.ts:7-11](file://src/index.ts#L7-L11) +- [src/server.ts:5-9](file://src/server.ts#L5-L9) +- [src/websocket.ts:1-4](file://src/websocket.ts#L1-L4) +- [src/class/websockethandler.ts:5-8](file://src/class/websockethandler.ts#L5-L8) +- [src/signaling.ts:1-3](file://src/signaling.ts#L1-L3) +- [src/swagger.ts:5-9](file://src/swagger.ts#L5-L9) + +章节来源 +- [package.json:14-60](file://package.json#L14-L60) +- [src/index.ts:7-11](file://src/index.ts#L7-L11) +- [src/server.ts:5-9](file://src/server.ts#L5-L9) +- [src/websocket.ts:1-4](file://src/websocket.ts#L1-L4) +- [src/class/websockethandler.ts:5-8](file://src/class/websockethandler.ts#L5-L8) +- [src/signaling.ts:1-3](file://src/signaling.ts#L1-L3) +- [src/swagger.ts:5-9](file://src/swagger.ts#L5-L9) + +## 性能考量 +- 连接组规模:每组 participants 使用 Set 存储,查找与删除均为 O(1),适合中小规模并发。 +- 广播策略:组内广播遍历 participants,建议限制单组成员数量或引入分区策略。 +- 心跳检测:当前注释掉定时器,避免不必要的 CPU 占用;如启用需合理设置周期与超时阈值。 +- 日志级别:生产环境建议提升日志级别,减少高频日志输出对性能的影响。 +- HTTPS 证书:WSS 需要正确配置证书与密钥,确保握手与传输安全。 + +## 故障排查指南 +- 启动与网络 + - 确认端口占用与防火墙放行。 + - HTTPS 模式检查 server.key 与 server.cert 是否存在且可读。 +- WebSocket 连接 + - 检查客户端 WebSocket URL(ws/wss)与主机一致。 + - 观察浏览器开发者工具 Network 面板中的 WebSocket 握手与帧。 +- 信令消息 + - 使用 Swagger 文档校验 HTTP 信令接口(PUT/GET/POST/DELETE)参数与会话 ID。 + - 对照客户端 signaling.js 的消息构造与事件派发。 +- 日志定位 + - 调整日志级别(info/warn/error/log),关注连接建立、断开、广播与路由关键节点。 +- 自动化测试 + - 参考单元测试用例,验证 public/private 模式下的连接、offer/answer/candidate 转发与广播行为。 + +章节来源 +- [src/index.ts:55-88](file://src/index.ts#L55-L88) +- [src/log.ts:11-51](file://src/log.ts#L11-L51) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [test/websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +## 结论 +该 WebSocket 服务器模块以简洁清晰的职责划分实现了 WebRTC 信令的核心需求:支持 WebSocket 与 HTTP 两种信令通道、基于连接组的 1对多/多对多路由、广播与定向转发、以及可扩展的消息类型。通过日志与 Swagger 的配合,具备良好的可观测性与可维护性。建议在高并发场景下进一步优化广播与心跳策略,并完善断线重连与幂等处理。 + +## 附录 + +### WebSocket 连接调试与监控 +- 浏览器开发者工具:Network 面板查看握手、帧与错误。 +- Swagger 文档:访问 /api-docs,使用 session-id 头部进行认证,查看 HTTP 信令接口。 +- 日志:通过命令行参数设置日志级别,观察连接、断开、广播与路由事件。 +- 心跳:若启用心跳定时器,注意 ping/pong 的周期与超时阈值配置。 + +章节来源 +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/log.ts:11-51](file://src/log.ts#L11-L51) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +### 连接池管理、心跳检测与断线重连 +- 连接池管理:clients 与 connectionGroup 维护连接映射与组关系,remove 时清理并广播断开事件。 +- 心跳检测:提供 AddHeartbeat/RemoveHeartbeat,当前代码注释掉定时器;可按需启用。 +- 断线重连:客户端应实现指数退避与自动重连逻辑;服务端在断开时通知组内成员并清理资源。 + +章节来源 +- [src/class/websockethandler.ts:115-137](file://src/class/websockethandler.ts#L115-L137) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [client/src/signaling.js:230-241](file://client/src/signaling.js#L230-L241) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/服务器核心/信令路由系统.md b/.qoder/repowiki/zh/content/服务器核心/信令路由系统.md new file mode 100644 index 0000000..929e666 --- /dev/null +++ b/.qoder/repowiki/zh/content/服务器核心/信令路由系统.md @@ -0,0 +1,403 @@ +# 信令路由系统 + + +**本文档引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/offer.ts](file://src/class/offer.ts) +- [src/class/answer.ts](file://src/class/answer.ts) +- [src/class/candidate.ts](file://src/class/candidate.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [src/log.ts](file://src/log.ts) +- [test/websockethandler.test.ts](file://test/websockethandler.test.ts) +- [test/httphandler.test.ts](file://test/httphandler.test.ts) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构概览](#架构概览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件为信令路由系统模块的技术文档,聚焦于 WebSocket 与 HTTP 轮询两种信令模式的实现机制与运行流程。文档详细说明了 HTTP 轮询信令处理器的会话管理、消息缓存与轮询接口设计;解释了信令消息的路由规则、负载均衡与错误恢复机制;并提供不同信令模式的选择指南与性能对比,以及路由配置优化与扩展开发的最佳实践。 + +## 项目结构 +系统采用分层架构,主要由入口启动、HTTP 服务器、WebSocket 信令、HTTP 轮询处理器、数据模型与日志等模块组成。Express 提供 HTTP 服务与路由,WebSocket 作为实时信令通道,HTTP 轮询提供兼容性支持。Swagger 文档自动生成 API 接口说明,便于调试与集成。 + +```mermaid +graph TB +A["入口启动
src/index.ts"] --> B["HTTP服务器
src/server.ts"] +B --> C["信令路由
src/signaling.ts"] +C --> D["WebSocket处理器
src/class/websockethandler.ts"] +C --> E["HTTP轮询处理器
src/class/httphandler.ts"] +D --> F["WebSocket信令服务
src/websocket.ts"] +E --> G["数据模型
Offer/Answer/Candidate"] +B --> H["Swagger文档
src/swagger.ts"] +B --> I["日志系统
src/log.ts"] +``` + +**图表来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 核心组件 +- 信令路由模块:统一暴露 HTTP 轮询接口,包含会话管理、连接管理、信令消息收发与轮询查询。 +- WebSocket 信令模块:基于 ws 库,负责实时双向通信、连接组管理与消息广播。 +- 数据模型:Offer、Answer、Candidate 三类信令消息的数据结构封装。 +- 日志与配置:统一日志级别控制与启动参数解析。 +- Swagger 文档:自动生成 API 文档,支持会话认证头 session-id。 + +**章节来源** +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/class/websockethandler.ts:44-168](file://src/class/websockethandler.ts#L44-L168) +- [src/class/httphandler.ts:91-213](file://src/class/httphandler.ts#L91-L213) +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) +- [src/swagger.ts:44-57](file://src/swagger.ts#L44-L57) + +## 架构概览 +系统支持两种信令模式: +- WebSocket 模式:长连接、低延迟、高吞吐,适合实时互动场景。 +- HTTP 轮询模式:基于 RESTful 接口,客户端周期性轮询获取信令,适配受限网络环境。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant HTTP as "HTTP服务器
src/server.ts" +participant Router as "信令路由
src/signaling.ts" +participant WS as "WebSocket处理器
src/class/websockethandler.ts" +participant WS_Srv as "WebSocket服务
src/websocket.ts" +participant HTTP_Handler as "HTTP处理器
src/class/httphandler.ts" +rect rgb(255,255,255) +Note over Client,HTTP : WebSocket模式 +Client->>WS_Srv : 建立WebSocket连接 +WS_Srv->>WS : 分发消息类型 +WS-->>Client : 实时转发信令 +end +rect rgb(255,255,255) +Note over Client,HTTP : HTTP轮询模式 +Client->>HTTP : 发送session-id头 +HTTP->>Router : 路由到signaling +Router->>HTTP_Handler : 会话校验与处理 +HTTP_Handler-->>Client : 返回信令消息列表 +end +``` + +**图表来源** +- [src/websocket.ts:27-116](file://src/websocket.ts#L27-L116) +- [src/class/websockethandler.ts:72-206](file://src/class/websockethandler.ts#L72-L206) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/signaling.ts:6-23](file://src/signaling.ts#L6-L23) + +## 详细组件分析 + +### WebSocket 信令处理器 +WebSocket 处理器负责连接生命周期管理、消息路由与广播。核心能力包括: +- 连接组管理:host 与 participants 的角色区分与消息定向转发。 +- 心跳检测:定期 ping/pong 维持连接活性,超时自动断开。 +- 广播与定向路由:根据消息类型与角色进行组内广播或跨角色转发。 +- 会话与连接映射:维护客户端到连接 ID 的映射,支持多连接场景。 + +```mermaid +classDiagram +class WebSocket处理器 { ++reset(mode) void ++add(ws) void ++remove(ws) void ++onConnect(ws, connectionId) void ++onDisconnect(ws, connectionId) void ++onOffer(ws, message) void ++onAnswer(ws, message) void ++onCandidate(ws, message) void ++onBroadcast(ws, message) void ++onMessage(ws, message) void ++AddHeartbeat(ws, connectionId) void ++RemoveHeartbeat(ws) void ++onGetAllConnectionIds() string[] ++isHost(ws, connectionId) bool ++broadcastToGroup(connectionId, senderWs, message) void +} +class 连接组 { ++host WebSocket ++participants Set~WebSocket~ +} +WebSocket处理器 --> 连接组 : "管理" +``` + +**图表来源** +- [src/class/websockethandler.ts:13-37](file://src/class/websockethandler.ts#L13-L37) +- [src/class/websockethandler.ts:44-168](file://src/class/websockethandler.ts#L44-L168) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +**章节来源** +- [src/class/websockethandler.ts:44-168](file://src/class/websockethandler.ts#L44-L168) +- [src/class/websockethandler.ts:208-338](file://src/class/websockethandler.ts#L208-L338) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +### HTTP 轮询信令处理器 +HTTP 轮询处理器以 RESTful API 形式提供信令服务,核心功能包括: +- 会话管理:创建、删除会话,维护会话存活时间戳。 +- 连接管理:创建、删除连接,支持私有模式下的连接对映射。 +- 消息缓存:Offer、Answer、Candidate 按会话与连接 ID 缓存,支持 fromtime 过滤。 +- 断开连接记录:记录断开连接的连接 ID 与时间戳,供轮询查询。 +- 轮询接口:提供 /signaling 与子资源的 GET/POST/PUT/DELETE 接口,统一通过 session-id 头进行鉴权。 + +```mermaid +flowchart TD +Start(["HTTP请求进入"]) --> CheckSession["检查session-id头"] +CheckSession --> SessionExists{"会话存在?"} +SessionExists --> |否| Return404["返回404"] +SessionExists --> |是| UpdateLast["更新最后请求时间"] +UpdateLast --> Route["路由到具体处理器"] +Route --> CreateSession["创建会话"] +Route --> DeleteSession["删除会话"] +Route --> CreateConnection["创建连接"] +Route --> DeleteConnection["删除连接"] +Route --> PostOffer["提交Offer"] +Route --> PostAnswer["提交Answer"] +Route --> PostCandidate["提交Candidate"] +Route --> GetOffer["获取Offer列表"] +Route --> GetAnswer["获取Answer列表"] +Route --> GetCandidate["获取Candidate列表"] +Route --> GetAll["获取所有信令消息"] +CreateSession --> ReturnOK["返回JSON"] +DeleteSession --> ReturnOK +CreateConnection --> ReturnOK +DeleteConnection --> ReturnOK +PostOffer --> ReturnOK +PostAnswer --> ReturnOK +PostCandidate --> ReturnOK +GetOffer --> ReturnJSON["返回JSON"] +GetAnswer --> ReturnJSON +GetCandidate --> ReturnJSON +GetAll --> ReturnJSON +Return404 --> End(["结束"]) +ReturnOK --> End +ReturnJSON --> End +``` + +**图表来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:664-675](file://src/class/httphandler.ts#L664-L675) +- [src/class/httphandler.ts:689-696](file://src/class/httphandler.ts#L689-L696) +- [src/class/httphandler.ts:739-783](file://src/class/httphandler.ts#L739-L783) +- [src/class/httphandler.ts:815-828](file://src/class/httphandler.ts#L815-L828) +- [src/class/httphandler.ts:855-886](file://src/class/httphandler.ts#L855-L886) +- [src/class/httphandler.ts:913-952](file://src/class/httphandler.ts#L913-L952) +- [src/class/httphandler.ts:985-998](file://src/class/httphandler.ts#L985-L998) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) + +**章节来源** +- [src/class/httphandler.ts:91-120](file://src/class/httphandler.ts#L91-L120) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/httphandler.ts:274-297](file://src/class/httphandler.ts#L274-L297) +- [src/class/httphandler.ts:398-407](file://src/class/httphandler.ts#L398-L407) +- [src/class/httphandler.ts:440-447](file://src/class/httphandler.ts#L440-L447) +- [src/class/httphandler.ts:492-501](file://src/class/httphandler.ts#L492-L501) +- [src/class/httphandler.ts:549-558](file://src/class/httphandler.ts#L549-L558) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) +- [src/class/httphandler.ts:739-783](file://src/class/httphandler.ts#L739-L783) +- [src/class/httphandler.ts:815-828](file://src/class/httphandler.ts#L815-L828) +- [src/class/httphandler.ts:855-886](file://src/class/httphandler.ts#L855-L886) +- [src/class/httphandler.ts:913-952](file://src/class/httphandler.ts#L913-L952) +- [src/class/httphandler.ts:985-998](file://src/class/httphandler.ts#L985-L998) + +### 信令消息模型 +Offer、Answer、Candidate 三类消息分别封装 SDP 描述、ICE 候选者信息与时间戳,确保消息缓存与查询的一致性。 + +```mermaid +classDiagram +class Offer { ++string sdp ++number datetime ++boolean polite ++constructor(sdp, datetime, polite) +} +class Answer { ++string sdp ++number datetime ++constructor(sdp, datetime) +} +class Candidate { ++string candidate ++number sdpMLineIndex ++string sdpMid ++number datetime ++constructor(candidate, sdpMLineIndex, sdpMid, datetime) +} +``` + +**图表来源** +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +**章节来源** +- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11) +- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8) +- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12) + +### WebSocket 信令服务 +WebSocket 服务监听连接事件,解析消息类型并调用处理器对应方法,支持 ping/pong 心跳与广播消息。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant WS_Srv as "WebSocket服务
src/websocket.ts" +participant Handler as "WebSocket处理器
src/class/websockethandler.ts" +Client->>WS_Srv : 建立连接 +WS_Srv->>Handler : add(ws) +Client->>WS_Srv : 发送消息 +WS_Srv->>Handler : 根据type分发 +Handler-->>Client : 转发信令或广播 +Client-->>WS_Srv : 关闭连接 +WS_Srv->>Handler : remove(ws) +``` + +**图表来源** +- [src/websocket.ts:27-116](file://src/websocket.ts#L27-L116) +- [src/class/websockethandler.ts:72-137](file://src/class/websockethandler.ts#L72-L137) + +**章节来源** +- [src/websocket.ts:27-116](file://src/websocket.ts#L27-L116) +- [src/class/websockethandler.ts:72-137](file://src/class/websockethandler.ts#L72-L137) + +## 依赖关系分析 +- 入口模块负责解析命令行参数、创建 HTTP 服务器与选择信令模式。 +- 服务器模块装配 CORS、静态资源、Swagger 文档与 /signaling 路由。 +- 信令路由模块挂载到 /signaling,内部通过中间件校验 session-id。 +- WebSocket 与 HTTP 处理器共享通信模式配置,保证行为一致性。 + +```mermaid +graph TB +Index["入口
src/index.ts"] --> Server["服务器
src/server.ts"] +Server --> Signaling["信令路由
src/signaling.ts"] +Signaling --> WS_Handler["WebSocket处理器
src/class/websockethandler.ts"] +Signaling --> HTTP_Handler["HTTP处理器
src/class/httphandler.ts"] +Server --> Swagger["Swagger
src/swagger.ts"] +Server --> Log["日志
src/log.ts"] +``` + +**图表来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:16-41](file://src/server.ts#L16-L41) +- [src/signaling.ts:10-23](file://src/signaling.ts#L10-L23) + +**章节来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:16-41](file://src/server.ts#L16-L41) +- [src/signaling.ts:10-23](file://src/signaling.ts#L10-L23) + +## 性能考虑 +- WebSocket 模式 + - 优势:全双工、低延迟、减少 HTTP 开销;心跳机制保障连接活性。 + - 注意:连接数增长带来内存与 CPU 压力,需结合负载均衡与连接池策略。 +- HTTP 轮询模式 + - 优势:兼容性好、易于部署;适合弱网或受限环境。 + - 注意:频繁轮询增加服务器压力,建议合理设置轮询间隔与缓存策略。 +- 会话超时清理 + - HTTP 轮询处理器按最后请求时间清理超时会话,避免内存泄漏。 +- 负载均衡与扩展 + - 建议使用反向代理与多实例部署,结合会话亲和或共享存储实现横向扩展。 + +[本节为通用性能指导,无需列出具体文件来源] + +## 故障排查指南 +- 会话不存在 + - 症状:HTTP 轮询返回 404。 + - 排查:确认请求头包含有效的 session-id;检查会话是否被超时清理。 + - 参考:[src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- 连接 ID 冲突(私有模式) + - 症状:创建连接时报错“连接ID已被使用”。 + - 排查:检查连接对映射,确保同一 connectionId 仅在两个会话间配对。 + - 参考:[src/class/httphandler.ts:754-776](file://src/class/httphandler.ts#L754-L776) +- 信令消息缺失 + - 症状:轮询未返回预期消息。 + - 排查:确认 fromtime 参数与消息时间戳;检查是否被超时清理。 + - 参考:[src/class/httphandler.ts:274-297](file://src/class/httphandler.ts#L274-L297) +- WebSocket 连接异常断开 + - 症状:心跳超时触发断开。 + - 排查:检查客户端 ping/pong 交互;确认网络稳定性。 + - 参考:[src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- Swagger 文档不可用 + - 症状:/api-docs 无法访问。 + - 排查:确认 Swagger 初始化与服务器地址配置正确。 + - 参考:[src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +**章节来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:754-776](file://src/class/httphandler.ts#L754-L776) +- [src/class/httphandler.ts:274-297](file://src/class/httphandler.ts#L274-L297) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 结论 +本信令路由系统通过 WebSocket 与 HTTP 轮询两种模式满足不同网络与部署需求。WebSocket 提供低延迟实时通信,HTTP 轮询提供兼容性与易用性。HTTP 处理器通过会话与连接映射、消息缓存与超时清理机制,确保在高并发场景下的稳定性。建议根据业务场景选择合适模式,并结合负载均衡与监控体系持续优化。 + +[本节为总结性内容,无需列出具体文件来源] + +## 附录 + +### 信令模式选择指南 +- 优先选择 WebSocket:实时性要求高、网络稳定、客户端支持良好。 +- 选择 HTTP 轮询:受限网络、防火墙严格、客户端仅支持 HTTP/HTTPS。 +- 混合策略:在 WebSocket 不可用时回退至 HTTP 轮询,提升兼容性。 + +[本节为通用指导,无需列出具体文件来源] + +### 配置与启动 +- 启动参数 + - -p/--port:监听端口 + - -s/--secure:启用 HTTPS + - -k/--keyfile/-c/--certfile:证书与密钥文件 + - -t/--type:信令类型(websocket 或 http) + - -m/--mode:通信模式(public 或 private) + - -l/--logging:HTTP 日志格式 +- 示例脚本 + - 开发模式:dev + - 生产模式:start + +**章节来源** +- [src/index.ts:20-40](file://src/index.ts#L20-L40) +- [package.json:5-12](file://package.json#L5-L12) + +### API 文档与安全 +- Swagger 文档:/api-docs +- 认证方式:session-id 头部 +- 会话超时:HTTP 轮询处理器内置超时清理逻辑 + +**章节来源** +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) + +### 测试参考 +- WebSocket 单元测试:覆盖公共与私有模式下的连接、消息路由与断开流程。 +- HTTP 单元测试:覆盖会话创建/删除、连接管理、消息提交与轮询查询、超时清理等场景。 + +**章节来源** +- [test/websockethandler.test.ts:9-191](file://test/websockethandler.test.ts#L9-L191) +- [test/httphandler.test.ts:6-510](file://test/httphandler.test.ts#L6-L510) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/服务器核心/日志系统.md b/.qoder/repowiki/zh/content/服务器核心/日志系统.md new file mode 100644 index 0000000..463089d --- /dev/null +++ b/.qoder/repowiki/zh/content/服务器核心/日志系统.md @@ -0,0 +1,332 @@ +# 日志系统 + + +**本文引用的文件** +- [src/log.ts](file://src/log.ts) +- [src/index.ts](file://src/index.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [client/src/logger.js](file://client/src/logger.js) +- [package.json](file://package.json) +- [src/class/options.ts](file://src/class/options.ts) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本技术文档聚焦于视频服务器项目的日志系统模块,系统性阐述日志级别管理、输出格式控制与日志轮转机制现状,解释 LogLevel 枚举的使用场景与差异,说明当前日志输出目标(控制台为主),并给出性能优化建议、调试技巧以及与日志分析与监控告警的集成思路。由于仓库中未发现文件落盘或远程日志服务的直接实现,本文在“输出目标”部分明确指出当前限制,并在“附录”中提供可扩展的实践建议。 + +## 项目结构 +日志系统主要由以下部分组成: +- 服务端日志模块:定义日志级别、格式化与输出逻辑 +- 应用入口:解析命令行参数并调用日志模块进行启动信息输出 +- 业务处理器:在 HTTP 与 WebSocket 层面使用统一日志模块进行运行态事件记录 +- 客户端日志模块:提供简易调试开关与条件输出(与服务端日志模块独立) + +```mermaid +graph TB +A["应用入口
src/index.ts"] --> B["日志模块
src/log.ts"] +C["HTTP处理器
src/class/httphandler.ts"] --> B +D["WebSocket处理器
src/class/websockethandler.ts"] --> B +E["客户端日志模块
client/src/logger.js"] -. 独立调试 .-> B +``` + +图表来源 +- [src/index.ts:11-91](file://src/index.ts#L11-L91) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/httphandler.ts:11](file://src/class/httphandler.ts#L11) +- [src/class/websockethandler.ts:8](file://src/class/websockethandler.ts#L8) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +章节来源 +- [src/index.ts:11-91](file://src/index.ts#L11-L91) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/httphandler.ts:11](file://src/class/httphandler.ts#L11) +- [src/class/websockethandler.ts:8](file://src/class/websockethandler.ts#L8) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +## 核心组件 +- 日志级别枚举与控制 + - LogLevel 枚举定义了 none、error、warn、log、info 五个级别,数值越大级别越高 + - 提供 setLogLevel 与 parseLogLevel,支持从字符串解析并动态调整全局日志级别 +- 日志输出与格式 + - 输出统一走浏览器/Node 控制台接口,自动添加 ISO 时间戳与大写的级别前缀 + - 不同级别对应不同的控制台输出方法,便于在浏览器开发者工具中区分 +- 使用分布 + - 应用入口在启动阶段输出网络地址、协议类型与模式等关键信息 + - HTTP 与 WebSocket 处理器在连接建立、断开、超时、信令交互等关键节点输出运行态日志 + +章节来源 +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/index.ts:63-90](file://src/index.ts#L63-L90) +- [src/class/httphandler.ts:230](file://src/class/httphandler.ts#L230) +- [src/class/websockethandler.ts:77](file://src/class/websockethandler.ts#L77) + +## 架构总览 +日志模块在服务端采用集中式设计:统一的枚举与格式化逻辑,配合各业务模块按需调用。整体调用链路如下: + +```mermaid +sequenceDiagram +participant CLI as "命令行/环境变量" +participant App as "应用入口
src/index.ts" +participant Log as "日志模块
src/log.ts" +participant HTTP as "HTTP处理器
src/class/httphandler.ts" +participant WS as "WebSocket处理器
src/class/websockethandler.ts" +CLI->>App : 解析参数(-l/-m/-t等) +App->>Log : 输出启动信息(info/warn) +HTTP->>Log : 关键事件(log/info/warn/error) +WS->>Log : 关键事件(log/info/warn/error) +Log-->>Console : 控制台输出(带时间戳与级别) +``` + +图表来源 +- [src/index.ts:63-90](file://src/index.ts#L63-L90) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [src/class/httphandler.ts:230](file://src/class/httphandler.ts#L230) +- [src/class/websockethandler.ts:413](file://src/class/websockethandler.ts#L413) + +## 详细组件分析 + +### 日志模块(src/log.ts) +- 设计要点 + - 通过全局变量维护当前日志级别,决定是否输出 + - parseLogLevel 支持从字符串解析级别,默认回退到 info + - formatTimestamp 统一 ISO 时间戳格式 + - 根据级别选择控制台输出方法,便于区分错误、警告、信息与普通日志 +- 性能与行为 + - 条件判断在输出前完成,避免不必要的字符串拼接 + - 控制台输出为同步 I/O,适合开发与调试;生产环境建议结合外部日志采集 + +```mermaid +flowchart TD +Start(["进入 log(level, ...args)"]) --> Check["比较 level 与当前级别
与 none 特判"] +Check --> |不满足| Exit["直接返回"] +Check --> |满足| Stamp["生成 ISO 时间戳"] +Stamp --> Prefix["构造前缀: [时间戳] [级别]"] +Prefix --> Switch{"根据级别选择输出方法"} +Switch --> |error| OutErr["console.error(...)"] +Switch --> |warn| OutWarn["console.warn(...)"] +Switch --> |info| OutInfo["console.info(...)"] +Switch --> |其他| OutLog["console.log(...)"] +OutErr --> End(["结束"]) +OutWarn --> End +OutInfo --> End +OutLog --> End +``` + +图表来源 +- [src/log.ts:30-50](file://src/log.ts#L30-L50) + +章节来源 +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +### 应用入口与日志使用(src/index.ts) +- 启动阶段日志 + - 输出 HTTP/HTTPS 地址、信令协议类型、模式等关键信息 + - 对不支持的信令类型发出警告并回退到默认值 +- 参数来源 + - 通过 commander 解析命令行参数,同时兼容环境变量 + - logging 参数与 morgan 中间件相关(见“依赖关系分析”) + +```mermaid +sequenceDiagram +participant Entrypoint as "应用入口
src/index.ts" +participant Log as "日志模块
src/log.ts" +Entrypoint->>Log : info("启动地址/模式/协议等") +Entrypoint->>Log : warn("不支持的信令类型/回退提示") +``` + +图表来源 +- [src/index.ts:63-90](file://src/index.ts#L63-L90) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) + +章节来源 +- [src/index.ts:11-91](file://src/index.ts#L11-L91) + +### HTTP 处理器中的日志(src/class/httphandler.ts) +- 关键节点日志 + - 会话超时清理:输出被删除的会话 ID + - 连接/断开/参与者加入/离开等事件:输出连接 ID 与上下文信息 + - 错误场景:对非法参数或冲突状态输出警告 +- 日志级别选择 + - 一般事件使用 log 或 info + - 异常或异常流程使用 warn + +```mermaid +sequenceDiagram +participant HTTP as "HTTP处理器
src/class/httphandler.ts" +participant Log as "日志模块
src/log.ts" +HTTP->>Log : log("超时删除会话 sessionId=...") +HTTP->>Log : warn("连接ID已使用/参数缺失等") +HTTP->>Log : log("参与者加入/离开/断开连接等") +``` + +图表来源 +- [src/class/httphandler.ts:230](file://src/class/httphandler.ts#L230) +- [src/class/httphandler.ts:761](file://src/class/httphandler.ts#L761) + +章节来源 +- [src/class/httphandler.ts:230](file://src/class/httphandler.ts#L230) +- [src/class/httphandler.ts:761](file://src/class/httphandler.ts#L761) + +### WebSocket 处理器中的日志(src/class/websockethandler.ts) +- 关键节点日志 + - 连接建立/断开、参与者加入/离开、主机断开、心跳超时等 + - 心跳定时器:周期性输出心跳状态与超时检测 +- 日志级别选择 + - 事件性信息使用 log + - 超时与异常使用 warn + +```mermaid +sequenceDiagram +participant WS as "WebSocket处理器
src/class/websockethandler.ts" +participant Log as "日志模块
src/log.ts" +WS->>Log : log("Add WebSocket/参与者加入/断开连接等") +WS->>Log : warn("WebSocket连接超时, closing...") +WS->>Log : log("WebSocket连接心跳 lastActivity=...") +``` + +图表来源 +- [src/class/websockethandler.ts:77](file://src/class/websockethandler.ts#L77) +- [src/class/websockethandler.ts:154](file://src/class/websockethandler.ts#L154) +- [src/class/websockethandler.ts:192](file://src/class/websockethandler.ts#L192) +- [src/class/websockethandler.ts:413](file://src/class/websockethandler.ts#L413) +- [src/class/websockethandler.ts:420](file://src/class/websockethandler.ts#L420) + +章节来源 +- [src/class/websockethandler.ts:77](file://src/class/websockethandler.ts#L77) +- [src/class/websockethandler.ts:154](file://src/class/websockethandler.ts#L154) +- [src/class/websockethandler.ts:192](file://src/class/websockethandler.ts#L192) +- [src/class/websockethandler.ts:413](file://src/class/websockethandler.ts#L413) +- [src/class/websockethandler.ts:420](file://src/class/websockethandler.ts#L420) + +### 客户端日志模块(client/src/logger.js) +- 功能概述 + - 提供启用/禁用调试开关 + - 在调试开启时输出 debug/info/log/warn/error 级别消息 +- 适用场景 + - 前端调试与本地开发,与服务端日志模块解耦 + +```mermaid +flowchart TD +Start(["调用 debug/info/log/warn/error"]) --> Check["检查 isDebug 标志"] +Check --> |false| Exit["直接返回"] +Check --> |true| Console["console.debug/info/log/warn/error(...)"] +Console --> End(["结束"]) +``` + +图表来源 +- [client/src/logger.js:11-29](file://client/src/logger.js#L11-L29) + +章节来源 +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +## 依赖关系分析 +- 日志模块依赖 + - 服务端日志模块仅依赖标准控制台输出,无第三方依赖 + - 客户端日志模块同样依赖浏览器/Node 控制台 +- HTTP 中间件与日志 + - 项目包含 morgan(HTTP 请求访问日志中间件),与自研日志模块互补 + - logging 参数影响 morgan 的日志格式(如 combined/dev/short/tiny 或 none) + +```mermaid +graph LR +Log["日志模块
src/log.ts"] --> Console["控制台输出"] +HTTP["HTTP中间件(morgan)"] --> Console +App["应用入口
src/index.ts"] --> Log +HTTPMod["HTTP处理器
src/class/httphandler.ts"] --> Log +WSMod["WebSocket处理器
src/class/websockethandler.ts"] --> Log +``` + +图表来源 +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [package.json:21](file://package.json#L21) +- [src/index.ts:28](file://src/index.ts#L28) + +章节来源 +- [package.json:14-27](file://package.json#L14-L27) +- [src/index.ts:28](file://src/index.ts#L28) + +## 性能考量 +- 当前实现特点 + - 控制台输出为同步 I/O,简单可靠,适合开发与小规模生产调试 + - 未内置异步写盘或远程上报能力 +- 性能优化建议 + - 降低高频事件日志量:将细粒度 log 降级为 info 或在高并发场景减少输出 + - 使用 parseLogLevel 与 setLogLevel 动态调整级别:生产环境默认 info 或 warn + - 结合 morgan 的短格式或关闭策略,减少 HTTP 访问日志对吞吐的影响 + - 对热点路径的日志进行采样或聚合,避免频繁 I/O +- 与 morgan 的协同 + - logging 参数可选:combined/dev/short/tiny 或 none,按需选择以平衡可观测性与性能 + +章节来源 +- [src/log.ts:15-24](file://src/log.ts#L15-L24) +- [src/index.ts:28](file://src/index.ts#L28) +- [package.json:21](file://package.json#L21) + +## 故障排查指南 +- 常见问题定位 + - 无法看到预期日志:确认当前日志级别是否高于目标级别,或是否被 none 特判拦截 + - 信令异常:关注 HTTP/WS 处理器中的 warn 与 error 输出,定位参数缺失、冲突或超时 + - 心跳超时:WebSocket 处理器会输出超时警告,检查网络质量与客户端活跃度 +- 调试技巧 + - 在应用入口处临时提升日志级别,获取更详细的启动与初始化信息 + - 使用客户端 logger.js 的调试开关,在前端侧快速验证消息流转 + - 结合浏览器开发者工具的控制台筛选功能,按级别过滤输出 + +章节来源 +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [src/class/websockethandler.ts:413](file://src/class/websockethandler.ts#L413) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +## 结论 +本项目的日志系统以轻量、集中式为核心:统一的级别与格式化逻辑,配合业务模块在关键节点输出运行态信息。当前实现以控制台输出为主,未包含文件落盘与远程上报。通过合理设置日志级别、结合 morgan 的访问日志策略,可在开发与生产环境中取得良好的可观测性与性能平衡。若需进一步增强,可在现有日志模块基础上扩展文件轮转与远程推送能力。 + +## 附录 + +### 日志级别与应用场景 +- none:完全屏蔽日志输出 +- error:致命错误,应立即修复 +- warn:潜在风险或异常流程,需关注 +- log:常规事件性信息,适合高频但非关键的运行态记录 +- info:重要状态变更与启动/停止信息 + +章节来源 +- [src/log.ts:1-7](file://src/log.ts#L1-L7) +- [src/class/httphandler.ts:230](file://src/class/httphandler.ts#L230) +- [src/class/websockethandler.ts:77](file://src/class/websockethandler.ts#L77) + +### 日志输出目标与扩展 +- 当前实现 + - 仅控制台输出,无文件落盘与远程日志服务 +- 扩展建议 + - 文件落盘:在日志模块中增加文件句柄与滚动策略(按大小/时间) + - 远程上报:在日志模块中增加 HTTP 推送或 SDK 接入(如 Loki、ELK、Sentry) + - 与 morgan 协同:通过 logging 参数控制 HTTP 访问日志格式与级别 + +章节来源 +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [package.json:21](file://package.json#L21) +- [src/index.ts:28](file://src/index.ts#L28) + +### 日志分析与监控告警集成 +- 分析工具 + - 将控制台输出重定向至文件或管道,接入日志收集系统(如 Fluent Bit、Filebeat) + - 使用正则提取时间戳与级别字段,构建结构化日志 +- 监控告警 + - 基于 error/warn 比例与速率阈值触发告警 + - 结合业务指标(连接数、超时率、信令成功率)进行综合评估 + +章节来源 +- [src/class/websockethandler.ts:413](file://src/class/websockethandler.ts#L413) +- [src/class/httphandler.ts:230](file://src/class/httphandler.ts#L230) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/服务器核心/服务器入口点.md b/.qoder/repowiki/zh/content/服务器核心/服务器入口点.md new file mode 100644 index 0000000..3948853 --- /dev/null +++ b/.qoder/repowiki/zh/content/服务器核心/服务器入口点.md @@ -0,0 +1,378 @@ +# 服务器入口点 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/log.ts](file://src/log.ts) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件聚焦于服务器入口点模块,系统性阐述 RenderStreaming 类的设计与实现,覆盖命令行参数解析、配置选项处理、服务器启动流程、HTTPS 与 HTTP 信令协议选择、通信模式(公开/私有)以及启动时的参数验证与默认值处理。同时提供多种启动配置示例、最佳实践建议、错误处理与配置验证的实现细节。 + +## 项目结构 +服务器入口点位于后端源码的入口文件中,配合 Express 应用创建、HTTP 信令路由、WebSocket 信令服务以及日志模块共同构成完整的启动与运行体系。 + +```mermaid +graph TB +A["src/index.ts
入口与启动"] --> B["src/server.ts
Express应用创建"] +A --> C["src/websocket.ts
WebSocket信令服务"] +A --> D["src/class/options.ts
配置接口"] +B --> E["src/signaling.ts
HTTP信令路由"] +E --> F["src/class/httphandler.ts
HTTP信令处理器"] +C --> G["src/class/websockethandler.ts
WebSocket信令处理器"] +A --> H["src/log.ts
日志模块"] +I["package.json
脚本与构建配置"] --> A +``` + +**图表来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/class/websockethandler.ts:63-66](file://src/class/websockethandler.ts#L63-L66) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [package.json:5-12](file://package.json#L5-L12) + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [package.json:5-12](file://package.json#L5-L12) + +## 核心组件 +- RenderStreaming:负责命令行参数解析、配置读取、HTTP/HTTPS 服务器创建、信令协议选择与启动。 +- Options 接口:统一承载端口、HTTPS 文件、信令类型、通信模式、日志级别等配置项。 +- createServer:基于 Options 构建 Express 应用,挂载静态资源、Swagger 文档、HTTP 信令路由与上传接口。 +- WSSignaling:当信令类型为 WebSocket 时,启动 WebSocket 信令服务并交由 websockethandler 处理。 +- HTTP 信令处理器:提供会话管理、offer/answer/candidate 的存储与查询接口。 +- 日志模块:统一输出级别控制与格式化输出。 + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:63-66](file://src/class/websockethandler.ts#L63-L66) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 架构总览 +RenderStreaming.run 是主入口,内部使用 commandeer 解析命令行参数,随后根据配置创建 Express 应用;若启用 HTTPS,则以密钥与证书文件创建 HTTPS 服务器;否则使用 HTTP。随后根据信令类型选择 WebSocket 或 HTTP 信令通道,并打印启动信息与地址。 + +```mermaid +sequenceDiagram +participant CLI as "命令行" +participant RS as "RenderStreaming" +participant CS as "createServer" +participant APP as "Express应用" +participant WS as "WSSignaling" +participant LOG as "日志模块" +CLI->>RS : 传入 argv +RS->>RS : 解析命令行参数与环境变量 +RS->>CS : 传入 Options +CS-->>APP : 返回 Express 应用 +RS->>RS : 判断 secure 与 type +alt HTTPS 启用 +RS->>RS : 读取 keyfile/certfile +RS->>APP : 创建 HTTPS 服务器 +else HTTP +RS->>APP : 创建 HTTP 服务器 +end +opt type=websocket +RS->>WS : 初始化 WebSocket 信令 +WS->>LOG : 输出 ws 地址 +end +RS->>LOG : 输出启动模式与日志级别 +``` + +**图表来源** +- [src/index.ts:14-109](file://src/index.ts#L14-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) + +**章节来源** +- [src/index.ts:14-109](file://src/index.ts#L14-L109) + +## 详细组件分析 + +### RenderStreaming 类设计与实现 +- 命令行参数解析与默认值 + - 使用 commandeer 定义选项:端口、HTTPS 开关与证书文件、信令类型(websocket/http)、通信模式(public/private)、日志级别。 + - 默认值策略:端口默认 80;HTTPS 默认开启;信令类型默认 websocket;通信模式默认 public;日志默认 dev。 + - 环境变量回退:PORT、SECURE、KEYFILE、CERTFILE、TYPE、MODE、LOGGING。 +- 配置读取与校验 + - 读取 program.opts() 并构造 Options 对象,对 secure 与 type 进行布尔化与类型修正。 + - 若 type 非 websocket 且非 http,则警告并强制回退为 websocket。 +- 服务器启动 + - 创建 Express 应用:createServer(options)。 + - HTTPS:读取 keyfile/certfile 并创建 https.Server;HTTP:直接 app.listen。 + - 打印监听地址(支持多网卡 IPv4)。 + - 依据 type 启动 WebSocket 信令或提示使用 HTTP 轮询。 + - 输出当前通信模式。 + +```mermaid +flowchart TD +Start(["入口 run(argv)"]) --> Parse["解析命令行与环境变量"] +Parse --> BuildOpts["构造 Options 对象"] +BuildOpts --> TypeCheck{"type 是否为 websocket 或 http"} +TypeCheck --> |否| Warn["记录警告并回退为 websocket"] +TypeCheck --> |是| CreateApp["createServer(options)"] +Warn --> CreateApp +CreateApp --> Secure{"secure 为真?"} +Secure --> |是| HTTPS["读取 key/cert 并创建 https.Server"] +Secure --> |否| HTTP["app.listen()"] +HTTPS --> PrintAddr["打印监听地址"] +HTTP --> PrintAddr +PrintAddr --> Mode["输出启动模式"] +Mode --> End(["完成"]) +``` + +**图表来源** +- [src/index.ts:14-109](file://src/index.ts#L14-L109) + +**章节来源** +- [src/index.ts:14-109](file://src/index.ts#L14-L109) + +### Options 配置管理 +- 字段定义:secure、port、keyfile、certfile、type、mode、logging。 +- 作用范围:贯穿 Express 应用、HTTPS 服务器、WebSocket/HTTP 信令、日志级别控制。 +- 默认值与回退:参见“命令行参数解析与默认值”小节。 + +**章节来源** +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/index.ts:20-41](file://src/index.ts#L20-L41) + +### 服务器启动流程与 Express 应用 +- createServer 负责: + - 重置 HTTP 信令处理器模式(resetHandler)。 + - 条件化 Morgan 日志(logging != "none")。 + - CORS、URL 编码、JSON 解析。 + - 提供 /config 接口返回当前信令类型、启动模式与日志级别。 + - 挂载 /signaling 路由(HTTP 信令)。 + - 提供静态页面与模块目录。 + - 初始化 Swagger 文档。 + - 配置头像上传接口与目录结构。 + +```mermaid +sequenceDiagram +participant RS as "RenderStreaming" +participant CS as "createServer" +participant APP as "Express 应用" +participant SIG as "HTTP 信令路由" +participant HH as "HTTP 信令处理器" +RS->>CS : 传入 Options +CS->>APP : 初始化中间件与静态资源 +CS->>SIG : 挂载 /signaling 路由 +SIG->>HH : 调用处理器函数 +CS-->>RS : 返回 APP 实例 +``` + +**图表来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) + +**章节来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +### WebSocket 信令服务 +- WSSignaling 在 HTTP 服务器上创建 ws.Server,并根据通信模式初始化 websockethandler。 +- 连接生命周期:connection、message、close。 +- 支持的消息类型:connect/disconnect、offer/answer/candidate、broadcast、call-request、on-message(含 ping/pong 心跳)。 +- 私有模式下的主机与参与者角色管理、广播与单播路由。 + +```mermaid +classDiagram +class WSSignaling { ++server ++wss ++constructor(server, mode) +} +class WebSocketHandler { ++reset(mode) ++add(ws) ++remove(ws) ++onConnect(ws, connectionId) ++onDisconnect(ws, connectionId) ++onOffer(ws, message) ++onAnswer(ws, message) ++onCandidate(ws, message) ++onBroadcast(ws, message) ++onCallConnectionId(ws, message) ++onMessage(ws, message) +} +WSSignaling --> WebSocketHandler : "初始化与消息分发" +``` + +**图表来源** +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:63-66](file://src/class/websockethandler.ts#L63-L66) + +**章节来源** +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:145-473](file://src/class/websockethandler.ts#L145-L473) + +### HTTP 信令处理器 +- 会话与连接管理:会话 ID、连接 ID、连接对(connectionPair),私有模式下双向绑定。 +- 信令存储:Offer/Answer/Candidate 结构与时间戳,按会话与连接维度组织。 +- 查询接口:/signaling、/signaling/connection、/signaling/offer、/signaling/answer、/signaling/candidate。 +- 写入接口:POST /signaling/offer、/answer、/candidate。 +- 会话超时清理:定期检查并删除超时会话。 +- 房间与用户统计:聚合连接对信息,输出房间列表与用户数。 + +```mermaid +flowchart TD +S(["HTTP 信令请求"]) --> CheckSID["校验 session-id"] +CheckSID --> Route{"路由类型"} +Route --> |GET| GetOps["查询操作
getAll/getOffer/getAnswer/getCandidate/getConnection"] +Route --> |PUT| PutOps["写入/创建操作
createSession/createConnection"] +Route --> |DELETE| DelOps["删除操作
deleteSession/deleteConnection"] +GetOps --> Store["按会话/连接维度读取存储"] +PutOps --> Store +DelOps --> Store +Store --> Resp["返回 JSON 响应"] +``` + +**图表来源** +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:398-998](file://src/class/httphandler.ts#L398-L998) + +**章节来源** +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/class/httphandler.ts:398-998](file://src/class/httphandler.ts#L398-L998) + +### 日志模块 +- 日志级别枚举:none/error/warn/log/info,默认 info。 +- 级别解析:字符串到枚举的映射,未知值回退为 info。 +- 输出格式:ISO 时间戳 + 大写级别前缀 + 参数。 + +**章节来源** +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 依赖关系分析 +- 入口依赖:commander(参数解析)、express(Web 框架)、https/http(服务器)、fs/os(证书读取与地址枚举)、ws(WebSocket)、morgan(HTTP 日志)、cors/multer/swagger(中间件与静态资源)。 +- 入口与应用:RenderStreaming 依赖 createServer;createServer 依赖 HTTP 信令路由与处理器。 +- 信令通道:WebSocket 与 HTTP 二选一,分别由 WSSignaling 与 HTTP 信令处理器承担。 + +```mermaid +graph TB +IDX["src/index.ts"] --> CMDR["commander"] +IDX --> EXP["express"] +IDX --> HTTPS["https"] +IDX --> FS["fs"] +IDX --> OS["os"] +IDX --> WSS["ws"] +IDX --> MORGAN["morgan"] +IDX --> CORS["cors"] +IDX --> MULTER["multer"] +IDX --> SWAG["swagger"] +IDX --> SVR["src/server.ts"] +IDX --> WS["src/websocket.ts"] +SVR --> SIG["src/signaling.ts"] +SIG --> HHT["src/class/httphandler.ts"] +WS --> WHH["src/class/websockethandler.ts"] +``` + +**图表来源** +- [src/index.ts:1-11](file://src/index.ts#L1-L11) +- [src/server.ts:1-12](file://src/server.ts#L1-L12) +- [src/websocket.ts:1-4](file://src/websocket.ts#L1-L4) +- [src/signaling.ts:1-3](file://src/signaling.ts#L1-L3) +- [src/class/httphandler.ts:5-11](file://src/class/httphandler.ts#L5-L11) +- [src/class/websockethandler.ts:3-8](file://src/class/websockethandler.ts#L3-L8) + +**章节来源** +- [src/index.ts:1-11](file://src/index.ts#L1-L11) +- [src/server.ts:1-12](file://src/server.ts#L1-L12) + +## 性能考量 +- 服务器启动阶段: + - HTTPS 证书文件读取发生在启动时,建议确保文件存在与权限正确,避免启动失败。 + - 日志级别为 none 时可减少 I/O 开销。 +- WebSocket 信令: + - 私有模式下按连接组进行消息路由,避免全量广播带来的网络压力。 + - 心跳检测可及时清理长时间无活动的连接,降低资源占用。 +- HTTP 信令: + - 会话超时机制定期清理无效会话,防止内存泄漏。 + - GET 查询支持 fromtime 过滤,减少不必要的数据传输。 + +[本节为通用指导,无需具体文件引用] + +## 故障排查指南 +- 启动失败(HTTPS) + - 症状:启动时报证书文件读取错误。 + - 排查:确认 keyfile/certfile 路径正确且可读;secure 为 true 时必须提供有效证书。 + - 参考:入口读取证书文件与创建 HTTPS 服务器的逻辑。 +- 信令类型异常 + - 症状:type 非 websocket 或 http 时被强制回退。 + - 排查:检查命令行参数与环境变量 TYPE;确保只使用受支持的类型。 +- 通信模式问题 + - 症状:私有模式下连接对冲突或无法建立双向配对。 + - 排查:确认 connectionId 未被重复使用;检查连接对映射与会话 ID 关联。 +- 日志级别 + - 症状:日志过多或过少。 + - 排查:调整 logging 或使用 setLogLevel 控制输出级别。 +- 上传接口失败 + - 症状:头像上传报错或重命名失败。 + - 排查:确认 uploads 目录存在且具备写权限;检查 userId 与文件扩展名处理。 + +**章节来源** +- [src/index.ts:55-82](file://src/index.ts#L55-L82) +- [src/class/websockethandler.ts:150-167](file://src/class/websockethandler.ts#L150-L167) +- [src/class/httphandler.ts:754-782](file://src/class/httphandler.ts#L754-L782) +- [src/log.ts:11-24](file://src/log.ts#L11-L24) +- [src/server.ts:62-83](file://src/server.ts#L62-L83) + +## 结论 +RenderStreaming 将命令行参数解析、配置读取、服务器创建与信令通道选择整合为统一入口,结合 HTTP 与 WebSocket 两种信令模式满足不同部署场景需求。通过 Options 接口集中管理配置,配合 HTTP/WS 信令处理器实现可靠的信令传递与连接管理。建议在生产环境中优先使用 HTTPS 与 WebSocket,并合理设置日志级别与会话超时策略。 + +[本节为总结性内容,无需具体文件引用] + +## 附录 + +### 启动配置示例与最佳实践 +- 使用 HTTPS 并自定义端口与证书 + - 示例:node ./build/index.js -s -p 8443 -k ./server.key -c ./server.cert + - 最佳实践:将证书置于安全目录,限制文件权限;使用强密码保护私钥。 +- 使用 HTTP(不推荐生产) + - 示例:node ./build/index.js -p 8080 -s false + - 最佳实践:仅限开发调试;如需生产请改用 HTTPS。 +- 选择 HTTP 轮询信令 + - 示例:node ./build/index.js -t http -p 8080 -m private + - 最佳实践:客户端需实现轮询拉取信令;注意带宽与延迟。 +- 选择 WebSocket 信令与私有模式 + - 示例:node ./build/index.js -t websocket -p 8080 -m private + - 最佳实践:私有模式适合点对点或小房间;公共模式适合广播场景。 +- 自定义日志级别 + - 示例:node ./build/index.js -l combined -p 8080 + - 可选值:combined/dev/short/tiny/none;none 会禁用日志输出。 +- 使用 npm 脚本 + - 开发:npm run dev + - 生产:npm start + - 参考:package.json 中 scripts 与 bin 配置。 + +**章节来源** +- [package.json:5-12](file://package.json#L5-L12) +- [src/index.ts:20-29](file://src/index.ts#L20-L29) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/服务器核心/服务器核心.md b/.qoder/repowiki/zh/content/服务器核心/服务器核心.md new file mode 100644 index 0000000..574d0a4 --- /dev/null +++ b/.qoder/repowiki/zh/content/服务器核心/服务器核心.md @@ -0,0 +1,507 @@ +# 服务器核心 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [src/log.ts](file://src/log.ts) +- [src/服务端接口与WebSocket消息类型.md](file://src/服务端接口与WebSocket消息类型.md) +- [package.json](file://package.json) +- [run.bat](file://run.bat) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖分析](#依赖分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件面向服务器核心模块,系统化阐述以下主题: +- 服务器入口点的设计与实现:命令行参数解析、配置加载与服务器启动流程 +- HTTP 服务器与 WebSocket 服务器的配置与管理:HTTPS 支持、CORS 配置、中间件集成 +- 信令路由系统:HTTP 轮询与 WebSocket 的路由处理 +- 日志系统集成与配置选项 +- 性能优化建议与监控指标 +- 具体的代码示例与配置文件模板 + +## 项目结构 +服务器核心由入口模块、Express 服务器装配、信令路由、WebSocket 信令服务器以及日志与 Swagger 文档配置组成。核心文件如下: +- 入口与启动:src/index.ts +- 服务器装配:src/server.ts +- 信令路由(HTTP):src/signaling.ts +- WebSocket 信令:src/websocket.ts +- HTTP 信令处理器:src/class/httphandler.ts +- WebSocket 信令处理器:src/class/websockethandler.ts +- 配置选项接口:src/class/options.ts +- Swagger 文档:src/swagger.ts +- 日志工具:src/log.ts +- 接口与消息类型文档:src/服务端接口与WebSocket消息类型.md +- 启动脚本与包配置:package.json、run.bat + +```mermaid +graph TB +A["入口模块
src/index.ts"] --> B["服务器装配
src/server.ts"] +B --> C["HTTP 信令路由
src/signaling.ts"] +B --> D["静态资源与上传
src/server.ts"] +A --> E["WebSocket 信令服务器
src/websocket.ts"] +C --> F["HTTP 信令处理器
src/class/httphandler.ts"] +E --> G["WebSocket 信令处理器
src/class/websockethandler.ts"] +B --> H["Swagger 文档
src/swagger.ts"] +A --> I["日志工具
src/log.ts"] +A --> J["配置选项接口
src/class/options.ts"] +``` + +图表来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 核心组件 +- 入口与配置装载 + - 使用命令行参数解析器装载配置,支持端口、HTTPS、证书、信令类型、通信模式、日志级别等选项 + - 根据配置创建 Express 应用与 HTTP/HTTPS 服务器实例 +- Express 服务器装配 + - 配置日志中间件、CORS、JSON/URL 编码解析、静态资源、Swagger 文档、头像上传接口 +- 信令路由 + - HTTP 轮询:基于会话 ID 的 REST 接口,支持增量拉取 + - WebSocket:基于连接组的 1 对多路由,支持私有模式与公共模式 +- 日志系统 + - 支持多等级日志输出,可通过配置调整日志级别 +- Swagger 文档 + - 自动生成 API 文档,支持会话认证头 + +章节来源 +- [src/index.ts:14-109](file://src/index.ts#L14-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/class/httphandler.ts:1112-1129](file://src/class/httphandler.ts#L1112-L1129) +- [src/class/websockethandler.ts:478](file://src/class/websockethandler.ts#L478) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 架构总览 +服务器采用“入口模块 + Express 服务器 + 信令路由 + WebSocket 信令”的分层架构。入口模块负责参数解析与服务器启动;Express 负责 HTTP 层面的中间件、静态资源与 API;信令路由在 HTTP 层提供轮询接口,在 WebSocket 层提供实时消息分发。 + +```mermaid +graph TB +subgraph "入口与服务器" +R["入口模块
src/index.ts"] +S["服务器装配
src/server.ts"] +end +subgraph "HTTP 层" +SIG["HTTP 信令路由
src/signaling.ts"] +SW["Swagger 文档
src/swagger.ts"] +LOG["日志工具
src/log.ts"] +end +subgraph "WebSocket 层" +WS["WebSocket 信令服务器
src/websocket.ts"] +WSH["WebSocket 处理器
src/class/websockethandler.ts"] +end +subgraph "信令处理器" +HTH["HTTP 信令处理器
src/class/httphandler.ts"] +end +R --> S +S --> SIG +S --> SW +S --> LOG +R --> WS +WS --> WSH +SIG --> HTH +``` + +图表来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 详细组件分析 + +### 入口模块与启动流程 +- 命令行参数解析 + - 支持端口、HTTPS、证书文件、信令类型(websocket/http)、通信模式(public/private)、日志级别等 + - 环境变量可作为默认值来源 +- 服务器创建与启动 + - 根据 secure 选项创建 HTTP 或 HTTPS 服务器 + - 启动后打印访问地址(IPv4) +- 信令类型与模式 + - 校验信令类型,不支持则回退为 websocket + - 根据模式启动 WebSocket 信令服务器 + +```mermaid +sequenceDiagram +participant CLI as "命令行" +participant Entry as "入口模块
src/index.ts" +participant Server as "服务器装配
src/server.ts" +participant WS as "WebSocket 信令
src/websocket.ts" +CLI->>Entry : 传入参数/环境变量 +Entry->>Entry : 解析参数/构造 Options +Entry->>Server : createServer(options) +Server-->>Entry : Express 应用实例 +Entry->>Entry : 根据 secure 创建 HTTP/HTTPS 服务器 +Entry->>Entry : 校验信令类型/打印访问地址 +Entry->>WS : 启动 WebSocket 信令服务器(模式) +WS-->>Entry : 连接事件/消息处理就绪 +``` + +图表来源 +- [src/index.ts:14-109](file://src/index.ts#L14-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) + +章节来源 +- [src/index.ts:14-109](file://src/index.ts#L14-L109) + +### HTTP 服务器与中间件配置 +- 中间件与静态资源 + - 日志中间件:支持多种日志格式(combined/dev/short/tiny/none) + - CORS:允许任意来源 + - JSON/URL 编码解析 + - 静态资源:首页、模块资源 +- API 与路由 + - /config:返回服务器配置(是否使用 WebSocket、启动模式、日志级别) + - /signaling:挂载信令路由 + - /api/upload/avatar:头像上传(临时文件名,服务端重命名为 userId.{ext}) + - /uploads:头像目录静态访问 +- Swagger 文档 + - 自动扫描 HTTP 处理器注释,生成 API 文档 + +```mermaid +flowchart TD +Start(["请求进入"]) --> LogCheck{"日志级别是否为 none?"} +LogCheck --> |否| UseLog["使用 Morgan 日志中间件"] +LogCheck --> |是| SkipLog["跳过日志"] +UseLog --> CORS["CORS 允许任意来源"] +SkipLog --> CORS +CORS --> Parse["JSON/URL 编码解析"] +Parse --> Config["GET /config 返回配置"] +Config --> Signaling["挂载 /signaling 路由"] +Signaling --> Static["静态资源与模块资源"] +Static --> Upload["POST /api/upload/avatar 头像上传"] +Upload --> Swagger["初始化 Swagger 文档"] +Swagger --> End(["完成"]) +``` + +图表来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +章节来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +### 信令路由系统(HTTP 轮询) +- 会话与连接管理 + - 会话超时:10 秒无请求自动删除 + - 连接对:私有模式下通过 connectionId 建立 host/participants 关系 +- 路由与处理器 + - 无需会话 ID 的路由:/signaling/connection-ids + - 需要会话 ID 的路由:统一中间件校验 session-id + - 提供连接、offer、answer、candidate 的增删改查与增量拉取 +- 增量拉取 + - fromtime 查询参数用于增量获取消息,避免重复传输 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant Router as "HTTP 路由
src/signaling.ts" +participant Handler as "HTTP 处理器
src/class/httphandler.ts" +Client->>Router : GET /signaling?fromtime=... +Router->>Handler : checkSessionId 中间件 +Handler-->>Router : 校验通过 +Router->>Handler : getAnswer/getOffer/getCandidate/getAll +Handler-->>Router : 返回增量信令消息 +Router-->>Client : JSON 响应 +``` + +图表来源 +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +章节来源 +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +### WebSocket 信令服务器 +- 连接与路由 + - 基于 ws 的 WebSocket 服务器,与 HTTP 服务器共享端口 + - 私有模式:1 个 host + 多个 participants,支持单播/广播 + - 公共模式:全连通,消息广播给所有其他客户端 +- 消息类型与处理 + - 生命周期:connect/disconnect/participant-joined/participant-left + - WebRTC:offer/answer/candidate(双向,按模式路由) + - 控制:ping/pong(可选心跳) + - 自定义:on-message/broadcast/call-request +- 心跳检测(可选) + - 定时发送 ping,超时断开 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant WS as "WebSocket 信令服务器
src/websocket.ts" +participant WSH as "WebSocket 处理器
src/class/websockethandler.ts" +Client->>WS : 连接 ws : //host : port +WS->>WSH : add(ws) +Client->>WS : {"type" : "connect","connectionId" : id} +WS->>WSH : onConnect(ws,id) +WSH-->>Client : {"type" : "connect","role" : "host|participant","participantId" : pid} +Client->>WS : {"type" : "offer","data" : {"sdp" : ...,"connectionId" : id}} +WS->>WSH : onOffer(ws,msg) +WSH-->>Client : 根据模式路由到 host 或 participants +Client->>WS : {"type" : "disconnect","connectionId" : id} +WS->>WSH : onDisconnect(ws,id) +WSH-->>Client : {"type" : "disconnect","reason" : "host-left"|normal} +``` + +图表来源 +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) + +章节来源 +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) + +### 日志系统集成与配置 +- 日志等级 + - 支持 none/error/warn/log/info 等等级 + - 可通过配置字符串解析为等级枚举 +- 输出行为 + - 根据等级决定是否输出 + - 统一时间戳与前缀格式 +- 使用场景 + - 启动信息、访问日志、错误告警、运行时事件 + +章节来源 +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +### Swagger 文档与 API 文档 +- 自动扫描 + - 扫描 HTTP 处理器与路由文件,提取注释生成 OpenAPI 文档 +- 安全方案 + - 会话认证头:session-id +- 访问 + - /api-docs + +章节来源 +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 依赖分析 +- 入口模块依赖 + - 命令行解析、HTTPS/HTTP 服务器、WebSocket 信令、日志工具、配置选项 +- 服务器装配依赖 + - Express、Morgan、CORS、Multer、Swagger UI、静态资源 +- 信令处理器依赖 + - UUID、WebSocket 处理器、日志工具 +- 文档与脚本 + - Swagger 配置、NPM 脚本、批处理脚本 + +```mermaid +graph TB +IDX["src/index.ts"] --> SRV["src/server.ts"] +IDX --> WSC["src/websocket.ts"] +SRV --> SIG["src/signaling.ts"] +SRV --> SWG["src/swagger.ts"] +SRV --> LOG["src/log.ts"] +SIG --> HTH["src/class/httphandler.ts"] +WSC --> WSH["src/class/websockethandler.ts"] +IDX --> OPT["src/class/options.ts"] +``` + +图表来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/httphandler.ts:1-1130](file://src/class/httphandler.ts#L1-L1130) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 性能考量 +- 服务器启动与监听 + - 仅监听 IPv4 地址,避免 IPv6 广播带来的额外开销 +- HTTP 轮询 + - 使用 fromtime 增量拉取,降低带宽与 CPU 开销 + - 会话超时 10 秒,及时释放内存与连接资源 +- WebSocket + - 私有模式下按连接组路由,避免广播风暴 + - 可选心跳检测,超时断开异常连接 +- 中间件 + - 日志级别可设为 none 以禁用日志输出 + - CORS 允许任意来源,生产环境建议限制来源 +- 存储与上传 + - 头像上传使用磁盘存储,建议结合 CDN 与缩略图策略 + +[本节为通用性能建议,不直接分析具体文件] + +## 故障排查指南 +- 启动失败(HTTPS 证书) + - 确认证书与密钥文件路径正确,secure 选项开启 +- 无法访问 /api-docs + - 确认 Swagger 初始化成功,服务器 URL 与端口一致 +- HTTP 轮询无响应 + - 检查 session-id 请求头是否正确传递 + - 确认会话未超时(10 秒) +- WebSocket 无法连接 + - 检查 ws:// 地址与端口,确认服务器已启动 + - 查看心跳/断线日志(如启用) +- 头像上传失败 + - 检查 uploads 目录权限与磁盘空间 + - 确认 userId 参数与文件大小限制 + +章节来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:44-83](file://src/server.ts#L44-L83) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +## 结论 +服务器核心模块通过清晰的分层设计实现了高性能、可扩展的信令服务。入口模块负责配置与启动,Express 提供稳定的 HTTP 层,WebSocket 提供低延迟的实时通信,HTTP 轮询满足兼容性需求。配合日志与 Swagger,系统具备良好的可观测性与可维护性。 + +[本节为总结性内容,不直接分析具体文件] + +## 附录 + +### 命令行参数与环境变量 +- 端口:-p/--port,默认 80 +- HTTPS:-s/--secure,默认启用 +- 证书:-k/--keyfile、-c/--certfile +- 信令类型:-t/--type,websocket 或 http +- 通信模式:-m/--mode,public 或 private +- 日志级别:-l/--logging,combined/dev/short/tiny/none + +章节来源 +- [src/index.ts:20-29](file://src/index.ts#L20-L29) + +### HTTPS 与证书 +- 启用 HTTPS 时需提供 server.key 与 server.cert +- 启动后打印 https:// 地址 + +章节来源 +- [src/index.ts:55-65](file://src/index.ts#L55-L65) + +### CORS 配置 +- 允许任意来源(*) + +章节来源 +- [src/server.ts:22](file://src/server.ts#L22) + +### Swagger 文档访问 +- /api-docs + +章节来源 +- [src/swagger.ts:63](file://src/swagger.ts#L63) + +### 头像上传 API +- POST /api/upload/avatar + - 参数:userId(body)、avatar(file) + - 响应:success、avatarUrl、message + +章节来源 +- [src/server.ts:62-83](file://src/server.ts#L62-L83) + +### 服务器启动脚本 +- package.json scripts + - start:构建并启动 + - dev:开发模式 +- run.bat:Windows 批处理示例 + +章节来源 +- [package.json:5-12](file://package.json#L5-L12) +- [run.bat:8](file://run.bat#L8) + +### 服务器核心类图(代码级) +```mermaid +classDiagram +class RenderStreaming { ++run(argv) ++app ++server? ++options ++constructor(options) ++getIPAddress() +} +class WSSignaling { ++server ++wss ++constructor(server, mode) +} +class HttpHandler { ++reset(mode) ++checkSessionId(req,res,next) ++getAll(req,res) ++getConnection(req,res) ++getOffer(req,res) ++getAnswer(req,res) ++getCandidate(req,res) ++createSession(req,res) ++deleteSession(req,res) ++createConnection(req,res) ++deleteConnection(req,res) ++postOffer(req,res) ++postAnswer(req,res) ++postCandidate(req,res) ++onGetConnections(req,res) ++getAllConnectionIds(req,res) +} +class WebSocketHandler { ++reset(mode) ++add(ws) ++remove(ws) ++onConnect(ws,connectionId) ++onDisconnect(ws,connectionId) ++onOffer(ws,message) ++onAnswer(ws,message) ++onCandidate(ws,message) ++onCallConnectionId(ws,message) ++onBroadcast(ws,message) ++onGetAllConnectionIds() ++onMessage(ws,message) ++isHost(ws,connectionId) ++broadcastToGroup(connectionId,senderWs,message) +} +RenderStreaming --> WSSignaling : "启动" +WSSignaling --> WebSocketHandler : "使用" +RenderStreaming --> HttpHandler : "使用" +``` + +图表来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/httphandler.ts:1112-1129](file://src/class/httphandler.ts#L1112-L1129) +- [src/class/websockethandler.ts:478](file://src/class/websockethandler.ts#L478) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/测试框架.md b/.qoder/repowiki/zh/content/测试框架.md new file mode 100644 index 0000000..c3d652c --- /dev/null +++ b/.qoder/repowiki/zh/content/测试框架.md @@ -0,0 +1,484 @@ +# 测试框架 + + +**本文引用的文件** +- [jest.config.js](file://jest.config.js) +- [jest.config.js](file://client/jest.config.js) +- [jest.setup.js](file://client/jest.setup.js) +- [package.json](file://package.json) +- [package.json](file://client/package.json) +- [httphandler.test.ts](file://test/httphandler.test.ts) +- [websockethandler.test.ts](file://test/websockethandler.test.ts) +- [signaling.test.js](file://client/test/signaling.test.js) +- [mocksignaling.js](file://client/test/mocksignaling.js) +- [peerconnection.test.js](file://client/test/peerconnection.test.js) +- [peerconnectionmock.js](file://client/test/peerconnectionmock.js) +- [resizeobservermock.js](file://client/test/resizeobservermock.js) +- [testutils.js](file://client/test/testutils.js) +- [sender.test.js](file://client/test/sender.test.js) +- [httphandler.ts](file://src/class/httphandler.ts) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖分析](#依赖分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向视频流服务器项目的测试体系,系统性阐述测试策略与架构,覆盖单元测试、集成测试与端到端测试的组织方式;详解 Jest 测试框架在服务端与客户端的配置与使用,包括测试环境设置、模拟对象与工具库;提供 WebRTC 信令与数据通道输入远程控制等关键功能的测试示例;说明测试覆盖率要求与报告生成;总结测试最佳实践与调试技巧,并给出测试用例编写指南。 + +## 项目结构 +项目采用分层与按功能划分的组织方式: +- 服务端(src):核心业务逻辑与 HTTP/WebSocket 信令处理 +- 服务端测试(test):基于 Express 模拟与 WebSocket 模拟器的集成测试 +- 客户端(client):前端 JS 模块与 WebRTC 交互逻辑 +- 客户端测试(client/test):JSDOM 环境下的单元与集成测试,含 WebRTC 模拟与信令模拟 + +```mermaid +graph TB +subgraph "服务端" +S1["HTTP处理器
src/class/httphandler.ts"] +S2["WebSocket处理器
src/class/websockethandler.ts"] +end +subgraph "服务端测试" +T1["HTTP信令测试
test/httphandler.test.ts"] +T2["WebSocket信令测试
test/websockethandler.test.ts"] +end +subgraph "客户端" +C1["信令模块
client/src/signaling.js"] +C2["WebRTC封装
client/src/peer.js"] +C3["输入远程控制
client/src/inputremoting.js"] +end +subgraph "客户端测试" +CT1["信令测试
client/test/signaling.test.js"] +CT2["Peer连接测试
client/test/peerconnection.test.js"] +CT3["发送器测试
client/test/sender.test.js"] +CM1["信令模拟
client/test/mocksignaling.js"] +CM2["Peer模拟
client/test/peerconnectionmock.js"] +CU1["测试工具
client/test/testutils.js"] +end +T1 --> S1 +T2 --> S2 +CT1 --> C1 +CT2 --> C2 +CT3 --> C3 +CT1 --> CM1 +CT2 --> CM2 +CT2 --> CU1 +``` + +图表来源 +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) +- [peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +章节来源 +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [package.json:1-60](file://package.json#L1-L60) +- [package.json:1-19](file://client/package.json#L1-L19) + +## 核心组件 +- 测试运行器与配置 + - 服务端 Jest 配置启用 TypeScript 转换、Node 环境、覆盖率收集与默认匹配规则 + - 客户端 Jest 配置启用 JSDOM 环境、setupFilesAfterEnv 引入全局模拟与 WebRTC 模拟 +- 测试工具与模拟 + - 服务端:Express 模拟中间件与 WebSocket 模拟器,验证 HTTP/WebSocket 信令流程 + - 客户端:JSDOM 环境下注入 fetch、TextEncoder/Decoder、RTCPeerConnection 及 ResizeObserver 的模拟实现 + - 信令模拟:MockSignaling 与 MockPublic/PrivateSignalingManager,支持公共/私有模式下的信令广播与连接管理 + - WebRTC 模拟:PeerConnectionMock、SessionDescriptionMock、IceCandidateMock,覆盖 SDP 描述、ICE 候选、事件触发与状态机 + - 工具库:waitFor、sleep、getRTCConfiguration 等,支撑异步等待与测试配置 +- 关键模块测试 + - HTTP 信令:会话创建、连接创建、offer/answer/candidate 收发、断开清理与超时回收 + - WebSocket 信令:连接建立、消息转发、断开通知与多客户端广播 + - 信令集成:公共/私有模式下跨客户端的信令收发与连接极性判定 + - WebRTC Peer:addTrack/addTransceiver/createDataChannel、协商事件、候选收集、错误状态抛出 + - 输入远程控制:鼠标/键盘/触摸事件捕获与 RTC 数据通道发送 + +章节来源 +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) +- [mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) +- [peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) +- [resizeobservermock.js:1-18](file://client/test/resizeobservermock.js#L1-L18) +- [testutils.js:1-39](file://client/test/testutils.js#L1-L39) +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [sender.test.js:1-143](file://client/test/sender.test.js#L1-L143) + +## 架构总览 +测试架构分为三层: +- 单元测试层:针对纯函数与小模块(如工具函数、模型类),快速验证边界条件与异常分支 +- 集成测试层:组合模块与外部依赖(Express 中间件、WebSocket 服务器),验证端到端流程 +- 端到端测试层:启动真实服务进程,进行跨浏览器/设备的信令与媒体交互验证 + +```mermaid +sequenceDiagram +participant Test as "测试用例" +participant HTTP as "HTTP处理器
httphandler.ts" +participant WS as "WebSocket处理器
websockethandler.ts" +participant MockSig as "信令模拟
mocksignaling.js" +participant Peer as "Peer连接模拟
peerconnectionmock.js" +Test->>HTTP : "创建会话/连接" +HTTP-->>Test : "返回JSON响应" +Test->>MockSig : "创建连接/发送offer/answer/candidate" +MockSig-->>Test : "触发connect/offer/answer/candidate事件" +Test->>Peer : "onGotDescription/onGotCandidate" +Peer-->>Test : "触发sendoffer/sendanswer/sendcandidate事件" +Test->>WS : "WebSocket信令收发" +WS-->>Test : "消息转发/断开通知" +``` + +图表来源 +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) +- [peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) + +## 详细组件分析 + +### 服务端 HTTP 信令测试(单元/集成) +- 测试目标 + - 会话生命周期:创建、检查、删除 + - 连接生命周期:创建、断开、清理 + - 信令消息:offer/answer/candidate 的收发与查询 + - 模式差异:公共/私有模式下的连接极性与广播行为 + - 超时机制:长时间无请求的会话清理 +- 关键断言 + - 响应状态码与 JSON 结构 + - 会话内连接列表与消息队列 + - 私有模式下仅配对连接可互相接收信令 +- 异步与超时 + - 使用定时器与延迟模拟,验证超时清理逻辑 + +```mermaid +flowchart TD +Start(["开始"]) --> CreateSession["创建会话"] +CreateSession --> CreateConn["创建连接"] +CreateConn --> SendOffer["发送Offer"] +SendOffer --> ReceiveOffer["接收Offer"] +ReceiveOffer --> SendAnswer["发送Answer"] +SendAnswer --> ReceiveAnswer["接收Answer"] +ReceiveAnswer --> SendCandidate["发送Candidate"] +SendCandidate --> ReceiveCandidate["接收Candidate"] +ReceiveCandidate --> DeleteConn["删除连接"] +DeleteConn --> Cleanup["清理会话/断开通知"] +Cleanup --> End(["结束"]) +``` + +图表来源 +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +章节来源 +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +### 服务端 WebSocket 信令测试(集成) +- 测试目标 + - 多客户端连接与消息广播 + - 私有模式下仅配对连接可互相接收信令 + - 断开连接时双向通知 +- 关键断言 + - WebSocket 消息协议字段(from/to/type/data) + - 消息到达顺序与数量 + +```mermaid +sequenceDiagram +participant C1 as "客户端1" +participant C2 as "客户端2" +participant WS as "WebSocket服务器" +C1->>WS : "connect(connectionId)" +WS-->>C1 : "connect(own)" +WS-->>C2 : "connect(other)" +C1->>WS : "offer({connectionId,sdp})" +WS-->>C2 : "offer({from,to,type,data})" +C2->>WS : "answer({connectionId,sdp})" +WS-->>C1 : "answer({from,to,type,data})" +C1->>WS : "disconnect(connectionId)" +WS-->>C1 : "disconnect" +WS-->>C2 : "disconnect" +``` + +图表来源 +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +章节来源 +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +### 客户端信令测试(集成/端到端) +- 测试目标 + - 公共/私有模式下信令收发与连接极性 + - HTTP 与 WebSocket 两种信令通道 + - 本地 MockSignaling 与真实服务进程的对比 +- 关键断言 + - connect/offer/answer/candidate 事件的 detail 字段 + - 断开事件触发次数与 connectionId 匹配 +- 端到端验证 + - 通过 jest-dev-server 启动服务进程,使用真实 Signaling/WebSocketSignaling 进行连通性测试 + +```mermaid +sequenceDiagram +participant T as "测试用例" +participant M as "MockSignaling" +participant S as "Signaling(HTTP)" +participant W as "WebSocketSignaling" +participant P as "服务进程" +T->>M : "start/createConnection/sendOffer" +M-->>T : "dispatchEvent(connect/offer/answer/candidate)" +T->>S : "start/createConnection/sendOffer" +S->>P : "HTTP请求" +P-->>S : "响应/消息存储" +T->>W : "start/createConnection/sendOffer" +W->>P : "WebSocket消息" +P-->>W : "消息转发" +``` + +图表来源 +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) + +章节来源 +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) + +### WebRTC Peer 连接测试(单元/集成) +- 测试目标 + - 构造函数初始化与事件监听器绑定 + - addTrack/addTransceiver/createDataChannel 触发协商 + - onGotDescription/onGotCandidate 的事件与状态变化 + - 候选收集与错误状态抛出 +- 关键断言 + - 发送 offer/answer/candidate 的事件详情 + - negotiated 事件触发时机 + - 候选未被接受的边界条件 + +```mermaid +classDiagram +class Peer { ++connectionId ++pc ++addEventListener(event, handler) ++close() ++addTrack(connectionId, track) ++addTransceiver(connectionId, kind) ++createDataChannel(connectionId, label) ++onGotDescription(connectionId, desc) ++onGotCandidate(connectionId, candidate) +} +class PeerConnectionMock { ++localDescription ++remoteDescription ++addTrack(track) ++addTransceiver(kind) ++createDataChannel(label) ++setLocalDescription(desc) ++setRemoteDescription(desc) ++addIceCandidate(candidate) ++close() +} +Peer --> PeerConnectionMock : "使用" +``` + +图表来源 +- [peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) + +章节来源 +- [peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) + +### 输入远程控制测试(单元) +- 测试目标 + - 设备注册与事件订阅 + - 鼠标/键盘/触摸事件序列化与发送 + - RTC 数据通道状态与发送调用 +- 关键断言 + - 设备数量与事件回调注册 + - 数据通道 send 调用次数与 ArrayBuffer 内容 + +章节来源 +- [sender.test.js:1-143](file://client/test/sender.test.js#L1-L143) + +## 依赖分析 +- 测试运行与环境 + - 服务端:Jest + ts-jest + @jest-mock/express + jest-websocket-mock + - 客户端:Jest + jest-environment-jsdom + jest-dev-server + node-fetch +- 模拟与工具 + - WebRTC 模拟:RTCPeerConnection、SessionDescription、IceCandidate + - DOM/ResizeObserver 模拟:JSDOM 环境缺失时的 polyfill + - 信令模拟:MockSignaling 与管理器 + - 工具函数:等待、睡眠、唯一 ID、RTC 配置 + +```mermaid +graph LR +Jest["Jest"] --> TS["ts-jest"] +Jest --> JSDOM["jest-environment-jsdom"] +Jest --> DevServer["jest-dev-server"] +Jest --> WS["jest-websocket-mock"] +Client["客户端测试"] --> JSDOM +Client --> DevServer +Client --> Fetch["node-fetch"] +Server["服务端测试"] --> ExpressMock["@jest-mock/express"] +Server --> WS +``` + +图表来源 +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [package.json:1-60](file://package.json#L1-L60) +- [package.json:1-19](file://client/package.json#L1-L19) + +章节来源 +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [package.json:1-60](file://package.json#L1-L60) +- [package.json:1-19](file://client/package.json#L1-L19) + +## 性能考虑 +- 测试并发与超时 + - 客户端测试配置了较短的 testTimeout,避免长耗时阻塞 + - 使用延迟与等待函数替代硬编码 sleep,提升稳定性 +- 覆盖率与报告 + - 启用 v8 覆盖收集与输出目录,建议结合 CI 生成 LCOV 报告 + - 对关键路径(HTTP/WebSocket 信令、WebRTC 状态机)优先保证高覆盖率 +- 端到端成本 + - 真实服务进程启动与关闭需注意资源释放,Linux 平台增加等待时间 + +章节来源 +- [jest.config.js:170-171](file://client/jest.config.js#L170-L171) +- [jest.config.js:20-26](file://jest.config.js#L20-L26) +- [jest.config.js:33-34](file://jest.config.js#L33-L34) + +## 故障排查指南 +- 环境变量与脚本 + - 服务端脚本使用 Jest 直接执行 test/*.ts;确保 ts-jest 与 TypeScript 配置正确 + - 客户端脚本使用 Node VM 模块运行 Jest;确保 package.json 中的脚本与依赖一致 +- JSDOM 缺失 API + - 在 jest.setup.js 中注入 fetch、TextEncoder/Decoder、RTCPeerConnection、ResizeObserver + - 若仍报错,确认 jest-environment-jsdom 版本与 jest.setup.js 的引入顺序 +- WebRTC 模拟问题 + - 确认 PeerConnectionMock 的事件回调与状态机逻辑与实际浏览器行为一致 + - 对于 InvalidStateError 等异常,需在测试中显式断言 +- 信令模拟一致性 + - MockSignaling 与真实服务端的字段保持一致(connectionId、sdp、polite、candidate 等) + - 私有模式下仅配对连接可互相接收信令,否则应断言未收到 +- 端到端启动失败 + - 检查 jest-dev-server 的命令拼接与端口占用 + - Linux 平台在 teardown 后增加等待时间,避免进程残留导致后续测试失败 + +章节来源 +- [package.json:5-12](file://package.json#L5-L12) +- [package.json:5-8](file://client/package.json#L5-L8) +- [jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) +- [signaling.test.js:23-65](file://client/test/signaling.test.js#L23-L65) +- [peerconnectionmock.js:157-160](file://client/test/peerconnectionmock.js#L157-L160) + +## 结论 +本项目采用“单元测试 + 集成测试 + 端到端测试”的分层策略,结合 Jest 与多种模拟技术,实现了对 HTTP/WebSocket 信令、WebRTC 连接与输入远程控制的全面覆盖。通过 MockSignaling、PeerConnectionMock 与工具库,测试在可控环境中复现复杂交互;通过公共/私有模式与超时清理逻辑的验证,确保系统在真实场景下的健壮性。 + +## 附录 + +### 测试策略与组织 +- 单元测试 + - 针对纯函数与小模块,断言边界与异常 + - 示例:工具函数、模型类、状态转换 +- 集成测试 + - 组合模块与外部依赖,验证流程完整性 + - 示例:HTTP 信令、WebSocket 信令、WebRTC 状态机 +- 端到端测试 + - 启动真实服务进程,验证跨客户端交互 + - 示例:HTTP/WebSocket 信令通道、真实浏览器环境 + +章节来源 +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) + +### Jest 配置与使用要点 +- 服务端 + - 启用 ts-jest 转换,Node 环境,收集覆盖率,匹配 test/*.ts +- 客户端 + - JSDOM 环境,setupFilesAfterEnv 引入全局模拟,匹配 client/test/**/*.test.js +- 脚本 + - 服务端:npm/yarn test 指向 Jest 并传入测试文件 + - 客户端:使用 Node VM 模块运行 Jest + +章节来源 +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [jest.config.js:1-196](file://client/jest.config.js#L1-L196) +- [package.json:5-12](file://package.json#L5-L12) +- [package.json:5-8](file://client/package.json#L5-L8) + +### 覆盖率要求与报告 +- 覆盖率收集 + - 开启 collectCoverage,输出目录为 coverage,提供 v8 覆盖器 +- 报告生成 + - 建议在 CI 中生成 LCOV 报告并与平台集成 +- 要求建议 + - 关键模块(HTTP/WebSocket 信令、WebRTC 状态机)覆盖率不低于 80% + - 重要分支与异常路径必须覆盖 + +章节来源 +- [jest.config.js:20-26](file://jest.config.js#L20-L26) +- [jest.config.js:33-34](file://jest.config.js#L33-L34) + +### WebRTC 功能测试示例 +- 信令处理测试 + - 使用 MockSignaling 验证公共/私有模式下的 offer/answer/candidate 分发 + - 使用 jest-websocket-mock 验证 WebSocket 信令通道 +- Peer 连接测试 + - 使用 PeerConnectionMock 验证 addTrack/addTransceiver/createDataChannel 的事件触发 + - 验证 onGotDescription/onGotCandidate 的状态变化与候选收集 + +章节来源 +- [mocksignaling.js:1-225](file://client/test/mocksignaling.js#L1-L225) +- [peerconnectionmock.js:1-316](file://client/test/peerconnectionmock.js#L1-L316) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +### 客户端测试与服务器端测试示例 +- 客户端 + - 信令:公共/私有模式、HTTP/WebSocket 三种通道 + - WebRTC:构造、关闭、事件触发、候选收集 + - 输入远程控制:事件捕获与发送 +- 服务器端 + - HTTP 信令:会话/连接生命周期、消息收发、超时清理 + - WebSocket 信令:多客户端广播、断开通知 + +章节来源 +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) +- [sender.test.js:1-143](file://client/test/sender.test.js#L1-L143) +- [httphandler.test.ts:1-510](file://test/httphandler.test.ts#L1-L510) +- [websockethandler.test.ts:1-191](file://test/websockethandler.test.ts#L1-L191) + +### 测试最佳实践与调试技巧 +- 最佳实践 + - 使用 describe/it 分层组织测试,命名清晰表达意图 + - 使用 beforeEach/beforeAll 准备测试上下文,避免重复代码 + - 对异步流程使用 waitFor/sleep 替代固定延时 + - 对模拟对象进行 spy/restore,避免副作用影响其他测试 +- 调试技巧 + - 在 jest.setup.js 注入必要 polyfill,减少环境差异 + - 使用 console.log 或断点定位事件触发时机 + - 对 WebSocket 与 HTTP 信令分别断言消息字段与数量 + +章节来源 +- [jest.setup.js:1-35](file://client/jest.setup.js#L1-L35) +- [signaling.test.js:1-485](file://client/test/signaling.test.js#L1-L485) +- [peerconnection.test.js:1-251](file://client/test/peerconnection.test.js#L1-L251) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/SSL_TLS 配置.md b/.qoder/repowiki/zh/content/部署指南/SSL_TLS 配置.md new file mode 100644 index 0000000..ab929ac --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/SSL_TLS 配置.md @@ -0,0 +1,346 @@ +# SSL/TLS 配置 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/log.ts](file://src/log.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [package.json](file://package.json) +- [run.bat](file://run.bat) +- [server.cert](file://server.cert) +- [server.key](file://server.key) +- [tsconfig.json](file://tsconfig.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件围绕当前代码库中的 HTTPS 与证书使用现状,提供一份面向实际部署的 SSL/TLS 配置参考。重点涵盖以下方面: +- 当前 HTTPS 启用方式与证书加载机制 +- 证书文件位置与格式要求 +- HTTPS 服务器启动参数与运行脚本 +- 与证书相关的配置项与环境变量 +- 为后续扩展 ACME 自动续期、HSTS、OCSP Stapling 等安全加固提供落地建议 + +说明:当前仓库已内置示例证书与密钥文件,用于本地演示 HTTPS;但未包含 Let's Encrypt 或商业证书的自动化流程、ACME 续期脚本等。本文将基于现有实现,给出可操作的扩展路径与最佳实践。 + +## 项目结构 +与 SSL/TLS 相关的关键文件与职责如下: +- 证书与密钥:server.key、server.cert +- 应用入口与 HTTPS 启动:src/index.ts +- Express 服务器创建与静态资源:src/server.ts +- 运行脚本与命令行参数:package.json、run.bat +- 日志与选项定义:src/log.ts、src/class/options.ts +- WebSocket 信令服务:src/websocket.ts +- TypeScript 编译配置:tsconfig.json + +```mermaid +graph TB +A["应用入口
src/index.ts"] --> B["HTTPS 服务器创建
读取 server.key / server.cert"] +A --> C["Express 应用创建
src/server.ts"] +C --> D["静态资源与路由
客户端页面与 API"] +A --> E["WebSocket 信令服务
src/websocket.ts"] +F["运行脚本
package.json"] --> A +G["启动批处理
run.bat"] --> A +H["证书与密钥
server.key / server.cert"] --> B +``` + +图表来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) +- [server.key](file://server.key) +- [server.cert](file://server.cert) + +章节来源 +- [src/index.ts:13-106](file://src/index.ts#L13-L106) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) +- [server.key](file://server.key) +- [server.cert](file://server.cert) + +## 核心组件 +- HTTPS 启用与证书加载 + - 在应用入口中,当启用 HTTPS 时,通过读取 server.key 与 server.cert 文件创建 HTTPS 服务器,并监听指定端口。 + - 证书与私钥文件路径可通过命令行参数覆盖,默认值指向项目根目录下的 server.key 与 server.cert。 +- Express 应用与静态资源 + - Express 应用负责提供前端页面、API 接口与静态资源;HTTPS 仅影响传输层安全,不影响应用逻辑。 +- 运行脚本与参数 + - package.json 中的 start 脚本默认以 HTTPS 启动,并传入 -s、-p、-k、-c 等参数。 + - run.bat 提供 Windows 环境下的构建与启动流程示例。 +- 日志与选项 + - 日志系统支持多级别输出,便于排障。 + - Options 接口定义了端口、HTTPS 开关、证书与密钥路径、信令类型、日志级别等配置项。 + +章节来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/index.ts:20-41](file://src/index.ts#L20-L41) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 架构总览 +下图展示了 HTTPS 启动与请求处理的整体流程,以及证书加载与静态资源服务的关系。 + +```mermaid +sequenceDiagram +participant CLI as "命令行/脚本" +participant App as "应用入口
src/index.ts" +participant HTTPS as "HTTPS 服务器" +participant Express as "Express 应用
src/server.ts" +participant WS as "WebSocket 信令
src/websocket.ts" +CLI->>App : 传入参数(-s/-p/-k/-c) +App->>App : 解析 Options 并校验 +App->>HTTPS : 读取 server.key / server.cert 创建 HTTPS 服务器 +HTTPS-->>App : 监听端口并输出访问地址 +App->>Express : 初始化 Express 应用 +Express-->>App : 提供静态资源与 API +App->>WS : 启动 WebSocket 信令服务 +Note over HTTPS,Express : 所有请求均通过 HTTPS 传输 +``` + +图表来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:15-19](file://src/websocket.ts#L15-L19) + +章节来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:15-19](file://src/websocket.ts#L15-L19) + +## 详细组件分析 + +### HTTPS 启动与证书加载 +- 参数解析与默认值 + - HTTPS 开关、端口、证书与密钥路径均可通过命令行参数配置;默认启用 HTTPS。 +- 证书与密钥读取 + - 使用文件系统读取 server.key 与 server.cert,作为 HTTPS 服务器的凭据。 +- 启动行为 + - 成功启动后输出 https:// 访问地址;若未启用 HTTPS,则输出 http://。 + +```mermaid +flowchart TD +Start(["应用启动"]) --> Parse["解析命令行参数
确定 HTTPS 与证书路径"] +Parse --> Secure{"是否启用 HTTPS?"} +Secure --> |是| LoadCert["读取 server.key / server.cert"] +Secure --> |否| HTTPListen["启动 HTTP 服务器"] +LoadCert --> HTTPSListen["启动 HTTPS 服务器并监听端口"] +HTTPSListen --> LogAddr["输出 https:// 访问地址"] +HTTPListen --> LogAddr +LogAddr --> End(["服务就绪"]) +``` + +图表来源 +- [src/index.ts:20-41](file://src/index.ts#L20-L41) +- [src/index.ts:55-74](file://src/index.ts#L55-L74) + +章节来源 +- [src/index.ts:20-41](file://src/index.ts#L20-L41) +- [src/index.ts:55-74](file://src/index.ts#L55-L74) + +### Express 应用与静态资源 +- CORS 与日志 + - 默认开启跨域与 Morgan 日志记录,日志级别可配置。 +- 路由与静态资源 + - 提供根路径页面、/signaling 信令路由、静态资源目录等。 +- 上传与媒体资源 + - 提供头像上传与静态媒体访问路径。 + +章节来源 +- [src/server.ts:18-20](file://src/server.ts#L18-L20) +- [src/server.ts:25-39](file://src/server.ts#L25-L39) +- [src/server.ts:62-86](file://src/server.ts#L62-L86) + +### WebSocket 信令服务 +- 与 HTTP 服务器共享底层 TCP 连接 + - WebSocket 服务器依附于同一 HTTP 服务器实例,因此也受 HTTPS 保护。 +- 连接生命周期管理 + - 连接建立、消息分发、断开清理等逻辑由处理器模块完成。 + +章节来源 +- [src/websocket.ts:15-19](file://src/websocket.ts#L15-L19) +- [src/websocket.ts:27-38](file://src/websocket.ts#L27-L38) +- [src/websocket.ts:44-114](file://src/websocket.ts#L44-L114) + +### 运行脚本与参数 +- package.json + - start 脚本默认以 HTTPS 启动,并传入 -s、-p、-k、-c 等参数。 +- run.bat + - Windows 环境下先构建再启动,便于本地调试。 + +章节来源 +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) + +### 证书与密钥文件 +- server.key 与 server.cert + - 示例证书与密钥文件位于项目根目录,用于演示 HTTPS 功能。 + - 请确保文件权限正确,避免被其他用户读取。 + +章节来源 +- [server.key](file://server.key) +- [server.cert](file://server.cert) + +## 依赖关系分析 +- 应用入口依赖文件系统读取证书与密钥,依赖 Express 提供 Web 服务,依赖 WebSocket 提供信令通道。 +- Express 应用依赖日志模块与配置模块,提供静态资源与 API。 +- 运行脚本与批处理文件负责编译与启动流程。 + +```mermaid +graph LR +Index["src/index.ts"] --> FS["文件系统
读取 server.key / server.cert"] +Index --> Express["Express 应用
src/server.ts"] +Express --> Static["静态资源与路由"] +Index --> WS["WebSocket 信令
src/websocket.ts"] +Run["package.json 脚本"] --> Index +Bat["run.bat"] --> Index +``` + +图表来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:15-19](file://src/websocket.ts#L15-L19) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) + +章节来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:15-19](file://src/websocket.ts#L15-L19) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) + +## 性能考虑 +- 证书与密钥读取 + - 采用同步读取方式,启动阶段一次性加载,运行时无额外 IO 开销。 +- 日志级别 + - 可通过日志级别控制输出量,避免在高并发场景下产生过多 I/O。 +- 静态资源 + - 建议在生产环境中配合反向代理缓存与压缩,提升静态资源访问效率。 + +章节来源 +- [src/index.ts:57-58](file://src/index.ts#L57-L58) +- [src/log.ts:15-24](file://src/log.ts#L15-L24) + +## 故障排查指南 +- 无法启动 HTTPS 服务器 + - 检查 server.key 与 server.cert 是否存在且可读。 + - 确认端口未被占用,且进程具备相应权限。 +- 访问出现证书错误 + - 若使用自签名证书,请在浏览器或客户端信任该证书。 + - 确认域名与证书匹配,避免主机名不一致导致的校验失败。 +- 日志定位问题 + - 使用日志模块提供的不同级别输出,结合 Express 的 Morgan 日志,快速定位请求异常。 + +章节来源 +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [src/server.ts:18-20](file://src/server.ts#L18-L20) + +## 结论 +当前代码库已具备基本的 HTTPS 启用能力与证书加载机制,适合在开发与演示环境中使用。若需在生产环境长期稳定运行,建议进一步完善以下方面: +- 引入 ACME 自动续期(如 acme.sh),实现 Let's Encrypt 免费证书的自动化申请与续期。 +- 部署反向代理(如 Nginx/Caddy),统一处理 TLS 终止、证书管理与安全头部。 +- 配置 HSTS、OCSP Stapling、前向保密等安全策略,提升整体安全性与性能。 + +## 附录 + +### 证书与密钥文件位置 +- server.key:HTTPS 私钥文件 +- server.cert:HTTPS 证书文件 + +章节来源 +- [server.key](file://server.key) +- [server.cert](file://server.cert) + +### HTTPS 启动参数与脚本 +- 命令行参数 + - -s/--secure:启用 HTTPS + - -p/--port:监听端口 + - -k/--keyfile:私钥文件路径 + - -c/--certfile:证书文件路径 +- 启动脚本 + - package.json 的 start 脚本默认以 HTTPS 启动 + - run.bat 提供 Windows 环境下的构建与启动流程 + +章节来源 +- [src/index.ts:20-41](file://src/index.ts#L20-L41) +- [package.json:9-10](file://package.json#L9-L10) +- [run.bat:8](file://run.bat#L8) + +### 与证书相关的配置项与环境变量 +- Options 接口定义了 secure、port、keyfile、certfile、type、mode、logging 等配置项 +- 环境变量映射 + - PORT、SECURE、KEYFILE、CERTFILE、TYPE、MODE、LOGGING + +章节来源 +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/index.ts:20-41](file://src/index.ts#L20-L41) + +### 证书格式与转换(概念性说明) +- PEM 与 DER + - PEM 是文本格式,DER 是二进制格式;两者可互相转换。 +- PKCS#12 + - 包含私钥与证书链,常用于导入导出。 +- OpenSSL 常用命令(概念性说明) + - 证书转 DER:openssl x509 -in server.pem -outform DER -out server.der + - DER 转 PEM:openssl x509 -in server.der -inform DER -out server.pem -outform PEM + - 生成自签名证书:openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes + - 导出 PKCS#12:openssl pkcs12 -export -out keystore.p12 -inkey key.pem -in cert.pem + +说明:本节为通用概念说明,不直接对应具体源码文件。 + +### Let's Encrypt 与商业证书申请(概念性说明) +- Let's Encrypt + - 使用 acme.sh 等工具自动申请与续期免费证书。 +- 商业证书 + - 通过 CA 申请,通常包含中间证书链,需按要求配置证书链文件。 +- 证书链验证 + - 生产环境应提供完整链路,避免中间证书缺失导致校验失败。 + +说明:本节为通用概念说明,不直接对应具体源码文件。 + +### HTTPS 服务器配置要点(概念性说明) +- TLS 版本 + - 建议禁用过时版本,启用现代 TLS 1.2/1.3。 +- 加密套件 + - 优先选择支持前向保密的套件,避免弱加密算法。 +- 证书链 + - 确保证书链完整,避免客户端校验失败。 + +说明:本节为通用概念说明,不直接对应具体源码文件。 + +### 证书自动续期方案(概念性说明) +- acme.sh + - 支持自动申请、验证与续期,可配置定时任务。 +- 定时任务 + - 使用 crontab 或计划任务定期执行续期检查与重启服务。 + +说明:本节为通用概念说明,不直接对应具体源码文件。 + +### 安全最佳实践(概念性说明) +- HSTS 头部 + - 设置严格传输安全,提升抗降级攻击能力。 +- OCSP Stapling + - 减少在线证书状态查询延迟与隐私泄露风险。 +- 前向保密 + - 使用支持 ECDHE 的套件,确保会话密钥不被历史泄露影响。 + +说明:本节为通用概念说明,不直接对应具体源码文件。 \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/反向代理配置.md b/.qoder/repowiki/zh/content/部署指南/反向代理配置.md new file mode 100644 index 0000000..cb747c3 --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/反向代理配置.md @@ -0,0 +1,632 @@ +# 反向代理配置 + + +**本文档引用的文件** +- [src/server.ts](file://src/server.ts) +- [src/index.ts](file://src/index.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/服务端接口与WebSocket消息类型.md](file://src/服务端接口与WebSocket消息类型.md) +- [package.json](file://package.json) +- [run.bat](file://run.bat) +- [client/public/onebyone/README.md](file://client/public/onebyone/README.md) +- [client/public/onebyone/code-structure.md](file://client/public/onebyone/code-structure.md) + + +## 目录 +1. [简介](#简介) +2. [项目架构概览](#项目架构概览) +3. [WebRTC 场景特殊考虑](#webrtc-场景特殊考虑) +4. [Nginx 反向代理配置](#nginx-反向代理配置) +5. [Apache 反向代理配置](#apache-反向代理配置) +6. [性能优化建议](#性能优化建议) +7. [安全配置](#安全配置) +8. [故障排除指南](#故障排除指南) +9. [总结](#总结) + +## 简介 + +本指南针对视频流服务器项目提供完整的反向代理配置方案。该项目是一个基于 WebRTC 和 WebSocket 的视频通话应用,支持 HTTP 轮询和 WebSocket 两种信令协议,以及公共模式和私有模式两种通信方式。 + +项目采用 Node.js + Express 构建,支持 HTTPS 和 WebSocket 升级,具备完整的 WebRTC 信令交换能力。反向代理配置对于部署此类实时通信应用至关重要,需要特别关注 WebSocket 升级、长连接保持、头部转发和压缩配置等方面。 + +## 项目架构概览 + +```mermaid +graph TB +subgraph "客户端应用" +A[WebRTC 客户端] +B[浏览器应用] +end +subgraph "反向代理层" +C[Nginx/Apache] +D[负载均衡器] +end +subgraph "应用服务器" +E[Express 服务器] +F[WebSocket 服务器] +G[HTTP 轮询服务器] +end +subgraph "数据库/存储" +H[会话存储] +I[文件上传存储] +end +A --> C +B --> C +C --> E +C --> F +C --> G +E --> H +E --> I +F --> H +G --> H +``` + +**图表来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) + +### 核心组件分析 + +项目包含以下关键组件: + +1. **Express 服务器**:处理 HTTP 请求、静态文件服务和 API 路由 +2. **WebSocket 服务器**:处理实时信令消息和 WebRTC 信令交换 +3. **HTTP 轮询服务器**:提供 HTTP 轮询模式的信令服务 +4. **会话管理系统**:管理用户会话和连接状态 +5. **静态资源服务**:提供前端应用和媒体文件 + +**章节来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) + +## WebRTC 场景特殊考虑 + +### WebSocket 升级要求 + +WebRTC 应用对 WebSocket 升级有严格要求: + +```mermaid +sequenceDiagram +participant Client as 客户端 +participant Proxy as 反向代理 +participant Server as 应用服务器 +participant WS as WebSocket服务器 +Client->>Proxy : HTTP Upgrade 请求 +Proxy->>Proxy : 检查 Upgrade 头部 +Proxy->>Server : 转发升级请求 +Server->>WS : 创建 WebSocket 连接 +WS->>Server : 连接建立成功 +Server->>Proxy : 返回 101 Switching Protocols +Proxy->>Client : 完成协议升级 +Client->>WS : 开始实时通信 +``` + +**图表来源** +- [src/websocket.ts:15-117](file://src/websocket.ts#L15-L117) +- [src/class/websockethandler.ts:27-115](file://src/class/websockethandler.ts#L27-L115) + +### 长连接保持机制 + +项目实现了心跳检测机制来维持长连接: + +- **心跳间隔**:30秒发送一次 ping 消息 +- **超时检测**:60秒无活动自动断开连接 +- **活动跟踪**:记录最后活动时间戳 + +### 通信模式支持 + +项目支持两种通信模式: + +1. **公共模式**:所有客户端都可以直接通信 +2. **私有模式**:1对多房间通信(1个host + 多个participants) + +**章节来源** +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [src/服务端接口与WebSocket消息类型.md:489-504](file://src/服务端接口与WebSocket消息类型.md#L489-L504) + +## Nginx 反向代理配置 + +### 基础配置模板 + +以下提供完整的 Nginx 配置示例: + +```nginx +# HTTP 到 HTTPS 重定向 +server { + listen 80; + server_name your-domain.com www.your-domain.com; + + # 强制 HTTPS 重定向 + return 301 https://$server_name$request_uri; +} + +# HTTPS 服务器配置 +server { + listen 443 ssl http2; + server_name your-domain.com www.your-domain.com; + + # SSL 证书配置 + ssl_certificate /path/to/your/certificate.crt; + ssl_certificate_key /path/to/your/private.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # 安全头部 + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options DENY always; + add_header X-Content-Type-Options nosniff always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # 静态资源缓存 + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # WebSocket 升级配置 + location / { + proxy_pass http://localhost:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket 超时设置 + proxy_read_timeout 86400; + proxy_send_timeout 86400; + + # 代理缓冲区 + proxy_buffering on; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + } + + # API 端点配置 + location /api/ { + proxy_pass http://localhost:8080/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 静态文件服务 + location /uploads/ { + alias /path/to/your/uploads/; + expires 30d; + add_header Cache-Control "public, immutable"; + } +} +``` + +### 负载均衡配置 + +```nginx +# 负载均衡配置 +upstream websocket_backend { + server 127.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s; + server 127.0.0.1:8081 weight=2 max_fails=3 fail_timeout=30s; + server 127.0.0.1:8082 backup; +} + +upstream http_backend { + server 127.0.0.1:8080; + server 127.0.0.1:8081; + server 127.0.0.1:8082; +} + +server { + listen 443 ssl http2; + server_name your-domain.com; + + # WebSocket 负载均衡 + location / { + proxy_pass http://websocket_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket 超时设置 + proxy_read_timeout 86400; + proxy_send_timeout 86400; + } + + # HTTP 负载均衡 + location /api/ { + proxy_pass http://http_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +### 静态资源缓存策略 + +```nginx +# 静态资源缓存配置 +location ~* \.(css|js)$ { + expires 1y; + add_header Cache-Control "public, immutable, stale-while-revalidate=31536000"; + add_header Vary Accept-Encoding; + gzip_static on; +} + +location ~* \.(png|jpg|jpeg|gif|ico|svg|webp)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + add_header Vary Accept-Encoding; +} + +location ~* \.(woff|woff2|ttf|otf)$ { + expires 1y; + add_header Cache-Control "public, immutable"; +} + +# 压缩配置 +gzip on; +gzip_vary on; +gzip_min_length 1024; +gzip_types text/plain text/css application/json application/javascript application/xml+rss image/svg+xml; +gzip_comp_level 6; +gzip_proxied any; +gzip_disable "MSIE [1-6]\."; +``` + +**章节来源** +- [src/server.ts:22-28](file://src/server.ts#L22-L28) +- [src/server.ts:85-86](file://src/server.ts#L85-L86) +- [package.json:9](file://package.json#L9) + +## Apache 反向代理配置 + +### 基础 Apache 配置 + +```apache +# 启用必要的模块 +LoadModule proxy_module modules/mod_proxy.so +LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule ssl_module modules/mod_ssl.so +LoadModule headers_module modules/mod_headers.so + + + ServerName your-domain.com + ServerAlias www.your-domain.com + + # HTTP 到 HTTPS 重定向 + RewriteEngine On + RewriteCond %{HTTPS} off + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=310,L] + + + + ServerName your-domain.com + ServerAlias www.your-domain.com + + # SSL 配置 + SSLEngine on + SSLCertificateFile /path/to/your/certificate.crt + SSLCertificateKeyFile /path/to/your/private.key + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384 + + # 安全头部 + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + Header always set X-Frame-Options "DENY" + Header always set X-Content-Type-Options "nosniff" + + # 反向代理配置 + ProxyPreserveHost On + ProxyRequests Off + + # WebSocket 升级 + ProxyPass /ws/ ws://127.0.0.1:8080/ + ProxyPassReverse /ws/ ws://127.0.0.1:8080/ + + # HTTP 轮询 + ProxyPass /api/ http://127.0.0.1:8080/api/ + ProxyPassReverse /api/ http://127.0.0.1:8080/api/ + + # 静态资源 + ProxyPass /uploads/ http://127.0.0.1:8080/uploads/ + ProxyPassReverse /uploads/ http://127.0.0.1:8080/uploads/ + + # 代理超时设置 + ProxyTimeout 86400 + ProxyPass / http://127.0.0.1:8080/ + ProxyPassReverse / http://127.0.0.1:8080/ + +``` + +### mod_proxy_wstunnel 模块启用 + +确保启用 WebSocket 支持: + +```apache +# 启用 WebSocket 隧道 +LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so + +# WebSocket 升级配置 + + ProxyPass ws://127.0.0.1:8080/ + ProxyPassReverse ws://127.0.0.1:8080/ + ProxySet match=Upgrade + ProxySet force-proxy-request-1.0 1 + +``` + +### 虚拟主机配置 + +```apache +# 主虚拟主机配置 + + ServerName your-domain.com + DocumentRoot /var/www/html + + # SSL 证书 + SSLEngine on + SSLCertificateFile /etc/ssl/certs/your-domain.crt + SSLCertificateKeyFile /etc/ssl/private/your-domain.key + + # 安全配置 + SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 + SSLCompression off + SSLSessionTickets off + + # 头部安全 + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + Header always set X-Frame-Options "SAMEORIGIN" + Header always set X-Content-Type-Options "nosniff" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + + # 反向代理设置 + ProxyPreserveHost On + ProxyRequests Off + + # WebSocket 支持 + RewriteEngine On + RewriteCond %{HTTP:Upgrade} websocket [NC] + RewriteCond %{HTTP:Connection} upgrade [NC] + RewriteRule ^/?(.*) "ws://127.0.0.1:8080/$1" [P,L] + + # 一般 HTTP 请求 + ProxyPass / http://127.0.0.1:8080/ + ProxyPassReverse / http://127.0.0.1:8080/ + +``` + +**章节来源** +- [src/server.ts:22](file://src/server.ts#L22) +- [src/server.ts:25](file://src/server.ts#L25) + +## 性能优化建议 + +### 连接池配置 + +```nginx +# 连接池优化 +upstream backend { + server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; + keepalive 32; +} + +server { + location / { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 连接保持 + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } +} +``` + +### 超时设置优化 + +```nginx +# 超时配置优化 +proxy_connect_timeout 10s; +proxy_send_timeout 120s; +proxy_read_timeout 120s; +proxy_buffering on; +proxy_buffer_size 128k; +proxy_buffers 4 256k; +proxy_busy_buffers_size 256k; + +# WebSocket 超时 +location / { + proxy_read_timeout 86400; + proxy_send_timeout 86400; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; +} +``` + +### 错误处理配置 + +```nginx +# 错误处理 +error_page 502 503 504 /50x.html; +location = /50x.html { + internal; +} + +# 健康检查 +location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; +} + +# 缓存策略 +location ~* \.(css|js|png|jpg|jpeg|gif|ico)$ { + add_header Cache-Control "public, immutable, stale-while-revalidate=31536000"; + expires 1y; +} +``` + +**章节来源** +- [src/websocket.ts:19](file://src/websocket.ts#L19) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +## 安全配置 + +### CORS 设置 + +项目默认启用了 CORS 支持,但建议在生产环境中进行更严格的配置: + +```nginx +# CORS 配置 +add_header Access-Control-Allow-Origin "*" always; +add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; +add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-Real-IP" always; +add_header Access-Control-Max-Age 86400 always; + +# 预检请求处理 +location OPTIONS { + add_header Access-Control-Allow-Origin "*"; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-Real-IP"; + add_header Access-Control-Max-Age 86400; + return 204; +} +``` + +### HSTS 配置 + +```nginx +# HSTS 配置 +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; +add_header X-Frame-Options "DENY" always; +add_header X-Content-Type-Options "nosniff" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Permissions-Policy "geolocation=(), microphone=()" always; +``` + +### 安全头部 + +```apache +# 安全头部配置 +Header always set X-Content-Type-Options "nosniff" +Header always set X-Frame-Options "DENY" +Header always set X-XSS-Protection "1; mode=block" +Header always set Referrer-Policy "strict-origin-when-cross-origin" +Header always set Feature-Policy "geolocation=(), microphone=()" +Header always set Expect-CT "enforce, max-age=86400" +``` + +### SSL/TLS 优化 + +```nginx +# SSL 优化配置 +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; +ssl_prefer_server_ciphers off; +ssl_session_cache shared:SSL:10m; +ssl_session_timeout 10m; +ssl_dhparam /path/to/dhparam.pem; +ssl_ecdh_curve prime256v1; +``` + +**章节来源** +- [src/server.ts:22](file://src/server.ts#L22) +- [src/server.ts:25](file://src/server.ts#L25) + +## 故障排除指南 + +### WebSocket 连接问题 + +常见 WebSocket 连接问题及解决方案: + +1. **升级失败** + - 检查 `Upgrade` 和 `Connection` 头部是否正确传递 + - 确认代理服务器版本支持 WebSocket 升级 + +2. **连接超时** + - 增加 `proxy_read_timeout` 和 `proxy_send_timeout` + - 检查防火墙设置允许 WebSocket 端口 + +3. **心跳检测失败** + - 确认客户端和服务端的心跳配置一致 + - 检查网络中间设备是否丢弃长连接 + +### 性能问题诊断 + +```nginx +# 启用详细日志 +error_log /var/log/nginx/error.log debug; +access_log /var/log/nginx/access.log combined; + +# 监控连接状态 +location /status { + stub_status on; + access_log off; + allow 127.0.0.1; + deny all; +} +``` + +### 负载均衡问题 + +```nginx +# 健康检查配置 +upstream backend { + server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; + server 127.0.0.1:8081 max_fails=3 fail_timeout=30s; + server 127.0.0.1:8082 backup; +} + +# 会话粘性(可选) +ip_hash; +``` + +**章节来源** +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +## 总结 + +本指南提供了针对视频流服务器项目的完整反向代理配置方案。关键要点包括: + +1. **WebSocket 升级**:必须正确配置 Upgrade 头部和 Connection 字段 +2. **长连接保持**:合理设置超时参数和心跳检测 +3. **负载均衡**:支持多实例部署和故障转移 +4. **性能优化**:连接池、缓存策略和压缩配置 +5. **安全配置**:CORS、HSTS 和安全头部设置 + +部署时建议: +- 先在测试环境验证配置 +- 逐步增加流量监控 +- 定期检查日志和性能指标 +- 制定回滚和应急响应计划 + +通过正确的反向代理配置,可以确保 WebRTC 应用的稳定运行和良好用户体验。 \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/容器化部署.md b/.qoder/repowiki/zh/content/部署指南/容器化部署.md new file mode 100644 index 0000000..74092ba --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/容器化部署.md @@ -0,0 +1,351 @@ +# 容器化部署 + + +**本文引用的文件** +- [package.json](file://package.json) +- [tsconfig.json](file://tsconfig.json) +- [tsconfig.build.json](file://tsconfig.build.json) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/服务端接口与WebSocket消息类型.md](file://src/服务端接口与WebSocket消息类型.md) +- [.gitignore](file://.gitignore) +- [run.bat](file://run.bat) +- [server.cert](file://server.cert) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本文件面向视频流服务器项目,提供从本地开发到容器化部署的完整指南,覆盖以下主题: +- Docker 镜像构建:Dockerfile 配置与多阶段构建优化 +- Docker Compose:服务定义、端口映射、卷挂载、环境变量 +- 容器网络:端口暴露、内部通信、外部访问 +- Kubernetes 部署:Deployment、Service、Ingress 资源定义 +- 安全配置:证书、密钥、只读根文件系统、非 root 用户 +- 资源限制与健康检查 +- 监控与日志最佳实践 + +## 项目结构 +该仓库为基于 Node.js + Express + WebSocket 的视频信令与静态资源服务,核心运行方式如下: +- 使用命令行参数控制端口、HTTPS、信令协议类型、通信模式、日志级别等 +- 默认监听端口可在脚本中查看,支持 HTTP 或 HTTPS +- 提供静态页面与上传接口,集成 Swagger 文档 +- 通过 HTTP 轮询或 WebSocket 提供 WebRTC 信令 + +```mermaid +graph TB +subgraph "应用进程" +A["Express 应用
src/server.ts"] +B["HTTP/HTTPS 服务器
src/index.ts"] +C["WebSocket 信令
src/websocket.ts"] +D["信令路由
src/signaling.ts"] +end +subgraph "静态资源" +E["客户端页面
client/public"] +F["前端模块
client/src"] +end +subgraph "配置与脚本" +G["启动脚本
package.json scripts"] +H["编译配置
tsconfig.json/tsconfig.build.json"] +I["运行批处理
run.bat"] +end +B --> A +A --> D +A --> E +A --> F +B --> C +G --> B +H --> G +I --> G +``` + +**图表来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/websocket.ts:15-39](file://src/websocket.ts#L15-L39) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [package.json:5-12](file://package.json#L5-L12) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [run.bat:1-6](file://run.bat#L1-L6) + +**章节来源** +- [package.json:5-12](file://package.json#L5-L12) +- [src/index.ts:20-40](file://src/index.ts#L20-L40) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [run.bat:1-6](file://run.bat#L1-L6) + +## 核心组件 +- 应用入口与参数解析:负责读取命令行参数与环境变量,初始化 Express 应用与 HTTPS/HTTP 服务器 +- Express 服务器:配置 CORS、日志、静态资源、上传接口、Swagger 文档 +- WebSocket 信令:基于 ws 的信令通道,支持连接管理、offer/answer/candidate 广播等 +- 信令路由:HTTP REST 接口,按会话隔离,支持轮询与 WebSocket 两种模式 +- 配置项接口:统一承载端口、HTTPS、信令类型、通信模式、日志级别等 + +**章节来源** +- [src/index.ts:13-106](file://src/index.ts#L13-L106) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/class/options.ts:1-9](file://src/class/options.ts#L1-L9) + +## 架构总览 +容器化部署建议采用“单容器一进程”原则,将应用打包为镜像并在容器内运行。网络层面需明确: +- 端口暴露:HTTP/HTTPS 端口对外暴露 +- 内部通信:WebSocket 与 HTTP 信令在同一进程内处理 +- 外部访问:通过反向代理或 Ingress 统一入口 + +```mermaid +graph TB +subgraph "宿主机" +L["负载均衡/反向代理
Nginx/Ingress"] +end +subgraph "容器" +S["应用进程
Node.js + Express + WebSocket"] +P["端口映射
80/443 -> 容器端口"] +V["卷挂载
静态资源/上传目录"] +end +L --> P +P --> S +V -.-> S +``` + +[此图为概念性架构示意,不直接对应具体源码文件,故不提供图表来源] + +## 详细组件分析 + +### Docker 镜像构建 +- 基础镜像选择:建议使用官方 Node.js LTS 镜像作为基础层 +- 多阶段构建: + - 第一阶段:安装依赖并编译 TypeScript 源码 + - 第二阶段:仅复制构建产物与必要运行时依赖,减小镜像体积 +- 工作目录与用户: + - 设置非 root 用户与只读根文件系统,提升安全性 +- 启动命令: + - 使用 package.json 中的 start 脚本,结合环境变量控制端口、HTTPS、信令类型、通信模式、日志级别 + +```mermaid +flowchart TD +Start(["开始"]) --> Stage1["阶段1:安装依赖与编译
Node LTS + npm ci + tsc"] +Stage1 --> Stage2["阶段2:复制构建产物
最小化运行时镜像"] +Stage2 --> Config["配置运行用户与权限
非root + 只读根"] +Config --> Expose["暴露端口
80/443"] +Expose --> Entrypoint["设置入口命令
npm start"] +Entrypoint --> End(["完成"]) +``` + +[此图为流程图示意,不直接对应具体源码文件,故不提供图表来源] + +**章节来源** +- [package.json:5-12](file://package.json#L5-L12) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [src/index.ts:20-40](file://src/index.ts#L20-L40) + +### Docker Compose 配置要点 +- 服务定义:单服务运行应用进程 +- 端口映射:将宿主 80/443 映射到容器端口 +- 卷挂载: + - 静态资源目录:client/public + - 上传目录:client/public/uploads(确保写权限) +- 环境变量:通过环境变量传递端口、HTTPS 开关、证书路径、信令类型、通信模式、日志级别 +- 依赖与健康检查:可选添加健康检查探针,探测 /config 或 /signaling + +```mermaid +sequenceDiagram +participant U as "用户" +participant C as "Compose 服务" +participant N as "Node 应用" +participant WS as "WebSocket 信令" +U->>C : 访问 / 或 /signaling +C->>N : 请求转发 +N-->>C : 返回静态页面/接口响应 +U->>WS : 建立 WebSocket 连接 +WS-->>U : 信令数据offer/answer/candidate +``` + +[此图为概念性交互示意,不直接对应具体源码文件,故不提供图表来源] + +**章节来源** +- [src/server.ts:25-86](file://src/server.ts#L25-L86) +- [src/websocket.ts:15-39](file://src/websocket.ts#L15-L39) +- [src/index.ts:20-40](file://src/index.ts#L20-L40) + +### 容器网络配置 +- 端口暴露:根据 HTTPS 开关暴露 80(HTTP)或 443(HTTPS),默认监听端口可在启动参数中调整 +- 内部通信:WebSocket 与 HTTP 信令在同一进程内,无需额外网络组件 +- 外部访问:建议通过反向代理或 Ingress 提供 TLS 终止与域名路由 + +```mermaid +flowchart TD +A["外部请求"] --> B{"是否启用 HTTPS?"} +B --> |是| C["443/TCP 映射"] +B --> |否| D["80/TCP 映射"] +C --> E["应用进程监听端口"] +D --> E +E --> F["HTTP 轮询 /signaling"] +E --> G["WebSocket /ws 信令"] +``` + +[此图为流程图示意,不直接对应具体源码文件,故不提供图表来源] + +**章节来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:25-86](file://src/server.ts#L25-L86) + +### Kubernetes 部署配置 +- Deployment:单副本或按需副本数,设置资源请求与限制 +- Service:ClusterIP/LoadBalancer,暴露 80/443 +- Ingress:TLS 终止、路径路由至 Service +- Secret:存放 server.key 与 server.cert +- ConfigMap:存放运行参数(如端口、信令类型、通信模式、日志级别) + +```mermaid +graph TB +subgraph "Kubernetes" +I["Ingress"] +S["Service"] +D["Deployment"] +P["Pod"] +end +I --> S +S --> D +D --> P +``` + +[此图为概念性部署示意,不直接对应具体源码文件,故不提供图表来源] + +**章节来源** +- [src/index.ts:20-40](file://src/index.ts#L20-L40) +- [src/server.ts:25-86](file://src/server.ts#L25-L86) + +### 容器安全配置 +- 证书与密钥: + - 将 server.key 与 server.cert 以 Secret 方式挂载,避免硬编码 + - 应用通过环境变量或挂载路径读取证书 +- 最小权限: + - 使用非 root 用户运行 + - 根文件系统只读,仅对上传目录进行写入 +- 网络策略:限制入站流量,仅开放必要端口 +- 配置注入:通过 ConfigMap/环境变量注入运行参数 + +**章节来源** +- [src/index.ts:55-65](file://src/index.ts#L55-L65) +- [server.cert:1-22](file://server.cert#L1-L22) +- [src/server.ts:44-57](file://src/server.ts#L44-L57) + +### 资源限制与健康检查 +- 资源限制:为 CPU 与内存设置 requests/limits,避免资源争抢 +- 健康检查: + - HTTP 探针:探测 /config 或 /signaling,确认服务可用 + - TCP 探针:若仅需端口存活,可使用 TCPSocket +- 重启策略:根据业务需求设置 restartPolicy + +**章节来源** +- [src/server.ts:25-26](file://src/server.ts#L25-L26) +- [src/index.ts:52-91](file://src/index.ts#L52-L91) + +### 监控与日志收集 +- 日志: + - 使用 morgan 输出访问日志,支持多种格式 + - 将日志输出到 stdout/stderr,便于容器平台采集 +- 指标: + - 可扩展 Prometheus 指标导出(如连接数、消息速率) +- 链路追踪: + - 结合 OpenTelemetry 或平台内置追踪能力 + +**章节来源** +- [src/server.ts:18-20](file://src/server.ts#L18-L20) +- [src/index.ts:62-72](file://src/index.ts#L62-L72) + +## 依赖关系分析 +- 应用入口依赖服务器创建与 WebSocket 信令 +- Express 服务器依赖信令路由与静态资源 +- 信令路由依赖 HTTP 处理器 +- 配置项接口贯穿各组件,作为统一输入 + +```mermaid +graph LR +IDX["src/index.ts"] --> SRV["src/server.ts"] +SRV --> SIG["src/signaling.ts"] +IDX --> WS["src/websocket.ts"] +SRV --> OPT["src/class/options.ts"] +``` + +**图表来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/websocket.ts:15-39](file://src/websocket.ts#L15-L39) +- [src/class/options.ts:1-9](file://src/class/options.ts#L1-L9) + +**章节来源** +- [src/index.ts:52-91](file://src/index.ts#L52-L91) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/websocket.ts:15-39](file://src/websocket.ts#L15-L39) +- [src/class/options.ts:1-9](file://src/class/options.ts#L1-L9) + +## 性能考虑 +- 多阶段构建减少镜像体积,缩短拉取时间 +- 仅复制构建产物与运行时依赖,避免 node_modules 进入最终镜像 +- 合理设置资源限制,避免 OOM +- WebSocket 与 HTTP 共用同一进程,降低进程间通信开销 +- 静态资源与上传目录分离,便于缓存与持久化 + +[本节为通用指导,不直接分析具体文件,故不提供章节来源] + +## 故障排查指南 +- 端口占用:确认容器端口未被占用,或调整映射端口 +- HTTPS 证书:检查 server.key 与 server.cert 是否正确挂载与读取 +- CORS 问题:确认前端域名与跨域配置 +- 上传失败:检查上传目录权限与磁盘空间 +- 信令异常:验证会话 ID 与 WebSocket 连接状态 + +**章节来源** +- [src/index.ts:55-65](file://src/index.ts#L55-L65) +- [src/server.ts:44-86](file://src/server.ts#L44-L86) +- [src/websocket.ts:27-38](file://src/websocket.ts#L27-L38) + +## 结论 +通过多阶段构建与最小化运行时镜像,结合 Compose/Kubernetes 的标准化编排,可实现稳定、安全、可观测的容器化部署。配合证书管理、资源限制与健康检查,可满足生产环境的高可用与高安全要求。 + +[本节为总结性内容,不直接分析具体文件,故不提供章节来源] + +## 附录 + +### 关键运行参数与环境变量 +- 端口:-p/--port,默认值来自环境变量 PORT +- HTTPS:-s/--secure,证书与密钥路径由 -k/--keyfile 与 -c/--certfile 指定 +- 信令类型:-t/--type,支持 websocket/http +- 通信模式:-m/--mode,public/private +- 日志级别:-l/--logging,支持 combined/dev/short/tiny/none + +**章节来源** +- [src/index.ts:20-40](file://src/index.ts#L20-L40) +- [package.json:9](file://package.json#L9) + +### API 与信令概览 +- 配置接口:返回当前信令类型、通信模式与日志级别 +- 上传接口:支持头像上传并重命名为用户 ID +- 信令接口:按会话隔离,支持连接管理与 WebRTC 信令交换 + +**章节来源** +- [src/server.ts:25-86](file://src/server.ts#L25-L86) +- [src/服务端接口与WebSocket消息类型.md:7-33](file://src/服务端接口与WebSocket消息类型.md#L7-L33) +- [src/服务端接口与WebSocket消息类型.md:37-83](file://src/服务端接口与WebSocket消息类型.md#L37-L83) +- [src/服务端接口与WebSocket消息类型.md:139-234](file://src/服务端接口与WebSocket消息类型.md#L139-L234) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/性能优化.md b/.qoder/repowiki/zh/content/部署指南/性能优化.md new file mode 100644 index 0000000..4279b72 --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/性能优化.md @@ -0,0 +1,399 @@ +# 性能优化 + + +**本文引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/log.ts](file://src/log.ts) +- [client/src/signaling.js](file://client/src/signaling.js) +- [client/src/peer.js](file://client/src/peer.js) +- [client/src/sender.js](file://client/src/sender.js) +- [client/src/logger.js](file://client/src/logger.js) +- [client/public/js/stats.js](file://client/public/js/stats.js) +- [client/public/onebyone/store.js](file://client/public/onebyone/store.js) +- [client/src/memoryhelper.js](file://client/src/memoryhelper.js) +- [package.json](file://package.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南围绕视频流服务器与前端 WebRTC 客户端的整体性能进行系统化优化,覆盖以下方面: +- 内存管理优化:垃圾回收调优、内存泄漏检测与进程监控 +- 并发处理优化:连接池配置、线程数调整、异步处理优化 +- 资源限制设置:文件描述符、网络连接、CPU 使用率控制 +- WebRTC 性能优化:编码参数、带宽自适应、网络质量监控 +- 缓存策略:静态资源、API 响应、浏览器缓存 +- 性能监控指标与基准测试方法 + +## 项目结构 +该项目采用前后端分离的结构: +- 服务端基于 Express 提供 HTTP/WebSocket 信令与静态资源托管,同时支持 HTTPS +- 客户端包含多种示例页面与 WebRTC 输入/渲染相关脚本,以及统计与日志工具 + +```mermaid +graph TB +subgraph "服务端" +IDX["入口: src/index.ts"] +SRV["HTTP 服务器: src/server.ts"] +WS["WebSocket 信令: src/websocket.ts"] +WSH["WS 处理器: src/class/websockethandler.ts"] +HTTPH["HTTP 信令处理器: src/class/httphandler.ts"] +SIG["信令路由: src/signaling.ts"] +LOG["日志: src/log.ts"] +end +subgraph "客户端" +CLISIG["信令客户端: client/src/signaling.js"] +PEER["Peer 连接: client/src/peer.js"] +SENDER["输入发送: client/src/sender.js"] +STATS["统计: client/public/js/stats.js"] +STORE["质量分析: client/public/onebyone/store.js"] +LOGGER["日志: client/src/logger.js"] +end +IDX --> SRV +SRV --> WS +SRV --> SIG +WS --> WSH +SIG --> HTTPH +CLISIG --> |"WebSocket"| WS +CLISIG --> |"HTTP 轮询"| SRV +PEER --> |"WebRTC 统计"| STATS +PEER --> |"WebRTC 统计"| STORE +SENDER --> |"输入事件"| PEER +LOG -.->|"服务端日志级别"| SRV +LOGGER -.->|"客户端调试开关"| CLISIG +``` + +图表来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) +- [client/public/js/stats.js:40-91](file://client/public/js/stats.js#L40-L91) +- [client/public/onebyone/store.js:1130-1380](file://client/public/onebyone/store.js#L1130-L1380) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +章节来源 +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) + +## 核心组件 +- 服务端启动与 HTTPS/HTTP 选择、日志级别、信令类型与运行模式 +- Express 中间件链:CORS、JSON/URL 编码、静态资源、Swagger 文档 +- WebSocket 信令服务器与处理器:连接生命周期、广播、1对多模式、心跳 +- HTTP 信令轮询:会话管理、超时清理、消息队列(offer/answer/candidate) +- 客户端信令:WebSocket 与 HTTP 轮询双栈;WebRTC Peer 连接与统计 +- 日志系统:服务端日志级别控制;客户端调试输出开关 + +章节来源 +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:63-137](file://src/class/websockethandler.ts#L63-L137) +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/signaling.ts:4-24](file://src/signaling.ts#L4-L24) +- [src/log.ts:15-24](file://src/log.ts#L15-L24) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [client/src/peer.js:37-82](file://client/src/peer.js#L37-L82) + +## 架构总览 +服务端通过 Express 提供 HTTP 接口与静态资源,WebSocket 作为实时信令通道;HTTP 轮询作为备用方案。客户端根据配置选择 WebSocket 或 HTTP 轮询进行信令交互,WebRTC 数据通道承载媒体与控制消息。 + +```mermaid +sequenceDiagram +participant C as "客户端" +participant S as "Express 服务器" +participant WS as "WebSocket 信令" +participant HTTP as "HTTP 信令" +participant H as "HTTP 处理器" +C->>S : "GET /config" +S-->>C : "{useWebSocket, mode, logging}" +alt "WebSocket 模式" +C->>WS : "建立连接" +WS-->>C : "connect" +C->>WS : "offer/answer/candidate/on-message" +WS->>WS : "路由/广播/心跳" +WS-->>C : "响应/广播" +else "HTTP 轮询模式" +C->>HTTP : "PUT /signaling (创建会话)" +HTTP-->>C : "{sessionId}" +loop "轮询" +C->>HTTP : "GET /signaling?fromtime=..." +HTTP->>H : "读取 offer/answer/candidate/disconnect" +H-->>HTTP : "聚合消息" +HTTP-->>C : "{messages, datetime}" +end +end +``` + +图表来源 +- [src/server.ts:25-41](file://src/server.ts#L25-L41) +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) + +## 详细组件分析 + +### 服务端启动与运行模式 +- 支持命令行参数:端口、HTTPS 证书、信令类型(websocket/http)、运行模式(public/private)、日志级别 +- 自动选择 HTTP 或 HTTPS 监听,打印可用地址 +- 信令类型校验与回退至默认值 +- WebSocket 信令服务器初始化并注入运行模式 + +章节来源 +- [src/index.ts:14-91](file://src/index.ts#L14-L91) + +### HTTP 服务器与静态资源 +- 开启 Morgan 日志(可配置) +- CORS 允许任意来源 +- 解析 JSON/URL 编码请求体 +- 提供 /config、/signaling 路由、静态资源目录 +- Swagger 文档初始化 +- 头像上传接口:multer 存储、重命名、返回相对 URL + +章节来源 +- [src/server.ts:18-89](file://src/server.ts#L18-L89) + +### WebSocket 信令服务器 +- 基于 ws 的服务器实例绑定到 HTTP 服务器 +- 连接生命周期:add/remove +- 消息分发:connect/disconnect/offer/answer/candidate/ping/pong/broadcast/call-request/on-message +- 心跳机制(注释掉的实现),可通过处理器扩展 + +章节来源 +- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) + +### WebSocket 处理器(1对多模式与广播) +- 私有模式:host 与多个 participants;offer/answer/candidate 按 participantId 路由 +- 公共模式:全局广播 +- 连接组管理:host/participants、参与者加入/离开通知 +- 广播消息:支持按连接组或全局广播 +- 心跳与超时(注释实现) + +章节来源 +- [src/class/websockethandler.ts:63-168](file://src/class/websockethandler.ts#L63-L168) +- [src/class/websockethandler.ts:175-206](file://src/class/websockethandler.ts#L175-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:370-402](file://src/class/websockethandler.ts#L370-L402) + +### HTTP 信令处理器(轮询模式) +- 会话管理:创建/删除、连接集合、超时检测 +- 消息存储:offer/answer/candidate 映射,断开连接记录 +- 查询接口:/signaling、/offer、/answer、/candidate、/connection +- 私有模式下的连接配对与跨会话消息路由 + +章节来源 +- [src/class/httphandler.ts:110-120](file://src/class/httphandler.ts#L110-L120) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/httphandler.ts:398-407](file://src/class/httphandler.ts#L398-L407) +- [src/class/httphandler.ts:492-501](file://src/class/httphandler.ts#L492-L501) +- [src/class/httphandler.ts:549-558](file://src/class/httphandler.ts#L549-L558) +- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641) + +### 客户端信令与 WebRTC +- WebSocketSignaling:连接建立、消息分发、发送 offer/answer/candidate/on-message +- HTTP 轮询 Signaling:循环拉取消息、事件派发 +- Peer:RTCPeerConnection 生命周期、ICE 候选收集、状态变化监听、重发 offer +- Sender:鼠标/键盘/手柄/触摸输入采集与事件队列 +- 统计:客户端侧统计计算(比特率、帧率、抖动、往返时间等) + +章节来源 +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) +- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97) +- [client/src/peer.js:37-82](file://client/src/peer.js#L37-L82) +- [client/src/peer.js:124-130](file://client/src/peer.js#L124-L130) +- [client/src/sender.js:14-188](file://client/src/sender.js#L14-L188) +- [client/public/js/stats.js:40-91](file://client/public/js/stats.js#L40-L91) +- [client/public/onebyone/store.js:1130-1380](file://client/public/onebyone/store.js#L1130-L1380) + +### 日志系统 +- 服务端:枚举日志级别、动态设置、格式化输出 +- 客户端:调试开关、条件输出 + +章节来源 +- [src/log.ts:15-24](file://src/log.ts#L15-L24) +- [client/src/logger.js:3-29](file://client/src/logger.js#L3-L29) + +## 依赖关系分析 +- 服务端依赖:Express、ws、Morgan、Swagger、UUID、Multer +- 客户端依赖:浏览器原生 WebRTC API、EventTarget、fetch + +```mermaid +graph LR +P["package.json 依赖"] --> E["Express"] +P --> W["ws"] +P --> M["Morgan"] +P --> SW["Swagger"] +P --> U["UUID"] +P --> MU["Multer"] +E --> SRV["src/server.ts"] +W --> WS["src/websocket.ts"] +SRV --> SIG["src/signaling.ts"] +SIG --> HTTPH["src/class/httphandler.ts"] +WS --> WSH["src/class/websockethandler.ts"] +``` + +图表来源 +- [package.json:14-26](file://package.json#L14-L26) +- [src/server.ts:1-14](file://src/server.ts#L1-L14) +- [src/websocket.ts:1-6](file://src/websocket.ts#L1-L6) +- [src/signaling.ts:1-4](file://src/signaling.ts#L1-L4) +- [src/class/httphandler.ts:1-11](file://src/class/httphandler.ts#L1-L11) +- [src/class/websockethandler.ts:1-11](file://src/class/websockethandler.ts#L1-L11) + +章节来源 +- [package.json:14-26](file://package.json#L14-L26) + +## 性能考虑 + +### 内存管理优化 +- 垃圾回收调优 + - 服务端:合理设置 Node.js 堆大小与 GC 参数(通过环境变量或启动脚本传入),避免大对象频繁分配与长生命周期持有 + - 客户端:避免在事件回调中累积大量闭包引用;及时释放媒体轨道与数据通道 +- 内存泄漏检测 + - 服务端:启用堆快照与内存采样,关注 Map/Set 的增长趋势(如连接组、会话映射) + - 客户端:使用浏览器开发者工具的内存面板,检查事件监听器与 DOM 引用是否正确清理 +- 进程监控 + - 服务端:定期记录内存使用、连接数、请求量;异常时触发重启或降级 + - 客户端:统计页面生命周期内的内存峰值与抖动 + +章节来源 +- [src/class/websockethandler.ts:19-37](file://src/class/websockethandler.ts#L19-L37) +- [src/class/httphandler.ts:42-77](file://src/class/httphandler.ts#L42-L77) +- [client/src/peer.js:184-186](file://client/src/peer.js#L184-L186) + +### 并发处理优化 +- 连接池配置 + - 服务端:限制并发请求数与 WebSocket 连接数,结合负载均衡部署 + - 客户端:合理设置轮询间隔,避免过于频繁的短连接请求 +- 线程数调整 + - Node.js:使用集群模式(cluster)或多进程部署,避免单进程阻塞 +- 异步处理优化 + - 服务端:将耗时操作(文件写入、统计计算)放入任务队列或子线程 + - 客户端:WebRTC 统计与事件处理使用 requestAnimationFrame 或微任务 + +章节来源 +- [src/server.ts:44-83](file://src/server.ts#L44-L83) +- [client/src/signaling.js:49-91](file://client/src/signaling.js#L49-L91) + +### 资源限制设置 +- 文件描述符限制 + - Linux:ulimit -n;Docker:--ulimit nofile;确保并发连接与文件上传场景充足 +- 网络连接限制 + - 服务端:限制每 IP 的连接数与速率;启用反向代理限流 + - 客户端:合理设置轮询超时与重试次数 +- CPU 使用率控制 + - 服务端:限制并发任务数量;对高 CPU 操作(编码/解码统计)做节流 + - 客户端:降低统计频率、减少不必要的事件派发 + +章节来源 +- [src/server.ts:44-83](file://src/server.ts#L44-L83) +- [client/public/js/stats.js:40-91](file://client/public/js/stats.js#L40-L91) + +### WebRTC 性能优化 +- 编码参数调整 + - 服务端:通过信令协商合适的编解码器与参数(如分辨率、帧率、码率范围) + - 客户端:根据网络质量动态调整发送轨道参数 +- 带宽自适应 + - 使用 WebRTC 的 BWE 与 REMB;客户端周期性上报统计,服务端下发降级指令 +- 网络质量监控 + - 客户端统计:比特率、帧率、抖动、丢包率、往返时间 + - 服务端:聚合统计与告警阈值 + +```mermaid +flowchart TD +Start(["开始"]) --> GetStats["获取 WebRTC 统计"] +GetStats --> Compute["计算比特率/帧率/抖动/丢包率"] +Compute --> Check{"是否异常?"} +Check --> |否| End(["结束"]) +Check --> |是| Adapt["调整编码参数/带宽"] +Adapt --> SendCmd["通过信令下发降级指令"] +SendCmd --> End +``` + +图表来源 +- [client/public/js/stats.js:40-91](file://client/public/js/stats.js#L40-L91) +- [client/public/onebyone/store.js:1130-1380](file://client/public/onebyone/store.js#L1130-L1380) +- [client/src/peer.js:124-130](file://client/src/peer.js#L124-L130) + +章节来源 +- [client/src/peer.js:37-82](file://client/src/peer.js#L37-L82) +- [client/public/js/stats.js:40-91](file://client/public/js/stats.js#L40-L91) +- [client/public/onebyone/store.js:1130-1380](file://client/public/onebyone/store.js#L1130-L1380) + +### 缓存策略配置 +- 静态资源缓存 + - Express 静态托管:设置合理的 Cache-Control 与 ETag + - 前端构建产物:版本化文件名,长期缓存 +- API 响应缓存 + - HTTP 信令:对只读查询设置短期缓存,结合 Last-Modified/ETag +- 浏览器缓存设置 + - 客户端脚本:强缓存;HTML:协商缓存 + - 服务端:通过中间件统一设置缓存头 + +章节来源 +- [src/server.ts:27-28](file://src/server.ts#L27-L28) +- [src/server.ts:86](file://src/server.ts#L86) + +### 性能监控指标与基准测试 +- 指标 + - 服务端:QPS、P95/P99 延迟、连接数、内存、GC 次数与暂停时间 + - 客户端:首帧时间、卡顿次数、统计上报频率、丢包率、抖动 +- 基准测试 + - 使用压测工具模拟多路并发;对比 WebSocket 与 HTTP 轮询的延迟与稳定性 + - WebRTC:不同分辨率/帧率组合下的端到端延迟与质量 + +章节来源 +- [src/log.ts:15-24](file://src/log.ts#L15-L24) +- [client/public/js/stats.js:40-91](file://client/public/js/stats.js#L40-L91) + +## 故障排查指南 +- WebSocket 连接异常 + - 检查心跳实现与超时阈值;确认处理器中的连接移除逻辑 +- HTTP 轮询堆积 + - 检查会话超时清理与消息去重;优化轮询间隔 +- 内存泄漏 + - 关注 Map/Set 的清理时机;客户端事件监听器与 DOM 引用释放 +- 日志定位 + - 调整日志级别;区分服务端与客户端日志输出 + +章节来源 +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) +- [client/src/logger.js:3-29](file://client/src/logger.js#L3-L29) + +## 结论 +通过合理的内存管理、并发控制、资源限制与 WebRTC 统计监控,可在保证用户体验的同时提升系统的稳定性与吞吐能力。建议在生产环境中结合监控指标持续迭代优化策略,并针对不同部署场景(单机/集群/容器)制定差异化的性能基线。 + +## 附录 +- 启动与配置 + - 服务端启动脚本与参数:端口、HTTPS、信令类型、运行模式、日志级别 + - 客户端信令选择:WebSocket 或 HTTP 轮询 + +章节来源 +- [package.json:5-12](file://package.json#L5-L12) +- [src/index.ts:20-41](file://src/index.ts#L20-L41) +- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/环境配置.md b/.qoder/repowiki/zh/content/部署指南/环境配置.md new file mode 100644 index 0000000..aa2c87a --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/环境配置.md @@ -0,0 +1,347 @@ +# 环境配置 + + +**本文引用的文件** +- [package.json](file://package.json) +- [src/index.ts](file://src/index.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/server.ts](file://src/server.ts) +- [run.bat](file://run.bat) +- [tsconfig.json](file://tsconfig.json) +- [tsconfig.build.json](file://tsconfig.build.json) +- [.eslintrc.cjs](file://.eslintrc.cjs) +- [.gitignore](file://.gitignore) +- [client/package.json](file://client/package.json) +- [jest.config.js](file://jest.config.js) +- [test/env_macos.postman_environment.json](file://test/env_macos.postman_environment.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向部署与运维人员,提供该视频 socket 服务器项目的环境配置说明,涵盖 Node.js 版本要求、操作系统兼容性、系统资源建议、开发与生产差异、环境变量与运行参数、以及常见问题排查。文档中的所有技术细节均来自仓库内现有配置文件与源码。 + +## 项目结构 +该项目采用前后端同库的组织方式:服务端为 Node.js + Express + WebSocket,前端静态资源位于 client/public 与 client/src;构建与打包通过 TypeScript 编译器与 pkg 工具完成;测试与质量工具包括 Jest、ESLint、Swagger 文档生成等。 + +```mermaid +graph TB +subgraph "服务端" +IDX["src/index.ts
入口与命令行参数解析"] +SRV["src/server.ts
Express 应用与路由"] +OPT["src/class/options.ts
运行参数接口"] +end +subgraph "客户端" +PUB["client/public
静态页面与资源"] +SRC["client/src
前端模块与示例"] +end +subgraph "构建与工具" +PKG["package.json
脚本与依赖"] +TSC["tsconfig.json
编译配置"] +TSB["tsconfig.build.json
构建扩展"] +ESL[".eslintrc.cjs
ESLint 规则"] +JST["jest.config.js
测试配置"] +end +IDX --> SRV +SRV --> PUB +SRV --> SRC +PKG --> IDX +PKG --> SRV +TSC --> IDX +TSC --> SRV +TSB --> PKG +ESL --> IDX +ESL --> SRV +JST --> PKG +``` + +**图示来源** +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [package.json:1-60](file://package.json#L1-L60) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) + +**章节来源** +- [package.json:1-60](file://package.json#L1-L60) +- [src/index.ts:1-109](file://src/index.ts#L1-L109) +- [src/server.ts:1-90](file://src/server.ts#L1-L90) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [jest.config.js:1-196](file://jest.config.js#L1-L196) + +## 核心组件 +- 入口与参数解析:负责从命令行读取运行参数,支持端口、HTTPS 密钥证书、信令类型、通信模式、日志级别等;同时支持通过环境变量覆盖默认值。 +- Express 应用:统一处理静态资源、上传接口、Swagger 文档、CORS、JSON/URL 编码请求体等。 +- 运行参数接口:定义了安全模式、端口、密钥/证书路径、信令类型、通信模式、日志级别等字段。 + +**章节来源** +- [src/index.ts:14-91](file://src/index.ts#L14-L91) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +## 架构总览 +下图展示了启动流程与关键组件交互:命令行参数解析后创建 Express 应用,按需启用 HTTPS,随后根据信令类型选择 WebSocket 或 HTTP 轮询,并输出启动日志与访问地址。 + +```mermaid +sequenceDiagram +participant CLI as "命令行/环境变量" +participant IDX as "RenderStreaming.run()" +participant SRV as "createServer()" +participant HTTPS as "HTTPS 服务器" +participant HTTP as "HTTP 服务器" +participant WS as "WebSocket 信令" +CLI->>IDX : 解析参数(-p/-s/-k/-c/-t/-m/-l) +IDX->>SRV : 创建 Express 应用 +alt 启用安全模式 +IDX->>HTTPS : 使用 key/cert 创建 HTTPS 服务器 +HTTPS-->>IDX : 输出 https : // 地址 +else 非安全模式 +IDX->>HTTP : 使用 Express 监听端口 +HTTP-->>IDX : 输出 http : // 地址 +end +IDX->>WS : 按信令类型启动 WebSocket 信令 +WS-->>IDX : 信令就绪 +``` + +**图示来源** +- [src/index.ts:14-91](file://src/index.ts#L14-L91) +- [src/server.ts:14-42](file://src/server.ts#L14-L42) + +## 详细组件分析 + +### 命令行参数与环境变量 +- 支持的参数与默认值(优先级:命令行 > 环境变量 > 默认值): + - 端口:-p/--port,默认值来自环境变量 PORT,未设置时为 80(注意:脚本 start/dev 默认 8080) + - 安全模式:-s/--secure,默认开启(脚本 start/dev 默认开启) + - 密钥文件:-k/--keyfile,默认 server.key + - 证书文件:-c/--certfile,默认 server.cert + - 信令类型:-t/--type,默认 websocket;支持 websocket/http + - 通信模式:-m/--mode,默认 public;支持 public/private + - 日志级别:-l/--logging,默认 dev;支持 combined、dev、short、tiny、none +- 环境变量映射: + - PORT、SECURE、KEYFILE、CERTFILE、TYPE、MODE、LOGGING + +```mermaid +flowchart TD +Start(["启动"]) --> Parse["解析命令行参数"] +Parse --> EnvCheck{"检查对应环境变量"} +EnvCheck --> |存在| UseEnv["使用环境变量值"] +EnvCheck --> |不存在| UseDefault["使用内置默认值"] +UseEnv --> Merge["合并为最终配置"] +UseDefault --> Merge +Merge --> Mode{"是否指定安全模式"} +Mode --> |是| HTTPS["启用 HTTPS 并加载 key/cert"] +Mode --> |否| HTTP["启用 HTTP"] +HTTPS --> Signaling["按信令类型启动"] +HTTP --> Signaling +Signaling --> Log["输出启动日志与访问地址"] +Log --> End(["完成"]) +``` + +**图示来源** +- [src/index.ts:16-42](file://src/index.ts#L16-L42) +- [src/index.ts:55-91](file://src/index.ts#L55-L91) + +**章节来源** +- [src/index.ts:16-42](file://src/index.ts#L16-L42) +- [src/index.ts:55-91](file://src/index.ts#L55-L91) + +### Express 应用与静态资源 +- CORS:允许任意来源访问 +- 请求体解析:支持 JSON 与 URL 编码 +- 静态资源: + - 根路径 / 映射到 client/public/index.html + - /module 映射到 client/src + - /uploads 映射到 client/public/uploads +- 上传接口:/api/upload/avatar,使用 multer 将头像保存至 uploads/avatars,并按用户 ID 重命名 +- Swagger 文档初始化 +- /config 接口返回当前信令与模式、日志级别等配置 + +**章节来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +### 构建与打包配置 +- TypeScript 编译: + - tsconfig.json:目标 ES5,输出目录 build,包含 src 与 test + - tsconfig.build.json:仅包含 src,继承 tsconfig.json +- 打包: + - package.json 中的 pkg 字段声明 assets 与 targets,其中 targets 包含 "node10",表明以 Node.js 10 可执行文件为目标进行打包 + +**章节来源** +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) +- [tsconfig.build.json:1-5](file://tsconfig.build.json#L1-L5) +- [package.json:50-58](file://package.json#L50-L58) + +### 测试与质量工具 +- Jest:测试框架,收集覆盖率,测试匹配规则覆盖 src 与 test 下的 ts 文件 +- ESLint:基于 TypeScript 推荐规则与 jest 插件,解析 tsconfig.lint.json +- 客户端 JS 测试:client/package.json 提供测试脚本与 eslint 配置 + +**章节来源** +- [jest.config.js:1-196](file://jest.config.js#L1-L196) +- [.eslintrc.cjs:1-25](file://.eslintrc.cjs#L1-L25) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +## 依赖关系分析 +- 运行时依赖(节选):express、ws、cors、multer、morgan、swagger-jsdoc、swagger-ui-express、uuid、debug 等 +- 开发依赖(节选):jest、ts-jest、ts-node、typescript、eslint、pkg 等 +- 构建与打包:通过 tsc 与 pkg 实现,pkg targets 指向 node10 + +```mermaid +graph LR +P["package.json 依赖"] --> E["express"] +P --> W["ws"] +P --> C["cors"] +P --> M["multer"] +P --> MO["morgan"] +P --> SW["swagger-jsdoc / ui-express"] +P --> U["uuid"] +P --> D["debug"] +P -.dev.-> J["jest / ts-jest / ts-node"] +P -.dev.-> T["typescript / eslint"] +P -.dev.-> K["pkg"] +K --> OUT["可执行产物(目标 node10)"] +``` + +**图示来源** +- [package.json:14-46](file://package.json#L14-L46) +- [package.json:50-58](file://package.json#L50-L58) + +**章节来源** +- [package.json:14-46](file://package.json#L14-L46) +- [package.json:50-58](file://package.json#L50-L58) + +## 性能考虑 +- 日志级别:dev/combined/short/tiny 可按需调整,none 可减少 I/O 开销 +- 上传与静态资源:头像上传使用磁盘存储,建议确保磁盘空间充足并定期清理 +- 信令类型:WebSocket 通常比 HTTP 轮询更高效,建议在生产环境使用 websocket +- 并发与资源:Node.js 进程的并发能力取决于 CPU 核心数与内存大小,建议为每个实例分配至少 1GB 内存并预留系统开销 + +[本节为通用指导,不直接分析具体文件] + +## 故障排查指南 +- 端口占用或权限不足 + - 症状:启动失败或端口被占用 + - 排查:确认 -p/--port 指定端口可用;在类 Unix 系统上避免使用小于 1024 的特权端口 +- HTTPS 证书缺失 + - 症状:启动时报错无法读取 server.key/server.cert + - 排查:确认 -k/--keyfile 与 -c/--certfile 指向的文件存在且可读;或关闭安全模式 -s +- 信令类型错误 + - 症状:日志提示信令类型不受支持并回退为 websocket + - 排查:确保 -t/--type 设置为 websocket 或 http +- 静态资源 404 + - 症状:访问 / 或 /module 返回 404 + - 排查:确认 client/public 与 client/src 目录存在且包含所需文件 +- 上传失败 + - 症状:/api/upload/avatar 返回错误 + - 排查:确认 uploads/avatars 目录存在且写权限正常;检查文件名重命名逻辑 +- 环境变量未生效 + - 症状:PORT/SECURE 等未按预期工作 + - 排查:确认环境变量名正确且在启动前导出;命令行参数优先于环境变量 + +**章节来源** +- [src/index.ts:55-91](file://src/index.ts#L55-L91) +- [src/server.ts:29-39](file://src/server.ts#L29-L39) +- [src/server.ts:62-83](file://src/server.ts#L62-L83) + +## 结论 +本项目对 Node.js 的最低版本要求可参考打包目标 node10;实际运行建议使用较新的 LTS 版本以获得更好的稳定性与性能。生产环境推荐使用 WebSocket 信令、HTTPS 加密、合理设置日志级别与磁盘空间,并通过环境变量或命令行参数进行灵活配置。 + +[本节为总结,不直接分析具体文件] + +## 附录 + +### Node.js 版本要求 +- 打包目标:node10 +- 建议使用:Node.js LTS(如 18.x/20.x) + +**章节来源** +- [package.json:55-57](file://package.json#L55-L57) + +### 操作系统兼容性 +- 服务器端:Express 与 Node.js 核心模块在 Windows、Linux、macOS 上均可运行 +- 客户端静态资源:无平台特定限制 +- 注意:Windows 批处理脚本 run.bat 用于本地快速启动 + +**章节来源** +- [run.bat:1-17](file://run.bat#L1-L17) + +### 系统资源建议 +- 内存:建议每实例至少 1GB,视并发连接与媒体负载适当增加 +- CPU:多核 CPU 更有利于并发处理 +- 磁盘:确保 uploads/avatars 目录有足够空间;静态资源目录 client/public 与 client/src 需要可读权限 + +**章节来源** +- [src/server.ts:44-57](file://src/server.ts#L44-L57) + +### 开发环境与生产环境 +- 开发环境 + - 启动:npm run dev(使用 ts-node 直接运行 src/index.ts) + - 参数示例:-s -p 8080 -m private -l dev +- 生产环境 + - 构建:npm run build(TypeScript 编译到 build) + - 启动:npm run start(运行 build/index.js) + - 参数示例:-s -p 8080 -m private -k ./server.key -c ./server.cert -l dev +- 打包:npm run pack(使用 pkg,目标 node10) + +**章节来源** +- [package.json:5-12](file://package.json#L5-L12) +- [run.bat:3-4](file://run.bat#L3-L4) + +### 环境变量与运行参数对照 +- PORT:覆盖端口默认值 +- SECURE:启用 HTTPS(布尔) +- KEYFILE:HTTPS 秘钥文件路径 +- CERTFILE:HTTPS 证书文件路径 +- TYPE:信令类型 websocket/http +- MODE:通信模式 public/private +- LOGGING:日志级别 combined/dev/short/tiny/none + +**章节来源** +- [src/index.ts:20-29](file://src/index.ts#L20-L29) + +### 信令模式与通信模式 +- 信令模式:websocket(默认)或 http(轮询) +- 通信模式:public(默认)或 private(私有模式,用于限制访问) + +**章节来源** +- [src/index.ts:26-27](file://src/index.ts#L26-L27) +- [src/server.ts:25-25](file://src/server.ts#L25-L25) + +### 端口与证书路径 +- 端口:可通过 -p/--port 或环境变量 PORT 指定 +- 证书:HTTPS 模式下需提供 -k/--keyfile 与 -c/--certfile + +**章节来源** +- [src/index.ts:22-25](file://src/index.ts#L22-L25) +- [src/index.ts:56-59](file://src/index.ts#L56-L59) + +### Swagger 与静态资源 +- Swagger 文档:通过 initSwagger 初始化 +- 静态资源:根路径与 /module、/uploads 均已配置 + +**章节来源** +- [src/server.ts:41-42](file://src/server.ts#L41-L42) +- [src/server.ts:27-28](file://src/server.ts#L27-L28) +- [src/server.ts:86-86](file://src/server.ts#L86-L86) + +### Postman 环境 +- macOS 环境示例:包含 url 变量 localhost:8080 + +**章节来源** +- [test/env_macos.postman_environment.json:1-14](file://test/env_macos.postman_environment.json#L1-L14) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/监控与日志.md b/.qoder/repowiki/zh/content/部署指南/监控与日志.md new file mode 100644 index 0000000..09a245a --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/监控与日志.md @@ -0,0 +1,412 @@ +# 监控与日志 + + +**本文引用的文件** +- [src/log.ts](file://src/log.ts) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [src/swagger.ts](file://src/swagger.ts) +- [client/src/logger.js](file://client/src/logger.js) +- [client/public/js/stats.js](file://client/public/js/stats.js) +- [package.json](file://package.json) +- [run.bat](file://run.bat) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考量](#性能考量) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南聚焦于视频信令服务器的监控与日志配置,覆盖以下方面: +- 日志系统配置:日志级别、输出格式与时序、HTTP访问日志策略 +- 性能监控指标:连接数统计、内存使用、请求响应时间与错误率 +- 分布式追踪:请求链路跟踪与性能瓶颈定位思路 +- 告警规则:阈值设定、通知渠道与故障恢复建议 +- 工具集成:Prometheus、Grafana、ELK Stack 的对接要点 +- 日志分析与故障诊断:常见问题与监控解决方案 + +## 项目结构 +该项目采用分层与职责分离的设计: +- 应用入口负责解析命令行参数、创建HTTP/HTTPS服务器、启动WebSocket信令服务 +- Express服务器负责HTTP路由、静态资源、Swagger文档与HTTP信令接口 +- WebSocket服务器负责实时信令消息的收发与连接生命周期管理 +- 日志模块提供统一的日志级别控制与格式化输出 +- 客户端侧提供前端调试日志开关与媒体统计展示 + +```mermaid +graph TB +A["应用入口
src/index.ts"] --> B["Express服务器
src/server.ts"] +A --> C["WebSocket信令
src/websocket.ts"] +B --> D["HTTP信令路由
src/signaling.ts"] +D --> E["HTTP处理器
src/class/httphandler.ts"] +C --> F["WebSocket处理器
src/class/websockethandler.ts"] +A --> G["日志模块
src/log.ts"] +B --> H["Swagger文档
src/swagger.ts"] +I["客户端调试日志
client/src/logger.js"] -.-> A +``` + +**图表来源** +- [src/index.ts:13-108](file://src/index.ts#L13-L108) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [src/signaling.ts:1-24](file://src/signaling.ts#L1-L24) +- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120) +- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +**章节来源** +- [src/index.ts:13-108](file://src/index.ts#L13-L108) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [src/signaling.ts:1-24](file://src/signaling.ts#L1-L24) +- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120) +- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) + +## 核心组件 +- 日志模块:提供日志级别枚举、动态级别设置、时间戳格式化与不同级别输出 +- HTTP服务器:基于Express,启用Morgan进行HTTP访问日志,支持多种格式 +- WebSocket信令:基于ws,维护连接组、广播与心跳检测 +- HTTP信令处理器:基于会话与连接ID管理,提供offer/answer/candidate等信令存取 +- Swagger文档:自动生成API文档,便于监控与排障时查阅接口规范 + +**章节来源** +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120) +- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66) +- [src/swagger.ts:16-65](file://src/swagger.ts#L16-L65) + +## 架构总览 +下图展示了从请求进入、HTTP处理、WebSocket处理到日志输出的整体流程。 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant HTTP as "HTTP服务器
src/server.ts" +participant Sig as "HTTP信令路由
src/signaling.ts" +participant Hdl as "HTTP处理器
src/class/httphandler.ts" +participant WS as "WebSocket服务器
src/websocket.ts" +participant WSH as "WebSocket处理器
src/class/websockethandler.ts" +participant Log as "日志模块
src/log.ts" +Client->>HTTP : "HTTP请求" +HTTP->>Sig : "路由到/signaling" +Sig->>Hdl : "调用处理器(如创建会话/获取offer)" +Hdl-->>HTTP : "响应(200/4xx)" +HTTP-->>Client : "HTTP响应" +Client->>WS : "WebSocket连接" +WS->>WSH : "on('connection')" +WSH-->>Log : "记录连接/断开/消息" +WSH-->>Client : "信令消息(offer/answer/candidate)" +``` + +**图表来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-24](file://src/signaling.ts#L1-L24) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) +- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115) +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/log.ts:30-50](file://src/log.ts#L30-L50) + +## 详细组件分析 + +### 日志系统配置 +- 日志级别 + - 支持 none/error/warn/log/info 五级,可通过运行参数或环境变量设置 + - 默认级别为 info;当级别为 none 时不输出任何日志 +- 输出格式 + - 自动添加ISO时间戳与大写的级别前缀 + - 使用不同console方法区分 error/warn/info/log +- HTTP访问日志 + - 通过 Morgan 中间件启用,支持 combined/dev/short/tiny 或禁用 + - 日志级别与Morgan日志可并行使用,便于区分业务日志与访问日志 + +```mermaid +flowchart TD +Start(["设置日志级别"]) --> Parse["解析字符串级别
parseLogLevel()"] +Parse --> Set["setLogLevel() 更新全局级别"] +Set --> Check{"当前级别是否允许输出?"} +Check --> |否| Exit["直接返回"] +Check --> |是| Format["formatTimestamp()
拼装前缀"] +Format --> Emit{"根据级别选择输出方法"} +Emit --> Console["console.error/warn/info/log"] +Console --> End(["完成"]) +``` + +**图表来源** +- [src/log.ts:11-24](file://src/log.ts#L11-L24) +- [src/log.ts:26-50](file://src/log.ts#L26-L50) + +**章节来源** +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/server.ts:18-20](file://src/server.ts#L18-L20) +- [src/index.ts:28](file://src/index.ts#L28) +- [package.json:9](file://package.json#L9) +- [run.bat:8](file://run.bat#L8) + +### HTTP访问日志与路由 +- 访问日志 + - 通过 Morgan 中间件以配置项启用,支持多种格式 + - 可通过运行参数控制,避免生产环境产生过多日志 +- 信令路由 + - 提供会话创建、连接管理、offer/answer/candidate获取与推送 + - 使用会话ID作为安全约束,确保接口访问受控 + +```mermaid +sequenceDiagram +participant Client as "客户端" +participant Server as "Express服务器" +participant Router as "HTTP信令路由" +participant Handler as "HTTP处理器" +Client->>Server : "GET /signaling/connection" +Server->>Router : "匹配路由" +Router->>Handler : "checkSessionId() 校验" +Handler-->>Router : "返回连接列表" +Router-->>Server : "JSON响应" +Server-->>Client : "HTTP 200" +``` + +**图表来源** +- [src/server.ts:25-86](file://src/server.ts#L25-L86) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) + +**章节来源** +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/signaling.ts:1-24](file://src/signaling.ts#L1-L24) +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) + +### WebSocket信令与连接管理 +- 连接生命周期 + - 建立连接时注册,断开时清理;记录连接/断开/消息等事件 +- 1对多模式 + - host与participants角色分工,offer/answer/candidate按角色路由 + - 支持广播与定向消息,便于扩展聊天、白板等场景 +- 心跳与超时 + - 定期发送ping并检查lastActivity,超时自动断开,降低僵尸连接占用 + +```mermaid +classDiagram +class WSSignaling { ++server ++wss ++constructor(server, mode) +} +class WebSocketHandler { ++reset(mode) ++add(ws) ++remove(ws) ++onConnect(ws, connectionId) ++onDisconnect(ws, connectionId) ++onOffer(ws, message) ++onAnswer(ws, message) ++onCandidate(ws, message) ++onBroadcast(ws, message) ++onMessage(ws, message) ++AddHeartbeat(ws, connectionId) ++RemoveHeartbeat(ws) +} +WSSignaling --> WebSocketHandler : "委托处理" +``` + +**图表来源** +- [src/websocket.ts:15-117](file://src/websocket.ts#L15-L117) +- [src/class/websockethandler.ts:63-479](file://src/class/websockethandler.ts#L63-L479) + +**章节来源** +- [src/websocket.ts:6-117](file://src/websocket.ts#L6-L117) +- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338) + +### HTTP处理器与会话/连接模型 +- 会话与连接 + - 会话ID唯一标识一次通话或交互;连接ID标识具体端点 + - 支持私有模式下的双向配对与公共模式下的广播 +- 信令存储 + - offer/answer/candidate按连接ID与会话ID存储,支持按时间过滤 +- 超时与清理 + - 基于lastRequestedTime定期清理超时会话,释放资源 + +```mermaid +flowchart TD +S["创建会话(createSession)"] --> C["创建连接(createConnection)"] +C --> O["接收/存储offer"] +C --> A["接收/存储answer"] +C --> K["接收/存储candidate"] +O --> R["按fromtime过滤返回(getOffer)"] +A --> R2["按fromtime过滤返回(getAnswer)"] +K --> R3["按fromtime过滤返回(getCandidate)"] +T["超时检测(_checkForTimedOutSessions)"] --> Del["删除会话(_deleteSession)"] +``` + +**图表来源** +- [src/class/httphandler.ts:664-675](file://src/class/httphandler.ts#L664-L675) +- [src/class/httphandler.ts:739-783](file://src/class/httphandler.ts#L739-L783) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/httphandler.ts:200-213](file://src/class/httphandler.ts#L200-L213) + +**章节来源** +- [src/class/httphandler.ts:106-120](file://src/class/httphandler.ts#L106-L120) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641) + +### 客户端日志与媒体统计 +- 客户端调试日志 + - 提供enable/disable与多级别输出,便于前端问题定位 +- 媒体统计 + - 基于RTCStats计算比特率、帧率等指标,辅助性能分析 + +**章节来源** +- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30) +- [client/public/js/stats.js:7-91](file://client/public/js/stats.js#L7-L91) + +## 依赖关系分析 +- 运行时依赖 + - Express、ws、morgan、swagger-ui-express、uuid等 +- 开发与测试 + - Jest、ts-jest、mock-socket等 +- 构建与打包 + - TypeScript编译、pkg打包静态资源 + +```mermaid +graph LR +P["package.json"] --> E["express"] +P --> W["ws"] +P --> M["morgan"] +P --> S["swagger-ui-express"] +P --> U["uuid"] +P --> J["jest/ts-jest"] +P --> T["typescript"] +``` + +**图表来源** +- [package.json:14-46](file://package.json#L14-L46) + +**章节来源** +- [package.json:14-46](file://package.json#L14-L46) + +## 性能考量 +- 连接数统计 + - WebSocket侧:通过连接集合与连接组统计当前活跃连接数 + - HTTP侧:通过会话与连接映射统计会话数量与连接数 +- 内存使用 + - 信令消息以Map/Set存储,需关注超时清理与消息大小 + - 前端媒体统计可辅助判断网络拥塞与编码开销 +- 请求响应时间与错误率 + - HTTP接口响应时间可通过中间件埋点或外部APM采集 + - 错误率可通过HTTP状态码分布统计 +- 轮询与长连接 + - HTTP轮询模式下应限制轮询频率,避免CPU与带宽浪费 + - WebSocket心跳与超时可减少无效连接占用 + +[本节为通用性能指导,无需特定文件引用] + +## 故障排查指南 +- 常见问题 + - 会话不存在:HTTP处理器校验session-id,缺失或过期导致404 + - 连接ID冲突:私有模式下重复使用连接ID会触发400 + - WebSocket超时:长时间无活动将被自动断开 + - 文件上传失败:服务端重命名异常导致500 +- 排查步骤 + - 启用更细粒度日志(info/log),复现问题并核对日志时间线 + - 使用Swagger查看接口规范,确认请求头与参数 + - 检查WebSocket消息类型与路由逻辑,确认角色与目标参与者 + - 对比前后两次RTCStats,定位带宽与帧率异常 + +**章节来源** +- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145) +- [src/class/httphandler.ts:747-763](file://src/class/httphandler.ts#L747-L763) +- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430) +- [src/server.ts:74-82](file://src/server.ts#L74-L82) + +## 结论 +本项目提供了清晰的日志体系与信令处理能力,结合Morgan访问日志与Swagger文档,能够满足日常运维与排障需求。建议在生产环境中: +- 明确日志级别与轮转策略,避免磁盘与IO压力 +- 引入APM或Prometheus/Grafana进行指标采集与可视化 +- 建立告警规则与通知通道,配合健康检查与自动恢复机制 +- 使用分布式追踪定位跨服务链路瓶颈 + +[本节为总结性内容,无需特定文件引用] + +## 附录 + +### 日志系统配置清单 +- 日志级别 + - 可选:none/error/warn/log/info + - 默认:info +- 输出格式 + - ISO时间戳 + 大写级别前缀 + - 区分 error/warn/info/log +- HTTP访问日志 + - 可选:combined/dev/short/tiny 或 none + - 通过运行参数控制 + +**章节来源** +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/server.ts:18-20](file://src/server.ts#L18-L20) +- [src/index.ts:28](file://src/index.ts#L28) + +### 性能监控指标定义 +- 连接数 + - WebSocket活跃连接数:基于连接集合统计 + - HTTP会话数与连接数:基于会话映射统计 +- 内存使用 + - 服务端:关注Map/Set规模与消息队列长度 + - 客户端:媒体统计辅助判断编码与传输效率 +- 请求响应时间与错误率 + - 通过中间件或外部APM采集 + - 错误率基于HTTP状态码分布 + +**章节来源** +- [src/class/websockethandler.ts:20-37](file://src/class/websockethandler.ts#L20-L37) +- [src/class/httphandler.ts:42-84](file://src/class/httphandler.ts#L42-L84) +- [client/public/js/stats.js:7-91](file://client/public/js/stats.js#L7-L91) + +### 分布式追踪配置建议 +- 链路跟踪 + - 在HTTP入口与WebSocket消息处理处打点 + - 关键节点:创建会话、创建连接、发送/接收offer/answer/candidate +- 瓶颈定位 + - 结合日志时间戳与追踪ID,定位慢查询与阻塞点 + - 媒体统计与网络指标联动分析 + +[本节为通用实践建议,无需特定文件引用] + +### 告警规则与通知 +- 阈值建议 + - 连接数:单节点并发连接数峰值阈值 + - 错误率:HTTP 5xx占比、WebSocket断连率 + - 响应时间:P95/P99延迟阈值 +- 通知渠道 + - 邮件、IM、电话分级告警 +- 故障恢复 + - 自动扩容、熔断降级、快速回滚 + +[本节为通用实践建议,无需特定文件引用] + +### 监控工具集成要点 +- Prometheus + - 暴露指标端点,采集连接数、内存、请求速率与错误率 +- Grafana + - 仪表板展示趋势、告警与热力图 +- ELK Stack + - 收集HTTP与业务日志,建立检索与聚合分析 + +[本节为通用实践建议,无需特定文件引用] \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/部署指南/部署指南.md b/.qoder/repowiki/zh/content/部署指南/部署指南.md new file mode 100644 index 0000000..ef9ac1d --- /dev/null +++ b/.qoder/repowiki/zh/content/部署指南/部署指南.md @@ -0,0 +1,348 @@ +# 部署指南 + + +**本文引用的文件** +- [package.json](file://package.json) +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/class/options.ts](file://src/class/options.ts) +- [src/log.ts](file://src/log.ts) +- [run.bat](file://run.bat) +- [tsconfig.json](file://tsconfig.json) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖关系分析](#依赖关系分析) +7. [性能考虑](#性能考虑) +8. [故障排除指南](#故障排除指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +本指南面向生产环境部署“视频流服务器”应用,覆盖以下主题: +- 生产环境配置要求:Node.js 版本、系统资源、网络与安全参数 +- 容器化部署:Docker 方案(含镜像构建与运行) +- 编排部署:Kubernetes 部署清单模板与要点 +- 反向代理:Nginx 与 Apache 的典型配置思路 +- SSL/TLS 证书:申请与配置流程 +- 性能优化:内存管理、并发处理、资源限制 +- 监控与日志:日志级别与输出策略 +- 故障排除与维护:常见问题定位与修复 +- 自动化与 CI/CD:打包、运行与部署脚本建议 + +## 项目结构 +该仓库为基于 TypeScript 的 Express 服务器,提供 WebSocket 信令与静态资源服务,并内置 Swagger 文档与头像上传接口。 + +```mermaid +graph TB +A["入口脚本
src/index.ts"] --> B["HTTP(S) 服务器
Express 应用"] +B --> C["静态资源
client/public"] +B --> D["信令路由
/signaling"] +B --> E["Swagger 文档
/swagger*"] +B --> F["头像上传接口
/api/upload/avatar"] +A --> G["日志模块
src/log.ts"] +A --> H["选项解析
src/class/options.ts"] +``` + +图表来源 +- [src/index.ts:13-106](file://src/index.ts#L13-L106) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +章节来源 +- [src/index.ts:13-106](file://src/index.ts#L13-L106) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [package.json:1-60](file://package.json#L1-L60) + +## 核心组件 +- 启动与配置 + - 命令行参数解析与默认值:端口、HTTPS 开关、密钥/证书路径、信令类型、通信模式、HTTP 访问日志级别 + - 环境变量覆盖:PORT、SECURE、KEYFILE、CERTFILE、TYPE、MODE、LOGGING +- HTTP(S) 服务器 + - 支持 HTTP 与 HTTPS;HTTPS 由外部证书文件加载 + - 动态输出监听地址列表(IPv4) +- Express 应用 + - CORS 允许跨域 + - JSON/URL 编码请求体解析 + - 静态资源托管与根页面路由 + - Swagger 文档初始化 + - 头像上传接口(Multer 存储到 uploads/avatars) +- 日志系统 + - 日志级别枚举与动态设置 + - 控制台输出格式化时间戳与级别前缀 + +章节来源 +- [src/index.ts:20-44](file://src/index.ts#L20-L44) +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/server.ts:14-89](file://src/server.ts#L14-L89) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +## 架构总览 +下图展示从客户端到服务器的典型交互路径,包括 HTTP(S) 访问、静态资源、信令与上传流程。 + +```mermaid +graph TB +subgraph "客户端" +U["浏览器/前端"] +end +subgraph "反向代理层" +RP["Nginx/Apache"] +end +subgraph "应用服务器" +S["Express 应用"] +WS["WebSocket 信令"] +FS["静态资源
client/public"] +SW["Swagger 文档"] +UP["头像上传接口"] +end +subgraph "证书" +CERT["server.key / server.cert"] +end +U --> RP +RP --> S +S --> FS +S --> SW +S --> UP +S --> WS +S -. 使用证书 .-> CERT +``` + +图表来源 +- [src/index.ts:55-65](file://src/index.ts#L55-L65) +- [src/server.ts:25-28](file://src/server.ts#L25-L28) +- [src/server.ts:44-83](file://src/server.ts#L44-L83) + +## 详细组件分析 + +### 启动与配置组件 +- 命令行与环境变量映射 + - 端口:支持 -p/--port 与 PORT + - HTTPS:-s/--secure 与 SECURE;需配合 -k/--keyfile 与 -c/--certfile 或 KEYFILE/CERTFILE + - 信令类型:-t/--type 支持 websocket/http,默认 websocket + - 模式:-m/--mode 支持 public/private,默认 public + - 日志:-l/--logging 支持 combined/dev/short/tiny/none,默认 dev +- 服务器启动 + - HTTPS 时读取密钥与证书文件 + - 输出监听地址与协议(http/https) + +```mermaid +sequenceDiagram +participant CLI as "命令行/环境变量" +participant IDX as "src/index.ts" +participant APP as "Express 应用" +participant HTTPS as "HTTPS 服务器" +CLI->>IDX : 解析参数与默认值 +IDX->>APP : 创建 Express 应用 +IDX->>HTTPS : 加载 server.key/server.cert +HTTPS-->>IDX : 绑定端口并输出监听地址 +IDX-->>CLI : 打印启动信息 +``` + +图表来源 +- [src/index.ts:20-44](file://src/index.ts#L20-L44) +- [src/index.ts:55-74](file://src/index.ts#L55-L74) + +章节来源 +- [src/index.ts:20-44](file://src/index.ts#L20-L44) +- [src/index.ts:55-74](file://src/index.ts#L55-L74) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +### HTTP 服务器与静态资源 +- 路由与中间件 + - CORS 允许所有来源 + - JSON/URL 编码请求体解析 + - 静态资源:根目录与 /module + - 根路径返回 index.html(若存在) +- Swagger 文档 + - 初始化并暴露文档端点 +- 头像上传 + - Multer 存储到 uploads/avatars + - 接口返回重命名后的访问路径 + +```mermaid +flowchart TD +Start(["请求进入"]) --> CORS["CORS 中间件"] +CORS --> Body["JSON/URL 编码解析"] +Body --> Routes{"匹配路由"} +Routes --> |"/" 或 "/index.html"| StaticIndex["返回静态首页"] +Routes --> |"/module/*"| StaticModule["返回模块源码"] +Routes --> |"/signaling"| Signaling["信令路由"] +Routes --> |"/swagger*"| Swagger["Swagger 文档"] +Routes --> |"/api/upload/avatar"| Upload["头像上传处理"] +Routes --> |"/uploads/*"| StaticUploads["返回上传文件"] +Routes --> |其他| NotFound["404 未找到"] +``` + +图表来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +章节来源 +- [src/server.ts:14-89](file://src/server.ts#L14-L89) + +### 日志系统 +- 日志级别:none/error/warn/log/info +- 动态设置:通过命令行 -l/--logging 或环境变量 LOGGING +- 输出格式:带 ISO 时间戳与级别前缀 + +章节来源 +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/index.ts:28](file://src/index.ts#L28) + +## 依赖关系分析 +- 运行时依赖 + - express、ws、cors、multer、morgan、swagger-ui-express 等 +- 开发与构建 + - TypeScript、Jest、ESLint、ts-node、pkg 等 +- 包装与分发 + - pkg 将构建产物与静态资源打包为可执行文件 + +```mermaid +graph LR +P["package.json"] --> TSC["TypeScript 编译"] +P --> JEST["Jest 测试"] +P --> ESL["ESLint 规则"] +P --> TS["ts-node 开发"] +P --> PKG["pkg 打包"] +TSC --> BUILD["build 目录产物"] +PKG --> BIN["可执行二进制"] +``` + +图表来源 +- [package.json:14-46](file://package.json#L14-L46) +- [package.json:50-58](file://package.json#L50-L58) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) + +章节来源 +- [package.json:14-46](file://package.json#L14-L46) +- [package.json:50-58](file://package.json#L50-L58) +- [tsconfig.json:1-13](file://tsconfig.json#L1-L13) + +## 性能考虑 +- 内存管理 + - 使用 Node.js 的垃圾回收与进程退出策略,避免内存泄漏 + - 对大文件上传(如头像)采用流式处理与合理缓存大小 +- 并发处理 + - Express 默认并发能力受 Node.js 事件循环影响;高并发场景建议前置反向代理或使用集群模式 + - WebSocket 信令应结合连接数与消息频率进行限流与心跳检测 +- 资源限制 + - 设置合理的 ulimit、文件句柄上限与 Node.js 堆内存上限 + - 在容器中配置 CPU/内存资源配额与重启策略 +- I/O 与静态资源 + - 将静态资源交由反向代理缓存与压缩(gzip/br),减轻应用压力 +- 日志与监控 + - 使用 morgan 输出访问日志,结合日志切割与归档 + - 结合系统监控(CPU/内存/磁盘/网络)与应用指标(QPS/响应时间/错误率) + +## 故障排除指南 +- 启动失败(证书相关) + - 确认 server.key 与 server.cert 文件存在且可读 + - 检查 -s/--secure 是否启用,以及 -k/-c 或 KEYFILE/CERTFILE 是否正确 +- 端口占用 + - 更换 -p/--port 或释放占用端口;确认防火墙放行 +- CORS 与跨域 + - 当前实现允许所有来源;生产环境建议限定来源 +- 上传失败 + - 检查 uploads/avatars 目录权限与磁盘空间 + - 确认 Multer 存储配置与文件大小限制 +- 日志级别过低 + - 提升 -l/--logging 或设置 LOGGING=dev/info 等 +- 信令类型不支持 + - 仅支持 websocket 与 http;非预期值会被修正为 websocket + +章节来源 +- [src/index.ts:55-65](file://src/index.ts#L55-L65) +- [src/server.ts:44-83](file://src/server.ts#L44-L83) +- [src/log.ts:15-24](file://src/log.ts#L15-L24) + +## 结论 +本指南提供了从配置、部署到运维的完整路径。生产环境建议: +- 使用反向代理统一接入与证书终止 +- 以容器化与编排方式提升弹性与可观测性 +- 严格控制日志级别与输出,配合监控告警 +- 对上传与媒体数据进行容量与安全评估 + +## 附录 + +### A. 生产环境配置要求 +- Node.js 版本 + - 建议使用长期支持版本(LTS),确保与依赖兼容 +- 系统资源 + - CPU:根据并发连接与媒体处理需求评估 + - 内存:预留足够堆内存与静态资源缓存 + - 磁盘:为 uploads/avatars 与日志保留充足空间 +- 网络 + - 开放端口:HTTP(80)/HTTPS(443) 与 WebSocket 端口 + - 防火墙与安全组放行 + - 反向代理层启用 TLS 终止与压缩 + +### B. Docker 容器化部署 +- 构建镜像 + - 使用多阶段构建,先安装依赖、再编译、最后复制运行时产物 + - 将 client/public 与构建产物纳入镜像 +- 运行容器 + - 映射端口:80/443 至宿主机 + - 挂载证书卷:server.key 与 server.cert + - 挂载上传目录:确保持久化 + - 设置环境变量覆盖默认配置(如 PORT、MODE、LOGGING) + +### C. Kubernetes 部署配置 +- Deployment + - 设置副本数、就绪探针与存活探针 + - 资源请求与限制 +- Service + - ClusterIP/LoadBalancer/Ingress 选择 +- ConfigMap + - 非敏感配置项(如日志级别) +- Secret + - 证书与敏感参数(如密钥文件) +- Ingress + - TLS 配置与路径转发规则 + +### D. 反向代理设置 +- Nginx + - HTTPS 终止:配置 server 与 ssl_certificate/ssl_certificate_key + - 负载均衡:upstream 与健康检查 + - 静态资源缓存与压缩 + - WebSocket 反向代理:升级头部透传 +- Apache + - mod_ssl 终止 HTTPS + - mod_proxy_http 与 mod_proxy_wstunnel 代理 HTTP 与 WebSocket + - 启用压缩与缓存 + +### E. SSL/TLS 证书 +- 申请 + - Let’s Encrypt(免费)、商业 CA 或企业内 CA +- 配置 + - 将证书与私钥放置于安全位置 + - 在启动参数中指定 -k/--keyfile 与 -c/--certfile + - 或通过环境变量 KEYFILE/CERTFILE 指定 + +### F. 监控与日志 +- 日志 + - 使用 morgan 输出访问日志,按天切割 + - 结合日志聚合(如 ELK/Fluentd)与告警 +- 指标 + - 连接数、吞吐量、错误率、响应时间 + - 媒体帧率、丢包率等业务指标 + +### G. 自动化部署与 CI/CD +- 构建 + - 使用 npm ci、tsc 与 pkg 生成可执行文件 +- 测试 + - Jest 单元测试与 Postman 集成测试 +- 部署 + - Docker 镜像推送至镜像仓库 + - Kubernetes 应用部署与滚动更新 +- 脚本 + - run.bat 示例展示了构建与启动流程,可参考扩展为 CI/CD 步骤 + +章节来源 +- [run.bat:1-17](file://run.bat#L1-L17) +- [package.json:5-12](file://package.json#L5-L12) \ No newline at end of file diff --git a/.qoder/repowiki/zh/content/项目概述.md b/.qoder/repowiki/zh/content/项目概述.md new file mode 100644 index 0000000..20e3f67 --- /dev/null +++ b/.qoder/repowiki/zh/content/项目概述.md @@ -0,0 +1,439 @@ +# 项目概述 + + +**本文档引用的文件** +- [src/index.ts](file://src/index.ts) +- [src/server.ts](file://src/server.ts) +- [src/websocket.ts](file://src/websocket.ts) +- [src/class/websockethandler.ts](file://src/class/websockethandler.ts) +- [src/class/httphandler.ts](file://src/class/httphandler.ts) +- [src/signaling.ts](file://src/signaling.ts) +- [client/public/bidirectional/js/main.js](file://client/public/bidirectional/js/main.js) +- [client/public/onebyone/main.js](file://client/public/onebyone/main.js) +- [client/src/peer.js](file://client/src/peer.js) +- [client/src/sender.js](file://client/src/sender.js) +- [package.json](file://package.json) +- [client/package.json](file://client/package.json) +- [src/log.ts](file://src/log.ts) +- [src/class/options.ts](file://src/class/options.ts) + + +## 目录 +1. [简介](#简介) +2. [项目结构](#项目结构) +3. [核心组件](#核心组件) +4. [架构总览](#架构总览) +5. [详细组件分析](#详细组件分析) +6. [依赖分析](#依赖分析) +7. [性能考虑](#性能考虑) +8. [故障排查指南](#故障排查指南) +9. [结论](#结论) +10. [附录](#附录) + +## 简介 +Video Socket Server 是一个基于 WebRTC 的实时双向视频通信服务端与前端示例项目,支持两种信令模式(WebSocket 与 HTTP 轮询)与两种通信模式(公共模式与私有模式)。项目旨在为 Unity Render Streaming、远程视频监控、在线教育等场景提供低延迟、高可靠性的视频传输基础设施。 + +- 核心目标 + - 提供稳定可靠的 WebRTC 信令与媒体转发能力 + - 支持公共模式(广播式)与私有模式(点对点/一对多) + - 提供 WebSocket 与 HTTP 两种信令通道,适配不同网络环境 + - 提供丰富的前端示例,覆盖双向视频通话、一对一通话、输入遥测等 + +- 技术定位 + - 后端:Node.js + TypeScript + Express.js + - 前端:原生 JavaScript(ES Modules),结合 WebRTC API + - 协议:WebSocket(ws:// 或 wss://)与 HTTP RESTful 接口 + - 数据结构:Offer/Answer 与 ICE Candidate 的信令封装 + +- 主要应用场景 + - Unity Render Streaming:通过 WebRTC 实现远端渲染画面的低延迟传输 + - 远程视频监控:支持多路摄像头与回放控制 + - 在线教育:支持课堂互动、屏幕共享与聊天 + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) + +## 项目结构 +项目采用前后端分离的组织方式: +- 后端(src):服务启动、HTTP 路由、WebSocket 信令、日志与配置 +- 前端(client):静态资源与示例页面,包含多个演示场景(双向视频、一对一通话、输入遥测等) + +```mermaid +graph TB +subgraph "后端(src)" +IDX["入口: index.ts"] +SRV["HTTP服务: server.ts"] +WS["WebSocket信令: websocket.ts"] +WSH["WS处理器: class/websockethandler.ts"] +HTH["HTTP处理器: class/httphandler.ts"] +SIG["信令路由: signaling.ts"] +LOG["日志: log.ts"] +OPT["配置: class/options.ts"] +end +subgraph "前端(client)" +PUB["静态资源: public/*"] +SRC["模块: src/*"] +end +IDX --> SRV +SRV --> SIG +SRV --> PUB +SRV --> SRC +SRV --> WS +WS --> WSH +SIG --> HTH +IDX --> LOG +IDX --> OPT +``` + +**图表来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) + +**章节来源** +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +## 核心组件 +- 服务入口与启动 + - 解析命令行参数,创建 HTTP/HTTPS 服务器,根据配置选择信令模式 + - 启动 WebSocket 信令或 HTTP 轮询信令 + +- HTTP 服务与静态资源 + - 提供 /config 接口返回运行配置(是否使用 WebSocket、启动模式、日志级别) + - 提供 /signaling 路由,挂载 HTTP 信令处理器 + - 提供静态页面与模块资源,首页自动指向 client/public/index.html + +- WebSocket 信令 + - 监听连接、消息、关闭事件,解析多种信令类型(connect/disconnect/offer/answer/candidate/broadcast/ping/pong/call-request/on-message) + - 将消息分派至 WS 处理器进行业务逻辑处理 + +- WS 处理器(1对多/私有模式) + - 维护连接组(host 与 participants),支持广播与定向转发 + - 处理 offer/answer/candidate 的路由与转发,支持 participantId 标识 + +- HTTP 信令处理器(HTTP 轮询) + - 提供会话管理与连接管理接口,支持轮询获取信令消息 + - 支持超时会话清理与断开连接记录 + +- 日志与配置 + - 可配置日志级别,统一输出格式 + - Options 接口定义运行参数(端口、证书、信令类型、通信模式、日志级别) + +**章节来源** +- [src/index.ts:13-109](file://src/index.ts#L13-L109) +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) +- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10) + +## 架构总览 +系统采用“HTTP 服务 + WebSocket/HTTP 信令”的双信令架构,支持公共模式与私有模式两种通信策略。 + +```mermaid +graph TB +subgraph "客户端" +FE1["双向视频示例
bidirectional/main.js"] +FE2["一对一通话示例
onebyone/main.js"] +PEER["Peer类
peer.js"] +SEND["输入遥测Sender
sender.js"] +end +subgraph "服务端" +HTTP["HTTP服务
server.ts"] +WS["WebSocket信令
websocket.ts"] +WSH["WS处理器
websockethandler.ts"] +HTH["HTTP处理器
httphandler.ts"] +SIG["信令路由
signaling.ts"] +end +FE1 --> |WebSocket/HTTP| HTTP +FE2 --> |WebSocket/HTTP| HTTP +HTTP --> SIG +HTTP --> WS +WS --> WSH +SIG --> HTH +FE1 --> PEER +FE2 --> PEER +FE1 --> SEND +``` + +**图表来源** +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [client/public/bidirectional/js/main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) +- [client/public/onebyone/main.js:1-216](file://client/public/onebyone/main.js#L1-L216) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) + +## 详细组件分析 + +### WebSocket 信令流程(序列图) +展示客户端通过 WebSocket 发送/接收信令的典型流程。 + +```mermaid +sequenceDiagram +participant C as "客户端" +participant WS as "WebSocket服务器" +participant H as "WS处理器" +C->>WS : "connect" 连接建立 +WS->>H : "onConnect(connectionId)" +H-->>C : "connect" {connectionId, role, participantId} +C->>WS : "offer" {connectionId, sdp} +WS->>H : "onOffer(ws, message)" +H-->>C : "offer" 转发/广播 +C->>WS : "answer" {connectionId, sdp} +WS->>H : "onAnswer(ws, message)" +H-->>C : "answer" 转发/广播 +C->>WS : "candidate" {connectionId, candidate, sdpMLineIndex, sdpMid} +WS->>H : "onCandidate(ws, message)" +H-->>C : "candidate" 转发/广播 +C->>WS : "disconnect" {connectionId} +WS->>H : "onDisconnect(ws, connectionId)" +H-->>C : "disconnect" 通知 +``` + +**图表来源** +- [src/websocket.ts:65-114](file://src/websocket.ts#L65-L114) +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) + +**章节来源** +- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118) +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +### HTTP 信令轮询流程(序列图) +展示客户端通过 HTTP 轮询获取信令消息的流程。 + +```mermaid +sequenceDiagram +participant C as "客户端" +participant S as "HTTP服务" +participant R as "信令路由" +participant H as "HTTP处理器" +C->>S : "PUT /signaling" 创建会话 +S->>R : "路由到 httphandler" +R->>H : "createSession()" +H-->>C : "{sessionId}" +C->>S : "PUT /signaling/connection" {connectionId} +S->>R : "路由到 httphandler" +R->>H : "createConnection()" +H-->>C : "{connectionId, polite}" +loop 轮询 +C->>S : "GET /signaling/offer?fromtime=..." +S->>R : "路由到 httphandler" +R->>H : "getOffer(fromtime)" +H-->>C : "{offers}" +end +C->>S : "POST /signaling/offer" {sdp} +S->>R : "路由到 httphandler" +R->>H : "postOffer()" +H-->>C : "OK" +C->>S : "DELETE /signaling" 删除会话 +S->>R : "路由到 httphandler" +R->>H : "deleteSession()" +H-->>C : "200" +``` + +**图表来源** +- [src/server.ts:25-89](file://src/server.ts#L25-L89) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [src/class/httphandler.ts:661-696](file://src/class/httphandler.ts#L661-L696) +- [src/class/httphandler.ts:268-318](file://src/class/httphandler.ts#L268-L318) +- [src/class/httphandler.ts:492-501](file://src/class/httphandler.ts#L492-L501) +- [src/class/httphandler.ts:549-558](file://src/class/httphandler.ts#L549-L558) + +**章节来源** +- [src/server.ts:14-90](file://src/server.ts#L14-L90) +- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25) +- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800) + +### 前端组件与 WebRTC 集成 +- 双向视频示例(bidirectional) + - 通过 RenderStreaming 管理连接,动态选择 WebSocket 或 HTTP 信令 + - 使用 Peer 类封装 RTCPeerConnection 生命周期与事件 + - 使用 Sender 类实现鼠标/键盘/手柄/触摸的输入遥测与数据通道发送 + +- 一对一通话示例(onebyone) + - 基于 store/UIRenderer 的状态管理与 UI 渲染 + - 支持通话请求、接听/拒接、媒体切换、录制控制等 + +```mermaid +classDiagram +class Peer { ++connectionId ++polite ++pc ++ontrack() ++ondatachannel() ++onicecandidate() ++onnegotiationneeded() ++getTransceivers() ++addTrack() ++addTransceiver() ++createDataChannel() ++getStats() ++onGotDescription() ++onGotCandidate() +} +class Sender { ++devices ++addMouse() ++addKeyboard() ++addGamepad() ++addTouchscreen() ++_queueStateEvent() ++_queueTextEvent() +} +class Observer { ++channel ++onNext(message) +} +Sender --> Observer : "通过数据通道发送" +Peer --> Sender : "配合输入遥测" +``` + +**图表来源** +- [client/src/peer.js:3-188](file://client/src/peer.js#L3-L188) +- [client/src/sender.js:14-209](file://client/src/sender.js#L14-L209) + +**章节来源** +- [client/public/bidirectional/js/main.js:1-383](file://client/public/bidirectional/js/main.js#L1-L383) +- [client/public/onebyone/main.js:1-216](file://client/public/onebyone/main.js#L1-L216) +- [client/src/peer.js:1-188](file://client/src/peer.js#L1-L188) +- [client/src/sender.js:1-209](file://client/src/sender.js#L1-L209) + +### 通信模式与连接组(流程图) +展示私有模式下的连接组管理与消息转发逻辑。 + +```mermaid +flowchart TD +Start(["进入 onConnect"]) --> CheckPrivate{"是否私有模式?"} +CheckPrivate --> |否| Public["公共模式: 创建连接组(若无)"] +CheckPrivate --> |是| Private["私有模式: host/参与者管理"] +Public --> BroadcastOffer["广播 offer 给其他客户端"] +Private --> HostCheck{"是否已有 host?"} +HostCheck --> |否| CreateHost["创建 host 并标记为 polite=false"] +HostCheck --> |是| AddParticipant["添加为 participant 并通知 host"] +OfferRoute{"消息来源角色"} --> |host| ToParticipants["转发给所有 participants"] +OfferRoute --> |participant| ToHost["转发给 host"] +AnswerRoute{"消息来源角色"} --> |host| ToTarget["转发给指定 participant"] +AnswerRoute --> |participant| ToHost2["转发给 host"] +CandidateRoute{"消息来源角色"} --> |host| ToParticipants2["转发给所有 participants"] +CandidateRoute --> |participant| ToHost3["转发给 host"] +``` + +**图表来源** +- [src/class/websockethandler.ts:145-206](file://src/class/websockethandler.ts#L145-L206) +- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338) +- [src/class/websockethandler.ts:309-338](file://src/class/websockethandler.ts#L309-L338) + +**章节来源** +- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479) + +## 依赖分析 +- 后端依赖 + - express:HTTP 服务框架 + - ws:WebSocket 服务器 + - morgan:HTTP 访问日志 + - cors/multer:跨域与文件上传 + - uuid:会话 ID 生成 + - commander:命令行参数解析 + +- 前端依赖 + - Jest(开发):单元测试 + - ESLint(开发):代码规范 + - 浏览器原生 WebRTC API + +```mermaid +graph LR +P["package.json"] --> E["express"] +P --> W["ws"] +P --> M["morgan"] +P --> C["cors"] +P --> U["uuid"] +P --> CMD["commander"] +CP["client/package.json"] --> J["jest"] +CP --> ESL["eslint"] +``` + +**图表来源** +- [package.json:14-46](file://package.json#L14-L46) +- [client/package.json:9-18](file://client/package.json#L9-L18) + +**章节来源** +- [package.json:1-60](file://package.json#L1-L60) +- [client/package.json:1-19](file://client/package.json#L1-L19) + +## 性能考虑 +- 信令模式选择 + - WebSocket:低延迟、高吞吐,适合实时交互 + - HTTP 轮询:兼容性更好,适合受限网络或代理环境 + +- 通信模式选择 + - 私有模式:host 与 participants 明确分离,便于控制与扩展 + - 公共模式:广播式转发,适合简单场景但需注意带宽与 CPU 开销 + +- 日志与监控 + - 可配置日志级别,避免生产环境过度输出 + - 建议结合浏览器 WebRTC 统计 API 与服务端日志进行性能分析 + +- 媒体参数 + - 合理设置分辨率、帧率与编解码器偏好,平衡画质与延迟 + +[本节为通用指导,无需具体文件分析] + +## 故障排查指南 +- 启动与证书 + - HTTPS 启用需提供 server.key 与 server.cert;可通过命令行参数指定路径 + - 若证书缺失,服务将以 HTTP 方式启动 + +- 信令类型校验 + - 仅支持 websocket 与 http 两种类型;非法值会被重置为 websocket + +- WebSocket 心跳与断开 + - 服务器内置心跳检测(ping/pong),长时间无活动将触发断开 + - 客户端需正确处理断开事件并清理资源 + +- HTTP 会话超时 + - 会话在指定时间内无请求将被清理,避免内存泄漏 + - 客户端应定期轮询或及时释放会话 + +**章节来源** +- [src/index.ts:75-82](file://src/index.ts#L75-L82) +- [src/websocket.ts:95-113](file://src/websocket.ts#L95-L113) +- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232) +- [src/log.ts:1-51](file://src/log.ts#L1-L51) + +## 结论 +Video Socket Server 提供了完整的 WebRTC 信令与媒体传输方案,具备灵活的信令与通信模式选择、清晰的前后端分离架构,以及丰富的前端示例。通过合理配置与优化,可在多种实际场景中实现高质量的实时视频通信。 + +[本节为总结性内容,无需具体文件分析] + +## 附录 +- 快速启动 + - 开发模式:执行脚本 dev,启动 HTTPS 服务,监听指定端口 + - 生产模式:构建后运行 build/index.js,或使用打包工具 + +- 常用接口 + - GET /config:获取运行配置 + - GET /signaling/connection-ids:获取所有连接 ID + - PUT /signaling:创建会话 + - PUT /signaling/connection:创建连接 + - GET /signaling/offer|answer|candidate:轮询获取信令 + - POST /signaling/offer|answer|candidate:推送信令 + - DELETE /signaling:删除会话 + +**章节来源** +- [src/server.ts:25-89](file://src/server.ts#L25-L89) +- [src/signaling.ts:6-24](file://src/signaling.ts#L6-L24) +- [package.json:5-12](file://package.json#L5-L12) \ No newline at end of file diff --git a/.qoder/repowiki/zh/meta/repowiki-metadata.json b/.qoder/repowiki/zh/meta/repowiki-metadata.json new file mode 100644 index 0000000..71d51e2 --- /dev/null +++ b/.qoder/repowiki/zh/meta/repowiki-metadata.json @@ -0,0 +1 @@ +{"knowledge_relations":[{"id":1,"source_id":"61d25281-9508-46b0-b5f5-e98340a424b2","target_id":"a18fb879-c53d-45f7-a0e6-65518ead784f","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 61d25281-9508-46b0-b5f5-e98340a424b2 -\u003e a18fb879-c53d-45f7-a0e6-65518ead784f","gmt_create":"2026-05-16T13:21:38.7486692+08:00","gmt_modified":"2026-05-16T13:21:38.7486692+08:00"},{"id":2,"source_id":"61d25281-9508-46b0-b5f5-e98340a424b2","target_id":"1b4157ca-4c87-461f-bb56-051340bbca1e","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 61d25281-9508-46b0-b5f5-e98340a424b2 -\u003e 1b4157ca-4c87-461f-bb56-051340bbca1e","gmt_create":"2026-05-16T13:21:38.749676+08:00","gmt_modified":"2026-05-16T13:21:38.749676+08:00"},{"id":3,"source_id":"61d25281-9508-46b0-b5f5-e98340a424b2","target_id":"87689515-cccf-4c24-9817-beb105a160ad","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 61d25281-9508-46b0-b5f5-e98340a424b2 -\u003e 87689515-cccf-4c24-9817-beb105a160ad","gmt_create":"2026-05-16T13:21:38.7501821+08:00","gmt_modified":"2026-05-16T13:21:38.7501821+08:00"},{"id":4,"source_id":"61d25281-9508-46b0-b5f5-e98340a424b2","target_id":"d1dc2fd0-8acc-483b-8b48-a631b00b3db7","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 61d25281-9508-46b0-b5f5-e98340a424b2 -\u003e d1dc2fd0-8acc-483b-8b48-a631b00b3db7","gmt_create":"2026-05-16T13:21:38.7506876+08:00","gmt_modified":"2026-05-16T13:21:38.7506876+08:00"},{"id":5,"source_id":"61d25281-9508-46b0-b5f5-e98340a424b2","target_id":"019782c6-56d6-47da-8dc9-90dd3c5cc78b","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 61d25281-9508-46b0-b5f5-e98340a424b2 -\u003e 019782c6-56d6-47da-8dc9-90dd3c5cc78b","gmt_create":"2026-05-16T13:21:38.7506876+08:00","gmt_modified":"2026-05-16T13:21:38.7506876+08:00"},{"id":6,"source_id":"d33fe06c-8396-47dc-90b3-e6799315126d","target_id":"4cea0b6d-9c76-4625-9ecf-a7e8ab8514a8","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d33fe06c-8396-47dc-90b3-e6799315126d -\u003e 4cea0b6d-9c76-4625-9ecf-a7e8ab8514a8","gmt_create":"2026-05-16T13:21:38.7506876+08:00","gmt_modified":"2026-05-16T13:21:38.7506876+08:00"},{"id":7,"source_id":"d33fe06c-8396-47dc-90b3-e6799315126d","target_id":"848b8177-5da0-4c80-b1b2-0dfadad67e2e","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d33fe06c-8396-47dc-90b3-e6799315126d -\u003e 848b8177-5da0-4c80-b1b2-0dfadad67e2e","gmt_create":"2026-05-16T13:21:38.7517037+08:00","gmt_modified":"2026-05-16T13:21:38.7517037+08:00"},{"id":8,"source_id":"d33fe06c-8396-47dc-90b3-e6799315126d","target_id":"53d99faa-d1e3-42be-8474-c977db3da42c","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d33fe06c-8396-47dc-90b3-e6799315126d -\u003e 53d99faa-d1e3-42be-8474-c977db3da42c","gmt_create":"2026-05-16T13:21:38.7517037+08:00","gmt_modified":"2026-05-16T13:21:38.7517037+08:00"},{"id":9,"source_id":"d33fe06c-8396-47dc-90b3-e6799315126d","target_id":"f908d033-1e13-451d-be55-93fb9a9f181c","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d33fe06c-8396-47dc-90b3-e6799315126d -\u003e f908d033-1e13-451d-be55-93fb9a9f181c","gmt_create":"2026-05-16T13:21:38.752359+08:00","gmt_modified":"2026-05-16T13:21:38.752359+08:00"},{"id":10,"source_id":"d33fe06c-8396-47dc-90b3-e6799315126d","target_id":"f11438e7-f21b-467b-9011-af1c2e3881aa","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d33fe06c-8396-47dc-90b3-e6799315126d -\u003e f11438e7-f21b-467b-9011-af1c2e3881aa","gmt_create":"2026-05-16T13:21:38.752359+08:00","gmt_modified":"2026-05-16T13:21:38.752359+08:00"},{"id":11,"source_id":"d4d5d7d1-e282-4f68-ae3f-fb6945f659eb","target_id":"8a8dc12e-8f1a-43db-b330-96dd3f8c56b3","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d4d5d7d1-e282-4f68-ae3f-fb6945f659eb -\u003e 8a8dc12e-8f1a-43db-b330-96dd3f8c56b3","gmt_create":"2026-05-16T13:21:38.752359+08:00","gmt_modified":"2026-05-16T13:21:38.752359+08:00"},{"id":12,"source_id":"d4d5d7d1-e282-4f68-ae3f-fb6945f659eb","target_id":"2c75e3e7-eeb6-4356-9355-00277b33c568","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d4d5d7d1-e282-4f68-ae3f-fb6945f659eb -\u003e 2c75e3e7-eeb6-4356-9355-00277b33c568","gmt_create":"2026-05-16T13:21:38.752359+08:00","gmt_modified":"2026-05-16T13:21:38.752359+08:00"},{"id":13,"source_id":"d4d5d7d1-e282-4f68-ae3f-fb6945f659eb","target_id":"bd5891a7-c453-4ada-b8ea-30fb4aaf7a1a","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d4d5d7d1-e282-4f68-ae3f-fb6945f659eb -\u003e bd5891a7-c453-4ada-b8ea-30fb4aaf7a1a","gmt_create":"2026-05-16T13:21:38.753367+08:00","gmt_modified":"2026-05-16T13:21:38.753367+08:00"},{"id":14,"source_id":"d4d5d7d1-e282-4f68-ae3f-fb6945f659eb","target_id":"f7e1d371-a005-48ff-9493-574ea74c9bc7","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d4d5d7d1-e282-4f68-ae3f-fb6945f659eb -\u003e f7e1d371-a005-48ff-9493-574ea74c9bc7","gmt_create":"2026-05-16T13:21:38.7538719+08:00","gmt_modified":"2026-05-16T13:21:38.7538719+08:00"},{"id":15,"source_id":"d4d5d7d1-e282-4f68-ae3f-fb6945f659eb","target_id":"b5178261-7194-4e7b-8332-d9bffdd37e23","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: d4d5d7d1-e282-4f68-ae3f-fb6945f659eb -\u003e b5178261-7194-4e7b-8332-d9bffdd37e23","gmt_create":"2026-05-16T13:21:38.7544217+08:00","gmt_modified":"2026-05-16T13:21:38.7544217+08:00"},{"id":16,"source_id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","target_id":"7de21301-1800-46c4-9e90-252230ab0880","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0 -\u003e 7de21301-1800-46c4-9e90-252230ab0880","gmt_create":"2026-05-16T13:21:38.7544217+08:00","gmt_modified":"2026-05-16T13:21:38.7544217+08:00"},{"id":17,"source_id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","target_id":"656adbc4-c074-411d-8c5e-ba6611bf599d","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0 -\u003e 656adbc4-c074-411d-8c5e-ba6611bf599d","gmt_create":"2026-05-16T13:21:38.7550932+08:00","gmt_modified":"2026-05-16T13:21:38.7550932+08:00"},{"id":18,"source_id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","target_id":"d4fd3a89-ae04-4f08-95e7-4ed903b28c49","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0 -\u003e d4fd3a89-ae04-4f08-95e7-4ed903b28c49","gmt_create":"2026-05-16T13:21:38.7550932+08:00","gmt_modified":"2026-05-16T13:21:38.7550932+08:00"},{"id":19,"source_id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","target_id":"f51cc9c8-b978-40a2-bb68-b06fd46df9aa","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0 -\u003e f51cc9c8-b978-40a2-bb68-b06fd46df9aa","gmt_create":"2026-05-16T13:21:38.7556094+08:00","gmt_modified":"2026-05-16T13:21:38.7556094+08:00"},{"id":20,"source_id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","target_id":"53ee4d63-72a8-4732-bc7d-00eda6d8d780","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0 -\u003e 53ee4d63-72a8-4732-bc7d-00eda6d8d780","gmt_create":"2026-05-16T13:21:38.7561393+08:00","gmt_modified":"2026-05-16T13:21:38.7561393+08:00"},{"id":21,"source_id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","target_id":"d95a5269-84e0-4428-b367-dcbe5fcdf516","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0 -\u003e d95a5269-84e0-4428-b367-dcbe5fcdf516","gmt_create":"2026-05-16T13:21:38.7561393+08:00","gmt_modified":"2026-05-16T13:21:38.7561393+08:00"},{"id":22,"source_id":"7ff7c770-eec8-49dd-afda-7dc0b0e25487","target_id":"97215c0a-a8fa-4946-8d6f-ffdc1d5f09d2","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 7ff7c770-eec8-49dd-afda-7dc0b0e25487 -\u003e 97215c0a-a8fa-4946-8d6f-ffdc1d5f09d2","gmt_create":"2026-05-16T13:21:38.75765+08:00","gmt_modified":"2026-05-16T13:21:38.75765+08:00"},{"id":23,"source_id":"7ff7c770-eec8-49dd-afda-7dc0b0e25487","target_id":"8bac35a5-6784-4e7b-b543-e1cbbf86f0d0","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 7ff7c770-eec8-49dd-afda-7dc0b0e25487 -\u003e 8bac35a5-6784-4e7b-b543-e1cbbf86f0d0","gmt_create":"2026-05-16T13:21:38.7581964+08:00","gmt_modified":"2026-05-16T13:21:38.7581964+08:00"}],"wiki_catalogs":[{"id":"a9dcd690-f864-48f3-8b00-ab83d8beef13","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"项目概述","description":"project-overview","prompt":"为 Video Socket Server 项目创建全面的概述内容。解释项目的核心目标、技术架构和在实时视频传输领域的定位。详细介绍基于 WebRTC 的双向视频通信能力、多模式通信支持(公共模式和私有模式)、以及双信令系统(WebSocket 和 HTTP 轮询)。说明项目的主要应用场景,如 Unity Render Streaming、远程视频监控、在线教育等。提供技术栈概览,包括 Node.js/TypeScript 后端、原生 JavaScript 前端、Express.js 框架、WebSocket 协议和 WebRTC API。包含项目结构图和核心组件关系图,帮助开发者快速理解整体架构。","progress_status":"completed","dependent_files":"package.json,README.md,src/index.ts,src/server.ts","gmt_create":"2026-05-16T12:31:02.6502977+08:00","gmt_modified":"2026-05-16T12:34:31.6762069+08:00","raw_data":"WikiEncrypted:0MI1/XkBoMl0lTbK6t0Cn/+8FdvqrJ62ianMLvZj02elJtBUgH0Ns0veIx1WPtM9wwkxI+/XAqHLxJLlHB8wV3TbVTwChn/8g3/Jvqy5Vx1Gr0/hd85jpY9OByhDcSXrKbJXim6ejLIeZhLyxIKVgp0GibD0iD+Rnra0JgHnJ8ezTJeB0Z6Xn7SbWjUbB3urjf09pCgFnEx51ZrW8E1dG7TSN+t7PF3ZOywFWfF1qQwhwwwc5QczKRpBCqavRoq99W2FnrKmXlOsnNk6cin0qvuSYdSs9onLBfyRQhVg1mvaLNAD7zEGOnfUo1Q351tDFxVKDJJ5mdqZvEgI1QWmeh1eKaPBIVkvmm3AjfPmC6f+gXtCWwYkzHIpw/1nvs4dsGZMciQJuBfEJ5psGx4AQbSSde7hz3qcnXDBOSpmUwNYxVcB7VQBOlXPpLkp8PJJHU4dZP0aqdS6GfYG5nJb3OKJmTx95rOz5mfAvBhRba3abeTtqZmbFyPCkVhnhHEZSKrob0Q1lTn6DMj2TizUmO26QlDOMzsrmOEnLz9oyHwS51ZoO54tZ1JqbbG34IViwvIiM0us83+uwDt9YtCGc+R7HIZFreqCUCqkjk6FqgtqTYr3fEg3pWxgRWyPDP97Q8L3NGxhJ8+kyraIhvmGo0TQR8viqpG7BqV6vkBvUuMF24ZyovAQynNol4GGrd924IcqULo8JA74Sta7Ny4kY8kmwDVqZs7ARSRerbp6GCB2dqdVK/vpwsBAixRLCNE0wk1rm1Ndtm0uZkc+m8BlCwkliUn5WPknHOgP4J7ZFRwMw9wyTDtlyS9hKAe5P4G1S+EXiTxdGXOcxD2emnRHsE/26F6Cha8BvIm7aBP7VfsZm2aYAP72mHTiJ217OhdlLJSqIw26kPXAlndiG3mEsqjjESV/SK6SOfyoZLp8bpI5L+AGEhdw8VOoeiTjLuyZ+g3a5MoF2aCLAyFlor0rAzUbM/k3JgQOPkb1HXb9VsodJnmNoMNu/yAzVt/yCAZ77Fcj8PKXW4H/4MqQygR37AIASkt0FmFZho3k78DHPKL5cz/ixA2wd+Ssc1Rc7+mZsDHGjbr+luAeGYVZoOISFiKXrJYKk6vdVTSxZXlG26T1scoWHewhbggy2wYJ+bbL8PYKWTXcq7J9iH9aWnkn7Vh8T4UHxwmgqoFKw4oUTmn5JM6YvLtOBHpyIIiRAGPQPztrp+l+F0xPc0zxmVW2luuHXA9q5ottG8ePG73vJwrkE92+pZb5ofd7j3B9I7QY"},{"id":"5d349e63-8143-47c8-a4d8-9abf775836a0","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"服务器入口点","description":"server-entrypoint","prompt":"为服务器入口点模块创建详细的技术文档。深入解释 RenderStreaming 类的设计和实现,包括命令行参数解析机制、配置选项处理和服务器启动流程。详细说明 Options 类的配置管理,包括端口设置、HTTPS 配置、信令协议选择和通信模式配置。解释程序启动时的参数验证和默认值处理逻辑。提供各种启动配置的示例和最佳实践。包含错误处理和配置验证的实现细节。","parent_id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","progress_status":"completed","dependent_files":"src/index.ts,src/class/options.ts","gmt_create":"2026-05-16T12:31:19.7747432+08:00","gmt_modified":"2026-05-16T12:46:18.1852693+08:00","raw_data":"WikiEncrypted:DfAL4INOq29Pd0V8no8kjO5e3C7f6O7e6ABrkMfBm460flADxdC2Ym6wFmxT4T7LI0eujv05jLEFAPRbxUqcmR5QHC5Sf/QqC2cKrGi19/YFpC/HTMzXmrQWh/cCZ7i6+5OvI2IrYEli5z9+RREHJL3P4qmPbhRTAI+p1OcbiRqD5k5tbDfcSwChFQgf94DUdIWVi1NBB4DmL/+kZ8OQqAZ+5llsF1gj3r6MQP6Rx8R1QtLrnfbaBlBCaisBLxpdm5HZBH1QnUdtf8mb3XhJ/qjOqP7/rBIBCXuD/LY0jYdKC3yRPDL4xssAA8MZk3HXgWKoOqNzXpCYTufxWQ0kBLcVK2vB9a44jzf+FViTfjp0r+++9qJh22sMoAjzQOtDWoPTxQ/eeBOToWlqjY/eVRvWpLHQTWambuEeejlYA1TXM90l9Ni2fJkGKCB0NiWoKRJG3KGox9kwblJVR0PuXLLgo61bmKWSg5IxgXLr13G/4PPtpRYTbimU5e9SNLDkvPu4GqyNuTZEqnSoasDP9VkqFMTbpmcSnrRklsyL5Hg+28z9Tjq/jWeP8UqyMVsmKB+t+S/u74fMHFphIOzvggpdQi9cHdWQNhfKbhxpgrif13Kg6jUGQcDMY2Kt0CupSbKb3wTY0vdqztd4LTnCoQcaWkgXDuyXm5y0k3cXnRenACHa7uk58EU9YKi/r7CPUOBINOcVJdN2XCt8b8N8qWkBoF8w9Yg/iua7aa4oYrv2nGi/hNm8hFrXt2lpQP+zSANW18dW8sufiKmet8CLW7y+nn1rBQvWAPemNsO4TKK0j9PoVjMjva+LZFBK7P/AIZNmbWpjcL0LO3rqXuzsnxdLDmWQlSU0INldEPz3WFKwpeoj7HtwK83RENNr+a/6S/UcalcapsQMOebWlbfp8RRRzjJtbwBKSr9M1kmxY82omxMd+j6FAGqTAMk+toN6b4NFyEcteA5bGs5/duu3Ev8XQPTNo5Ue1pKxRGICSNR81Pl0y8c0/ct8NlXQ9+0U","layer_level":1},{"id":"a4cc8c7f-7b51-446f-a7e8-f4c4cbca61af","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"双向通信示例","description":"bidirectional-sample","prompt":"为双向通信示例创建详细的使用和开发文档。深入解释该示例如何实现视频和音频的双向传输,包括 WebRTC 连接建立过程、媒体流发送和接收机制。详细说明 main.js 中的核心逻辑,包括信令客户端的使用、PeerConnection 的配置和管理。解释 sendvideo.js 中的视频发送功能实现。提供完整的使用步骤,包括如何启动服务器、打开示例页面、建立连接和进行双向通信。包含常见的配置选项和故障排除指南。提供实际的代码示例和最佳实践建议。","parent_id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","progress_status":"completed","dependent_files":"client/public/bidirectional/index.html,client/public/bidirectional/js/main.js,client/public/bidirectional/js/sendvideo.js,client/public/bidirectional/css/style.css","gmt_create":"2026-05-16T12:31:24.8939968+08:00","gmt_modified":"2026-05-16T12:46:36.1698925+08:00","raw_data":"WikiEncrypted:MqItpDuY9YqDnR5qOIZ6DkGvxP9REZHWqcuESnzDgIGmuhJrV5lXD/ijJYkTN7DUjOGgO9f+IFEPeyd4eIdhhlFZ8RREv+UA/XZPUhbPoQrmcPN3hVw7LWbxLxXr2kYXqOP4RPtOYlZet1aj70glPncJRSFUUCUfa2FuoiuquW/yRwy61uog4iw0/NnZApGIciQzEMYuVptMljNzLKq9XnbaSBAM2uBVAFRYZCVUSXG18AYUuvDorjh8X0/+wYUn9r53QbGXVYn2n1z2Xs251haWfP1Xe1F8+EebEIHLtcYi7XbS+80uooaAFscillNKSWPfHWUIDu+tAewwSbHE3MSPpvZ+EVessPNlJfHKpEUhRBd8kq04VCFUrfa1cRXwhzkS+nzfhvH1TmJ2mg7obQOxwyCoYT+WyJy4/DRVBDFN7OVmM+f9EK4h3dLySB5sHXZXFaN6cakAGNzTMc2ht7wLORc4EK3wSJReS4elONbZjoVw+SphiEDLI8ig1umlvuSgo+Ekl0agNx9SvIyi3TPXnmH/WAqfKBoGhD05MKK+Y8Lbi45L+QyWqsQODnDEPFudIfm/u52PzUo8rNfPxYEWsAvsd1P0/SAsApUUUC2gbFkGrJlMMxNgKgZC1CebdoU61uHvk8TascUXvyS3uBdvfKZI7iGjf4++nVHYb/Wkr4e4s4XH+OQAgfqoFyof0Q44IDxyjhzWbkE/N0CcC6DnA5U+LDs1NfySiapEtmY6mO4dp2hyeFDXoIwZ90jya5u2OeTfYW9/PkjVDBsOLS6rMsqN4SrCP8ZoxqirkT8YhFIOF7GDddHPlKPgG5Jd7qXw6E8/lwCebYYFoDxIzWkLuYrVZNrcVMVZpCgrGAbXCHiuKd5ywsVNht8V6WrxyxKRrLh8RO3LNo9BdnyDJsJzTi1B7NfHlceb7VBtdMViCaTAmZeeALx3TvmE1pE2As3VCsJb5SFanLFHy2m6OakHAF2GzgBEGsq6thJsxjnah3oUnxzSFGPIuEEb2sNfHENk+wNdi/ULI9iBhELCFqqznb/MqOIMbYwbrDVXQZ/3a8pKfeSAEFjg3MphNfoVESa4fmbZ0FhLx3ltTOivQtoIOwE4n+6KLvkDPtcEmS8uxoIP8Hm1wLzkEjBD6QM4E+mCbKJ+oUQHjytjUYrzWg382g5TrO7d/fPAVscppr6F8J/Jd1snMAZicuGeZhnWOZzFDFaYmTeNvqCn9NaF2M71e0sb4tF5y1DdS3/66IQQ9m+AaKakAKu09H3KaJs528HSxnAag/4c7mD2PuJAOmp1QRUADzlJHTMui9HixHmJUnsZPKshV7rMY9YzOilUTrfPl+eATf9Xt042LzuTDr9zVWhrI2KyGoeKHxkuPv1dlGzJQpszVE06C+2mIVvS","layer_level":1},{"id":"0b227205-af0d-4e95-a18b-9d354af01ac1","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"WebSocket 信令处理器","description":"websocket-handler","prompt":"创建 WebSocket 信令处理器的详细技术文档。深入解释 WebSocket 连接管理机制,包括连接添加、移除和状态维护。详细说明连接组管理算法,包括主机(host)和参与者(participants)的角色分配和通信规则。文档化 Offer/Answer 协商流程在 WebSocket 中的实现,包括 SDP 交换和媒体协商过程。解释 ICE 候选者在 WebSocket 中的传输机制和处理逻辑。提供私有模式和公共模式的实现差异分析。包含心跳检测机制、超时处理和连接恢复策略。提供实际的 WebSocket 信令流程示例和调试技巧。","parent_id":"1e326ae6-1e03-4dcb-979d-90e1865f0bf0","progress_status":"completed","dependent_files":"src/class/websockethandler.ts,src/websocket.ts","gmt_create":"2026-05-16T12:31:42.3654238+08:00","gmt_modified":"2026-05-16T12:47:17.6601811+08:00","raw_data":"WikiEncrypted:846PjIgkeMHsDVBj61Op9/eL6AVfXeMwv77NzrbEoeayqIvGFC0n1i7ba/GxMj6f1wQ5VX5LP9wdfFwhmZINC8Jss17yYqbe6DAE36Pk1SkiltfbOvcj8Dokbdda3JG+3Af0ogq0+pZZe65OTDH0n6ebgsNLT2GIlFB0tsYlNKe8A8ahF1E7XDRf/FhTQJUqQrJUg8mPGJnp1X/HhGvKCuafpFFFTqzTxsCfH/s+53sPYirLxzZ68ArNUIxKBjzEDCTF4jYAR77JWUSRC7weJwjw5gH3WJZkJkqWA9sstDP/EHymI2U3E2FghhefrBoGjIR5q+3EU56NlhQXzYMUVPkgsKM9H5zTQPh/1ru6F11jqdhLT43ZK+KYf5uVGVjaI5cCidRc04gbW4ZI+VnU+JMYw5hPrQRDcCGFT+XMJ+Uu7tr4WlRpZyqkAEO1sWIT7Nqd0tQRT+G//N2U+WiYGiS6AwsdgZkHDG6KYDLcnHg+P1nbxZ/9AKvdfu2yyMERExXBYeQCt7ny7a0AqwzUKlkAL3zWCV6EJOoAk3b03BnhdsoYfmXBjPziDDm319QtKrj7G5mRF+a4sOxQkdh16uPNs4ANe/bVcqwqeAdWyijU9sWA/lv6z4L9/ECj8IdmiLxzolNkpstPCGRsmwfoxTFEioKv0fBkDOcXWBTENU1Yii7mQL4mxvBblK4r4N6elgrA+J3y5dsbCrbmcsFY3UAQnu9oHivaWqTc4KXQuCX6qD66kepRsfRs8XA3UF4G1vCzJgtJfs5BFMxlfvKCJrYtU4bvUU7dw/Umf3dSueWYOU15N/BCGOP+MkYqFgGHFZg5v5L3yBnB+IlWa5zNIuWQm/RILtZwMUTaFqW+eGLBSU4XOzZBmTlx/FWeAgdMb14OpnD917KalUa8KlpdicHyFoT3u/3YYvRIRJGBSBZKdkEhIJ5UQYUrpoXP7hCJV9MbLkyZLXivAcHRmyoPECRMY7ZhBZXbdlgx+mo0oJ8M8bq1yWPKshXP2aOcZTRHEgpHzDHRWtZOyVWFKMO9CV7YqMNP+Q6jRKyH/dOvkl6Ci20y63EkqFxEgT0zIdX7hGxFPOoWRrFhD3mBjwrBmLGlpjU83FaX5ginPSVUwWg+wvafJGcewzkUuzKB3inoIjKulA+lyPiLvgjiq3QONLOQA0LzfZ8RDIfXtzQnAPlkZPEWxWJyQBEfJ2TNfjR/Z3DNbBcVh5rTssqOAzg03nG5yfZbAle+nZIjALdpsTs/uiov7iKR8GmaEv6UFN1qh0mE1e1LIhyXnlu4gK8CdkgFO0KQNOru4u4Aomh3vt9b9PK2tImMNRViE808hEVq","layer_level":1},{"id":"5ef4dc8a-3a84-4e7e-a5f7-40db73889380","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"环境配置","description":"environment-setup","prompt":"创建详细的环境配置指南。说明 Node.js 版本要求(基于 package.json 中的依赖版本),操作系统兼容性(Windows、Linux、macOS)。详细列出系统资源要求,包括内存、CPU 和磁盘空间需求。解释开发环境和生产环境的区别,包括依赖安装、编译配置和运行参数。提供环境变量配置说明,包括端口设置、SSL 证书路径、信令模式等参数配置。包含常见环境问题的排查和解决方案。","parent_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","progress_status":"completed","dependent_files":"package.json,tsconfig.json,.eslintrc.cjs","gmt_create":"2026-05-16T12:31:50.8657899+08:00","gmt_modified":"2026-05-16T12:47:18.3467186+08:00","raw_data":"WikiEncrypted:mJFb0eo+82adP8NAeq7d4LBCxC5w9J+0Fo3COLpkFPsv5pzQPfNIC9hSOAQ0FGEcW3X7M53PFGVNz7i8Y9H6LMPPTwQ9TzwBmfKdK+h2mzYYDu8UFWbTL6idISn6jahNrhzOjieZj8zjwTU34bGk+4e5P4LsTqvvisBYZgyJaoTJ/oejmnngZ69/y0BdyPCHMjeL/+DkIszNK+9ZXRINE8uPZQ+0LoZFQfHQ2gSQkhLw0SQaCwUGBtfy9dlPmKRjV+AiExpH6NICtchTN/qFSnDyRkLOkR9YJZdmplJdnXikIxdXdq1dvHyY4na7aqkKp705u+tv8pHPAlB1zAUapCkT0WoZ0oDzqHIrMxYTpsUC19nG/WnEFUVU/+3anRlt/Sz5uCltEPxWViQDvYsdOowt8XNDE2zFzVczBVawFPrQEnjHnPgzxsa39FP2+/iCxXtFIAIfapKt4w9kq1oYZrPrtqCaglWDecu3YwOlUKNAaHHxfonMdcIsqQMY42KYLDPxVHr+KeLQdCqkXkn4GX01B0FogNTCBX/t5ws+fcpdg7JUDDKegOZ2NxsZg3p5bjzdCswCktd+WABy9IDOtmro934oNNW0TWKT+g6FSmOwxhX6JktrcX0xTKgmQY84/YOmXYRsqaXLw3YOpWOqCDHopDhzrz6edqxyGNFPQQ/gDrx17FNNuaVCI0gV0BVZYU1WOANqNnZDwPq58FN3NkaDQEb1phXNd6/oHvCo80AJT2mwixcTUyYo1r2mqpLcYqaLTmnz1m2qM1sYkjIdx30Jg20Qx98y2XgDzuPUnCY1xEMSg+vDtm2XMGkJjbdmDXEFIpgyOx7dpVkDyasELpux+OTyTD2I8Ki12ZsMnbHsu1vG5AO8AkfbYiNgcz4xRk1jv0vFxpVrtayn1NKaZBgttAkTPx1skx5EZmUymYcnKJN4xMd1DCSGTLT/z6JEsiaZJ3xaIrMn1nd8S/3BaN0Mk4FKmOZAbLiFOpUb4yWM4OVM5T/+fDIrQ9vsPe14vYw00LbtLOfvgLGobupD9g==","layer_level":1},{"id":"20805460-563b-4af7-8279-1cf55b0c7a90","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"PeerConnection 管理","description":"peer-connection-management","prompt":"创建 PeerConnection 管理模块的详细技术文档。深入解释 RTCPeerConnection 的创建和配置,包括连接标识符、极性判断和配置参数的作用。详细说明 SDP 协商过程的实现,包括 Offer/Answer 模型的工作原理、协商状态管理和竞态条件处理。解释 ICE 候选者的收集和处理机制,包括候选者事件的触发和 ICE 连接状态监控。文档化连接生命周期管理,包括连接建立、维护和关闭的完整流程。提供具体的代码示例展示如何处理各种连接状态变化和错误情况。","parent_id":"85699f5e-ca94-4146-b489-1bd32e25bf31","progress_status":"completed","dependent_files":"client/src/peer.js","gmt_create":"2026-05-16T12:31:51.4451482+08:00","gmt_modified":"2026-05-16T12:48:19.7440884+08:00","raw_data":"WikiEncrypted:tpW79Xttt2IDuJ9djVnufLeevi9TAw+DR17O1dw2HLYpQWb4jV2i6PGWCqnyfXoZy8uxtHbrogAoikyPT42HyP7DuEJtyBJmBkXF0ftVDm9VKZFv5ayMCi/YoaRuMMBas8rB30HXzTl/MWv1TcW9rHkdKzCWp43LPr2JqgD2hDx33x8rfpMAWGZ6oVdSojDRBtYnlULp1nuemklc3JXfZInO7uIzloz+1QSRYX55AaK+zTnBzH6utw1oIta1xEP06GRPBOGRVpmYvNdmQJkoZ/zQabyImMyg44vrPdlTr2kG5mYWcAZVrL+CqCvP6oO6Bc07XojGcd/xopbs542wQWw+NP+wOyeN2EJG5eRKh8FCs45X/0C7o6+pB3i3zAmcdAcDLhgho4W3p2/w9QryfPYQmIlAzbNqjJ5sPRi48b2m3ULpTdvyXVg8jpPVY8jNI2YTHqBre0yujsdsgO2T5FwqJ1Vlx0ZFHRZvRHoap1un+1tn5NWw3suPzY2UBsUA64rvxDykn3VWeZ0S/R7r2SkeiGsrFPuU/B2vaggauZW9UQt25TswUraUUbyYoFEcy/UlGnDeqLcOdt3PvszchekH2AFsJ579JNtP1ld+ojeOmrNnVRqNFrEJI5S4trQ+MHaanu26AihW13Ndz9nX6+utQUapsv07lkqxzaO9ZRgKN1Q6IESoqguCXVS5h3XYhlK0aTW8LtpIUpjv6KiBVZKicebRzydCGgfWj+sHii/wdu1PbJNqh1DPcTt2rZ6PH1TocC4Q3jmCGlB9KyeFhZg9MeIKsMgg7BobGWTzRE3oQgGov7zseVrSX5AT5CdM005cQGbEEacKFs3mP1r6PLBG7KzA/zcWIO20cmJPNAFTM7ZVaL8kbE1YSTCelNa6dW6HstjPcKfrFuKLoMKrEt45ib+iSn2iXly/lm1PO3Bnyvh4lC8+CRKeSq3/wWWPTg7tiL+VhoCW/J1R5URVSCVYLOZoEf8mT9JNWBP7T+Va9mO3HFuVBpQkpuqJEMgafmWXLxc/kPPJz0FbRByDh9Ndeh3gDajjIdukLxOJCPdRCAcvYbO+slbvExnyfZ0sHEvkeKZpImoBj2rneJokAWKCGBMlpvUiyj4sTz34TMGcLi9XsSwJfr6n4iVQqkRB/9zU2LYVoDaJAn5294WByQnmwQ6pJJTcQtUXSJGdiFswjuhohHuTEMLSbWgoU+r0fFRua75rl1R1qiSM19DYXn1wpKsE1YEl900FjXnGNL1ejvdkmIw5/h5mWDv9Z07s","layer_level":1},{"id":"73389b15-3341-4577-a7f8-93a58709c76f","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"开发环境搭建","description":"environment-setup","prompt":"创建开发环境搭建指南。详细说明 Node.js 版本要求和安装步骤,包括 LTS 版本的选择建议。解释 TypeScript 环境的配置,包括编译选项和构建配置。提供包管理器的选择建议(npm vs yarn)。详细说明项目依赖的安装过程,包括生产依赖和开发依赖的区别。解释开发脚本的作用和使用方法,包括 dev、build、test 等命令。提供 IDE 配置建议,包括 VS Code 插件推荐和调试配置。包含常见环境问题的排查和解决方案。","parent_id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","progress_status":"completed","dependent_files":"package.json,tsconfig.json,tsconfig.build.json,client/.eslintrc.json","gmt_create":"2026-05-16T12:31:56.6827842+08:00","gmt_modified":"2026-05-16T12:48:27.5144309+08:00","raw_data":"WikiEncrypted:mJFb0eo+82adP8NAeq7d4LBCxC5w9J+0Fo3COLpkFPueKEbQmlzZW8ahrMhEmCXtuop5Yg0lSYYKjoYPiCANIRZLZpOM+EECM3o02H9RDLujtWGpKajc2AQYi0Fd/blier6qodK9Z8TLQbjcaApBTLxVHhaY31kmqQd9jdXlDWjF6JjqkAikfuajjQTNBVU6o1AklK7EJdiZTVHAgk+JAcKGjxHX6s4ue080ii24XFU0rpESW9jYzXfT5mesPH5U13r8WqZCmmijF8d2ZOd/82loqAyk1i8rdcg2/ulLDK4xcky/5feKsOaZXHxk4soZYdUjToGq5SyFVfG6z4ilXCZ4es+HHXhvGxlAPf0RBPS8WTlVy6FtqO30y7SSc7W5IoJh2vITFJxnw83Ck68nCWuX0qq/BWoib7t7IzACn5CJ1gsu1UnSD5pEzBKTjdXrbbiS6fL2UNz/+gFedu8WpUMBXTVUdnDj7PJEW3gXA8MXLpLqEq5+ASba6gtv4kfsEo67sGr1CctqtrhJby6Sxb0WUAqsm1oMYitzJkq4eHBTOGZSe5UrPQRrB72Cwe+qreu2sXsJnPkvVz8kZWlhoHfuOtmcHmN5JLKQ29sCnHdsz4P85Oxt26b/BUzG7ZAQ5u7vv7t+MoNQqV1ndTGDQKUglVa8F+oU47eX4l/MGnmvjbnQChQzy7EjKx6/+JQ8p0H4OqiHk3cxTdbwHnR/68lS3oDW01Xata2SpD6pb8WzYHXTuY3GCDH/MNjhTu67q8K9SA3u3Yo/8NNMUnyg8LpV3Xph4dcbPKdlVzY5OY1/R8P+Bm9J5Z3SvATjPmpNjW21biG9DIQn0YPtpPbNePzrf39DgvoSlVEkBKpPDKlNzk4ZB9m3WV0WLUI+F1rC61HSrRJtr22//xq8ZyfpXwsICajI9+HEqTkpr3h3ArxOhpkPhKN7ufWsHVQpgGzt4LZ3p4uIIX5kb5zpMJVkSWJMr9HPw2zVPTXtD47KEUi0ENtxepSRldMiKOHvfwm2MzyHEQLkpWsK5Ix/HKrysMga94Q8FzW9LXFsEZCpQQbaVkKpKHcnHNL1iUPgmx457R5enbET9ERi8D8+9IRYHd+dO4iqzonLCBrQMQpUXA4=","layer_level":1},{"id":"008e823f-93b2-403a-9d60-08e7cd494f18","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"鼠标输入处理","description":"mouse-input-handling","prompt":"创建鼠标输入处理的详细技术文档。深入解释鼠标事件的捕获、转换和转发机制,包括鼠标移动、点击、滚轮等事件类型的处理流程。详细说明鼠标坐标系统、按钮状态映射和事件数据结构。文档化鼠标事件的时间戳同步、坐标变换和延迟补偿策略。提供具体的代码示例展示如何处理鼠标事件的捕获、序列化和在网络间传输,以及在远端设备上的重放机制。","parent_id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","progress_status":"failed","dependent_files":"client/src/inputremoting.js,client/src/mousebutton.js,client/src/inputdevice.js","gmt_create":"2026-05-16T12:32:15.7272477+08:00","gmt_modified":"2026-05-16T13:21:38.5409245+08:00","raw_data":"WikiEncrypted:wjuZIvqtMZ9FwucWL2xjKEVHCUdgn6bi03awOikZqkk/BJVzg5ikQP/VZ4mtJWhtPfLk38O1gwZaR+bIxF4MsU17R7Kf6XRASRa1fBQgSXLwZyPLYbzcMxDRIC/7OjJ0vmuBjTlMvrjwSDZmGwC6JQ+ryuR5iz61f8s1+cgJCL+UvUoYKzZNlmh+PDk9gJI1rpLJva+HL5AOWhThAUk9+S9BVXlMClfdEdb1gTmle0Rpk0SElMITSJ4DGnJUu81N8iWyEiZovgmSe25H/XJW0NGVuMzH/SrDj+/jkkLNBc6mwl3W6IcUfXcQgcXDsZjhOxpBPKMeFgtGoGGmXh97q3fhdSeOjbGCM61tsk3rbQTzjD5AIjsGCV+3Fymho8LpNk8b/D+3fKoRFzneYpWtArWuuGQChva6OAUaNvUHQMr4VHlYuCxJt9V7lu4ZIZg2EhTx0b1Jo28o9U6BIugwIxu7rMcS/fA6yuLDPVRWwVanm/isTNCjuyCEYBQTstuVeEuK47uJFtnfpWUOGbjEX0aes9rsHO5Scnx/ZodZWVGInbW6IXew5FZzagEloIKchQ6qntTkgbOpUOp7jysoD+c3UR23cUP9WjDRqeoWM2tvZRzKacyP8NiYsAykyH/PDTsT5QiFD6b8p5iNimiyUjNCyLyQzqzMNMiIyioMmlNdJX9Fi2RUfheHcW3ynsgizcAQ+J3w4Tm6os8OVXEdtU8iiV61IEfQXUKE6LzsAklKClfL9mhtGBn/tOMx96i+QfoxMFCmpqGU7x36y4A5R0SGJnEZvC3O94w1V50C6lEPggX+Og1kGfmPkiIV+WG9Q5V4CbA8EpWGs3GcGkE6eg5NfF2nB5JooaIErSoC5DoWxTM78Dt8fs9AP+/uvsNoJI7VqXQ6cQuZzyu0/Z1/aJxsMPgZLKKZE0u5Ok8z7pWiPYb4mSE9Hhk1NyfhZguqGcA/pujM/7luFZJmlegtMixkIGovdlFZaJEJ2Er7CGARbZBqGLnegEdsJ5lAnnI/hF36j7GPaDjXT9W964nCAqd0/aa5bFuCI3FRQJwz/Tvqb0ksZJRU4CsQNi/ZsHTt","layer_level":2},{"id":"ab3ffc79-b32f-4ce9-9e92-122cae33eaae","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"快速开始","description":"getting-started","prompt":"创建 Video Socket Server 的快速开始指南。提供详细的环境要求说明,包括 Node.js 版本要求和操作系统兼容性。详细说明依赖安装步骤,包括全局安装 TypeScript 和其他开发工具。提供服务器启动的完整流程,包括命令行参数说明(端口、模式、SSL 证书等)。演示如何运行内置的客户端示例,包括双向通信、视频接收、多客户端播放等功能。提供常见问题的解决方案和故障排除指南。包含实际的命令行示例和预期输出结果。","order":1,"progress_status":"completed","dependent_files":"package.json,run.bat,src/index.ts,client/public/index.html","gmt_create":"2026-05-16T12:31:02.6502977+08:00","gmt_modified":"2026-05-16T12:33:44.2656433+08:00","raw_data":"WikiEncrypted:qfgbutC7oyxR6nMxrwk1ODnNMBEQ3/sG78fQT1yXWju83LC7H/jAM8CEAvWT8huE5OClmQiIfNYNxHQ9s7RRS59UcYwHhsvrAQS1mvhD+wuHBw9RiG5G4R2zPwwRy3mNXA/rK1rZA7uY4mMtHoUIh2I5oZF6pT3uDbrc6lo7e7TgRjWPsVedkGHYPAM7bvDU4mnvoBm/L2iZIqIHmnkkn6qZfvJaKJ7EPhQmftsFIV4SQ5QLlSBFocmustQzEN1E8cpd1wI/578+P76JuNBhlqTiqvDdsbYhLAhcQQeD0szf1Tjfb+vRmhbKtHuFc5FP9k6uG7u+6Nbq6LdTZgawvEoRWlRnKklZuS1rMkuLd1UrLQ1byC//a2v0+swrr8UF8Gf9aG9Gf4EJkkDi7yhLvqILK8TpguTg/FJKQMU2zNaHpgSvr/zUXSiQDFRbTebwcIv3XuOLtKoJV1+SPcnSfvRaTC1xznBlzRhZrVueoTTrym+8IYEzLxTyMhVYVDgsCqMlkc/q0o7GIs8I6KQi3fhxKd7Qg2KeS0HGIQq7tfK5ppVw3rivIGmOO3Y6YHmDkCPzH0aaIpiwgKukXGwRUmjm9ikPO115tV5XYmcBE20fpX9ejkL9NogiQmfJabZVQLm/U+AZgBXDjawffuVIFK2NCFMZfv+RRrXxeNtIeNmrC/sJNdmoX27z8RB6oL2IdZxF3JFhxdYnIRV/PVOdOeX3vK5lsSyJHwmSuD5Jy9shdVYUeTiXnpSUNfX8XYhJ+++dD7TaCNHuDXJJybXD02HmvCL03a06V93XveYC1HTkDJtQpHt5x89yNFhLrLjE+e93TA9Z6s6I9e71BA3exxGUf+qOw3bMSMZhRWlY1zR1+mPMS/Q5uohvQvcXz+47bsLfBNqhKu98rj0zM4KE5fuOkVVaEchsKHOmlBtG9TULA/s7fkb7sZIXaCp1Sk6DSmwJDWk1GGHKFhqBIPKi9lnjLHXvZiuUrWOTri3+F6Hir5fjDdVuKOpiq/6hNuQDtIHE7Z++pCFG8dofoI8L9nXpKN0aF4qg573v0ZgiI79EIQ66IfeZ/naJLlZr9yknnlD243rZGkRfCDA0av0owUwPlS7fVW+A9no7Bwa8of6Il2wDlBj+QEDTQjEP+Wi9"},{"id":"eca2b26d-d22e-43d2-8e65-b238a269789d","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"HTTP 服务器配置","description":"http-server","prompt":"为 HTTP 服务器配置模块创建详细的技术文档。深入解释 Express 应用程序的创建和配置,包括中间件集成、静态资源托管和路由设置。详细说明 HTTPS 服务器的配置选项,包括 SSL 证书加载、密钥文件处理和安全连接建立。解释 CORS 配置、日志记录中间件和错误处理机制。提供服务器性能优化建议和安全配置最佳实践。包含具体的配置示例和故障排除指南。","parent_id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","order":1,"progress_status":"completed","dependent_files":"src/server.ts,package.json","gmt_create":"2026-05-16T12:31:19.7747432+08:00","gmt_modified":"2026-05-16T12:48:31.5546713+08:00","raw_data":"WikiEncrypted:TL+8cnFaK/dnHCy6UlYEJc9PbMe0R96zZZIIy0QVUGBKgomdMXUWqV4xs+hUfAKGOP25u6tl2wiEBfLZGIl9S5YCqKuq/HC5piLgxynRj2DBbx5NMZY8rhq13ostn6Peyek7OXwFP0lSQhNSBnkKskElboZ+mxi0MawXLh6gonu/PUKk0c3dLxv3tmtQu5MXbvvq8PSzTUjQ6ZpuScp1mG5zBYnSdBsaYbqC0Q+mJz23osaZuoDmx4iJNXSl5pKvRiO8QVmVstBRCz0AGRMRx57Xg3MpWTzweSEn/suOaq0GJ2EJbq9QajbCGOOZDa4BqXfTu4/7Tqm+XA8UkKKDjhbcVfRA/cyHqwPx2S4c64dtJUQ8hYsM/jf/CWOJsNI/4Iz/CQ0UAm70Z28HDej5xQbR+heYF3C0anR4Aj/k90np6wSbW4JIIQgTx/9Vdj/WQRtrh/rKiTnyCzuTjYghU62Iz41ZYdFs91U1Kh7KugyDYbYO2LrKLRQ259M4HdWKLpN5obTcLAdsO4mJ8UAt758tERDM5Cu6rnGO3+q+jkE+xvf7DEZLjcz0I4dZ8xDUYWKpRx3O38pvZhS6W06+Gb7eYhCTkAod1hASI7pMf27fIgcnh2o0KcNGRcfetc/MzoC0kK2QATafNSvc+dhWC8oLy65CDe0/oh+0FizK9VjWcL5BJHv4CniMaJi04NfVDh67Oo9B0IOmOYnfwsGYOM3Bjlqp1kVjBd9WofHcBoyVUrRrO+aUT83Fj1JT3bY4s10sFxoth6pCkGb68+vpZRaxGfEeT/UFgRNrHfEUiCY7EDmqGsEOTv53Ek0pzlk6pK9yATNJd/drceFPfPp5RFROG+yrCCD2re0IrVV69IrZx6XHgJD+DkGs0DNENkoqsGOvJMDPNbGTkDsAkoUvMSbmtW6LGSDpiHdPD0hpbpK1AeOqGEmbsW13nfd3jMnvi00VLHJPPKBcQCVYDWkj+XX5YQhx9MTbxV/GeqMJ5mhIMKVQaDaNF3OfaY4OnLMSzdAZUB6jUA3qtPJyKSvYrw==","layer_level":1},{"id":"68e75bda-0f61-404b-8038-89345a43e067","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"接收端示例","description":"receiver-sample","prompt":"为接收端示例创建全面的使用指南。详细介绍该示例如何接收来自 Unity Render Streaming 的视频和音频流,包括媒体流的接收、解码和播放过程。解释 main.js 中的实现细节,包括 RTCPeerConnection 的配置、媒体轨道的处理和视频元素的渲染。提供详细的使用说明,包括如何连接到服务器、处理媒体流和调试播放问题。包含性能优化建议和兼容性注意事项。提供实际的使用场景和配置示例。","parent_id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","order":1,"progress_status":"completed","dependent_files":"client/public/receiver/index.html,client/public/receiver/js/main.js,client/public/receiver/css/style.css","gmt_create":"2026-05-16T12:31:24.8939968+08:00","gmt_modified":"2026-05-16T12:50:18.4512043+08:00","raw_data":"WikiEncrypted:OxLw75zJyzGExYdh/w1VRxP6Cs7sYYZX6BGL5xinxPx4UoTgYUkF04qyjGdTX8ixdx0zvlRp37k3ySDvLv7frYOH5iTsoyML3VOW7DILMjo4l2NweQ0DkEzFGsDnpGlGfaXAICHNy2kuR5/SMHwuFmd7j9IyeFbmzoiN4IiIvs9srBMQFtslRizWusCqmIKip+8Jdcye6tGXKd1D9HuWkhoDxvPEpG/wn7eAkhPIrrADWExkYafBiUqXdwjtNeZtob1+qvF3el5T7qSyRtG2KuwtTAvA0/SOkBQKfwbFqZBSemcblWQLxPrfCaStFSDS1tGVlKp9f1CMYAxyIvXiS0vmDWYGpO/yrllWpKpD3Vd0jJvUY1CLQyDh7iflpZztd2hEtZYO2Qoh9z/SVV0Amj/sp7KrWxoZqD/BrU7s6dZFF38tM20qnESuME+cM041Gv7u5DqjZEfPHg+tbmZqtwbCYJyx/jgqM+jW5Jg4fUrj2/1ztlJ+XA+ej2Ia/f6KT/W6WzjHI4E380KYc7VEojkCjyESnELKrzJsRm+s00lp5/fisW6GMOHkzBbA0kn9XE6cuM4foWusROC0NWKRBNi2ruvma21oxEzztwQi//d5WjDc5GQ6tIF27ZpXcgJOIwejnnorH9LbeVFmSKYRZ9PdRnyFsQBbBE2Ym7X/gbL7ZN0vMWSp236q0StX6uaXjwIARw4eIkVIaAPpbxm2350WbsRcMvtdWZnNxVfnEM3c4exrEaGaIWOMNSGMt0AIi2ihS4Nv5vtxPYbV3mhLP1B3KWJEGZRQUp2LbmWSuLoNcfbFjaGyEiAUEo+NWCGdTKbKBk5SFMcU8itcsZX5zV3gcxibqTzjfbqa67j/e9qpay6sY1gFpmkhtb+FmhHxsOBY/xzgILRQqZgsVXl2tXrBgsaPva/hcl8vqlMnW4oAdxa0LPVwAEZ0m0VcFDG3KVQ/voqF4/wKiCG9Ihhie19l4TyqZkfLGvRFxsWwqugRzO+VWf1rzUgbeRaLdOjiwunubk7zfgPqJRbhtBSqPSSb63Xu8a6rPfuNoX9Bop7Ff/gpQ/mLMqjtObDSXl8MSwlk82zXEx96AwjcDA5d6i5cbaC1X6ayCm3lUrtwkeY7ix5dxqgJBwsY+Ou0Yno9","layer_level":1},{"id":"482a84ec-9e84-4271-ac38-8ea5dfe61e9e","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"HTTP 信令处理器","description":"http-handler","prompt":"创建 HTTP 信令处理器的综合技术文档。详细解释 HTTP 轮询信令的工作原理和实现机制。深入说明会话管理策略,包括会话创建、维护和销毁流程。文档化 HTTP 信令的消息缓存机制,包括 Offer、Answer 和 Candidate 的存储和检索。解释轮询接口的设计和实现,包括 GET /signaling 端点的响应格式和数据结构。提供 HTTP 信令与 WebSocket 信令的对比分析,包括各自的适用场景和性能特点。包含错误处理、超时管理和重试机制的实现细节。提供 HTTP 信令的实际使用示例和最佳实践建议。","parent_id":"1e326ae6-1e03-4dcb-979d-90e1865f0bf0","order":1,"progress_status":"completed","dependent_files":"src/class/httphandler.ts,src/class/options.ts","gmt_create":"2026-05-16T12:31:42.3659688+08:00","gmt_modified":"2026-05-16T12:50:09.5864059+08:00","raw_data":"WikiEncrypted:nODnyuhS8iIsw8A7yrQNpJtJ/m366Y+2d6UAkw8EUq9Dp9Uf0K9lYQi6OO5njTh8acWP0mamtn/5a4TWPHcyJtMdl8yLWiAAWAFOWeOaWjUoluOFGVSCoeV/vFoLP2BiOueT06XdSIBq/ld35VorIK5fV+1FcaWHmX2Sx3EHvDH6Fm5h3Q+BRgY/Pl+h8PVE6OaGZQbPhPWYC+su5u5xnsX07o0C49dPvkWajI4P/V5NKxgazZPbLPbXMoWTbxffQ53n5k0fB7tSLbECoAfPpJV+ZAKO5GWzL2A2y9y4BVlpzL6nTIf4V/iqTIclLlEraW/Y1QOERu1jPRukLQSxpGgkNi4Z1hHgS9cmjgXBnJJDnqB6J/z7oNHS3MuRphvNLL0JILMHLWvpYsbuQ3hXQio4b4GptdTvagfnWly4xLOMvGsg80w1xsoY6nYBQI/PgCTTMK3MlLfOcqYfIgQc57RentCIDRMuWWbEQ8+6s9sZlCfjypSx1UsxsyqZ2FadH9eNVhV9qLFEDBf1n7p8b+dIZgR1pgXbPPD6BIPhGUSc1RAuRlmj7+Qpeh3r04U1YMdAzXwIfNEPmE0mwrthWm9eLoQdxGKIK/R8QTl4X342dAfJnF5KGT/cmEqEs9vnj1ND742GMqeEs+aXY53cTunB3iFPCRlDFBYt9O6XocrGX/o2oTV6cPqmMAjzENx1rLAGjUih2BF4SaiPrpPkftZK1HNzSeL0fElCJ0P3QWysrAHzlFbR+yQjV85FyFKxBqXiY4w7QifdF9kTbwfuAg4SjAdF6B0KZyzZPMGMSrVXGtAiDiIVNK/AOiPwOxPq1Nzkgn5gWSFmzSxk012DZ8XPQnPMgXZsW8E1iVXvAe0H7Qe9H4DpemHkPtJLEXC1rIjetpOUy1WCT7ZzhP7H3G32RXdjcCR6pIrceJBovY1y4SpoGo6WJEE1sn1msD9EcDu9kUEyJh+WufDGnz0/xkeSQ1VLhD9qMXJzDl2s+JuFFn0aiIKRvfIKxpC+qE9BRzECFMJ9rlEmZe9TEHKyCdV7jb/0SlUM+K6JNgFRAa6+23Irgh5NsGchuQkCLhbkU9sonT2xQ1dYDP17253Mh3XmuuqTsEEhtCT0mR4QVsSAkUkYxmE+CBqbBv/qfC9h4m75hhkk5LnWogsSlXbXeHrd8VXD73B0uNdGKK5o0Fwj0ZNarOBQVTrW6ypqp1SxFywgXn/av3zSQ1vf86oY6z2UuIwMA/4gZPshM3UKVZQkGkIXuruXxiLXkwWnNIobmD1QkBWqEWxprR3rqgcf4A==","layer_level":1},{"id":"bffb854e-14ea-4aa3-8c58-bbb08bef5c9f","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"容器化部署","description":"containerization","prompt":"创建容器化部署文档。提供 Docker 镜像构建指南,包括 Dockerfile 配置和多阶段构建优化。详细说明 Docker Compose 配置文件,包括服务定义、端口映射、卷挂载和环境变量设置。解释容器网络配置,包括端口暴露、内部通信和外部访问。提供 Kubernetes 部署配置,包括 Deployment、Service 和 Ingress 资源定义。包含容器安全配置、资源限制和健康检查设置。提供容器监控和日志收集的最佳实践。","parent_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","order":1,"progress_status":"completed","dependent_files":"package.json,Dockerfile,docker-compose.yml","gmt_create":"2026-05-16T12:31:50.8657899+08:00","gmt_modified":"2026-05-16T12:50:20.8690039+08:00","raw_data":"WikiEncrypted:OYVOcFWO8QG2KTNzD99v4xxMusUyWhQxf3J/07wObNtFIyC4CbcNIuFY4V1ewVc8ZQcA3boon6xnUDfRpiaK2I9pkmLK/0LdWC76JlXPw6maFAj/2vDJLKW6fFXlZa8Z5zbaWsdI3RuMfTuLIxIu1HOiOWe9caHiq5coDx5mSUcb9jExsPNjzPH5JbHg+DRZ/OPSWdVExwoZidxrBoZPYZNUElPBQSw0gFOw95AUcS+gC0rvrJUfNHRFRBqmFcm+LvKLQ1JoiiDRvmJ0rfTSuzZQyMviCV8zgA/PUThEYkm7pppuTq0xOAMr2XF+Hi+95sRCYMudh/OiW57sPVC0R7JkQpFuR+QzaR0DnkZfSMRy0jRrjeFPysoUzTxnsXCN7874V50GTU53vE7yme0i8d0WCUki6P8CdhrqHQdURpE0Va8XPdUvBnIBoZO9v6AMEeLose8m2CQOsk8nUUybSD6WswC1ERsMeaPM4Ocw1KZYyddLAahEG+9KdQMMUMIklVPrpxxIn/5cHiwCTYsbx1Pgia50t+CQeoOvp0t1dnxH1l/qp0Uc9WZwVTYATdG5OU6fD12gqm/inLNrJ/4oPrZTrhuYlLj+19RALUoN4zlaouaBEUJFurkDgYUhsVj2qc6dL4SgkjcNEyGAfiwbVwv06rWzwfOkt1BpbDVl9cJccdymYRtAUT2XIQj4aZarLIkvGHPqEK/AZH0TDyIElsGsjEn2coRrPenShX00akhrRF0iy4KZDixuuVS+B1AcG30EIjeAOpTTw/weZDvSt+Pw/1ZD20NudsbZoaw8xbQNEEW0oUVGb9r/5ElHS3ec+ndCJEJzGBbn05cBm5is4llW8u/B4UaoRLTWXRR5CbOJdUzF7GZVhPr/FpKGz0EAkPdg4eLj7ZFj1QVxbdI2xoIFQmInVvqAHei8j3iy9HrHslfBwiaDz4mzGDwYf23yruZtYzKtl+6cPCjLctTnoNPwc0svy6qu7Ee8sWWnA00Do52FmolmS9FLRHWYBxiDOV/DWDAYZ+6xfCFnppKDp2rFvohQ3L9eMQ4ucC9LZbsf/J9dB3avyKAQ9NEr0ABeM4khNohNTfx8nDpWim6bT5S7NH0oHIQk1myDsmYl1BA=","layer_level":1},{"id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"输入设备控制","description":"input-device-control","prompt":"创建输入设备控制系统的详细技术文档。深入解释输入设备远程控制的实现原理,包括鼠标事件、键盘事件、触摸事件和游戏手柄事件的转发机制。详细说明输入设备抽象层的设计,包括事件捕获、转换和重放的完整流程。解释各种输入类型的数据结构和事件格式,包括坐标系统、按键映射和手柄按钮状态。文档化输入同步机制,包括时间戳处理、事件队列管理和延迟补偿策略。提供具体的代码示例展示如何处理不同类型的输入事件和实现跨设备的输入同步。","parent_id":"85699f5e-ca94-4146-b489-1bd32e25bf31","order":1,"progress_status":"completed","dependent_files":"client/src/inputremoting.js,client/src/inputdevice.js,client/src/mousebutton.js,client/src/touchflags.js,client/src/touchphase.js,client/src/keymap.js,client/src/gamepadbutton.js,client/src/gamepadhandler.js","gmt_create":"2026-05-16T12:31:51.4451482+08:00","gmt_modified":"2026-05-16T12:52:41.7386979+08:00","raw_data":"WikiEncrypted:yvmiCbNZO/j/crebb6wVo8z9pEvXiQp+Q8OBuQcwVcz78gsXS4XFUV1mNGrRnsUILrPgWfsE3vzZI3p2VoLjS7KoMI6TGMZoxZneV0drH8XjOc/BzBLVaiLr6BfpltiMVFjjmZwQKLdD4bjLZ+aam3eW4yJUjsN/yUpeqQM3/Uensvo9sHlQIM98ceWokQU/CT9RAnY1PDhyRZb1Pz0uLI3ECXUAiOEGHwt26lzXZOS/18Cek6CqCjL9894UV9Nn63ulihgWVfusI2c7mtnvtfdfjWNq5ZEt+SIMwuE2pHiUFYoSWx6EmcektPxrxfkh+/WkydZwZw0qJ+/lhCkoS0Md1ovE7wKfikS19dlXRwfQIkJ276LzZ8092P7PtyPa+a446bFpucnDYoOCTkCCoB6d0MVVT7CpBAf3hBXN5xGWF04XbwjRkES4aA2JnvW9mxGHcJJXN65ecUuhzCE/fG+XpdFnysh1N/JLkk4+qOVj2iLIGDdpqSIgnCWhJ+sT/N4Oz+5tnIGcaOrM9xDBmkzW/j3HvKLS6zQFdedpfaXu5CmMmMvvTOdEXDglkiHWsfgZ90JfjUjlSovRML+PienyyICHtTmmr62C8hI+TTyirisaoFKYHsDnEL7u08b4Qxl2CrSDyje827jdPVvA+7nWK/rntjDCfaXkf5MMEdJPYgoTwJwpkS5070tc5XegyAfgzGxTSYaQ+1gPI9Rk2hurA3GfTocxkZ8OGMhh2IgTxOvKzhzPPS0I7LgSNYITQ9m9eQiyTsB975U1AvQA2dd1mgcNn1Y0c2U/qV/5hiCupzy2RKFpF2k5PK0VC2JjlQr74c8p4K02iCAGS3yB360gUP1aPHJMb01kFEifZKkrwSV0Nc3PwIycT4NX/FFNZ2iFLTlBNtYn1zVeLVULSOndSPskfngMfcuyie7yNDLFoyy1Sd5fe1CcTJdPUU6MIAl11GmqMYjQZB+wBYZp6l5s3yH5qjcqOJNJxKdYILwgkzOyQUYceqWbnIYMPrpFMUPYQMvh46o0a31f5quzKwNqhTWZ9RyW6T2lCpbyF1PG5iRDEy2SNy+e8E6NgkSUrpR2AMAXdYhT8H1f5HC897u8dCrHOpW//23o8AAwQ7eCw0A/wk0v1aCWVs5vdhiBJaYROZxrxttMqr8W3QWI2ii26tkYvB3fec/FKyoOw8/UPRukydtZdjPHuZKd0n4aVT/ie9VR3Qjl75Kd9PdbpekTKssXZmVk+ppd++Ey0R1Bs4HGKQrD1wBFSIT64WSAsk4F7xiVmzpD1RULlAOGrZ2mrU5eD43oRzUF6dAbzwV9IKArvQQRJqy9L2XzR4cwlQq8yYYV1cY6yHm4SfffJmuoZeUkb0WMvMoaVlZVmG5daQ/pwxu7BZ74qjVWktvpghE6MMSCB+XgP2bH+UQmp0hYPjK69P7d8mJEFTBuEln5W1jEODDUfUg6oir/dDLG0kf8ZbE5XVfxwDKVLQ6Vo6ilE3OUMOy83brkeJ5z56btLR3FMaIxtmAoPghwUqtOFSiyiR+EDS1ip1MOISJAynVKwFLUpNuRzq1l3mQmYHQeMZ2AfGAvw2tdcD5ROKV6","layer_level":1},{"id":"d532c7ba-1b0c-43f0-8f5b-1741ca9f7b91","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"代码规范与质量","description":"code-standards","prompt":"创建代码规范与质量指南。详细说明 ESLint 配置规则和代码风格要求,包括 TypeScript 和 JavaScript 文件的不同规则。解释代码格式化工具的使用,包括 Prettier 配置和自动格式化设置。提供命名约定和变量命名的最佳实践。详细说明注释规范和文档编写标准。解释代码审查流程和质量检查要点。包含常见代码问题的识别和修复方法。提供自动化质量检查工具的配置和使用方法。","parent_id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","order":1,"progress_status":"completed","dependent_files":".eslintrc.cjs,client/.eslintrc.json,tsconfig.json,tsconfig.lint.json","gmt_create":"2026-05-16T12:31:56.6832964+08:00","gmt_modified":"2026-05-16T12:51:21.1557498+08:00","raw_data":"WikiEncrypted:Gw00+yNdsAXvNpUHE0sZ2+QbHW33I6XBnIRt1QjI/FU8cxOfb3vCgHLU1Bq+8to1yZknBTvEMZf49B9SY8hL23QCtnhj38VZwAnk0jM00Bo9kFeZPWySiK4EJoHaLcIokjvUypRugiK43K88dj/pqwlE3U20P13+hQfqKX3CG3UoZsGZ99KqiT0+JLDkhIRz/xFZJiBewC/ST2RZBuF927dNqWXFgIX8hR9V4wlMv2sseEjk6TsJbCKmR+qqYnzNb3rOQxxnFiSFj0GGMtSmhp/WyS1uuegJTjvf2n3gQDG6bJoSygmB9V+y19xwATC/CVmBvbleHgTKi/nr8s4tW/0dEQn+bjSZbtdulju1V054PDIG7ZjDt8fyIKlthGk+WhpOQer2fxHuCkLYvlMgxAjsGDxE+gncvocjCbXw9u+pPjOLUhvbZj8lW4WkonUAZoxLH8MXN5EpoJM3Wkl88r18N33wWX0Gf2bM7aYpcgffYtVKveTA30hm5D4Fg882FdHXUqf9bdJ307AxHBV5yoyU6r5zATlGWmN2G1DBb0qHOLcN+3LPFlAw6YR5iqR0BGrKrfw3pYQh4rMQhIqyIzT9ht4YCuV1ne9sv/ST6Pq/knfCa7PjZeKlBIfv8htDAZb7jPWs27tFwlJfDTOONrrs2K8bpu2RXJoTGuIRqyiKYax4XUTLQojniQJRRKMxaEG0qTwIZkSgjgVNxo4+xUrLybaAD4hKq+F+aqOKlA7xD/LFojoSTXOKpeWSVm3blVtv8/TaAgo/6/S0ZG4i1G1PeeKtcFvDeu512ORvZ2V54eerk5cGnSChAUSOZL1WhjtUCWTcpgeqk8OCnEk7wkRoS6uBLcjrc/P4yCabnNlPfaxoLgoJxFXuShwmjzAEw5Q2YYIbaxbw4L5NAbw20O43Xv+v+gl6m0CqexU40C1DCCC3KlnfRjjmHCifmFY4MKxMxsPTCYQSKytvB30VmoSRr5INVCoJqXcR5R2FmBkWIPoL/t4F/hDlu9Oqeo8ZfrlMA62tti0utU6QZnYGzbrRhFSc5M8Lard5dIWVPQGrttxYdTCR9aIZUprpSoin","layer_level":1},{"id":"a34e1301-4130-410e-b147-4698c45c23ae","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"键盘输入处理","description":"keyboard-input-handling","prompt":"创建键盘输入处理的详细技术文档。深入解释键盘事件的处理机制,包括按键按下、释放和重复事件的捕获与转发。详细说明键盘映射表、按键码转换和特殊键处理。文档化键盘事件的状态同步、按键组合检测和输入法支持。提供具体的代码示例展示如何处理不同平台的键盘布局差异、修饰键状态和跨浏览器兼容性问题。","parent_id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","order":1,"progress_status":"completed","dependent_files":"client/src/inputremoting.js,client/src/keymap.js,client/src/inputdevice.js","gmt_create":"2026-05-16T12:32:15.7272477+08:00","gmt_modified":"2026-05-16T13:03:10.054634+08:00","raw_data":"WikiEncrypted:bOBBm1sWTMw1sXFyq6BOXM4Yzbo+UD+kVhf9jJxYtRydM0fcnUjcm9D9A4i92KCAMjLpNlHEJKTUjpsPUNHsdh/z+KwodPOsfGtnEImSMCFKTEkM3KLLzuJsRf9Wt4G3lRHuP6HtsDJe2iofL4heWpA0OKlpp7Xry521qmOcC6MbXh0ZWe2SXZcpqzRs0BM4exd5Baqj0dYRfHKpWrcQ5TgO9BydG7yYmXdOiSFa0nRmmOn4QeJLiiXC0Mmwe78WB0rmCcSqPBQEZ5ZdVudyuaj+VMPAahFDfErVNw3rweJ7YMwqR7IXNIho7IST3RX6pZSsOMM22dh005d/H8RSpkG9EtIQ9XAgXyysyCNr8RzVqU7vkRowcFdVehazX3wRXZH3eieQ3eB7gcZ1BO2WrBniFWF7U/WVbYh1JisG4rCpfvk2Gi7GtNMWOQe0b+vA9gMS9bHN7gwzWmarspexWKi2qfCCqkePMPpWuZxJWjMSLvknBIsB/gGyog6401BNXwi6/n+ShyhuQHEqaH3daumqTr5JdcvCnaPcdQUdwhGw0sx5i50u1nkyGBGJLMxa0VCmf+vD89yahSbDyZX9VhGzImzJlI5b3NmrQSdzqKZtmWxLNDsX1bkWUxg42POor2i52DpGG4ITTz0zI1wqr4jQk5eJvF8JiZMpDk0WhGsLexorDaR1thJsZEwO0Alxnfelsk1vr4r02EHAY7/yeBslJI4oGqHFEaDI7aeeFc7ErtB55Wr1gBLUQWq/LwQdeRJtDd4xqme5TGunsmYrhXZfLYQsrsr7gSV6VeSgKWUsVjw74ncPXMoNdfSOIIlAXJAMYdo7Q581zquzsomsELp+UInMjr60pzZS/iqWvWWE4GENJiz6UyBvtIcapDsfbYiy+fNgl0K03QNMjNXfx1SE+UIOPiwUQJ4ik2TvRGjLAfmT5unejFNEPEcwkjB8kTYRGhJVgBjBGt5W7xVSz2XIaGilr/NhQkx6RHcjmeA=","layer_level":2},{"id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"服务器核心","description":"server-core","prompt":"为服务器核心模块创建详细的技术文档。深入解释服务器入口点的设计和实现,包括命令行参数解析、配置加载和服务器启动流程。详细说明 HTTP 服务器和 WebSocket 服务器的配置和管理,包括 HTTPS 支持、CORS 配置和中间件集成。解释信令路由系统的实现,包括 WebSocket 和 HTTP 轮询的路由处理。文档化日志系统的集成和配置选项。提供服务器性能优化建议和监控指标。包含具体的代码示例和配置文件模板。","order":2,"progress_status":"completed","dependent_files":"src/index.ts,src/server.ts,src/websocket.ts,src/signaling.ts,src/log.ts","gmt_create":"2026-05-16T12:31:02.6510634+08:00","gmt_modified":"2026-05-16T12:34:45.9414399+08:00","raw_data":"WikiEncrypted:DfAL4INOq29Pd0V8no8kjMZ6fQVcyTmwYTbBEd4hCgJSr7mzidkbPm7rudelVDQ2whIUXL4Z+Yvi537PuXKjy7yuvBGecq4la5Et1SXTvBJ0H7lB2yrUBmmd+upXHzmY8qz/dEN+PlYkm2svVUV/C/QdFuFKR04on0k9M5f0IMuX9fvEPw4pZL2Q5NVFkh3WTubMt7dGe1IDHRvuittpU/tAxDZvmBFAHOwwgHOWdABWx4J5vX2c9j55p6BW9qHlUSZAgoiH3ZE6wCz90a64BECzOoluLWWB5TmvbUqpt1DpJ6LtUjsedxCKxc/gysviGEC+VCX6utM4ffnUmMrbjehT+dYH0V570Zoj/kViCThknTA9nPamx/LO2DzFFcaLaXFHaYZcm38kF6Vqu6gsyQBmz9/XCkPbBwPYlmnPaC6y2oSi84/45TH62DN4nsrBb+LtVbMycYUQtqPD71P92VtsotIr9Iw86HeILy56pQycUWjHTown+SzdoOIJTqDEqZZ1K+aQdIUNxrYrKl31xvPcjfucMzCFGAllYXXFjWw8xRCKIV8qBigjQBvS2q43gpC9yjcyIBioptgIvQDAC3j/u5vsBgkTc9S8Swhbi8zSRcqmYpomE3r6kreI7GLSnaJPBsWbEHjuHwvLrtOfd/kTQWSbq7dnZNh2qP02+5xuHNz42GxtYN//i2fvnBvwrAOhyHmoyRMbfaVeRQe6HgfImb3fTESeUVEVUx1hFlAHkR8mhiAuT4NdQf3ImzLDiMWk1wiJfXg+twWuMY10SoakXmxhOrBvCFyGl7Ahua4GCdyVNlrKxkUWLWSFJlhRlu3DT5tasgKixEOM+u6J8aIu/ZEBn0CLqljSo83w+zKRxjO2XTtmFBkbbtx5zsSqUcdrRRJm9fn9Z6p3o6vA23U9VEYMud0ptWin2QlmtuloMJNg/LXbLoCtsgNr3kIvtEsYhQzvWwGXymdJZgGztrfArkKfqspJLQnDGcvekrgJHTtKfmpGdxvvSoRMp2M18Mjh1lBTTxqdUkCaz6eU5aDS/hswDSi3KByQ2B1ifWCXc1xCHYG3OZfeECu31KEny1Idbun7gikuW+BPwc5j4B9stFEDtzcdQfKYyR8+7SPnO/B5wOlL+bhj80Zgdzorvx3YgwtmI1obEvOPFyaYUtQdvsg+Y8BvZzTu4UhEaYSzA0dzq/44jdDIFkxyERF7xaS6gZpXoTxcbMFXxtDwRkcPYqa4ChzEcdiOCi1R4KizDg4b+PHDa2aILji90cVu"},{"id":"3ad75da2-7ef6-4c9c-90ef-4b9b5adb9d39","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"WebSocket 服务器","description":"websocket-server","prompt":"为 WebSocket 服务器模块创建详细的技术文档。深入解释 WebSocket 服务器的创建和配置,包括连接处理、消息路由和连接管理。详细说明 WSSignaling 类的实现,包括连接生命周期管理、消息转发和错误处理。解释 WebSocketHandler 的核心功能,包括客户端连接管理、信令消息处理和广播机制。提供 WebSocket 连接调试和监控方法。包含连接池管理、心跳检测和断线重连的实现细节。","parent_id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","order":2,"progress_status":"completed","dependent_files":"src/websocket.ts,src/class/websockethandler.ts","gmt_create":"2026-05-16T12:31:19.7752554+08:00","gmt_modified":"2026-05-16T12:51:39.8694985+08:00","raw_data":"WikiEncrypted:846PjIgkeMHsDVBj61Op9/0DKQOpQOzIynXZ0s7rhZVevVcHleTHoTX6HcIO5VEwoBy1353oOelnTc240fEisc6yVjbRX4n/ImsKcT6eDl46JPsq71aI9RWN6bqMF0fhigIk5nA/qOGKAQoVS9vyl8E3t7LhFq7JgMkv604oQOS5P7l4TYD/q51r06ghqye3ZNDJ2WjeK/+284NYOjdF0YeluLaNiGLkNtJL3fJ0DNG8jovaS6csg+mrYyb4MeXaZY5m88IVS4Xj6XTJGXL/GPpmAH26LhyyJAIqqNYu8zbmQ0MbBZrC/yzdkE9rblKK3DfkjgsTV6pThQIaUphT7FFBcNkd6vLSnlcaXqjSYnLky15rLzWL3xYPCaWy1humgro82jnKaWrX8oLFqy/MMyfJp9d1pTnmroMX81CI5oa92yrndXmXg9tK+a1dEQ02ntMb5CIn2hYPB00KfwdGQ8XI1McnGwyqTc7iMfyJqvD8hEq+GBwqWkctd6znqxWqXqrP/uT9B4cGS8D0VvqLx1pbqRS2VzuBAHEwupAftRxBF5UWPTGv5c/YS+pGc1VVWqotyqUAfUqwFMbPwDOsHbOn2ZaoeKVj++oz6BQJizKCyL8PFRseTzsRVki8Qjork6LjCFjiy44/2ePwiMrT1zX9VhP+eIMGg0vGM38dVP2fFMY0a9ain0gRSVDl4yyOwU+JAFD5ny3wI69X/cltNsSpJTnq2pM7umDxxCEsXc+8wgz3rSFv3Gk/lAlcI4Axilx9LTqIWrWLbBwX//F6FK2Bs7USgrOZOSilto0p6V80kq7yXs0Gs4qgBj4YdSYqCHEDCihhnpJG67Cy81nqzcLbG39P1QtwDMChXV7e3UtpfG7KrVY+wA4c045DXHi2WOwUIfmJjcsjuBxFvhzFj5fw+2cRQy3jFL3CcxGedHz4Vp/ufbNlK4QxovzP1R0RyzGLnyFSTF09mcKmZ1fR5k9u/u+64tv1nlM+uhC8XTgOR3Edr1P8rmZjxrAlpvIMMyw6VhRD5Rx4FVnmmbv5Iv0kmlZOJAxJJeyXlZXqh/c+5IvIFuubK1RpAwnaYks/","layer_level":1},{"id":"16e0345a-01a0-42ac-9d58-6acd773ccd7a","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"多播放示例","description":"multiplay-sample","prompt":"为多播放示例创建详细的开发和使用文档。深入解释该示例如何支持多个客户端同时接收相同的媒体流,实现广播式的内容分发。详细说明 main.js 中的多客户端连接管理、媒体流复制和同步机制。解释如何在 Unity Render Streaming 的 Multiplay 场景中使用该示例。提供完整的集成指南,包括服务器配置、客户端连接和负载均衡考虑。包含多客户端场景下的性能优化策略和故障处理方案。提供实际的部署示例和监控建议。","parent_id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","order":2,"progress_status":"completed","dependent_files":"client/public/multiplay/index.html,client/public/multiplay/js/main.js,client/public/multiplay/css/style.css","gmt_create":"2026-05-16T12:31:24.8939968+08:00","gmt_modified":"2026-05-16T12:52:56.4881619+08:00","raw_data":"WikiEncrypted:61qbWz+R0QU/Pi7NYUA44ujwoCJEKP55/Ij5ZJxKBEiUAIJWu1fR31A6t7L3DieKt458F+PRL8tKiFNyLWFe3awUzhiWi/Yf0nk+9XEhPh0vgqoGHoM7OLFROvVlBBKdkuBo4IA8/Q8pmoDJj1uRlWhwnCfBgJ7pdPs2YwOKTZwmLVLEZAnMKB9tSSFuBhTTFVTdsQRtxh1gV7I3uWNx4MeQ4l7nR/KLQRa082fGObp3OoE1RUoiBBCWzvTOUDO7vx92+PK9iuAW8w+JJKD71K2XF1zVXdMYgp+1EoVc8RM74ubyCmagK+AC3JYY81/2//+x7CHYBYgZE+l33qHM4n52xbArRz9uDcGCOBh1m51aEf+AOJVr6zcAtO9JEwLujASrQDsqAMiQUlVxVlQwOvcupw8QSFXg6PXm09VkanaSd7vT3gB2106zVBGJHYpzeqng9HVHnl845IvGHntPYWMQ/3DPLj197kHkv/m0O8fHIsKQEEVCnlo7J6PayVkyHOzyyQTmOT8TOVMAc9yrnlXAUc3/Q/NlRQ6NjgezkbRUC3a6Nx5RAx28VQI4lp2Jm6MtB+rQ2wg2DRqUVhq5wqbY4WkfpXpSrDSzK0aj4TLp62BTyQLF2m6Xxz10IS2DIqp4/WwEcI/KjICJFTOskbEqcDJQmrMpziLWawP7WWAyf7ukK2TE0k5uAyD0UgwWdw12ppba4fc4Gu7R6Vq/RLZr64IkuqpOzE5OuRRB+MeSkrNWlb6VdMry1yNS1UOn1O5aOklBc5x7ahNVS0jWi5U5NcXzCYEtRhg2DI6qndbLI2aV06ZBvyzETrrgKV+LdoL3hZqlzkLDUfVEFxPBhVXD16nRbyYBVL1KD5SCuSK+jb3mphLHLATGPopHa+b6E8kxqzDdhGewG+fIe1ha4guBV8WUALdmRWtnFe8Qg8ZRXJbiD2dZI53JNEgApKhToU6Gpq+eGf+WRHtG5UX9AQFjCsLbNfPw8G9qadeaOyQGshUG9hA+oty6D5UDofeg1fBRRK1f+gonvtBho4LsND0Umdf/ppLQGtZaQ+8xdbnlaYH9I94iSZBZ0TSXZPJniepe1lo5wtgRrD+evtID5j06uKVKdoyBWbrNECUsyZ1hL7Z8HcBy34AX6NfXG9FaXAP0J2hgZnXvkiIQlZQetpivRyumz6lfvAePvwYyXkfGPQF5Ra0tuKMoLKtnl5Sfwh/twsJ6dz67paoBtxskyeJrU3FVcs24rVjk5luZ7IE=","layer_level":1},{"id":"bca5c096-0c2c-4f80-a594-4e3b0a2e1a51","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"信令消息对象","description":"signaling-messages","prompt":"创建信令消息对象的详细技术文档。深入解释 Offer、Answer 和 Candidate 三种核心信令消息的数据结构和字段定义。详细说明 Offer 类的实现,包括 SDP 描述信息的封装、时间戳管理和礼貌协商标志。文档化 Answer 类的设计,包括应答消息的构建和 SDP 交换流程。解释 Candidate 类的功能,包括 ICE 候选者的序列化和传输机制。提供消息对象的创建、验证和序列化方法。包含消息格式的 JSON 结构规范和字段含义说明。提供实际的消息交换示例和调试技巧。解释消息对象在不同通信模式下的使用差异。","parent_id":"1e326ae6-1e03-4dcb-979d-90e1865f0bf0","order":2,"progress_status":"completed","dependent_files":"src/class/offer.ts,src/class/answer.ts,src/class/candidate.ts","gmt_create":"2026-05-16T12:31:42.3659688+08:00","gmt_modified":"2026-05-16T12:53:29.6108452+08:00","raw_data":"WikiEncrypted:wLjCSFFOERUBp4iTLnCPtnu/pY3ogxZi+HbiEQQPu6GWv7F7oFpYnJyJPZzojY4rQBWVamav0pT/HInmdkh5UIbDudm2GU0eyneDu2rRExM2AvwIhNBten3DZykKIdj1nt/vMnhnOcVn7lf+5Fyz70SVpiu9r/IqiuzcHw2BRm/4yZyBEBl7YL7oXubsIkdPsOOhEf+qgWXNHEi2RADRbivU7NpdLdUo5ocCeiT2o7JUkLE+aj/MCxwoxsqebwghROlLLU/wJMk+5R5hkty7dp3hIE20EEBmjOkTmtSwmTAwVCGdxHtpErNF//qewy9zPpWxnNTjWJqOr9LKlZJDQ2P4QnepAxAFhu2uUoHs38qaefZsENf7+llkaAM0dwhrAOklhV36NXjyaVxhACTRBGLM70/CgQVgf/MZ8+X849tM33gXuvwrnZv1Mt8E1nREdtGnTxTTHm4yIwBRJk1UsYmi16oVVM+cf8JiioW8UAn2/ML+aEUaC7fbGjZA4OMyY5SRYYtddqLqzc2EDeLco42XCp/9+VmO9EN0B0xSlxJV5TY+8A+2oc4iqDaGpOwfLdQGzOW7gffXaUdm1TwXriOXMNWeLG/2gor2wd2AXFtDlSdI17n0Y3gfYu0gWrab71qEKsy1Kp9vyqk02qMrJyofOgbSPbjoIbdOtVe81e2DTxE6IVHlCbvF67dhCcsYiTS0QzZar2NARv9QoY7zxbaiTSmj+Ns4wDuhqGjM5o/DewfG1HKIXkRgHI13UbtbXZ1cBFKWrQGlSxmCoK79nrrxj9FUAf2Bs0o7wJLycY/UuNXhyLn8blPz8JvAJ0Gu3lVhWvrf7pHnqO7fYn8Z2Ehkk5E/VtF7Zt6fGtqEPwWF8PkYnHcyP2X9qPKrolJsTG1Alc9f8m+RLl1UID/59POd3krwRQMSi8ZVpkaIcfZervJqdwdLAK+t5nxL2tYEHdoBH3dhEWPEQoDjG8RWD8xVF8LVFqWFEEiNIEXFQri5beu6rtqrKtPsa5gIuC8Wpa1JvpPL0zLpP/StlN0UtDdd+3QafPSBUilYv2bQc3Mab0JjaeQaNcfKvYcWxSY4llpm6aV0tq0h9lM9ToYVuadwrkS48+6J1T//fzqMATDU5q37s6Cet4m3MA8QdW+Fr2n01ICAy95+XOcbTCT3RXzZ8W8kEuz26MyWf1xCcDw6jzpurM7PKQp0gaeaTzGh45qUU4p7OOD2VPXopT5h1tbJXyf/PkoTgimLRXljjNGu9b5Mpn0yUrRxgtJgr6A0PxxD3FfyLWdgfHdnYn9cCUS/rkzqoRxMk5p3ibAoYofXU/jUGZd3Dc9qD3xYpAOk2hJ9PXqSQvpUiA+ozKbCVKoi5wOxy8KB/fwKj8+BGmg=","layer_level":1},{"id":"b26e6ce5-8068-45e0-afad-aa894dff7bff","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"反向代理配置","description":"reverse-proxy","prompt":"创建反向代理配置指南。提供 Nginx 配置示例,包括 HTTP/HTTPS 重定向、WebSocket 升级、静态资源缓存和负载均衡配置。详细说明 Apache 配置,包括 mod_proxy_wstunnel 模块启用、虚拟主机配置和 SSL 终止设置。解释反向代理在 WebRTC 场景中的特殊考虑,包括长连接保持、头部转发和压缩配置。提供性能优化建议,包括连接池、超时设置和错误处理。包含安全配置,如 CORS 设置、HSTS 和安全头部。","parent_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","order":2,"progress_status":"completed","dependent_files":"package.json,nginx.conf,apache.conf","gmt_create":"2026-05-16T12:31:50.8657899+08:00","gmt_modified":"2026-05-16T12:54:12.2895983+08:00","raw_data":"WikiEncrypted:FK1S00S/So/d/XqgGPDwppTi4ZBSxvu5sdxdGSowapHtcYPhPBiwdEYascd8at8ddi70cLrYyywEkteLeQtXgw8+eyofVYDh3ilTJMG30FUH3fqqa4IsurFfv+sFsOmf9J+lK+BU1BQb60xZcqJiEfBIUU39X6P8mzq3MlwKq1MEJOC1L+RVzOovcJRk5vnBev/psNvIVvIZOgYacqXF5WqD8wF1WQjKp9Tq6pycn9k+CKWr2z4kKg4Q+MezjZHGcR7Lu16z0nNvjGcybtL0jeMgFOUBJNlAOwzOxsOk2YXqQHl5bWpgsiJECbpbiCjxs2t5lOZzN3bsO6mDh+Fg7RCP2VON8xnTki89i/+o1JXjTfL/zEFJ6zTbvphyLvIRfO76VVOlZIKa5M+ZeOeFV5i7ubQIsXZ5RQR7t0e14RNbqFK0kYWwwq8AiQ73caji3KuTjGfNjEQCcKnZypRDdFFtvlOZALFj3lM4D5Mws6mcY5CVM/zEo9IfIm9prOwCjPOuqrOavFB8ugVRXCi4lXAcgPNNZ9qONHSuOX5vmW0cdz3fwozNDLkZftjAmTwLybRtRWxjS4ILiU/emNopFOixl1leX/P9rVs9pvC5BykVomuH8vyotfLnhp5MRgNUS3tA6Lb4eKjsiC3QmWcXHctTYOBks0ERmxX18wk+ph3k68fUPtb/QO4AwZQLeD1DziPNaDzuq6RmzqJIk1tUdQnmKafsC30w0FQ7t8Pi/rUxZsZL+q6MBb4TDSrS//DRNxclv5ZtxdPnIdoMEHzXHBRFUJlIrDxnusF/bj4gtbv+A0NXuCdaulev48ASDIILb5etwyPFn5BpaikY4KgfZgD/ctXVx8RhU4RcfLvvp9IXo/gXX5t/rxKhCTrn1P8itVPqP4ZnOxSRFOkwioxv2r/HLOppYpAQxsYRPZ11FJMk56aant9uYSJV+hj7exMNjsAzz1c+PlskoK18NJ3mvlkJHlTG/cHnyN0oGBRDIX3pSdkpTnZHtW2zifhq+800Tv7DCeU2RxpwJhoblUBO4dyAY7utuII0EyZrFFfgzwGaOhazCr1sRfMcQM9jr7uLSt2KVMGkPnsrimpTQm1m6/wwPWT31GEyYqxdDoo9S4k=","layer_level":1},{"id":"fb0e54f9-8ba9-427a-a51d-0e20ee6d7fd6","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"渲染流处理","description":"render-streaming","prompt":"创建渲染流处理模块的详细技术文档。深入解释媒体流的接收、解码和显示过程,包括 WebRTC 媒体流的处理机制和渲染管道。详细说明渲染流的配置选项,包括分辨率设置、帧率控制和编码参数调整。解释媒体轨道的管理,包括音频和视频轨道的分离处理、质量自适应和带宽优化。文档化渲染性能优化策略,包括缓冲区管理、丢帧处理和延迟控制。提供具体的代码示例展示如何处理媒体流事件、监控渲染质量和调试渲染问题。","parent_id":"85699f5e-ca94-4146-b489-1bd32e25bf31","order":2,"progress_status":"completed","dependent_files":"client/src/renderstreaming.js","gmt_create":"2026-05-16T12:31:51.4451482+08:00","gmt_modified":"2026-05-16T12:54:20.8643932+08:00","raw_data":"WikiEncrypted:237Hp7b+m+6V491ZOMBhPAynVrxUFFpuPntrLhCPXPWlfnfm1U2Lnoik2bU3MQR6fJQtNGzSV6ErCWfthV9Zqo2zRSMY7oBhIjnDEYcJdATPRfgPus82y7T+xggXuLIYixbI2And5KbTm9L3C81kVhTqGZMBbjqncBYy6c7022cxnsM3GnJYe8LfXKikNZtAo8U51D/kuqUhjPKyN7YhVMYQ1J68BTVJ/54ayCIRUgrjnnl6T2pGvlNgpLo3RDYlbBNCngfGneiJUlTmF+9c8hPGFIMzRr8SeGXCV9qLPENn7iVCIApHk0jC2aC1A4m3oXH2xF1yI6veofJFbp65dm/43maN5O6Nq0gbOHbxPo68AXgu+ZFybJHgWVULgCAPMy9fODYmhnVgGf2sboDU2j+GLNnNAF39K/efLXEnUdJutDkIKea3huzgH8u2TK2GZcDS7Dwe5x/IASEzgHO543ZAu4sdABmMiafR92hzk00B/c/lsJ/JcKO1ZvU7j/tIdwiigBipCibPh6gmJce6vs+Bf6BZDb8/w1jOuyQre5yQhTqCBIljchEWFluH+FmVESDJQxGyD+cF3tzxzesk4QZArsQA3kaydDCpZpjRdhVaMXjbr4WLAtSZ4PwS+wfprVLDwDkydwjulyuF/cEWhB100DWXoPqv1WibRPuGAUr1x2LWBO0ocvuR4jUneOt2fE/NmuEhQ8GsBpiduC/2R+8ckG60CjXudeQMPqUJ0MiLhNEkVBFbMRgNKLjsO181nugRo2xkPhqA36b4HLC2lQ7qoFNnBM/QhOy7WUzU1bG4+STPaycyOzb68Ks2bgDfT1kYkdNZpANUpTsPEzyD4A0TIR22HbDWf+9EwZmoX5IFlNW4Qq8PqHr0KENj8YE7ELNFCGx9zH7gm6HdbiexXyYY0+Jx70oTp2C1hz0gJWpG5Gz3Ngeq4eQOombgC3Db+SQ7zzJVp+PbMSFyuWx8fbdFOPF+ridqIhcX9CrMXgsKKzvSMG0tDPtGUP9iRKQu0hxzaZ9cw4nNTJ7vzhYItdKya6jKD0efEPoVVXQwhaz7501nbjMuYvz24rUGuMktmnVyKDXhRhHhzGIjIZxdNV0t3J7msxToKRQSCJXiK8QwGP89pdJU3fexPMIMjvWwKP64eCNG/Vc6wYavpDmrzy942igr2ZZM/kSXHrkCQMQaXa4DYW1FJ4ttKNh8jWHl","layer_level":1},{"id":"f4c8bc1c-7182-4212-a6f0-5ce1ce114cc4","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"调试与测试","description":"debugging-testing","prompt":"创建调试与测试指南。详细说明 JUnit 测试框架的配置和使用方法,包括单元测试和集成测试的编写规范。解释测试环境的搭建和测试数据的准备。提供 WebSocket 和 HTTP 服务的调试技巧,包括断点设置和日志分析。详细说明客户端测试的实现方法,包括 WebRTC 连接测试和信令测试。解释模拟对象的使用,如 mock-socket 和 jest-websocket-mock。包含性能测试和压力测试的方法。提供测试覆盖率报告的生成和分析方法。","parent_id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","order":2,"progress_status":"completed","dependent_files":"jest.config.js,client/jest.config.js,client/jest.setup.js,test/websockethandler.test.ts,client/test/signaling.test.js","gmt_create":"2026-05-16T12:31:56.6832964+08:00","gmt_modified":"2026-05-16T12:55:22.146713+08:00","raw_data":"WikiEncrypted:IGJnZREY6KPscHFIFvSUZAkcZ2wP1NBeORsC3B12k8WEpAgjP9FaNBT5caZjFepbp/xo9XnaRPqRj0bSCC0Q6usnnT4oKs9XygkWX2D2uv5BVeUykN3tHb/BOEGEnVRCUopnEg2EMs7cegnJUVdKTs0COqv0FFlngjTy/cWqT1gt9DsblZ7P16CvYAQKjUZJslmRLQP9dN8HTpMdTTcCX/zXrvTF0WFzD5SZEoHknyvbJL/SiPp1A0q7csBWKZVA9kINcFtskbEbrEBo8Os4LGtJJ8McA3AjO5E7uyq7SwSfC1+57kZRCClb262SRkrbQ63LyzWdv9D2WImj5JbY0bDRIcg8ajwXBrOOrizZ9Qv12kvgtdkfeiNpfkPSjJdV3/CZ59T+iV+YzPEDHCad3oU/bDP1B7fl4eNAaLMrFvtmwEt8h1IgTfpfq3BVTkBhOLmHj9sLGhsd1krzX7K6XkNu6K4JGjEvbbhGSq3Mct81wsPr15PgjAK8f+50dyOM5lQvVJDLtC6nl1PmKQwdFuBv8gg3+qbsjZz6+rWteql/ALMGcl/iyM5EWwcqPJ+RsntvbdxO2qDRX3whxmIdqAa/B60wKvq+ypJTZFdDBzfY6CgHHfw5yv5YQprOeu6YqnXgiG1o8nlTKnUaMFkhYu2dKBhkBLhZJYJNHdKEDUJgv7DJfKe0+X1QS3IUWSNliYbyGBgnIMZ25an4Ef10EQ9fJnRP/9xHPfreOyINTfdn56oQ6seU+iqc93IKv6kiO9JZTmT5j6s8t4OIlyazQSnHcIw604GHj68yeFqjYSuzBEfsu7/TjcTvuyiknp737IQmgnaLeUhF4oss0C1RNQl2MSfI4+Q0VaUVE3/4rBiTzpcPJM8YT0lnjjfQFCACBPJqAg632vNnFqsbtVqE2gYG1Ltshmo1rh86UvNj6LeGVT58xH+tJ6YMIs8CloWXxZS8L3eGxyuUqKgr09q1sAphCkH6LIJS4lbFS3PCurHemzW/JD3/Em1ikQUQYAOUqs0ILT95A1OrF9skY7DBjQsB6ZDntFuJvDJ2CE+G5CER3VLCjBG9j2w6Y7ZOZT0lYcQUFMc0b4OcalS6yZtFX04QGYjLi0fr8PGh6jIUjdOOqLwQLbrTKB51jgWRSKbsuPy5xpM2lJ6U2PVLWLR8FTqotIzgd2WYr35D/iF2Q33kvU2Pp64rAZYTlNBSNl1c","layer_level":1},{"id":"0bfb7aaa-5a8f-42c6-a9d6-60845e7015f4","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"触摸输入处理","description":"touch-input-handling","prompt":"创建触摸输入处理的详细技术文档。深入解释触摸事件的处理机制,包括触摸开始、移动、结束和取消事件的捕获与转发。详细说明触摸点标识、多点触控支持和触摸手势识别。文档化触摸事件的坐标系统、压力感应和触摸相位管理。提供具体的代码示例展示如何处理移动端触摸事件、触摸轨迹预测和多点触控的手势合成算法。","parent_id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","order":2,"progress_status":"completed","dependent_files":"client/src/inputremoting.js,client/src/touchflags.js,client/src/touchphase.js,client/src/inputdevice.js","gmt_create":"2026-05-16T12:32:15.7272477+08:00","gmt_modified":"2026-05-16T13:03:24.3223653+08:00","raw_data":"WikiEncrypted:X6OoECXgU7f6wJfEFv+6UehSjBFgSCASaDifKWg09vhGwxIiiAOUYoBoN6wtKnQcSbd7NyzpDsgmccCpMFvhBf/oplzPNkm1CVXWYNCn3ig7BfENO3SdH2AdPBEmCf+nfzmCAXcc0b6en/bPV/J2Vj2SvQ2U/P4u/h8D5kHtKOG2GOoqkQjCwv6OmjnyZzm0dFLwp/BSun2UyJB8imMqhhNwx7HvGAlNUxg1R0kIUYLRmQlbeobLrmnk3M4cz2WsAodWjC5cT0A6G0fvhV7n8hqApSYnf0COAl99sd98332BUxbaQBbGkhE30JRCJXClMvTLXXdBTQGJNRBPlWzfRj78yI9Uh98wgw7+CSHHmhMh+lro0PHCTy6pKhw44rleAgAy7v8KabXWCQvTbCREZQYeGLf+jETX7VLA23N99jI9M8wDvvEe1BLxI8rBLv1Jg3aXWC5zeYUZSDdmJxknrRekWZ5nCDgK6bo7RwQ4SMU2S3oW6VvhhVsj31BuV4G6zYoR5N7Uj4VjNkVwgV8C2VC7Q66AYqpcjDxgWSZo92NSL8vvuHTI8YpJ/H5aEf2RB5G0lsGJRz3E+nZm4Kbhw/AYBNzsdRTVuVEGgOVqxo5HcI6/Jo9jwzg24mw5y8FqHRCzhaecTdR2rod0rPL6+/xSzSgKJgjnv1ICjUPrA2ERFFexFp2CqetXfl5T6vtm6hE4loB2HoOVy2NPfX8zVnwCujUNS9yLXE95fWe5PX/RTm7hWjB5zHYjzk9XyFGWVjpIl00OEGbHdGC4YqyEqUL40CFx+TogW99+S7EB1UmiiOt+6Jz2foqRXhWGUyhuFSJga+922TDL9L6pYRWfPsOv26+FwJ91MdDUCmzvp/eV77WX30d2xHAHTmmRCbFaMBR2RHwRB5JgG12bpSViBCWQ/V1dAVx0cOAZhDrqauJ578A1HbW+RZMg6iem+ZIBD3QaPI64VFgQ0LVcGQIbPe+VM4Oq0bxagjeWlNWbEndoHd7JqOEUyTgp5TuYBjIsN/Z+PlWgpi5XghLf2akxxUfvhKIyETz/ks4X3CcaTj6F4SNRXpiaZHhDV1RRaAzS","layer_level":2},{"id":"939c76fd-c27f-41fc-aa05-687375faa473","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"信令路由系统","description":"signaling-routing","prompt":"为信令路由系统模块创建详细的技术文档。深入解释信令路由的实现机制,包括 WebSocket 和 HTTP 轮询两种模式的路由处理。详细说明 HTTP 轮询信令处理器的实现,包括会话管理、消息缓存和轮询接口设计。解释信令消息的路由规则、负载均衡和错误恢复机制。提供不同信令模式的选择指南和性能对比。包含路由配置优化和扩展开发的最佳实践。","parent_id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","order":3,"progress_status":"completed","dependent_files":"src/signaling.ts,src/class/httphandler.ts","gmt_create":"2026-05-16T12:31:19.7752554+08:00","gmt_modified":"2026-05-16T12:56:42.4264457+08:00","raw_data":"WikiEncrypted:wLjCSFFOERUBp4iTLnCPtiycFSFFMOuIlriI+vrSIf/6Pmna1WcC9yjFQdEuKYviWSxg5fFFzwU6+fTr/SsChkkxY8I9Oq+SnyBkWFX7tw2NCqk4nZAuzByhH5gTo3ysMih2PVh9wS8wF71sPRYo3fCG3vpDkJcGx8dGsBbC/VJhOLxO/6EMBhkXnaO4zOhJhhKJKXFbyQ36BtJqS8PCUNaFp0IHL1SQ1DRAy1rwdjonEwMeohOrAlg2eVi6yvLuLVNcvz1AKPAndcUfLrbPHJF6bwrGvHkHp+KAaN4frXnb0Ew4rH4mMWVQ0bsm+QeacE5OFBq/Tl2x/fua0mBcZoua8SRJkqMR95Gl7aqIrs1bSF39vHl7V3CfkSgIPMg3LWRc76DlTqQVPPwJJAL4r+qgYiZM/cga3kFtF1IhoIfVvQH5eLWmRgj/TearZ2vbw0o+6fwAjIWM1bTW2hTirvPNotqU+xvGYsZohQeor3m/xUedhgZrnr4bVw/WKHiRIf/vJCnSgd37hvYzMzTkpyEJIvPl95PfY+9uMiH77DMnfchkcbQPJR/J7uGb9mXFHcVrOz/nT0Qe7ANfnEMSl8OoYlvSbq542uZE2OneKOy0Z5fg0hYwGWFv++9itdYfyDQpXP5xR02KdEgO2I8OJGaqB67OjUOYPbUCsQcwH3Rb5CfbDbiTEVgnlOOIXdgxwKmddoNJ0xuFu6O/YCptvwD3B+u1Qm24DKuBLiyK8V6Mc+lJ+qZBlvRAhV0a3otNM/7ntO6wIEH+vsQ5TqpcdF6LcZnQvNyqHpnS5PyTMJTOw8oZlXRU4U5Df0eCPI43+KfJzq9/LTp2CQhitg84Hh3OcDm+TFzfnyY11EAzEF+b5nJRCFweSGfNJYUuFd0aY6jn5A5TGu5jaP03DMhOv9tEbHMzRf6eJ6KbpXfeCSdzXV30nJZw+jHyGj9FFwJpEciRQwohEtKAQ0idD8A6k7rag7+0S/BwTQO2WKRIBvuaUQbWEpYZq/e7tIGAZdimt/UP6nvod3BKEfu41irwvw==","layer_level":1},{"id":"c225ba69-033c-432b-88e0-c96cafc612d5","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"视频播放器示例","description":"videoplayer-sample","prompt":"为视频播放器示例创建全面的技术文档。详细介绍该示例如何接收 Unity 渲染的相机图像,并允许从浏览器端操作 Unity 中的相机。深入解释 video-player.js 中的播放器控制逻辑、媒体流处理和用户交互响应。详细说明 gamepadEvents.js 中的游戏手柄事件处理机制,包括输入设备的检测和事件映射。解释 register-events.js 中的事件注册和处理流程。提供完整的使用指南,包括 Public 模式的配置要求、输入设备的使用方法和相机控制的操作说明。包含游戏手柄支持、键盘鼠标控制和触摸设备适配的详细说明。","parent_id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","order":3,"progress_status":"completed","dependent_files":"client/public/videoplayer/index.html,client/public/videoplayer/js/video-player.js,client/public/videoplayer/js/gamepadEvents.js,client/public/videoplayer/js/register-events.js,client/public/videoplayer/css/style.css","gmt_create":"2026-05-16T12:31:24.8939968+08:00","gmt_modified":"2026-05-16T12:55:54.794541+08:00","raw_data":"WikiEncrypted:mqJTkneGh5/Ed9h1f4JWJk1EvNY+VNxQO5/nCj67sYRHtBKUQGlJMaD9tyh0sOhYZF/EWh0lknF7q1reanVPGebTMmMlwR3IBUZz3yGYug2sgUVF6xVCZ0pZd6KB3ifOUMF/q8fmJ1fKcs9UstNt0M+As0/LRPXHKv8lK6Khidik0/CW1hUjd+C0Rs8U59DxXJmWYHaTE6zM1JNUbJJInR5/PeyJ9hDLyvOZ7L+wD+EYPRNNuaSd+Yfh8gRInKMjm4DHgDdCYPK7eFw1fNUaLW6wiL/MhwRPwZsJd4lJTkBt/uw8IxAB1RUVxjqyzGP62zLK7DaYYwVd9iR/fGoTeqx1YCZGL00USOAJMxbEgdsQtbYnewa98567zTuDplY4RHMTk61fHH25F9/yo0wWOvdFsQ1tOM5z4u48GDnpoh+scy8nCJfZO4+wQ3i3EJu93QphA6cfXI+Q4TEn48bi1VWoBP4B29X1exQ7HslbN8/14a53SiamwbtMJ9N77gVj8eUE29xcz4PwTv/+3kHWux0lFoDYvvzEE+HQcqSvCF0sY6KK717e9dF5LFDU5DiuM1u8FAsOAVX5djYCGcSS3P/styQCJ77QPN/NfRKq3RPoKZSRrgzz1cKdDWW+lZz5jIeL2FO4MRNt2KEnGctmLAjVvTwjub6Fxl3JVtrsUVOXPaonDsC7jM+DV3fjrKndw3qnSaGaJCuwMxTLYkyrz+msFtQv/FmmqD8bTSHmXekO2JoQ3ReT3ms3NDS95W38kpXgqlnDXkAbZZTxNrOgWvofn3d+htbPiqohFZrTqu3ftYwI5P4QgIu0MqGKEBnqZ7s4SjRWLrlP98SVXTWMi6e5RydoD77pbnsFizG7uw6bcnnagBU1cI7BoTML8/63trq6OQq5XDJMFuIjLuxu5HRdGZbKElUlMtVDJPex8k0blvLLgidNkDtr1eP06O6WQNAKpB2LqjDYimKt2I0ynuufsMELmRX7HOMS5hldzpl8YyHvYZz9b9TRKr/pEx8AGAJosHGx581mcd8XbRfCO+x50th9WvV0S33WoFoRl5FwaWWVWkF6IH7+Kx+rZo7HBpe2ANlg9vA9PUBEaIgVSEdqGqmAqlSBCIMraHeBtEFwDR++3tfWiUbLLO4wCaFy/Lo3AbCOqvPcfV9c8j5aIvMRtwb8+j8r6qkdwfx9mt8V8JPbPXUjm5+l4nfglFO+LhVQEKl/wWhi7ASi2lch6NRXh5ObICsam8YM6kJDuuxPLkmDblt1vbjwo4yFZy57WagAdx4XErm5rAScdEg1bOY2xp4okFZoS/L3hyiYZhNq+WYyahZkCcy0sAK4KnHspQXlJxb6ovf0mTciAkCCkOS1vYCuj6jW0nK2WgOXiyEDbz+w3TLYiOEEp4vnYz7caRoCm86aaM/0mfTy/yXHLlOihVhem8xbTfUBgDaXekl86I2sThry/waERywkCC9UExHwbNZG6RBF7cCuzNsJlJ09H4ZZuTkqjfne5BOWYfmGK4hr7zGR4l0NBtj9gNZkny9geNO6BAPfC74uqjlLsvS/mL1dHd9Yc0kIrcvy36nscwpq6xKe9pY+CCS1j1fP417Y9KvekoIidV1raR+qFA==","layer_level":1},{"id":"38f69d88-cf29-4ea2-8c51-f18ed2a172e6","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"信令路由配置","description":"signaling-routing","prompt":"创建信令路由配置的技术文档。详细解释信令服务器的路由配置机制,包括 WebSocket 和 HTTP 两种信令模式的路由规则。深入说明路由中间件的实现和消息分发逻辑。文档化信令端点的配置选项,包括端口号、SSL 证书和 CORS 设置。解释信令服务器的启动流程和配置参数。提供路由配置的最佳实践和性能优化建议。包含故障排除指南和常见配置问题的解决方案。提供实际的路由配置示例和部署注意事项。","parent_id":"1e326ae6-1e03-4dcb-979d-90e1865f0bf0","order":3,"progress_status":"completed","dependent_files":"src/signaling.ts,src/index.ts","gmt_create":"2026-05-16T12:31:42.3659688+08:00","gmt_modified":"2026-05-16T12:57:25.6553355+08:00","raw_data":"WikiEncrypted:wLjCSFFOERUBp4iTLnCPtiycFSFFMOuIlriI+vrSIf/6Pmna1WcC9yjFQdEuKYvimK+5GRY6xKTn+Ymv8ErY221K87u36qAUeeJTl5o0bjBlclW6iiGA3gladqsoq2ob5lTxu3RTjIpNr5Eq5p+Yeqs5Nxjtq7d/qwIEmT8E3LwjYfNCJEWrjiqhtIEm4wtaOIjSu1hmCm+Kkcp26ZW7AfXhuZXJKc0DCLhtIjq8uZxrh4dR++FCjD5baVO6fMY4cFwIXLIGla+PztaMShTC/Wi4UHrf261Q8I//+ljNgSULVAW4hrCGttK19yaSHOhueurrR5UFHhDwny8r56JgkTZ5uWc0hOZOSESfCQUF9qLlJwhZObr/ZVhIlown8VkIUc5NS3Ofj9EVE28OSHimZf/h/kBtFSaJ6Qo+/Uon8LDBJTr3eDnOrN0at4xereZDWYlW9TrGdNAje8YmuoVK9jv3sbXYbqnohxqU5GMK6717ZlCVJaVXsO1fi26+vkzlsAMZTJKMGVgMJKLcNcmIA1QQF7vez6vMJuPSwSKWmPgUB0PdhFM6cpjjgXX63VixUdgbz6TbErv5gJon8y3Wk9ZSyIZM1dO5SSYhsJ1JPhsHqfmTWmzd6a9x3RY+3Fd2UpkHjaa5f0AgrSyhiiIbEW8b1XO1RYQ+7yoZ/Qw0Sgp83x31MaFs9rYT/6WBmqKvPnkxgVRsR0cce7bHagWlb9twZYHPYzDpBcTIgZkeu3ZhzFvdJYin2pruIZV1v9I+mhaLwqpWXbayLjob2GEaSyAf2qDY5ytQ5QNerPTEOu6Clhk9mx1uDqL2yRN1T4s8yTPNloO6fhi0B7LwpRj4erjzC+0rrWLL3yafbNEIL4I5aXohAyd3Tnwp6b81luxcu28m+tfHQ1dmk0TPdrieXjg/WM3PAHL70BX7kt3PtTHlcXDfaV7v6UQcxG89ERsP7NFdFg5muRjtnKcSTckRZobeFpByDF/XQ4HazXBwaDSzF3HCw7FI9NU31C0FA6EFJcadk9EBSvLYgwIZBgC3jOIYL1XuzG0nWV8HWqciUw8O73FjSd1BYClqzuWhCre5tKlXGnrTczXytSMlTHPeQJup0Kj+QTZe4K69dK5/Ok9sHVhWfR8W0FrYJafB89nH57SY6FOkorhkTATmP5I1thNm/XCAYSMlmXkgg5/huPM=","layer_level":1},{"id":"341b2b54-3261-4147-8e1a-0f5c46f4d61d","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"SSL/TLS 配置","description":"ssl-tls-configuration","prompt":"创建 SSL/TLS 配置文档。详细说明 SSL 证书的申请流程,包括 Let's Encrypt 免费证书和商业证书的获取方法。提供 OpenSSL 命令生成自签名证书的步骤和注意事项。解释证书格式转换,包括 PEM、DER 和 PKCS#12 格式的相互转换。详细说明 HTTPS 服务器配置,包括 TLS 版本选择、加密套件配置和证书链验证。提供证书自动续期方案,包括 acme.sh 工具使用和定时任务设置。包含安全最佳实践,如 HSTS 头部设置、OCSP Stapling 和前向保密配置。","parent_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","order":3,"progress_status":"completed","dependent_files":"server.cert,package.json","gmt_create":"2026-05-16T12:31:50.8657899+08:00","gmt_modified":"2026-05-16T12:56:50.3164864+08:00","raw_data":"WikiEncrypted:mn97d5PlYkt5p4NuEBGlkEIoNXZmfVfgSGyclVKc4S5V5k970qBwyfP8NTnfRNqCaSrKltOgZZTUWo5Te0sPyrA/SAqaDwBYx/UfnX+bVySHsdbF+rSGCwkCZqr5X3nMbfHmCR9d3T9x5wk33zb6hYQSrrr0v/IMEappOq7j4rKJK0bPKPhElF4mLQgyF36oLeLeo8yG2tVO33u31GMIMcZmQU8v1OTsi24rTyiJl64AX7Rj5sD4YMBfl2yDBpU8rK5KcRbfm3jKLEgWcIBtHbxxl54N50G/yZpdzFnegppdq3rQn/kZWF+CDMbVFqVPCUwPSt7jdY/lv4yeuO18j8psj3jBlz1wKI/eldAeOBlPH2z6teFIB7QXVILvIwsHW7fSqRs3E/u9OHDjILl2wACP99g7sr6OWvgm06bV0CM7EObDTII2Z7M48LGbfv011dlIaPQ/NZuBW6sWpo4FX5v4hWlkUw7otzNcfKYEVXnTwvOIvKuJY1i4Ys5X1Zb5CWoOljaTkPJz9NUbMbFp5XKZ1e3aFLfNbxlyBPX/JX6v0QKZjqkms0ni+eROp0q5wFv/r34Lrn+KxWZdNlXrz0oNnxhY2IupwJnFxA7JO6+ufFK08sZ3KN1qm7lM+RyhQcCeqfWJNFBp+bZBC/h3rhPDuXeKp7BINtYtOZsrMZbWCSGEiGWUO9VyiYpA3IrXqjiu1aIPggm5ildB+jj0C9ucQ4590cbuRSjDGq9PtXnDbxo+9BmCGdLMiuRmpZlcFJS4p/LXDW1xrcJqrDCCXbmDtZ+H4aassCdxcAU6XPPuI4UqEBmjtKK/P4czom4D3EV2HI94YpvoKvpsDgMtJ7PcN0yX02zUp+CXCsjd/GRC8zigFwszV1zEG/WtsdBSAJjWv+fWoteOt7qWjja0agaZ/0ThMI68GRM7e2bAseGe2vA7Vo94NBs4W+2o566LQVMfHUMGmSvRNxM4lomM+WIgNR9o7u7xhTzpN5cmNdHRZ2iaC1BDbKL5FHLlgJYTAvSeRDqcFHNXqYjBDEtOc3O8/1uI5jqlUkNYYGoqALlu2Q+rB4Me/oFqlycWZEvOz0/0cWxxrVBJDSf4bT2h1Zv1SbunD3+zQnL2CdCBgbzSPyLs6K/5ESNSNZ3x4N72C+nXyMjq44hWkxkM0y9Pj8Ziq9RWJIzbr+sC0CvrBSg=","layer_level":1},{"id":"4079958b-e9eb-4610-9a7a-8fdec4af747d","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"信令客户端","description":"signaling-client","prompt":"创建信令客户端模块的详细技术文档。深入解释信令通信的实现机制,包括与服务器的连接建立、消息发送和接收处理。详细说明信令协议的设计,包括消息格式、事件类型和状态同步机制。解释 WebSocket 和 HTTP 轮询两种信令模式的实现差异和使用场景。文档化信令客户端的状态管理,包括连接状态监控、重连机制和错误处理策略。提供具体的代码示例展示如何处理信令消息、实现消息队列和调试信令通信问题。","parent_id":"85699f5e-ca94-4146-b489-1bd32e25bf31","order":3,"progress_status":"completed","dependent_files":"client/src/signaling.js","gmt_create":"2026-05-16T12:31:51.4456638+08:00","gmt_modified":"2026-05-16T12:58:11.4562432+08:00","raw_data":"WikiEncrypted:wLjCSFFOERUBp4iTLnCPtkt0lpS0ciKJ3wJy3q7JojvLNYr76zSUqn1jhUYPoi404QbghX4rEOua+iafsvTjfO2lxRDwmLFUXe7GNRNMOmi5xDYLJpX4/xoYgP8vO93j9hlXaopv21OWu8FYuBjziS1ZCnILZkCzLblxOUedfqpQakyS0EezPcHQwOI6r5BRZaXE8iime+rG6mI4jVLp0KkddlELFNZZr7tkxcHAPkk33nuXPuYHcrPsDqaBL953U+0ajhX3mWSInK3KMHAzd645CnarGyPBBpbtdAYLRc4vQR0Ir2IJOEZV53pHVPMY/PhmesZZ/MWuXDVk4s7j+SVqh8JeEt6dB0d2MXmpwexs0lW++g/q9Nng2YDkgNJgEPMl4tMrHEGuSYD+BQecf/hIhqvrBN8Qn/6ycGydYBUwuTOs67l6JyPO7fDNR88eNtRDkMNfghaPFLwvAEUD/Lcxgqr8LTHs4tndAHoZCAUyqg7zmrRS7CUE1yzzwcPkeBo/wg5GeRSCZW+ZY3eoqKG0Jih3qBfSFOvydk0h7QefJZk/WN0xbAzn3xdK5cPHK9zKxivVlXaTJsGBPHNQojL0nguoa/J5bAjAXOSU8fAKDji3v0PR2p+KBffCaL8QrHroyS+2167Ioqtat9EthEQPVqVYHP5xADCATIDJubjPOW9XaAPPBgGvjDRn39jUj5G6w/8ruBldcSg8wRNQXdBIzNRtEgtwQR7C3M6lRRWYwaAnD2Kfjpo54vefvUkVXId1Ecvrg04seLjIfxiHQb0gY77nxTVMYm5/jp4iMrpvErzAZqfFLl47KKJbBVeALHoMP8gihABog4inuMYotTgiCZFw5O3ASDrvkNmcgVllJMy3TxUMfqwnPTtT3QdToWnnqrFzG9gDkhCOKYPPirJ60gHGuZaYcz3bUS4uZaXGP2QfZvABw6xP0aDm7g2pdt9BE/CWaLSxCFbOOOvA3Tki0+Pnp405yy8GCEZYg2h1vBoOK9kUTipfUZokHTOdU6YuMzSC4WvcNshaKaE32nAG5ZFfF0zja2wWHDy2rfyNcugNmDzEwTOkn4raNFxGVPRM8o7o7BVOIvBvepJhU8BXVN0tWyeJEOrJqIrqRZUgSVP3zVVeqwxm7JymS3hAqVaCbn8XB1Z4Ifx0VKZf4IB/JvaAJuLutiIuUNfi3jQ=","layer_level":1},{"id":"7735317a-b747-4d88-9f5f-869465bfb4c0","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"扩展开发","description":"extension-development","prompt":"创建扩展开发指南。详细说明如何添加新的信令消息类型,包括 Offer、Answer、Candidate 类的扩展方法。解释 WebSocketHandler 和 HttpHandler 的扩展机制,包括新消息类型的处理逻辑。提供自定义信令处理器的开发指南,包括消息路由和处理流程。详细说明如何扩展客户端功能,包括新的 WebRTC 功能和用户界面组件。解释插件系统的使用方法和扩展点的设计原则。包含实际的扩展示例和最佳实践。提供版本兼容性和向后兼容性的考虑因素。","parent_id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","order":3,"progress_status":"completed","dependent_files":"src/class/websockethandler.ts,src/class/httphandler.ts,src/class/offer.ts,src/class/answer.ts,src/class/candidate.ts","gmt_create":"2026-05-16T12:31:56.6832964+08:00","gmt_modified":"2026-05-16T12:58:26.7949904+08:00","raw_data":"WikiEncrypted:9tFZPEMPWdLFkkE2XbXYMu38+LmF7cxBGtahEOMSIojOTMoaE/0vg7rsG3K5L5EQwVbodPm3HXNNwKwrYurfvJ+Whdjs1TFmLdMTqJ13iAq6M794cXQGGyI7eDk9fbdkBdabDuRWHS25AQgY1VdmuE3VIMhMXkh+S0uFYhuV1CZUgVdOl13cpb4d1RTqJTiRNvrQStAMmnZaoqGgwzJp/CQFyWXf/Kg3SlOyQBuYxUuzamgVcb6SugcuKMfpwUmQ1U9OG8slGEXe0OZRGif7sX/bxZTGBCTwruIjOstvjw/BOMuC3LC2/Xa9iMVE0SUJggA38DanV+Ut5qW7Rx+eemibDl4dwYqjGzHPHu+Ct7LSpwTU32NJW7bNik5lQxD6pr1fyv5OOjyx3swevK2KsG/I+c4C0WYvUH52F8YICYXQiMZQId5xPLw6z8Fu530BM6lzdtwZ+VlUXofN751o8fRW2bXrKUWuw6m0pjagUVvcCkuEpaRUQSrwhCyIPW4/D/GHpoeZIN6zWC3LIvEVIy7MBnfWt5D3e+py5UT04L8bgIbHNqAwtTpSC//d2h0i6c8bciQGcsncX+XNUkHlky2zy9qIx6hUOyJzrjDh1mieQNP5LrOUvyH9FZUrNJrc01dndLTpIbE3hckd11NkxizTzGBPhFeZldJ+YjoFVRegOObgvz+BUxRd0RGudk7ZLx38MVsXhh745riFtELBrbPb5lX+BcaeCLhgkw6C+Ebf+kJu5jCcD/0A29OUpWdhs00NMNBxgI0tpbn9pEl9jIduZ0HqtFZd9UQgIqMJR/swkPjTV0G+ff6zfv80wwZFrK0c2vb6ANhZrNeHct90VDIcPHuJb30p3T55kPFTHyDxpO4HYsoZS2LBJNwam3RPl+bNRZaqbrS34p2fwAsFq/Z3CcIoA8VInQlLna+sVrRBb616AIkOyb+npddsod5fZOLLAtZA/sXlJEgR5DzfCOS4aTcGdu1lNQtyzSzmZprE81mNuLQ1Fe6WIt9tHg4QbhmX7oJ/im2YeMzG+wkNFKRoY3tUhiCObBrRW8GJSdnBlV3JNPILKCyQPK/ODOJb+Nkkfx5HYA+YwvGfjrSeenpXkVCIPjAEh/FQJ4pw7oa06jH+A/51/DIhNG95qRRrJBZQTDZAVsVRM/8Hc9X++5Hnp3xn7QGfiD51A8+CQi7yE0rMauWjynYpVt4f4TPWA8bO+wmexLdUDvmeu4UdcHHAuqhZrQwuFyL4Q+P/bDceC+/T/+iywEN0nnKvFBrc","layer_level":1},{"id":"1e326ae6-1e03-4dcb-979d-90e1865f0bf0","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"信令系统","description":"signaling-system","prompt":"创建信令系统的综合技术文档。详细解释 WebSocket 信令处理器和 HTTP 信令处理器的实现原理和工作机制。深入说明 Offer/Answer 模型的 SDP 协商过程,包括媒体描述信息的交换和处理。文档化 ICE 候选者的收集、交换和处理流程。解释连接组管理机制,包括公共模式和私有模式的实现差异。提供信令消息格式的详细规范,包括 JSON 结构和字段含义。包含错误处理策略和重连机制的实现。提供实际的信令流程示例和调试技巧。","order":3,"progress_status":"failed","dependent_files":"src/class/websockethandler.ts,src/class/httphandler.ts,src/class/offer.ts,src/class/answer.ts,src/class/candidate.ts,src/signaling.ts","gmt_create":"2026-05-16T12:31:02.6510634+08:00","gmt_modified":"2026-05-16T13:21:38.4000048+08:00","raw_data":"WikiEncrypted:wLjCSFFOERUBp4iTLnCPth/jOqDlLBQvgKR2w3kewOZhTFSAWjogNkuhcWsGNz2Tw9dNewVDTA6qpRxnMx/1z0bULkoGA0ku0nvVVg9aU3I41zhgp4FCkEpo7q9+kwmzj18EF7pu626bniIaS+NQ5ogdNU5VBqdNv1snIvgh645LZxKe4nxvpNj93Dw9MzvTs/8U251aTsFn1JYS+L3/+1BDjsfUbwXYKhuAnFe1xIxK/HefUauljIgrWiCurXgWfoX2MEWuaAQyhAXqjiqNeWylq3rY9Y25TEgwaHgrFq7Ur7E4CPZb9gUcYB+vrzzYQpwNCDEkXwS92Yi5foCecVByycBd5dtgQyNLPDNyZCW8W7URrTfgm4Ud0bJdNXGrHkGvY7LiIpPxq9x8qNXeRmnRLaYtLfLwYwcG8JYkwAqeevGt5lNBpw/odAPE77va9LEEEdhMjamFma5s+4JTKB7eg5axMi9vNFX2aYUW32BKMROj2dmMGi2zVeP/onG9GrPj2/H0QyDopz/wwdSqQKMUCxRRe3YNsjbQ1zffb8nUYFhNGHyq0FrIak7xpk0NnxlyAXSc+rBtFMZ6R8KqEzElnblf0g3T9/P4kBjC/tSPa1sCtCQqyw0s8E5Sx4UrnXSpi6bLMIddD6/rFQxrDbtMLBpy5Yrz8fqlPDwStgJVDN3ZotqLten7BbMVFj0tvnVQvz+LuXtlOEFRF7GXUDKATJN8TSuYbKErzDFF8xSnM5uxoOBAgEJTLlGZrgNeKN4pFXVP/TQ6b7WDdNkCQAl8Ccktg/2WEcQD4KjggnD28msDlWPZwWgqYBwnQFkeXIqf9c3h/im14hUTKwDHRf3D1hDzB3smgeHpMkmetraLufAwsMqpECL7CQ5hultFnKXpzyTNu2/3XbWzJFJWd0kVDw7Qca5E5On9YoqAUOsAZQwakpy94yE7uOAVW/r0572QE+dpCOPnoqj9Q2gIp87zNBv5VwkGGJbKRja2uezagmNdVi3gkjMsaO/St3x56/GzVmnAZ2PGWReRko47fgffGRXla3bQQKkHMTCZX12wcnvs1aFtJmRoqjWs2X1nQYK+rydB8zwqDwN6CMLhUA6faImfuwAzBk0D3mkaZvbbucTO3aDFFTpIMjfNKvE7DDi0621qy5Y3OF1alu+4oNFgbpRwJ+9bSf5lzM0jNOOspCpqXmdKYR2aNGitNHLsV6/MnudSUqS6KdyMyPN+c/F7iDOVWvVh8P5DlcP5+hypDspOeIuzJC0OjyDmAx22l+CzurQeVyj5TCBaTFX3M6QVV+mN4PoI86GJtYBRhncm9iqY4wTRNESoBgxrRjB4aAMqPWHjCWfojY5gxNHb6p9VDCHoVDp8AEGIYtMeSYQ="},{"id":"20b00f21-82ae-4386-a080-a9b41d723288","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"游戏手柄输入处理","description":"gamepad-input-handling","prompt":"创建游戏手柄输入处理的详细技术文档。深入解释游戏手柄事件的处理机制,包括摇杆值、按钮状态和触发器事件的捕获与转发。详细说明游戏手柄识别、设备枚举和状态同步。文档化手柄输入的校准机制、死区处理和输入平滑算法。提供具体的代码示例展示如何处理不同厂商的游戏手柄差异、手柄连接状态监控和输入延迟优化策略。","parent_id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","order":3,"progress_status":"failed","dependent_files":"client/src/inputremoting.js,client/src/gamepadbutton.js,client/src/gamepadhandler.js,client/src/inputdevice.js","gmt_create":"2026-05-16T12:32:15.7277745+08:00","gmt_modified":"2026-05-16T13:21:38.4070013+08:00","raw_data":"WikiEncrypted:H1/m9OMG7+2SmVxBZUCnp0NNfrlAKBIDDZwD5OxBvNJc+4zfbGQm9MnykwIpMhoCX/AQVyBiOH4yfE4d9GhqenuAsgjxZxvL9QY+QEJ6H59t74vimBVF/4wHKusFNNIEbZL2TarXeJGCH7nEP34/HemQbXiYgfhPP9zutjo47EcwWLjHS3sAlnz5wyXWtlxxrZN2Yevq+nWuR5SlnH5iOTqiHF6s1NXRr66GU59a71FEGEbeJSgDnLsSkW9p5q7idAi/hA/eqdQPNWcs7PV5MDLGvkZ3XxaoNmVb6/cHXsXNYW9UK7DODeXgwQopDZG5adCGDBqdGnrG55cJCMlnqZzvQOpauZQnslIkts+XJm7+BJ+IE565Zm5i+Jby+7/HnaEf1FJCoCH7LB3OygXKuqOiU08Jxl+BF8iAJl90d1kGujT4f4kJ8VHvXn7Qqet8RHWeq0wz8AoCe7my/xLPq0p0+hZ+flZFEJxaQMGRGsXFtBsCHeKQNTACDFpLfy761iF+kRUkcrWSz1ohoBLloeL15Gw73v44ARyuvpVA5kLxsyX96RcpITzy9i/GVei5QIvWC54ogMcFulM2xPxfEVcGRsszJAr0F3FwhqWslm7VXRp1K5pSWV9SKAvyPt7Y4zSYiX+86F38Ff0/P5pekidlQEb4mvDeVbh5d1ThnPaGwwHNUSYeHqYz7ZUN+1F37vl0a9SMkzCKN+fTj6WcT7Aa7FEK+yTo+ZVsacQju/Ft6PV2jH2kCSjHvhkWa3sFfTB/nhRO8TtIRyy03DtPeMVfNQRRp3IXLSWKuO0wD3OkrkOCLB8+O83DXuYSPOrRjox7ewl6M/1ur3T9Lr+xK0OxpvkcPcxXhyaHoMSa7HLK9zoyNqKSgKIJT7RZ+s0WhjppjqAwGPNmzn+2rs9khQ/CcutGaGjt4p7N9C0CyyWJ1SvyVrZVSo//dXoa/7Qr6fpUNNtlw+mGkmr8QftzkK6j6MNGx+ssjd1Rir681WSo50CIr+yzqs3nHhAZ1K6KxVjYUu/BxutNYp5fgTE26aU0IHORyeb46v5mt9wmxkXaPeSAkrHfQqF2cDWjWA9Vy8pu8nsbJPeEHlDfSAOZx1vQxkue6NbF+m4ZqM41uwKTdmKUN1atJ6BPnzNZ+Lj+","layer_level":2},{"id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"客户端示例","description":"client-examples","prompt":"为客户端示例系统创建全面的使用和开发指南。详细介绍每个示例应用的功能特点和使用场景,包括双向视频通信、单向视频接收、多客户端播放和视频播放器控制。解释每个示例的实现架构和关键技术点,包括 WebRTC 连接建立、媒体流处理和用户界面设计。提供详细的集成指南,说明如何将示例代码作为参考集成到自己的项目中。包含自定义开发的最佳实践和常见问题解决方案。提供实际的使用示例和效果展示。","order":4,"progress_status":"completed","dependent_files":"client/public/index.html,client/public/bidirectional/,client/public/receiver/,client/public/multiplay/,client/public/videoplayer/","gmt_create":"2026-05-16T12:31:02.6510634+08:00","gmt_modified":"2026-05-16T12:43:51.0011753+08:00","raw_data":"WikiEncrypted:kSMJmF88qRkP2ukfVdKPFLqvHzwW0mwq1G+7q2rEMCHZz8OaKjsBrRXYcn9wlh+1ji55PE2jKeRiombDtTIHsF7J1dWbwFRLNGZ+7XZPCs53jcZZY1vrR7NEAlL8erAzga0Ld/jnm1nxHCVI1PTaJtFWG7kp2ur/vJVMjcEz/o7m9UCgmcAabdiPAFuuUyTEg9T8CY1gItf5I621ktg3OEPZq5fEae5SFu4yqcs6o0KR1qy5YaNddlmziZC2VL7v1V6sZeRLt0SMJO9GLydAbh+iQJ4NjijyGU+gwhmHsVsxhPAHru9vu0WDkElCgIVT7WL1zyjmRyt0F2BLRBz9nOf6V7eaXRfs0aKkm5hHNg/E9GbnRsJfqJ+gpto/arjK7yGeubuz+psVUqqzTlRBgUyk3lghJJKPjHR9Gm/KeniZ3rDTVzEcsPHNaoMxYQcIjhCb2CWePMcyz8yrZ9Uq2q+nPyumDkEGrJgvqF6Rl7DskpSJaTzutksMrPe3unMDWLvL2GochpxnfAfiH0bgmB7KPTTMioexaTs+E8c1daR4qnIG31RMl3XldZaD1y5LRWgnnaNStVvRyG51wkc9ZsaaZv/J+pYVGO1E7ZXzpd4d4KsHWVZrKlXJDCXmmvttEk5xpgL4cavRh0r3qsDIxFGoZ5HvBeZ12SBTvgh0quqsEmLB3YBVA7GRpZn23hbueSpCbh4FwjMlN1lMru5ywvZT8fZkB0um3zsJN2o/CM/0JPK99wfjO+yUOTV3fZR0QNEuF9oLuUn2/Urw2uJ8sqhdT6NUGO6DAgQEk8GYAaKuA32Esn2nUJL6XC/Be5pbIHll8Kwrbjm8pqlNjDlYH59n/VoWBlVkGRl+vrfDU8oYze8SB6czojRPZBW+NRL8Lk4I9TJPQzZwm08ddO+yVVo1J/2YafVEDaZvhLk5XufLpMugjdA2krt053ndiQY5SoBM8uPJiQ4NRb0HhEPITNIf6aKFejee6p4NAxkoGo9QFvwHw5JERfTcrTPW/Zj8EHrrRaBRv4ZFZstDsvcILSDODeULFQunQvjAPLyq86GEviUYvSL0LIiNWQ56HEqWJQ6V8D7KQvRdz3Jk7iOgZ4Ck6HQ4LRtLJtg2/E+0wF3ZO+PFFxUWlFqrc1wFtpnSyIgLtzTUmxpo5vXG6eR98uQo+qXJeF+S4e6IXyC39HIGBmBd2KMDjy4xyxWybVF7QIbNmZ3F8QsKkHHirs1wBpenoggBTNfAYqCAjRNV3l+LtPzJ7aNFfRCab1sICVcG/GskbeRe5rL5hNRsdzIvxVWeFrzwT9yLjshxoM4ydGbK6uMU/6f+2HZ1zyJMoMViDGycvJucTHQnhsY5R4cul2BhoJJtc5xv3zh87L4gdYEL6MHF288KsMZIXjU4JgOP"},{"id":"8bc8b2c5-bd3b-470e-8ce3-76f9eda2aef6","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"日志系统","description":"logging-system","prompt":"为日志系统模块创建详细的技术文档。深入解释日志系统的实现和配置,包括日志级别管理、输出格式控制和日志轮转机制。详细说明 LogLevel 枚举的使用,包括 info、warn、error 等级别的区别和应用场景。解释日志输出的目标配置,包括控制台输出、文件记录和远程日志服务。提供日志性能优化建议和调试技巧。包含日志分析工具集成和监控告警的配置方法。","parent_id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","order":4,"progress_status":"completed","dependent_files":"src/log.ts","gmt_create":"2026-05-16T12:31:19.7752554+08:00","gmt_modified":"2026-05-16T12:58:50.3583724+08:00","raw_data":"WikiEncrypted:U7R4DE1TfAUvjZuFqogRLnOghdG8YONf1+EXGFfCCKN64XPQEDdUeRHtBVRbgq1RTfu+PHdhhtH3Ub4edjV7OdtoYbLYQJCyNitSWTFM0fdEUd+mw7zMJBnHRCga8f49Wa7p6S5sx/cD/xjLKuHw/MMEsend1r9F91CNPjkZXRSqtapF7L21QSEj6eRJCKf/DqaxF24dZ2wHvsgL/dS05QCsiW05E8zScbzWsNDn2SURhlWnDf1aZEdqxy1NyPRoHKp0sj7PXiKsT8Pa6Gir2zPRsmorI191+17M5F2Vjjqb80WsOTZoIWIPvyYn5vsn5K7CKdbqNeQbQ5b4FkAXO56vgdA9kvhIM451iK1oUFthaJHcDqtOVyyHIs/zhf60O1dN+JFUsy2jlRPfV7DR0MsMZ77lfyVekntxhk+AtAoIcgQhO/zbbiZgjr9CBTsqGYf5asrpARfbOvCaHmA8VpV7HTbQCTme8jWPJ88xrGA4FBQdFCyvVVUELOTsingNBKeizfi+SEX1d8rNDeOYyhhE3Sdq2ZHQjhnWNghrd20gUIPL4KE95lXf3kSR9HCD/Zkr4uCiyv2XkarTVyTzaukfOSUNHFJ3D6AnIuw15PUUwp2pjYz6fJr+DLuggqYC4LPd8sW+xBP5BPlnyVXAsa9PnCCDjb/+HbxafvQXvip1/sJ9WXBPWs2HSQqGRkshZE2ofQnl1LHqIoAPAZj6Hc53lfSy6ilmuVWf73WNDiGevfW+rXZkpu50oobUlf0BdO1sR99LvMgs6yYrfdA+Qfe/wcd2rzJzyI5YY4pz6w08jwJfc3yjHuEZ9+u7Y/Cmlv3qPc7TKkQPusVNPBpDtIvs9/SpALuRrOo9eZg7TPqQIZ98CFIohCjps6W43PPA6yaKLrbWjGjSI9qwJ8yeC/jiC2iZSWoVAJ932dJ5O8HUp/3EXt8eZ8uNB2yWYE+GStkKHmpow2s0i5iq7Lz6AA==","layer_level":1},{"id":"eda098b0-7acb-4d00-9252-973100c3a66b","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"一对一通信示例","description":"onebyone-sample","prompt":"为一对一通信示例创建详细的技术文档。深入解释该示例如何实现点对点的视频通话功能,包括连接建立、媒体协商和通话管理。详细说明整个代码架构,包括 models.js 中的数据模型定义、store.js 中的状态管理和 renderer.js 中的视图渲染逻辑。解释 connect.js 中的连接建立流程和 endcall.js 中的通话结束处理。详细说明聊天消息功能的实现和用户界面的设计。提供完整的开发指南,包括如何扩展功能、自定义界面和集成其他服务。包含代码结构分析、设计模式说明和最佳实践建议。","parent_id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","order":4,"progress_status":"completed","dependent_files":"client/public/onebyone/index.html,client/public/onebyone/main.js,client/public/onebyone/connect/connect.js,client/public/onebyone/endcall/endcall.js,client/public/onebyone/chatmessage.js,client/public/onebyone/models.js,client/public/onebyone/renderer.js,client/public/onebyone/store.js,client/public/onebyone/utils.js,client/public/onebyone/knowledge-graph.md,client/public/onebyone/code-structure.md","gmt_create":"2026-05-16T12:31:24.8945342+08:00","gmt_modified":"2026-05-16T13:00:43.7062627+08:00","raw_data":"WikiEncrypted:CLfY7+EUJ9/MJWFLVE2DgNPyLZPK8ABVykd6XvsDXPHI++NznpLVWQM7RG6VxNRfClPD2TLbvvRyzc3iJbbt7MesB76h30TAwnjGM9iK2DaGykrWuxwlKTfdvOOY/oQSj8U6U8vXoa+K/MNIBbvpktRuqC6W9wjK1zrEuv7RZZ7hcm192cU/pqd9MvJaM07QjypazN1IdoXuicQk5XKx1w9om5J3cgv2JvvZbKtnb/vsfE+YXesEOWcoeiEcWbggcj0hf+bvUeD/UK2a/b6Wb/YGG+v/GfMkmwvICjFe3AhBQLmySTjjctkrVzLdPbkWB2+DRkryuwe3GpooQ6aj5ebhuXOMpu18xjRCt4eN4JQzf9VBA5qnWhXPVXzE0QAcftQ8zzffL4Ubh8AhcpkJxTkX0TcFum4dpA7ht41EztFbQr0+fqf0iE9H9Pv1N4qSoevxH2ak87EJ9mQZPfv+HgGOvSwe4wjCoKxVLkQ3P5zkdtc+DIbH4ZNZ5Zl7Ee2xouD9E42ojHUC45M6Uer/W5xJMlF6sTRyUzrSCCzqBqFLLaMlRF9WoWnvh0+ywuXLz0/21JE+7GHEG18EOl2Wndv8Mq3yALJmP1aloAO24KP3cUeNnIdBbNqzAaVOd2ErilqIjjn8sHq3l6MMrAfPrhysAcGkxtY8TjAN4IfCYU34GHuX4Djcz8xDtG0JSOD/wT6hY34MWpSx2pjaKJd5ZaKLihEkMTQUtlORUusLZiMlBwM0gkT/qCCox1URNYsbGmMOg5kTE7v9pjVVwGregwTf8e8DMxFQLO8EgERAf9K1x48plLhEQqHdmkJY8lELbaRaqSpYeVArpfMxYkr4Y7X1YRdvPTb5HJyGecTx7kF7xtTF++hEI+mrP0niJwMLqkzFsk65WegcNjzwR6k4skz8vOKg1BmMffzsmk9vAaCJdCUD+sX2917fznckCbYhjydaEES45nQ7p+//Kln7KGQxg+srx+8iDKhVDdpObCvYtTZoCny19BdSPMKV1YkVB52RKlWtwi7Fw21gbyZ7NrQNOdo8e0aRJg9yiw/5WZsZBBZ6DDHnHvSSMiQjwOIQLn0UULPKm/JmH7Wz9KXOuceseTR8GUlFkCHIYtsDNri8rVSJUUOqT4IqXDftfC7H6y/Qr0VcPlaGm3yanvjMlYaNbcF7T9hGDDiFa+M0gzb8FZTM6j9ZVdZ3lSkbcNDUjl9PCRT1IXVYsKu9zd+DI/SBYcd3u81p4qfm7hd4LBhQ3LcItOr+tIXtFVekRrZDPEUgBZSRtxFMpUP0fzeciS+laqBr4pp6nvJqVF/VPLb0rNksAOBK0KIY2/JxaOO0hUlu7OlMPrptbSJbBfqrgWTQI1jpUfgH0fplLHt1UO5rLB499SRsm4h8U0DBvoRZL91CZ6b0/DvwEvtL6YrzwnUbazhy5RZgpPFTCtuL32TsPTLoivxeAJFwWM6YdduZ563vZ/vKZfsqinT2x3xkHlBSsst/v9TiI9CdZDcMAy9EBZWIXtDduAIM0LsG2VOgp9BvnIi+Ed4qLyX1Sef/c0c0U+GQbwRCb76z/zazaqr+IInIaTztbsP2hdieXo75Pzicwu5w2euuaj9AOoztpAQsy9qlKF2/D+uQPjPw6W4ezB9ER67XrbNLfXr11XFE4nSHs4i6vMAFTw8WeVmB2W8y+58D62Dm3bNA7yfasr95y9f37OEVy9Vy9lSapG+q0x5VVhUhT/DM+c8H/BtPwbBdWv3dPTpYaFWsANU+P8lpgPpDA+TbXXSjRXVKGo6I3TnvVXPeg0Xq9jAEo3jgoh0eQXESJs1iLCd0Z3lvOWlybrfyRci5P4lYD4uS8Rt8","layer_level":1},{"id":"13019cef-b976-46c3-bd7f-63fdc6bbc074","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"性能优化","description":"performance-optimization","prompt":"创建性能优化指南。详细说明内存管理优化,包括垃圾回收调优、内存泄漏检测和进程监控。提供并发处理优化建议,包括连接池配置、线程数调整和异步处理优化。解释资源限制设置,包括文件描述符限制、网络连接限制和 CPU 使用率控制。提供 WebRTC 性能优化技巧,包括编码参数调整、带宽自适应和网络质量监控。包含缓存策略配置,包括静态资源缓存、API 响应缓存和浏览器缓存设置。提供性能监控指标和基准测试方法。","parent_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","order":4,"progress_status":"completed","dependent_files":"package.json,tsconfig.json","gmt_create":"2026-05-16T12:31:50.8664338+08:00","gmt_modified":"2026-05-16T13:00:02.448535+08:00","raw_data":"WikiEncrypted:9uOBpMbLX4DyZqW4us3Wm3Q7klXxmeD+JMMoGiSO2oNryWBo1gw9kLkawvxQIW9CASZSiyAjiA/zeT5DwD4S8BhyQl3i/2GHmPDKom0i7oiTbJa1LGjd8ldVICQ4t1T/Mk6wGtkVLp29IgGDGPhAnrF5cGTvJ/LavJoCRTrVRwLFDa2jYGC49sXTiII7TT9BN6BTJJE6eQ8S7M0dSGx7VHKRVvhfJN4G6H4b21o4wK54PqpJUXJPJqquMXbUSFB8t5p42rrAmU8R0tssT3eqxYmhtsLPmlGvHcCAp+yfyGCS6Fz/Iz1EL6qttfi8Rir46RLdA0RixTOTQrXAdGl7Y4ngG2ux1ejyun6bwfS78PuX7psfJfWC4NNINbn5hTzORxy7iwmWZA7PSH6QzF9DEZhNK8twZLegMquOIB8uiGNFIjqDi3NsVemDJeihgYPLxKhdUpUU8RQs0xeRVuOMyWtOHtA8edt/XveDnHiQNSUtxZgMJ+chWrs2lYV1CdyMA7nlt/5X6clQuta5oaoTKttV0kZANylF2M3ZZWXNBvg5tI9BLYMteIEToKpqeAo93ZJguDZHFHKxU5fuqzuY/bhi8n3O5MYbhuDDtHJUUdeVGtXc5+wJGWtfg/rNVHbDHi6EhaPWWgJmCUI/CjIqD6jE0W4ZlwDYMeT/CJ8/iBa9lFNpvjKVE6n9dxAJ2VVZXyPQB2IPCH49p16hmUIm/GRSmN596y//cus38lHDrx5aJ+5ya5OApqIod0qb01EhaRjjVj9/oMEwKyVhm1ilFcjwvphq1gXwSmYAwehaPDSdpWOb+Y50zEzsNICwylTUxDt05Y9iHs8XBCjLAo2vFmOBgqsn9kp5fi84gFWzUktHPZ5QMt2JPNDfa0Drbz25Sq0dp7oCtToJvMV84jU0uStIpED/UHw6G3hnP6XZAn6ObW5TErDCbG0EEo5shkiYNIrGNH3s2OSKfRzQD5UaSUaBwsu/eWOCkFHsYmHdH5GYcaeDPT9naUM9ea9meXUtLhUpa9DW31v+Ikx1aaCY2IiXX0UrimRHQApCF19XBRkdSBUnMRN4QNl2i3WOoboq9kLsVbIKzvPCWrjnlobbGBAf1Dsi4xn0aPIyVzjqU3w0rs5xbhwgN4Yqv5rkrWFMY99ti32vcxDqOFf/R1E++w==","layer_level":1},{"id":"869dd14b-54e9-4010-b1e0-746a3e176bd4","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"实用工具模块","description":"utility-modules","prompt":"创建实用工具模块的详细技术文档。深入解释日志系统的实现,包括日志级别管理、输出格式控制和调试信息记录。详细说明内存辅助工具的功能,包括内存使用监控、垃圾回收优化和内存泄漏检测。解释字符数字转换工具的实现,包括字符编码处理、数字格式化和文本处理功能。文档化指针校正工具的作用,包括坐标变换、屏幕适配和输入精度优化。提供具体的代码示例展示如何使用这些工具模块和集成到实际应用中。","parent_id":"85699f5e-ca94-4146-b489-1bd32e25bf31","order":4,"progress_status":"completed","dependent_files":"client/src/logger.js,client/src/memoryhelper.js,client/src/charnumber.js,client/src/pointercorrect.js","gmt_create":"2026-05-16T12:31:51.4456638+08:00","gmt_modified":"2026-05-16T13:00:05.549879+08:00","raw_data":"WikiEncrypted:SvMc2PRoWqDcMfdzcPf97CpdPzSdkCpiF+wx2oAYPxySiKK4bgyE+H+pWVyMoMy3oTP4JzTIBMfQVWI3Rty0VE2B9lW1WT7fb80Q8GEj73nrjOyGY6tmL6GyS8LS3ty15QLMHh12jQ4tCc/oWj7hN24FVIaAY4jwI336Ee27mZ01fXgofjbc32x7Ya+MnylwMxLE6jBnoHgHD+p3j78aEstA/ZDzmtkdVJlI9Wg08MjviBN1Qou5UWxjSNmJLZk5fWMzzTtBibQmBpoTM41bhRBs8Pl4S+gGkVbU97Yby4lOS854lFkDkM61PGpnF0sf2KeHbUTo3wsuUMN9RJ9gE5j3JIfmXBHTlHet2tGdRFSph2aUmmvV6pOEXgZ+ZW7bYPGS1536vCHLwzmk4qx280rv4PP9tCfPzW9rh1K+kiuPi1agF6YlB3CB/0xYXOLUU26dVkUJJRA12swAc9GYfigqkDIO16prE8Ok5MWNpDG90YZ9F+wsn9quiHEOHVVY9cT4VAymrNzz82ZMCq1aKC42URupAol/8gqkpPu/ujOm5NDZavtu5hrSw5gyv68ko2ypGAdM/bmi5e1snSC4k69nskKqmpHpxR/1Tgq4Sqnw0shJaxp4t7Fk3dXR9JFuXcZhbHhX6a+PBlPB3WE7W9i4rnbGbrls06fueHWmOeH76pkR724+b77pix3T4oETPzwobYw8EoiLooB3ScBmd6yZ8ipS6qEfVxfh04T3hzDL8PKdHsi1jzSfLVrgWzz9solYtlxWT0GcuchMG2uoR6EmSS5tpVZ6XfLQpVaH9ZdBPq/e42oe9j2I2poUh1ErDvNQ9d5gicvlBNZ6lEbDzTLocORduAK29e0Fre57zCPovtnu0Q1pTotY2lEzCb2SB2evoydqmX4CCjTuhcC+jXTft4/QcCP+2uNDOJJo2YhL8l3EPnCiSzthdr5X6XSpSHyVo9rAUczo7hTmLwMHqdLnlDTG9pIBWCgyFLhN7Gq3tnLd/V5Xv3zNxFAV4doOZfkQl0z0EdXdsxdiy0kM/698lnBr8/Iyfk57LDquXbu3OiCs5OAd7H2apwv+hWAQyfyOlUiM4GAQEd7E5/vW0/J8j9ArSeJ2bF4TnUs721aaeNMQTB6uEQ8DPCrxgm85dV2Crn1n5hsR40G6AKtlaYLrOCzT+pnT64ThphW32NWkwyUcFzvno40PfQEWWCkq3ki7QeuQEkMz7bRqv3lpzCAl6ljoKQQppksESPz8nOJBCp9k0xAwY4+OGqJOekq+nIJ2HDk46YeviOLd7FqvMYadmuIVxgMb7IccUZyMa+U=","layer_level":1},{"id":"bd459b28-50c4-4796-8528-69834467fbf7","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"贡献指南","description":"contribution-guidelines","prompt":"创建贡献指南文档。详细说明代码贡献的流程,包括 Fork、Clone、Branch 创建和 Pull Request 提交的步骤。解释分支命名规范和提交信息格式要求。提供代码审查的标准和流程,包括审查清单和反馈处理。详细说明 Issue 的提交规范,包括 Bug 报告和功能请求的模板。解释版本发布流程和变更日志的维护。包含社区行为准则和协作规范。提供新贡献者的入门指导和资源链接。","parent_id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","order":4,"progress_status":"completed","dependent_files":"package.json,.gitignore,run.bat","gmt_create":"2026-05-16T12:31:56.6832964+08:00","gmt_modified":"2026-05-16T13:01:26.052069+08:00","raw_data":"WikiEncrypted:BxDZrTl5aGXx1MaECOB3NfFszs6m92oa31EoKqbbUdYxQGfxBZTaO1R6NbCUcPV90mgYYTSi6LqXW9gfQ4sJPh8UNrr5WAuBos0vfA5wRG9w03YlnzX6UWnJ+HjrHH+1sTwmrNaPJAyA7zSxjDqdfLfSITmckq8mKaQXBAcWV2pu3ayhUekqsuCO53G+DdTaazwjDPOdyRm/7p13fQbYVY91PyW7TUUwFRJqnluv+Vj8ZoFcExATF7RIUpn3b49nocYW8CxS76NHDvg1o/xSmBeYPEipKM1h3IG4E+Ja23/M3u2a9wX+qCPzjI2XJwygfGethvlRsb6NdEyw64pDPtn2OGeqDdhx1kGmiNdLZCJn2ritLY288dYXizYJTcLBwMMBH5KIsurRjmAGTm7PfofnduK/i6XmvfCgg7Puu4erDCC2eoxjxO/3Ku1I6sBhsEC57Q9cVn3Fq1fuvtGSTZZczmZjyQd4w5CjrBwl5eF/hMUdM6WL/rTOljO0l6tWbp5l2z+zVgEVxTC6ODScGBmMsO3KEcEqciSlzNEAVdw/uY3RFOQxVFcFPW1m5eCYdG/OANrOgJigerUCboA5avcRoUa6s1/+QRetOVIu0d5f4kHvkQ/YcRpgGIVrDfja+qsNdYzywPKeO64RKop4Q+PW+osN5RxzQrzYkJb64tM03YQYH+7ib0IK7Sd6YGjO6MW6TyyEM6+Z2FrruW/jN84B6YfNv4UPkdMNqCAqs/UNpEo+V4mCLzl6AovUUKiQ9iwz4s+x9gdTdoEeYLcVd7KA8jOVBm88YJyl2rzsBTyF/MM0PcUyUPco3SaEu/p3tJmHnXJfUrG7XB7rXkXLKrHaXXa7Lc/p56RYlyJnNxBHb7fnkUdveyDi25zmPQQdpc/n07TrTGZZOI8JRKd4Ob0eV1OJD4Yu00I/9RoNgkFjkzuu4O1et0xzc6kUhUOfIQDPI/Y3e1lz/9nvAwczz59bhkYHThHRWkI/St/j618=","layer_level":1},{"id":"d80bb134-cf37-44ba-a780-c48b0df202a7","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"输入事件同步","description":"input-event-synchronization","prompt":"创建输入事件同步机制的详细技术文档。深入解释输入事件的时间同步、延迟补偿和状态一致性保证机制。详细说明事件队列管理、时间戳处理和网络传输优化。文档化输入事件的缓冲策略、丢包处理和重排序机制。提供具体的代码示例展示如何实现高精度的时间同步、事件去抖动和跨设备的输入一致性保证。","parent_id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","order":4,"progress_status":"failed","dependent_files":"client/src/inputremoting.js,client/src/memoryhelper.js,client/src/inputdevice.js","gmt_create":"2026-05-16T12:32:15.7277745+08:00","gmt_modified":"2026-05-16T13:21:38.4202746+08:00","raw_data":"WikiEncrypted:yvmiCbNZO/j/crebb6wVoysGCnjbAJCqMVyW3FLaQ4q7YGVekx9xEv4+gchGvt8J9bq98fdMmIk6gPkHcweUJWdFa2zVCpYhjUlBPmnikqBR0aVnBVYc50jwUE5XjeajQ5skf2Zmh1iHwE8w9rC5HnJ51J1ecvb6AprhUDBGIg24Sr8le55lMG0Zske2bEWuI5J4I22Idj727LO/Lx27A/OogkVq6G/Je0z7ph08LBxVZ8xZH/CrnHejPVemFRVtpi9mXBsHuOrkX2lx/jayjTfNFNdpG5m1ieEYh3AvI+219Gy6JaVxiFCUst7PjXbCOWUBngNVzTYzAot0RviNiEF2IXquRV33XGS9Usmw+/MMcOFvOul2jgLfa7SoXNy+mgzUiBRcD3/hHCPVLbp5CvKTh2XDErEr7AIIYRtAkcbLkyGQsurG/n0HqEw+rHwmO0sBOJZbxA29rAwTuGJU+HFrhKHSIAUvZzT2asnvi51H2IBUOgc3JopDixpr5T4LsUw4l7M/kNhINaT5QHU9mhlVg4/D/p/2vfD1EHDPZV5nZtUEtDnez2dwEPLiZiszSByT2wrNz9llTjIhThoPB+N5ZOfq3Ah7L3KsIbY18mVzxSbWn+sNnLHbfBeXOGtXOQsx7j6SpCCsnvpAOQMO7xgJtGetkh8ehuS7JvaBSYke1FbhOSln0R4bKQWDQl+TE1UqIOSU5L63hIz8vPbd02Il4C2TpudL4RUcIy3F6Nt/6vqqdNBKopgVsAAYwuy8LxgUIyVk4DNKa6rseoOvQpCLg6oWMFKufwW3YGivT2DV7s3ea7PaKDmapTSN9i5kZ//I+VLjs939S9bkdifQxAk8WHs4ZSXtulYmaFkPXkVjyFdb5peQBTRXRN02C3q9Dol0wCKk6Mig6ngKf1bnV9gKr9TgckmBvlZ1xyvJAb8T8nrW5Q6JCNMZO++VsmbjjVzccolo2rthjb1Fts7VR7vn213T8e4mcDrEzqGvJjTJPLiL7V2WLrxLj1lbN1Gy4y4XNvX9vgnGsQgbrRFAtQ==","layer_level":2},{"id":"a6f74331-cf80-4cd5-81ac-001111c93597","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"监控与日志","description":"monitoring-logging","prompt":"创建监控与日志配置指南。详细说明日志系统的配置,包括日志级别设置、输出格式和轮转策略。提供性能监控指标定义,包括连接数统计、内存使用情况、请求响应时间和错误率监控。解释分布式追踪配置,包括请求链路跟踪和性能瓶颈定位。提供告警规则设置,包括阈值配置、通知渠道和故障恢复机制。详细说明监控工具集成,包括 Prometheus、Grafana 和 ELK Stack 的配置。包含日志分析和故障诊断方法,提供常见问题的监控解决方案。","parent_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","order":5,"progress_status":"completed","dependent_files":"package.json,src/log.ts","gmt_create":"2026-05-16T12:31:50.8664338+08:00","gmt_modified":"2026-05-16T13:01:48.8163447+08:00","raw_data":"WikiEncrypted:HL3VqGjXq8A3aGeLxAjVPg+lnGgkT5PNghnzp51sgLDjuJwvzPZp6KDp64sXTqk/5Kv+l8CP0s93FbLMTArkben8wsJNnypQ0lBY5p+a1+mhy5V5Zg7ghkR+YHd2K8eNa3VSKiYk2M9/2t+isdMarHg6jD6LtoIFW43prXRaJTS2zLbBEM/bwmuv8CHGWgF2akmTQOIWWbWbax5caFTKffvOaRtoz+lbzeo49ZFBsz4V2LfK8WG6fZgnbOO3QrQdHJGII8mWB244e62hD9qgrorNANTEpEXcJf4R+18neVFMLbYR/BXM31YkrArQ55t8y/2g3uMrOrrDCx3Zp4KScP1zepFaze6BEA3A5Ge6xO4Edy+rmRHQ1l48xwk0cf+ygK5109xZ2Kz2/pbRkyYxp3oephMXUSEoexKx1Qcl/3Uu7SLGvX21etSEGwjZcJxP0vkxkavjxJBvU10PlZQZ3vYbBxYn2+gtLwPGhzqIv5r8bFfnE6E5on4vICrK6nWgg6B5B41AwdDl5RqZdpDIU3QmkwwT7ulGC6Cqhk3RQSahKZTHjpP/crO4tuitBbY1RbWBI5N5HrPckh3SdQYTd/E6tF0otIu/UVONRU+QlR1t90kwwE8zMemeqavb0oZ5n8bkZx6Umlu0NfmPx0rqOKES+Q5rrB4d+Jv/gAcGgfaa1xBaS1QIgT+s2UZ9Hpbjcs3X+ixhB81AKaWqqTWrkZfS6RwAbrXmaxtJhgoDuZ/ZJd4yFE8Kc5Bpzrs+ekiaSEeZSBmQqQZS4BfAUAxBYzYstUbbTxdmbp332mMY7O0VjYAyBjDzAeyW6ZRZ80sC/AEv33B18VqJkOYP1rZVUku66FED398I8Y9ys9UzdNAX8rWd+QQ394vs9QrVtRiUPfXY5fofzokF7jmojX4E1dA7WVve71JqqizamxDm2WymQadfUHDCZrCxo9s/uPfitvae4VsvPLt2HqsS8t0YUU8quynKq4NrcTeqD7Phc9d76ELCwioxDM8xiPkZ2dIxcP2dCenNCjMaldNOcnXJHGwEl9pZ/6TJrto/rFGYSCUO1mPD5ex0DdPpX9kYnUD3FOg69ueIuB+7CsouTr9lVK9RCT3bXPWMUuujUqBsG9v0uCKDgLfOsdW/5lJylDBZ/crvNOTM9LZB3WvkMjr6tL15BRBXxp90Zhqyf5KGxrvFC//3V3cF/ZxcZrndGGV5kgo/x/5pJNrtszp4aa62eOkhePgLl4lO5u3UjNByUNY=","layer_level":1},{"id":"85699f5e-ca94-4146-b489-1bd32e25bf31","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"WebRTC 实现","description":"webrtc-implementation","prompt":"创建 WebRTC 实现的详细技术文档。深入解释 PeerConnection 的管理和控制,包括连接建立、媒体协商和连接状态监控。详细说明输入设备远程控制的实现,包括鼠标、键盘和触摸事件的转发机制。解释渲染流处理的工作原理,包括媒体流的接收、解码和显示过程。文档化信令客户端的实现,包括与服务器的通信协议和消息处理。提供性能优化建议和最佳实践。包含具体的代码示例和调试技巧。","order":5,"progress_status":"failed","dependent_files":"client/src/peer.js,client/src/sender.js,client/src/renderstreaming.js,client/src/signaling.js,client/src/inputremoting.js","gmt_create":"2026-05-16T12:31:02.6517011+08:00","gmt_modified":"2026-05-16T13:21:38.4218652+08:00","raw_data":"WikiEncrypted:724B5pLpQC0DQiM7hKx4GcumGzr5QI6DT+VrEHAutsrbZq/9xrLnVRS93YYw7cZ8RvUz4M6eGoplzkU8VxTAnh0Dx4cl1+snh0isRd+8P/Tcek69BG6jkQ6z01wwVA5UYx7wb4utiGDqxWTOS0O9Gr2JLYTjbhlTcV6kavgHnWXdVMTuQd/vHjynJssespzmcdTrrfHuOqIGZLyVDNPpygwVleLXy+GjRKy+hEmKzdz+EA5r0rM9gAFKrDQ2KXRQlxZIxYj2LUchWoeHifblbfgJT8XXiRU+/DuEMV64eBwpyoG8Wh9vWN9w776pHsSnf6Y5CF/jcdYg1xJ46nR63TRPlixn5+mi6iKzPpbCxGgtrHcNeMY5m8vPxBDw8rBDcwLVz0FN+hotMXjVSmK3KzEpuafVzHS39NzwrXGnAt8UVzXJGbUy4Cc6uU9NoR/I6zYlrgHesLKJEtP8tY8FkMyg6bVzxpz/WWvugrx0S+aVSioPYie0iCPR2MYVp0bDi1qBplG0BNo/sPp5/zxIyXrm+yHnlHrTSS4fd77E/sOusBmAk36WwnyLv2RA87hC3rEx/b/aOLMR8Gq592kJBCRGL2LX2WMlOCOJ1LnOZ8gwbGf8eVOSKq2J41eNejYWICVcFqabBmZxuzQLJw/p+MsyQodZKoqRODtt8kvvsspFCSSIpsSFI6VQUGiyD/6mdMEfDlQwVyCHuFYEXvgwDDPSCAQB6I7FKse8xxHk7ViUGtefrkm/+cbsweo97FtpUTKDpphQjV19gl2R6I8ANBTB/DclC+aaFh3Td42bkgi1PLlK9Tdhosfjg5PPft3GGgtNAGMhlt7Sry72oGb/DRJnYawjU7pXaFYw2KivdNgDLfgM8vdBR1rYe1jwuqA9rJr5nHaB4hZzYdDhxtcJvBq2UNLrOT48k0EbpoT8lO4kbY4PrD6zkZUWl1GX9BZ7MKQWlzccxhZYtFY1x5tDBuvp8QgKwpMqT9vMxji7xW7Ug4ktV8MuAA54MtQMcrqZGP9q5omN02JoMORPZyDVoaCZFeVdZaeI0w1S24vNrdz0fOCCJMp6bWSFsH/YR6n/XdxE6e4Ww3KFcd915MoYOrO0lKQKePDSq3rrGFeIWbbirjMpr+wy4YuoUZkVjB7RXpQsknH4xOTLAQMZIIsxIAQVOzzc6VS7ikCFI7raLlHI9iHSS1aRHpqV184UTlWLeWrvP8AT9l1jFgmVmvBUkd/Vj6jZdcWI+ExvesLUB4eHwUgWqZlR98Thf2NEJaMcfmtHfEFp8x07FiNMi1ztwc2F3fJGWeSFUiierQdqa6JT+5mVuvgq1SWlBc5diU3nV+Yvvxj8GvV8n8MBkN2tgg=="},{"id":"1b647b21-5a93-48a7-8c85-e3bbc4fbab14","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"API 参考","description":"api-reference","prompt":"创建完整的 API 参考文档。详细记录 WebSocket API 接口规范,包括连接建立、消息格式、事件类型和实时交互模式。文档化 HTTP 轮询 API 的端点规范,包括请求/响应格式、状态码和错误处理。提供 Swagger 自动生成的 API 文档链接和使用说明。详细说明信令消息的数据结构,包括 Offer、Answer、Candidate 等消息类型的字段定义和使用场景。包含完整的错误代码列表和处理指南。提供 API 使用示例和集成代码模板。","order":6,"progress_status":"completed","dependent_files":"src/class/websockethandler.ts,src/class/httphandler.ts,src/swagger.ts,src/服务端接口与WebSocket消息类型.md","gmt_create":"2026-05-16T12:31:02.6517011+08:00","gmt_modified":"2026-05-16T12:43:54.6959309+08:00","raw_data":"WikiEncrypted:C34GewOyK1SlumqKiPsSg+WPNa2UHH7yP2PPjE4/OPk+72w1ikmb9n8XHEjg7JgRB04jwHzq1JJdEIKGb9MUa5iAiLpvmvefUPAejNoq6E038vrKSFkfqb4ggKpSJ6UoHrxWGDmLCGRCc8SwTFRjLHUBtrcF+yb+5uMtGW2TuOSPnlw7/8kM8JaE3k1fah4MMpinLARGZDSIjU0fDn+7GrILvHH6yXkXYl4zvHmaqr2leZ4rD7MkzL4e52xbmJLmeg96+GTdX2DHmT+ghOI2nRbrLxtAZteztaw45bNesJb99DQ4P4355cfapswgf+EtG1n3yol2bqpORXXSEIChIaRiAQjFKLTY2cJB6Lrc9Bqj5ENuAGc256MRMM6oAilETokoW3/ixNRkHPHK67ecG44JFkIpvSx6ayAkylG5dELeLFY28kPygnMykmg8DBjl6W12ws7OXnvD5gp6wLJGDnI1jW6ebba2kz+bkP+gddRFv50ACoq/kSsUYfwv+rAV65EQjIAiO9KG57v8feRS5qHyyamflAJfPwbg1sqsI0gS2dHav7PBr8to2AvIEQ/6ZsnCUFwoMXE7PCKpM54XVowdNgCvFN/5Xt4sSZ7pBC/JS6BYIRv5DAGwr59xOjzeE8g0QzLkQUffJ1GMqlfOW5SkVk52KEfjwTN9LVl6AnToQL+/Va/JdU0nFTXhr/fPyyw432m7dQR0e5LUnQ6F7Q2NRM1DooCOU/OlYDXwbHTL0XcOO8tEaupMbHLDauLrWmYiofuaBsFHlG5rm7tVx13PkeAmk+8DiSb8ydPUp/22R6NpcL5wFH4gfgTDxy+n+FX+3sbqZrVdJQcq7j+GqvVIT2+zZFLA0RMjN3fx+ReBECDEeu9EeUBS6p7936KkjNcF0fd05eRYQVsMtw4+j9kjm+XL91wU5sUt9m9NkqIbccNSaW6YBgvrW+Y3kzaAHUDIE1BmuCE8VGkX3Nm4C/qVbJqkRRHnwnFzLo8PCCOhpslEq7HfcydjS1xOFxT1cxPcehTA0q70oYtHN73LZIZCDEBiSkFeiiRPk/VlnynayZ1FKw+P9RtJ1dTT0bSWmk4JSp9K8aiWwM9JqqIb+lZLA8GogpjaMp26A7qJhH/5CeP775UbTwgYzgGdzQm3gkyVsoIkiuuqk7ZgrXnwTijI3lW4aOf56ev+sDNT5Lo="},{"id":"409a593a-5e33-4a86-b7c9-73ed92e79e8d","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"测试框架","description":"testing-framework","prompt":"创建测试框架的详细使用指南。解释项目的测试策略和架构,包括单元测试、集成测试和端到端测试的组织结构。详细说明 Jest 测试框架的配置和使用方法,包括测试环境设置和模拟对象的创建。提供客户端测试和服务器端测试的具体示例,包括 WebRTC 功能测试和信令处理测试。解释测试覆盖率的要求和报告生成。包含测试最佳实践和调试技巧。提供测试用例编写指南和代码示例。","order":7,"progress_status":"completed","dependent_files":"client/test/,test/,jest.config.js,client/jest.setup.js","gmt_create":"2026-05-16T12:31:02.6517011+08:00","gmt_modified":"2026-05-16T12:44:55.5671034+08:00","raw_data":"WikiEncrypted:a51tw9+B5Xez88YuZqi4cFcpX65Vd5rNV40o5oWZUs7Uec+vke5jBxcenYNTAliPZ4or3HDEuuvC5uVQBD6NfEz01mopjXI9TDQk2Hy3lR2OD7lKT9BMdQyhQRLTlkhRpPb7eH5HoiSesGvqTE8CLxh5FnCJQEGOI8oC7vBmVeo1PzmcuGaTUwXoZQQrrUOcdaplqN2RQY5v/5HSEU/328MmhelT+GbT4vEeYtbfd1Kt8Vfcn2tflc9vJPb7yy82uGHflWnnLK2t9oPrILRdTk2ukiBS5KtASLF/Jz0ZK0VcvGh+th2cFVixxy3BPHyxOs313Zt6Ly84o0aeqp64FHc79XgLCuph+qj8F8KnVSPlEXKE3OgD+dfHK87LyOAgMNhTMav8YG79VM6KKo4xSUoq6Uyf+e1+xgDu0iHHglWYXWgiS8mbOZ/CJD24ypn98z/i7ytO0HOCGqUh+CsDUnbV7lY0iSbJTCaFXgh8W53PpjA/37eAHj8jmORMTMc+9UGfrXa2LspKUJvx+dRR/9J/0WpiigJTFns5KDvfmRXKGOlR0F/87PCCx8YzO8HltS0+rVlAlbs2GSEpHcVWnYhrkjlH6qepKq7z/VwXwA28UWPiBMr0KnSNv+O8z27Y4GKfwp9KFkrJQ6Iwb4l+FpOW5aq/RRhdMZklpsWWWhKMrf7L5+7yMW338YZQGOPNwQkXpjFWFdgXQOdt4YEIieGea01v0fAVteC4wc1RFolHFPNKgtCbtsTlgip5eUbqlQRZm0d+cjtoQIiJJahxCaLPgbXv7Jdc/5TI5tv2rIlubpNeDfPqkTuX9V0h3mRoib95w8H3Ll8t2j7yP3KRYaj9AlLEV0NOM2HhPr1UE9ydpf4ejzLEEY7LcJxrFbPLcolZP8uihhKREOM3i8fKzguvZRLSxnPQQy7oxU19HLBfQfF57DGHP0AZzjNq0nb3oxX3IJv6VHHNtA4xw2xPwfX92chmymAKcGzFUZgShyWBNJuzK/WmZexdHaICYqqahnNzSMsiA/jFg3dtgQsfXjy931ffhMmJOwYCeK6/OwzDDE8CbWdafIaWzujKPqgHKv9OHg9Ae5DRp/ElftTBMIDxJtV7AgP54mcwNgcihgA="},{"id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"部署指南","description":"deployment-guide","prompt":"创建全面的部署指南。详细说明生产环境的配置要求,包括 Node.js 版本、系统资源和网络配置。提供 Docker 容器化部署方案和 Kubernetes 部署配置。解释反向代理的设置,包括 Nginx 和 Apache 的配置示例。详细说明 SSL/TLS 证书的申请和配置流程。提供性能优化建议,包括内存管理、并发处理和资源限制。包含监控和日志配置指南,以及故障排除和维护操作。提供自动化部署脚本和 CI/CD 集成方案。","order":8,"progress_status":"completed","dependent_files":"package.json,run.bat,.eslintrc.cjs,tsconfig.json","gmt_create":"2026-05-16T12:31:02.6517011+08:00","gmt_modified":"2026-05-16T12:45:05.4964458+08:00","raw_data":"WikiEncrypted:0IKfLNOWe9mZfG1jVts3wxq1Ie83+Lf6EMuycQSN7s3NFV5ooa3Scq2B9C67gTKq2J61WmIo8uwzopEI3o+rx08GVbGNcRuSVbTF7Ivc0QkpRO98cA0m2Tj2unPHoGDrHAOphV1rOGnXW9WU+C/Ijm9/APC3fcJOXQrIomhJ4Bv5vEMx08MhtLC0GYk5jaksVtAzpo1nxoHeh6XvfA/3tTeTMvh1bGDrzpZrJd7HYSw03I3DyHmm2Ybjt/CCOvqrvBXqrEEEjzmbCRJzfN4sBTeEqtGcZh4MMGdj6ph0EV/N+0ZsClk3ur0F7d2vTSQ4617E+5XEEon0MkqFmYR9RQF83hM2dIg+2K/RJM+4w/xxsa+Lfq/9Sf6V/8sNoW/PtkQvgbpXozACOjmKIx5zTvK42KM+KMnCT8+X2vLQhMQ5GI7YuthP4fS+V3fCS2ueQmootoZfzM03/SQ3FjIIHOzchDlZYbPt+6JZAVY7JH2/D3w1qLQ7cSK76AMpXy4wXj9BYQ70SpY1d/EU5FrxlJMPkfLlwComDTIV1WU15hlEBj4/cctH9FnIcupLViZi67fsu/lSiPcdNMVDnCZrqlzooy2EtLcmJ9XNTEhXUlwtwoMgMEKD5mN7+3nEmJvfe1x/Ay5Lhk/9AEa9sPB8VjQ9ylNyJurPOngECozRs51m9MOf+/6/FHjOFgPOx1M439ZBFmSxHEzBBR3xhPCKTNfuq2rNblEYAOuVD+gNXW9v/QSrYWpPonpNd6W1Y/z6NrXcbVb7LODADfDKfWDrM5N8U8UJs2paSxR38yMV/yIn1wthTNk5dzKZcG+UMdDQbEARqQ9GyKeoBBYSAqwrGIBO2PDlPkjlNqXSiiDsM2zEdii7Bqxv8Jlxu01BgzbGOsJcFZq6WmS//2YeOXIPWzGNAqKWky14pX6lUmzMpIaYF8tF6tAn9Qonknd1LBZtl9VKFMGIGR6viHFykUdlV5IptUp0VWXIRDhrhAaapLyNyV6vfemMsVjEEWH4RddCsu8J3cD+LB9JfL/5ivke+2/VT/5Ebgs8RErWB2xChDRy+uWpGmD4XnjiPNpD9oZGRsv+hSED21TUthmJsj48C+evRLHoVCmdZcu5GySoGUCJzs/2FXI4kPvzKarUIwPbRQTCN2F81zUwZl3/Ql8njw=="},{"id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"开发指南","description":"development-guide","prompt":"创建开发指南文档。详细说明开发环境的搭建步骤,包括 Node.js、TypeScript 和相关工具的安装配置。解释项目的代码规范和编码标准,包括 ESLint 配置和代码风格要求。提供调试技巧和开发工具的使用方法。详细说明扩展开发的机制,包括新功能模块的添加和现有功能的修改。包含版本管理、分支策略和发布流程的说明。提供贡献指南和代码审查标准。包含常见开发问题的解决方案和最佳实践。","order":9,"progress_status":"completed","dependent_files":"package.json,.eslintrc.cjs,tsconfig.json,client/.eslintrc.json","gmt_create":"2026-05-16T12:31:02.6517011+08:00","gmt_modified":"2026-05-16T12:44:50.9401085+08:00","raw_data":"WikiEncrypted:F3QgleoEfoy16cQggYe9C/ucQxiwh0okVA48AmODs5RKNS8ZgNngqmnHSFcG/1UaJxx+3dUn+EkMTpYUsOg40meGzJJt+1pqaKGkjKwpY9nep5VJX+SDuGY1/hSgRvDtqXHA0uQYww7cu5egOEOd5rPrcHxcVZP5YPUiuh7Jt5OqF8LxXqnRjscgzf8EaTfv5lnlYzabZtYO2f0OQRRRSmAT8238c4azzKfnPfTkEegoCJLryB8nbeN36998riCB5h32Q8xNubpZU0PBBn66ahGYB0Gnjh73Yii8XfjmQQVdAR8qmIUTavNopBn8DpK3jlddaq7xrCxs2X2e7rgOsbGGOWCA+BsTRYBkSisKceMglNQIArcohEJLPndeqjTj5NxWVnB5uCPkXaz+lb7iJ7qy3ze+sUbkceiRDC1SVi7i7Gd7XxhRTXkZ9UJHPqGDj8FOumBP3uyjglCPLJvrG2t8dE0kBO4C0dG+gKogHcXcBPBMn7NrZ7dpPZMM0c6uGUVAZbTPRDXzZg4GeH27NoRDmRx1pKtsCOoPsF7w4XENprdxrsmaRgOH5omQoQ98dKM//4jGz4iryP4ozoltYgbKkyek0BjArqlYR3SemZq8T5QniHw1H2vBO8UbMUmLAJ4KIgQ+AhW4WmssnggOG8ik8MzV1ukwRruSBOdATK7053uawi5SgF+07FDa/wnU8nbp+PBMQ4YFnTc8+GnDTfoy1ikCgNCyCXt3YOb9bH0qmGgJsksMzWvl89ehb43fAXNoMTpaUIB5zSonFZBAJV9CpN7yqdwQDffmYRvhe/63vgmzsYHJpRijagRl+0PPgVciz0Demf3tmwL4I14xvRfyYvwnmMnphXZr9xery8Uic8D2w0l6oxZphNYLC4Ra+gCpFRsQiCL2XGKYexNYEpa76PwQwikPwaETQJhM+eP4UxDB68kbQqZlTOWx5V+7F8opDl5M3yKOHKDfqnJS5G5Pt6o9omWovile/KI8rDOhdovk9kYnn6g4F8f4aGesOpAvCj6hUzXwWhLlXCXeP+EilC9tYo47nehZx2v3NfuNWhyRqHAdDdT7TRXrZ8Y+zdysipiuWnqZnPmssv0SFPpQQWDC1hkqk7chzCYt0mbuGNb4TLBXqnUAq7IQt32OdG3VqWYLAIZq4rZAYErDVLeuYw7QUbcmhng4iU63I2s="}],"wiki_items":[{"catalog_id":"ab3ffc79-b32f-4ce9-9e92-122cae33eaae","title":"快速开始","description":"getting-started","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"06bed196-5cca-45ae-9359-45acf127aed1","gmt_create":"2026-05-16T12:33:44.260369+08:00","gmt_modified":"2026-05-16T12:33:44.2661749+08:00"},{"catalog_id":"a9dcd690-f864-48f3-8b00-ab83d8beef13","title":"项目概述","description":"project-overview","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"ca3cbda1-a534-44c2-a8c3-89d6ee536e3f","gmt_create":"2026-05-16T12:34:31.670873+08:00","gmt_modified":"2026-05-16T12:34:31.6762069+08:00"},{"catalog_id":"14e70c29-58bc-4fa6-9655-abd66f3b0fbb","title":"服务器核心","description":"server-core","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"61d25281-9508-46b0-b5f5-e98340a424b2","gmt_create":"2026-05-16T12:34:45.9352915+08:00","gmt_modified":"2026-05-16T12:34:45.9414399+08:00"},{"catalog_id":"ca381b87-b639-48b1-b436-cf9ebe93ba21","title":"客户端示例","description":"client-examples","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"d33fe06c-8396-47dc-90b3-e6799315126d","gmt_create":"2026-05-16T12:43:50.9953118+08:00","gmt_modified":"2026-05-16T12:43:51.0011753+08:00"},{"catalog_id":"1b647b21-5a93-48a7-8c85-e3bbc4fbab14","title":"API 参考","description":"api-reference","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"39c95779-0da7-42c5-b88c-30ef91e0a14d","gmt_create":"2026-05-16T12:43:54.690081+08:00","gmt_modified":"2026-05-16T12:43:54.6959309+08:00"},{"catalog_id":"f36932c0-be78-46a7-bfd3-b0b5350b47cd","title":"开发指南","description":"development-guide","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"d4d5d7d1-e282-4f68-ae3f-fb6945f659eb","gmt_create":"2026-05-16T12:44:50.9366144+08:00","gmt_modified":"2026-05-16T12:44:50.9401085+08:00"},{"catalog_id":"409a593a-5e33-4a86-b7c9-73ed92e79e8d","title":"测试框架","description":"testing-framework","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"9c1de659-6b46-40e3-8f35-9d5dc36a1571","gmt_create":"2026-05-16T12:44:55.562807+08:00","gmt_modified":"2026-05-16T12:44:55.5671034+08:00"},{"catalog_id":"f3b1757a-3db0-4f32-aad5-c7e969de4ac8","title":"部署指南","description":"deployment-guide","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"8f2b9df1-403e-4f2c-8a40-75a3a24f6fd0","gmt_create":"2026-05-16T12:45:05.4926356+08:00","gmt_modified":"2026-05-16T12:45:05.4964458+08:00"},{"catalog_id":"5d349e63-8143-47c8-a4d8-9abf775836a0","title":"服务器入口点","description":"server-entrypoint","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"a18fb879-c53d-45f7-a0e6-65518ead784f","gmt_create":"2026-05-16T12:46:18.1791845+08:00","gmt_modified":"2026-05-16T12:46:18.1852693+08:00"},{"catalog_id":"a4cc8c7f-7b51-446f-a7e8-f4c4cbca61af","title":"双向通信示例","description":"bidirectional-sample","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"4cea0b6d-9c76-4625-9ecf-a7e8ab8514a8","gmt_create":"2026-05-16T12:46:36.1646167+08:00","gmt_modified":"2026-05-16T12:46:36.1702033+08:00"},{"catalog_id":"0b227205-af0d-4e95-a18b-9d354af01ac1","title":"WebSocket 信令处理器","description":"websocket-handler","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"0128328c-d6c1-447d-aeaf-99be20c6fbe1","gmt_create":"2026-05-16T12:47:17.6544436+08:00","gmt_modified":"2026-05-16T12:47:17.6601811+08:00"},{"catalog_id":"5ef4dc8a-3a84-4e7e-a5f7-40db73889380","title":"环境配置","description":"environment-setup","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"7de21301-1800-46c4-9e90-252230ab0880","gmt_create":"2026-05-16T12:47:18.3417045+08:00","gmt_modified":"2026-05-16T12:47:18.3467186+08:00"},{"catalog_id":"20805460-563b-4af7-8279-1cf55b0c7a90","title":"PeerConnection 管理","description":"peer-connection-management","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"411dc0f6-de65-4195-be76-dc48174538e0","gmt_create":"2026-05-16T12:48:19.7383099+08:00","gmt_modified":"2026-05-16T12:48:19.7440884+08:00"},{"catalog_id":"73389b15-3341-4577-a7f8-93a58709c76f","title":"开发环境搭建","description":"environment-setup","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"8a8dc12e-8f1a-43db-b330-96dd3f8c56b3","gmt_create":"2026-05-16T12:48:27.5086668+08:00","gmt_modified":"2026-05-16T12:48:27.5144309+08:00"},{"catalog_id":"eca2b26d-d22e-43d2-8e65-b238a269789d","title":"HTTP 服务器配置","description":"http-server","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"1b4157ca-4c87-461f-bb56-051340bbca1e","gmt_create":"2026-05-16T12:48:31.5510132+08:00","gmt_modified":"2026-05-16T12:48:31.5551909+08:00"},{"catalog_id":"482a84ec-9e84-4271-ac38-8ea5dfe61e9e","title":"HTTP 信令处理器","description":"http-handler","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"e99c777e-850d-4ada-88c6-72e6585c5be2","gmt_create":"2026-05-16T12:50:09.5817719+08:00","gmt_modified":"2026-05-16T12:50:09.5864059+08:00"},{"catalog_id":"68e75bda-0f61-404b-8038-89345a43e067","title":"接收端示例","description":"receiver-sample","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"848b8177-5da0-4c80-b1b2-0dfadad67e2e","gmt_create":"2026-05-16T12:50:18.4475878+08:00","gmt_modified":"2026-05-16T12:50:18.4512043+08:00"},{"catalog_id":"bffb854e-14ea-4aa3-8c58-bbb08bef5c9f","title":"容器化部署","description":"containerization","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"656adbc4-c074-411d-8c5e-ba6611bf599d","gmt_create":"2026-05-16T12:50:20.865896+08:00","gmt_modified":"2026-05-16T12:50:20.8690039+08:00"},{"catalog_id":"d532c7ba-1b0c-43f0-8f5b-1741ca9f7b91","title":"代码规范与质量","description":"code-standards","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"2c75e3e7-eeb6-4356-9355-00277b33c568","gmt_create":"2026-05-16T12:51:21.1510072+08:00","gmt_modified":"2026-05-16T12:51:21.1562654+08:00"},{"catalog_id":"3ad75da2-7ef6-4c9c-90ef-4b9b5adb9d39","title":"WebSocket 服务器","description":"websocket-server","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"87689515-cccf-4c24-9817-beb105a160ad","gmt_create":"2026-05-16T12:51:39.8628595+08:00","gmt_modified":"2026-05-16T12:51:39.8700061+08:00"},{"catalog_id":"b255a63b-cf59-411c-ba5e-f4f6c9a94848","title":"输入设备控制","description":"input-device-control","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"7ff7c770-eec8-49dd-afda-7dc0b0e25487","gmt_create":"2026-05-16T12:52:41.7301828+08:00","gmt_modified":"2026-05-16T12:52:41.7386979+08:00"},{"catalog_id":"16e0345a-01a0-42ac-9d58-6acd773ccd7a","title":"多播放示例","description":"multiplay-sample","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"53d99faa-d1e3-42be-8474-c977db3da42c","gmt_create":"2026-05-16T12:52:56.4834411+08:00","gmt_modified":"2026-05-16T12:52:56.4881619+08:00"},{"catalog_id":"bca5c096-0c2c-4f80-a594-4e3b0a2e1a51","title":"信令消息对象","description":"signaling-messages","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"96a1ec75-8efd-4372-9986-cc7310e56c8b","gmt_create":"2026-05-16T12:53:29.6045337+08:00","gmt_modified":"2026-05-16T12:53:29.6108452+08:00"},{"catalog_id":"b26e6ce5-8068-45e0-afad-aa894dff7bff","title":"反向代理配置","description":"reverse-proxy","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"d4fd3a89-ae04-4f08-95e7-4ed903b28c49","gmt_create":"2026-05-16T12:54:12.2843439+08:00","gmt_modified":"2026-05-16T12:54:12.2895983+08:00"},{"catalog_id":"fb0e54f9-8ba9-427a-a51d-0e20ee6d7fd6","title":"渲染流处理","description":"render-streaming","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"df958a5a-4c42-4aa4-99df-36618e31f4de","gmt_create":"2026-05-16T12:54:20.8591772+08:00","gmt_modified":"2026-05-16T12:54:20.8643932+08:00"},{"catalog_id":"f4c8bc1c-7182-4212-a6f0-5ce1ce114cc4","title":"调试与测试","description":"debugging-testing","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"bd5891a7-c453-4ada-b8ea-30fb4aaf7a1a","gmt_create":"2026-05-16T12:55:22.1415062+08:00","gmt_modified":"2026-05-16T12:55:22.146713+08:00"},{"catalog_id":"c225ba69-033c-432b-88e0-c96cafc612d5","title":"视频播放器示例","description":"videoplayer-sample","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"f908d033-1e13-451d-be55-93fb9a9f181c","gmt_create":"2026-05-16T12:55:54.7874524+08:00","gmt_modified":"2026-05-16T12:55:54.7950583+08:00"},{"catalog_id":"939c76fd-c27f-41fc-aa05-687375faa473","title":"信令路由系统","description":"signaling-routing","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"d1dc2fd0-8acc-483b-8b48-a631b00b3db7","gmt_create":"2026-05-16T12:56:42.4199472+08:00","gmt_modified":"2026-05-16T12:56:42.4264457+08:00"},{"catalog_id":"341b2b54-3261-4147-8e1a-0f5c46f4d61d","title":"SSL/TLS 配置","description":"ssl-tls-configuration","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"f51cc9c8-b978-40a2-bb68-b06fd46df9aa","gmt_create":"2026-05-16T12:56:50.3117019+08:00","gmt_modified":"2026-05-16T12:56:50.3164864+08:00"},{"catalog_id":"38f69d88-cf29-4ea2-8c51-f18ed2a172e6","title":"信令路由配置","description":"signaling-routing","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"c6634f0f-1c55-410d-89b9-815a3c153c46","gmt_create":"2026-05-16T12:57:25.6495247+08:00","gmt_modified":"2026-05-16T12:57:25.6553355+08:00"},{"catalog_id":"4079958b-e9eb-4610-9a7a-8fdec4af747d","title":"信令客户端","description":"signaling-client","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"bba374a4-6375-4f0a-a3bd-415caec58122","gmt_create":"2026-05-16T12:58:11.452073+08:00","gmt_modified":"2026-05-16T12:58:11.4562432+08:00"},{"catalog_id":"7735317a-b747-4d88-9f5f-869465bfb4c0","title":"扩展开发","description":"extension-development","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"f7e1d371-a005-48ff-9493-574ea74c9bc7","gmt_create":"2026-05-16T12:58:26.7885231+08:00","gmt_modified":"2026-05-16T12:58:26.7949904+08:00"},{"catalog_id":"8bc8b2c5-bd3b-470e-8ce3-76f9eda2aef6","title":"日志系统","description":"logging-system","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"019782c6-56d6-47da-8dc9-90dd3c5cc78b","gmt_create":"2026-05-16T12:58:50.3502325+08:00","gmt_modified":"2026-05-16T12:58:50.3589906+08:00"},{"catalog_id":"13019cef-b976-46c3-bd7f-63fdc6bbc074","title":"性能优化","description":"performance-optimization","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"53ee4d63-72a8-4732-bc7d-00eda6d8d780","gmt_create":"2026-05-16T13:00:02.4435898+08:00","gmt_modified":"2026-05-16T13:00:02.4492052+08:00"},{"catalog_id":"869dd14b-54e9-4010-b1e0-746a3e176bd4","title":"实用工具模块","description":"utility-modules","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"ca90a370-9cb9-4fa2-8568-3a333f897a33","gmt_create":"2026-05-16T13:00:05.5461445+08:00","gmt_modified":"2026-05-16T13:00:05.5504002+08:00"},{"catalog_id":"eda098b0-7acb-4d00-9252-973100c3a66b","title":"一对一通信示例","description":"onebyone-sample","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"f11438e7-f21b-467b-9011-af1c2e3881aa","gmt_create":"2026-05-16T13:00:43.7024944+08:00","gmt_modified":"2026-05-16T13:00:43.7062627+08:00"},{"catalog_id":"bd459b28-50c4-4796-8528-69834467fbf7","title":"贡献指南","description":"contribution-guidelines","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"b5178261-7194-4e7b-8332-d9bffdd37e23","gmt_create":"2026-05-16T13:01:26.0473835+08:00","gmt_modified":"2026-05-16T13:01:26.052069+08:00"},{"catalog_id":"a6f74331-cf80-4cd5-81ac-001111c93597","title":"监控与日志","description":"monitoring-logging","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"d95a5269-84e0-4428-b367-dcbe5fcdf516","gmt_create":"2026-05-16T13:01:48.8121265+08:00","gmt_modified":"2026-05-16T13:01:48.8168658+08:00"},{"catalog_id":"a34e1301-4130-410e-b147-4698c45c23ae","title":"键盘输入处理","description":"keyboard-input-handling","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"97215c0a-a8fa-4946-8d6f-ffdc1d5f09d2","gmt_create":"2026-05-16T13:03:09.101606+08:00","gmt_modified":"2026-05-16T13:03:10.0551537+08:00"},{"catalog_id":"0bfb7aaa-5a8f-42c6-a9d6-60845e7015f4","title":"触摸输入处理","description":"touch-input-handling","extend":"{}","progress_status":"completed","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","reference_count":0,"id":"8bac35a5-6784-4e7b-b543-e1cbbf86f0d0","gmt_create":"2026-05-16T13:03:24.3158482+08:00","gmt_modified":"2026-05-16T13:03:24.3223653+08:00"}],"wiki_overview":{"content":"基于我对项目的深入分析,现在我可以生成一个全面的项目概述:\n\n\u003cblog\u003e\n\n# Video Socket Server 项目架构分析\n\n## 1. 项目介绍\n\n### 项目目的\nVideo Socket Server 是一个基于 WebRTC 的实时视频传输服务器,专门为 Unity Render Streaming 提供双向视频通信解决方案。该项目实现了高效的信令传输、媒体流管理和多客户端连接支持。\n\n### 核心目标\n- 提供低延迟的双向视频传输\n- 支持多种通信模式(公共/私有)\n- 实现 WebRTC 信令协议的完整支持\n- 提供丰富的客户端示例和工具\n\n### 目标用户\n- Unity 开发者\n- 实时通信应用开发者\n- 远程协作平台构建者\n\n## 2. 技术架构\n\n### 整体架构图\n\n```mermaid\ngraph TD\n A[客户端浏览器] --\u003e B[Express HTTP服务器]\n B --\u003e C[WebSocket信令服务器]\n B --\u003e D[HTTP轮询信令]\n C --\u003e E[WebRTC Peer Connection]\n D --\u003e E\n E --\u003e F[媒体流传输]\n F --\u003e G[远端客户端]\n \n H[静态资源] --\u003e B\n I[配置文件] --\u003e B\n J[日志系统] --\u003e B\n```\n\n### 核心组件关系\n\n```mermaid\nclassDiagram\n class RenderStreaming {\n +run(argv: string[])\n +app: express.Application\n +server: Server\n +options: Options\n }\n \n class WSSignaling {\n +server: Server\n +wss: WebSocket.Server\n +constructor(server: Server, mode: string)\n }\n \n class WebSocketHandler {\n +reset(mode: string)\n +add(ws: WebSocket)\n +remove(ws: WebSocket)\n +onConnect(ws: WebSocket, connectionId: string)\n +onOffer(ws: WebSocket, message: any)\n +onAnswer(ws: WebSocket, message: any)\n +onCandidate(ws: WebSocket, message: any)\n }\n \n class HTTPSignaling {\n +router: express.Router\n +checkSessionId(req, res, next)\n +getConnection(req, res)\n +postOffer(req, res)\n }\n \n RenderStreaming --\u003e WSSignaling\n RenderStreaming --\u003e HTTPSignaling\n WSSignaling --\u003e WebSocketHandler\n HTTPSignaling --\u003e WebSocketHandler\n```\n\n### 数据流架构\n\n```mermaid\nsequenceDiagram\n participant Client as 客户端\n participant WS as WebSocket服务器\n participant Handler as 信令处理器\n participant Peer as WebRTC对等连接\n \n Client-\u003e\u003e+WS: 建立WebSocket连接\n WS-\u003e\u003e+Handler: onConnect(connectionId)\n Handler--\u003e\u003e-WS: 确认连接\n WS--\u003e\u003e-Client: 连接确认\n \n Client-\u003e\u003e+WS: 发送offer\n WS-\u003e\u003e+Handler: onOffer(message)\n Handler-\u003e\u003e+Peer: 处理offer\n Peer--\u003e\u003e-Handler: 生成answer\n Handler--\u003e\u003e-WS: 广播answer\n WS--\u003e\u003e-Client: answer消息\n```\n\n## 3. 关键实现\n\n### 主入口点\n- **服务器启动**: `src/index.ts` - 应用程序入口,配置命令行参数和服务器启动\n- **HTTP服务器**: `src/server.ts` - Express 服务器配置,静态资源托管和API路由\n- **WebSocket信令**: `src/websocket.ts` - WebSocket 信令服务器实现\n\n### 核心模块\n\n#### 信令处理模块\n- **WebSocket处理器**: `src/class/websockethandler.ts` - 实现完整的信令消息处理逻辑\n- **HTTP信令处理器**: `src/class/httphandler.ts` - HTTP 轮询信令实现\n- **信令路由**: `src/signaling.ts` - Express 路由配置\n\n#### WebRTC 实现\n- **Peer连接管理**: `client/src/peer.js` - WebRTC PeerConnection 管理\n- **输入设备处理**: `client/src/sender.js` - 输入设备远程控制\n- **渲染流处理**: `client/src/renderstreaming.js` - 媒体流渲染管理\n\n### 配置管理\n- **项目配置**: `package.json` - 依赖管理和脚本配置\n- **TypeScript配置**: `tsconfig.json` - 编译配置\n- **客户端配置**: `client/package.json` - 前端依赖管理\n\n### 外部依赖\n- **核心依赖**: Express.js, WebSocket, WebRTC\n- **开发依赖**: TypeScript, Jest, ESLint\n- **工具依赖**: Swagger 文档生成\n\n### 集成点\n- **静态资源集成**: `client/public/` - HTML/CSS/JS 客户端资源\n- **模块化导入**: `client/src/` - JavaScript 模块化代码\n- **测试集成**: `test/` - 单元测试和集成测试\n\n## 4. 关键特性\n\n### 功能概览\n\n#### 1. 多模式通信支持\n```mermaid\nstateDiagram-v2\n [*] --\u003e Public: 公共模式\n [*] --\u003e Private: 私有模式\n \n Public --\u003e Broadcast: 广播消息\n Public --\u003e MultiClient: 多客户端连接\n \n Private --\u003e Host: 主机角色\n Private --\u003e Participant: 参与者角色\n Private --\u003e OneToOne: 一对一通信\n```\n\n#### 2. WebRTC 信令协议\n- **Offer/Answer 模型**: 完整的 SDP 交换流程\n- **ICE 候选者**: STUN/TURN 服务器支持\n- **媒体流管理**: 视频/音频轨道处理\n\n#### 3. 客户端示例\n- **双向通信**: `client/public/bidirectional/` - 双向视频通话\n- **接收端**: `client/public/receiver/` - 单向视频接收\n- **多播放**: `client/public/multiplay/` - 多客户端播放\n- **视频播放器**: `client/public/videoplayer/` - 视频播放控制\n\n### 实现亮点\n\n#### 1. 连接管理\n```mermaid\nflowchart TD\n A[客户端连接] --\u003e B{连接模式}\n B --\u003e|Public| C[全局广播]\n B --\u003e|Private| D[连接组管理]\n \n D --\u003e E[主机角色]\n D --\u003e F[参与者角色]\n \n E --\u003e G[多参与者管理]\n F --\u003e H[单向通信]\n```\n\n#### 2. 信令消息类型\n- **连接管理**: connect, disconnect\n- **媒体协商**: offer, answer, candidate\n- **应用层消息**: broadcast, on-message\n- **控制消息**: ping, pong\n\n#### 3. 媒体流处理\n- **输入设备远程**: 鼠标、键盘、触摸、手柄\n- **媒体轨道**: 视频/音频轨道添加和管理\n- **编解码器偏好**: 浏览器编解码器支持\n\n## 5. 结论与建议\n\n### 项目架构优势\n1. **模块化设计**: 清晰的前后端分离和模块化组织\n2. **双信令支持**: WebSocket 和 HTTP 轮询两种信令模式\n3. **多模式通信**: 支持公共和私有通信场景\n4. **完整的 WebRTC 实现**: 从信令到媒体流的全流程支持\n\n### 潜在改进点\n1. **性能优化**: 可考虑引入连接池和负载均衡\n2. **安全性增强**: 添加认证和授权机制\n3. **监控完善**: 增加更详细的性能指标和错误追踪\n4. **文档扩展**: 完善 API 文档和部署指南\n\n### 推荐的下一步\n1. **部署优化**: 配置生产环境的 SSL 证书和反向代理\n2. **监控集成**: 添加 Prometheus/Grafana 性能监控\n3. **扩展功能**: 支持更多媒体格式和编解码器\n4. **测试覆盖**: 增加端到端测试和压力测试\n\nSources:\n- [package.json](package.json)\n- [src/index.ts](src/index.ts)\n- [src/server.ts](src/server.ts)\n- [src/websocket.ts](src/websocket.ts)\n- [src/class/websockethandler.ts](src/class/websockethandler.ts)\n- [src/class/httphandler.ts](src/class/httphandler.ts)\n- [src/signaling.ts](src/signaling.ts)\n- [client/src/sender.js](client/src/sender.js)\n- [client/src/peer.js](client/src/peer.js)\n- [client/public/index.html](client/public/index.html)\n- [client/public/bidirectional/js/main.js](client/public/bidirectional/js/main.js)\n\n\u003c/blog\u003e","gmt_create":"2026-05-16T12:28:32.9307707+08:00","gmt_modified":"2026-05-16T12:28:32.9307707+08:00","id":"930ea563-b716-47e6-b61a-05af6df0f600","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7"},"wiki_readme":{"content":"No readme file","gmt_create":"2026-05-16T12:27:20.7130973+08:00","gmt_modified":"2026-05-16T12:27:20.7130973+08:00","id":"1a80f275-5eb3-45da-8160-daca4ece7288","repo_id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7"},"wiki_repo":{"id":"4226e74d-2759-41d5-9170-ce61a2ed9ce7","name":"video_socket-server","progress_status":"completed","wiki_present_status":"COMPLETED","optimized_catalog":"\".\\n├── client\\\\\\n│ ├── public\\\\\\n│ │ ├── bidirectional\\\\\\n│ │ │ ├── css\\\\\\n│ │ │ │ └── style.css\\n│ │ │ ├── js\\\\\\n│ │ │ │ ├── main.js\\n│ │ │ │ └── sendvideo.js\\n│ │ │ └── index.html\\n│ │ ├── css\\\\\\n│ │ │ └── main.css\\n│ │ ├── images\\\\\\n│ │ ├── js\\\\\\n│ │ │ ├── config.js\\n│ │ │ ├── icesettings.js\\n│ │ │ ├── main.js\\n│ │ │ ├── stats.js\\n│ │ │ └── videoplayer.js\\n│ │ ├── multiplay\\\\\\n│ │ │ ├── css\\\\\\n│ │ │ │ └── style.css\\n│ │ │ ├── js\\\\\\n│ │ │ │ └── main.js\\n│ │ │ └── index.html\\n│ │ ├── onebyone\\\\\\n│ │ │ ├── connect\\\\\\n│ │ │ │ ├── connect.html\\n│ │ │ │ └── connect.js\\n│ │ │ ├── css\\\\\\n│ │ │ │ └── style.css\\n│ │ │ ├── endcall\\\\\\n│ │ │ │ ├── endcall.html\\n│ │ │ │ └── endcall.js\\n│ │ │ ├── README.md\\n│ │ │ ├── chatmessage.js\\n│ │ │ ├── code-structure.md\\n│ │ │ ├── index.html\\n│ │ │ ├── knowledge-graph.md\\n│ │ │ ├── main.js\\n│ │ │ ├── models.js\\n│ │ │ ├── renderer.js\\n│ │ │ ├── store.js\\n│ │ │ └── utils.js\\n│ │ ├── receiver\\\\\\n│ │ │ ├── css\\\\\\n│ │ │ │ └── style.css\\n│ │ │ ├── js\\\\\\n│ │ │ │ └── main.js\\n│ │ │ └── index.html\\n│ │ ├── uploads\\\\avatars\\\\\\n│ │ ├── videoplayer\\\\\\n│ │ │ ├── css\\\\\\n│ │ │ │ └── style.css\\n│ │ │ ├── images\\\\\\n│ │ │ ├── js\\\\\\n│ │ │ │ ├── gamepadEvents.js\\n│ │ │ │ ├── main.js\\n│ │ │ │ ├── register-events.js\\n│ │ │ │ └── video-player.js\\n│ │ │ └── index.html\\n│ │ └── index.html\\n│ ├── src\\\\\\n│ │ ├── charnumber.js\\n│ │ ├── gamepadbutton.js\\n│ │ ├── gamepadhandler.js\\n│ │ ├── inputdevice.js\\n│ │ ├── inputremoting.js\\n│ │ ├── keymap.js\\n│ │ ├── logger.js\\n│ │ ├── memoryhelper.js\\n│ │ ├── mousebutton.js\\n│ │ ├── peer.js\\n│ │ ├── pointercorrect.js\\n│ │ ├── renderstreaming.js\\n│ │ ├── sender.js\\n│ │ ├── signaling.js\\n│ │ ├── touchflags.js\\n│ │ └── touchphase.js\\n│ ├── test\\\\\\n│ │ ├── domrect.js\\n│ │ ├── domvideoelement.js\\n│ │ ├── inputdevice.test.js\\n│ │ ├── inputremoting.test.js\\n│ │ ├── memoryhelper.test.js\\n│ │ ├── mocksignaling.js\\n│ │ ├── peerconnection.test.js\\n│ │ ├── peerconnectionmock.js\\n│ │ ├── pointercorrect.test.js\\n│ │ ├── renderstreaming.test.js\\n│ │ ├── resizeobservermock.js\\n│ │ ├── sender.test.js\\n│ │ ├── signaling.test.js\\n│ │ └── testutils.js\\n│ ├── .eslintrc.json\\n│ ├── jest.config.js\\n│ ├── jest.setup.js\\n│ ├── package-lock.json\\n│ └── package.json\\n├── src\\\\\\n│ ├── class\\\\\\n│ │ ├── answer.ts\\n│ │ ├── candidate.ts\\n│ │ ├── httphandler.ts\\n│ │ ├── offer.ts\\n│ │ ├── options.ts\\n│ │ └── websockethandler.ts\\n│ ├── index.ts\\n│ ├── log.ts\\n│ ├── server.ts\\n│ ├── signaling.ts\\n│ ├── swagger.ts\\n│ ├── websocket.ts\\n│ └── 服务端接口与WebSocket消息类型.md\\n├── test\\\\\\n│ ├── env_macos.postman_environment.json\\n│ ├── httphandler.test.ts\\n│ ├── renderstreaming.postman_collection.json\\n│ └── websockethandler.test.ts\\n├── .editorconfig\\n├── .eslintrc.cjs\\n├── .gitignore\\n├── jest.config.js\\n├── package-lock.json\\n├── package.json\\n├── run.bat\\n├── server.cert\\n├── tsconfig.build.json\\n├── tsconfig.json\\n├── tsconfig.lint.json\\n└── 管理模块报价.md\\n\"","current_document_structure":"WikiEncrypted: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","catalogue_think_content":"WikiEncrypted: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","recovery_checkpoint":"wiki_generation_completed","last_commit_id":"eae60714b42bef288ac89822cd23884efb42648d","last_commit_update":"2026-05-16T13:21:38.9214269+08:00","gmt_create":"2026-05-13T14:56:11.4554036+08:00","gmt_modified":"2026-05-16T13:21:38.9214269+08:00","extend_info":"{\"language\":\"zh\",\"active\":true,\"branch\":\"master\",\"shareStatus\":\"\",\"server_error_code\":\"401\",\"cosy_version\":\"1.0.1\"}"}} \ No newline at end of file diff --git a/src/class/websockethandler.ts b/src/class/websockethandler.ts index cf52503..2acf2de 100644 --- a/src/class/websockethandler.ts +++ b/src/class/websockethandler.ts @@ -450,6 +450,8 @@ function onMessage(ws: WebSocket, message: any): void { const connectionId = message.connectionId; const chatMessage = message.message; const senderParticipantId = (ws as any).participantId; + chatMessage.participantId = senderParticipantId; + chatMessage.connectionId = connectionId; if (connectionGroup.has(connectionId)) { const group = connectionGroup.get(connectionId); if (group.host === ws) {