# 双向通信示例

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

## 目录
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 页面<br/>index.html"]
B["主控制器<br/>main.js"]
C["媒体处理<br/>sendvideo.js"]
D["信令封装<br/>signaling.js"]
E["连接管理<br/>renderstreaming.js"]
F["Peer 封装<br/>peer.js"]
G["配置与ICE<br/>config.js / icesettings.js"]
end
subgraph "后端"
H["入口与参数解析<br/>index.ts"]
I["WebSocket 信令<br/>websocket.ts"]
J["选项接口<br/>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)