# Phase 2e-2 Task 0 — mediasoup PoC Spike 结论 > **状态:** ✅ 通过 / 技术栈可用性已验证 > **执行日期:** 2026-04-21 > **位置:** [`media-server/poc/`](../poc) > **关联:** > - [Phase 2e-2 设计文档](../../docs/plans/2026-04-21-phase2e-2-design.md)(§4.1 组件拓扑 / §4.2 Go-Node 协同时序) > - [Phase 2e-2 实施计划](../../docs/plans/2026-04-21-phase2e-2-implementation.plan.md) §Task 0 --- ## 一、目标与验收 按实施计划 §Task 0 要求,用最小代码验证: 1. mediasoup Worker + 单 Router 可在本机跑起来 2. 浏览器 ↔ Node 的完整 SFU 信令链路:`rtpCapabilities → Transport → Producer → Consumer` 3. 两个浏览器上下文之间能互相收到对方的音视频流 4. peer 断开时资源能被自动回收,无泄漏 **验收结果**:全部通过。下述第 §三 章记录了实际运行数据。 --- ## 二、最小可行架构(PoC 版) ``` ┌──────────────┐ ws://:3300/ws ┌──────────────────────┐ │ Browser A │──────信令──────────→ │ Fastify + ws │ │ (mediasoup- │←──RtpCapabilities── │ │ │ client 3) │ │ ┌───────────────┐ │ └──────┬───────┘ │ │ mediasoup │ │ │ DTLS/ICE/RTP │ │ Worker │ │ └──────────────────────────────┼──→│ └── Router │ │ │ │ ├─Trans. │ │ ┌──────────────────────────────┼──→│ ├─Prod. │ │ │ DTLS/ICE/RTP │ │ └─Cons. │ │ ┌──────┴───────┐ │ └───────────────┘ │ │ Browser B │──────信令──────────→ │ /healthz /stats │ └──────────────┘ └──────────────────────┘ ``` PoC 刻意简化:**无权限、无房间、无鉴权**,全部 peer 加入同一个全局 Router,每个 peer 订阅所有其他 peer。真实 Go-Node 架构(设计文档 §4.2)中由 Go 承载这些权威状态。 信令协议(PoC 自定义,**与正式业务 WS 事件互不相关**,仅用于验证 mediasoup API): | 消息 | 方向 | 用途 | |---|---|---| | `getRtpCapabilities` | C→S | 取 Router capabilities | | `createTransport {direction}` | C→S | 创建 send / recv Transport | | `connectTransport {transportId, dtlsParameters}` | C→S | DTLS 握手 | | `produce {transportId, kind, rtpParameters}` | C→S | 推流 | | `consume {transportId, producerId, rtpCapabilities}` | C→S | 订阅远端流 | | `resumeConsumer {consumerId}` | C→S | 恢复 paused consumer | | `newProducer {peerId, producerId, kind}` | S→C 广播 | 通知其他 peer 新流出现 | | `peerLeft {peerId}` | S→C 广播 | 通知其他 peer 离开 | --- ## 三、实际运行数据(2026-04-21) ### 3.1 启动阶段 ``` mediasoup worker + router ready workerPid: 77170 routerId: "f21e2f6a-1361-413c-8f2a-41a8290d0611" rtcPortRange: "40000-40099" listenIp: "0.0.0.0" announcedIp: "(unset, use local LAN ip)" PoC ready: http://localhost:3300 ``` `/healthz` 返回 `{ok:true, workerPid, routerId, peers:0}`,服务就绪。 ### 3.2 2 peer 会议(Playwright 驱动 Chrome 双 tab) 两个 tab 分别点击「加入并推流」,控制台日志(Tab A): ``` WS connected as peer adb1eff1 mediasoup Device loaded local audio producer: 51c16d40 local video producer: 20d0d275 existing remote producers: 0 ← Tab A 先进,房间空 remote producer: peer=8dcec923 kind=audio ← Tab B 加入后广播 remote producer: peer=8dcec923 kind=video consuming audio from 8dcec923 consuming video from 8dcec923 ``` Tab B: ``` existing remote producers: 2 ← Tab B 后进,拿到 A 的两条 producer consuming audio from adb1eff1 consuming video from adb1eff1 ``` **双向互通成立**。浏览器端 WebRTC `iceGatheringState` 正常走到 `complete`,`connectionState` 正常走到 `connected`。 ### 3.3 资源统计(`/stats` 实测) | 阶段 | peers | transports | producers | consumers | RSS(MB) | |---|---:|---:|---:|---:|---:| | 空闲 | 0 | 0 | 0 | 0 | 60 | | Tab A 加入 | 1 | 2 | 2 | 0 | 60 | | Tab B 加入 | 2 | 4 | 4 | 4 | 61 | | Tab B 关闭 | 1 | 2 | 2 | 0 | 64 | **资源回收链路**:WS `close` → `cleanupPeer` → 级联 close producers/consumers/transports → mediasoup `transportclose`/`producerclose` 事件 → 对端 consumer 自动清理 → 广播 `peerLeft`。实测对端 consumers 自动归零,无泄漏。 ### 3.4 线性外推(单 Worker 单 Router) | N 人会议 | transports | producers | consumers | 单 peer 入向 track | |---:|---:|---:|---:|---:| | 2 | 4 | 4 | 4 | 2 | | 4 | 8 | 8 | 24 | 6 | | 6 | 12 | 12 | 60 | 10 | | 8 | 16 | 16 | 112 | 14 | consumers 数量平方级增长:`N × (N-1) × 2`。MVP 硬上限 8 人,Node 单进程承载毫无压力;以 baseline `61MB / 2 peers` 推断,8 人会议常驻约 200MB RSS,远低于风险阈值。 --- ## 四、关键坑与决策锁定 ### 4.1 `announcedIp` 在 localhost 的处理 - **现象**:本机 Demo 场景下,若 `announcedIp` 留空 + `listenIp="0.0.0.0"`,mediasoup 生成的 `iceCandidates` 会包含 `0.0.0.0` 条目,Chromium 会自动替换为 `127.0.0.1` 与可用 LAN IP 完成 ICE 协商 - **结论**:本机 Demo **留空 `MEDIASOUP_ANNOUNCED_IP` 即可工作**,不必显式配 `127.0.0.1`,否则反而会让远端浏览器只拿到回环地址从而无法在同机多 tab 之外跑通 - **公网**:必须填服务器对外公网 IP(或 A 记录解析到的 IP),否则 ICE Candidate 全是内网 IP 客户端无法连上 ### 4.2 DTLS 握手 - 默认 `enableUdp:true + enableTcp:true + preferUdp:true` 组合开箱即用 - 本机 Demo 全程走 UDP;公网若遇对称 NAT 走 coturn TURN 转发到 UDP - **未遇到 DTLS 超时**;若后续线上出现,排查顺序:`udp 40000-40199 防火墙` → `announcedIp 正确性` → `TURN 凭证有效性` → `dtlsParameters 传参顺序(先 create 后 connect)` ### 4.3 mediasoup-client 在浏览器端的加载 - mediasoup-client **未提供预构建 UMD bundle**,不能用 `