live-sfu-demo/docs/streaming-pipeline.md

223 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SyncLive 推流链路文档 · PC → WHIP → SRS → HLS / WHEP / FLV
> 默认推流链路已升级为 **一路 WHIP 推流,SRS 三协议同出**。本文档沉淀该链路的拓扑、配置与验证方法,便于后续维护与排查。
## 1. 拓扑总览
```
┌─→ WHEP (WebRTC 0.2-0.5s) → <video> RTCPeerConnection (watch.html: SRS WebRTC)
PC (getUserMedia) ─WHIP─→ SRS ─┼─→ HLS (5-10s 切片) → <video> hls.js / Safari原生 (watch.html: SRS HLS)
└─→ FLV (http-flv) → flv.js / 直接下载 (publish.html 展示链接)
│
└─→ 同时经 Go 网关反代,无需浏览器直连 8080
└──── 同一房间经 WatchRoom (SSE / gRPC stream) 广播分发目标 ────┘
另一条分流链路(可选,未配置不影响):
PC ─WHIP/tracks.new─→ Cloudflare Realtime SFU ─→ viewer (tracks.new location=remote)
```
**核心结论**:浏览器推流始终只走一次 WHIP(`/rtc/v1/whip/?app=live&stream=<room>`),SRS 在 `vhost __defaultVhost__` 内自动 remux 为 HLS / FLV / WHEP,无需二次推流或转码。
| 协议 | 播放地址(经 Go 网关 8088) | SRS 源地址(8080) | 延时 | 兼容性 |
|------|-----------------------------|-------------------|------|--------|
| WHEP | `POST /rtc/v1/whep/?app=live&stream=xxx` | 同上(经网关) | 0.2–0.5s | 需 WebRTC |
| HLS | `GET /live/xxx.m3u8` + `.ts` | `http://srs:8080/live/xxx.m3u8` 经 `GET/HEAD /live/` 反代 | 5–10s(3×10s 切片) | 全端,iOS 原生 |
| FLV | `GET /live/xxx.flv` | 同上 | 1–2s | 需 flv.js |
> 当前发布页(`/publish`)与观看页(`/watch`)已同时支持三种观看方式,发布页推流后自动显示 HLS/FLV 链接(见 `internal/server/static/app.js:hlsUrl`)。
---
## 2. 配置清单
### 2.1 SRS 服务端 · `deploy/srs.conf`
`http_server:8080` 产出静态切片,`http_api:1985` 负责 WHIP/WHEP 信令,`rtc_server:8000` 负责媒体。
```nginx
listen 1935;
max_connections 1000;
daemon off;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html; # HLS 切片落盘目录,Go 网关反代至此
}
http_api {
enabled on;
listen 1985;
crossdomain on;
}
rtc_server {
enabled on;
listen 8000;
tcp { enabled on; listen 8000; }
protocol all;
candidate $CANDIDATE; # 由 SRS_CANDIDATE 注入,公网部署填公网 IP
}
vhost __defaultVhost__ {
rtc { enabled on; nack on; twcc on; }
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
hls {
enabled on;
hls_path ./objs/nginx/html; # 与 http_server.dir 一致
hls_fragment 10; # 单切片 10s,首屏约 10–20s
hls_window 60; # 窗口 60s,保留 6 片
}
}
```
调整建议:
* 降低 `hls_fragment 5` 可将延时压至 5–7s,但会增加切片数与 I/O。
* 若需更低 HLS 延时,可启用 LL-HLS(SRS 6 支持 `hls_ll`),但前端需 ll-hls 客户端,当前未启用以保持兼容。
### 2.2 Docker · `deploy/docker-compose.yml`
```yaml
services:
srs:
image: ossrs/srs:6
ports:
- "1935:1935"
- "1985:1985"
- "8080:8080"
- "8000:8000/udp"
- "8000:8000/tcp"
environment:
CANDIDATE: "${SRS_CANDIDATE:-127.0.0.1}"
volumes:
- ./srs.conf:/usr/local/srs/conf/srs.conf:ro
command: ./objs/srs -c conf/srs.conf
```
`8080` 仅需宿主机验证时直连;生产经 Go 网关反代后可不暴露公网 8080,仅保留 1985/8000 供信令与媒体。
### 2.3 Go 控制面 · `internal/config/config.go` + `.env.example`
| 变量 | 默认 | 说明 |
|------|------|------|
| `SRS_API_BASE` | `http://localhost:1985` | WHIP/WHEP 信令反代目标(`srsProxyHandler`) |
| `SRS_HTTP_BASE` | `http://localhost:8080` | HLS/FLV 静态资源反代目标(`srsHlsProxyHandler`,本次新增) |
| `SRS_APP` | `live` | 推流 app,决定 URL 路径 `/live/<stream>` |
| `SRS_CANDIDATE` | `127.0.0.1` | ICE candidate,容器部署填宿主机/公网 IP |
| `SFU_PROVIDER` | `cloudflare,srs` | 后端顺序,(`GetConfig` 返回) |
| `TURSO_DATABASE_URL` | `file:./data/sync-live.db?cache=shared&_journal_mode=WAL` | 房间拓扑持久化(本次未改) |
### 2.4 反向代理 · `internal/server/proxy.go` + `internal/server/server.go`
`srsProxyHandler`:透传 `/rtc/v1/*` 至 `SRS_API_BASE`,可选校验 `?token=`(`SFU_TOKEN_REQUIRED=1` 时)。
`srsHlsProxyHandler`(新增):
```go
target, _ := url.Parse(cfg.SRSHttpURL) // 默认 :8080
rp := httputil.NewSingleHostReverseProxy(target)
mux.Handle("GET /live/", s.srsHlsProxy)
mux.Handle("HEAD /live/", s.srsHlsProxy)
```
* 统一经 `http://localhost:8088/live/*.m3u8/.ts/.flv` 访问,避免前端直连 8080 的跨域与端口暴露。
* 自动补 `Access-Control-Allow-Origin: *`,iOS/桌面端 `<video>` 可直接播放。
> 拉流 HLS 无需 `Subscribe` 创建 WHEP PeerConnection;观看页 `srs-hls` 模式仅用 `stream` 拼出 hlsUrl 并交由 `watchHLS()` 播放(见下)。
---
## 3. 前端链路 · `internal/server/static/app.js`
### 3.1 推流(WHIP)
```js
POST /rtc/v1/whip/?app=live&stream=live-demo&token=<publishToken>
Content-Type: application/sdp
Body: offer.sdp → 200 answer.sdp → pc.setRemoteDescription(answer)
```
* `publishSRS()`:创建 `RTCPeerConnection` → `createOffer` → `fetch WHIP` → `setRemoteDescription`。
* 成功后 `hlsLink` 立即显示 `hlsUrl(stream) = /live/<stream>.m3u8` 与 `flvUrl`,日志提示“三协议同出”。
### 3.2 观看
* **WHEP**(`watchSRS`):`POST /rtc/v1/whep/?app=live&stream=xxx` 同 WHIP 流程,`pc.ontrack` 挂 `<video>`。
* **HLS**(`watchHLS`,新增):
```js
hlsUrl = `/live/${stream}.m3u8`
if (video.canPlayType('application/vnd.apple.mpegurl')) video.src = hlsUrl; // iOS 原生
else if (Hls.isSupported()) { hls.loadSource(hlsUrl); hls.attachMedia(video); } // hls.js 1.5.7
```
* **切换**:观看页提供三档单选 `cloudflare / srs(whep) / srs-hls`,`srs-hls` 复用 `BACKEND_KIND_SRS` 枚举,仅前端分流。
### 3.3 UI
* `publish.html`:新增 `#hlsLink`,推流后展示 HLS/FLV 超链接;引入 `hls.js` CDN。
* `watch.html`:新增 `srs-hls` 选项 + `#hlsInfo` + `controls`;首屏文案强调“三协议同出”。
---
## 4. 控制面与房间拓扑
* `Publish(room, BACKEND_KIND_SRS)` → 分配 `stream = live-<room>` + JWT `publishToken` → `hub.setTarget(room, "srs", {stream, publishToken, url})` → 广播 SSE。
* `Subscribe(room, BACKEND_KIND_SRS)` → 取 `hub` 中 `stream` 返回,WHEP 与 HLS 共用同一 `stream`。
* `WatchRoom / GET /api/room/{room}/events`(SSE)推送 `RoomEvent{targets}`,观看页据此自动 `subscribe()`。
房间拓扑持久化至 Turso 嵌入式(`internal/db/db.go`),重启后恢复,不影响 HLS 切片(切片为临时文件,SRS 重启清空)。
---
## 5. 快速验证
```bash
# 1) 起 SRS(含 HLS)
cd deploy && SRS_CANDIDATE=127.0.0.1 docker compose up -d srs && docker logs -f sync-live-srs
# 2) 起控制面
cd .. && go run ./cmd/server
# 日志:sync-live ready: http://localhost:8088
# 3) 浏览器
# 发布: http://localhost:8088/publish?room=<房间名> → 勾选 SRS → 开始推流
# 观看 WHEP: http://localhost:8088/watch?room=<房间名> → 选 SRS WebRTC → 开始观看(<1s)
# 观看 HLS : 同页切 SRS HLS → 停止后重新开始观看(约 5–10s 后出画面)
# 裸 HLS: http://localhost:8088/live/live-demo.m3u8 (VLC / ffplay / curl 均可)
curl -i http://localhost:8088/live/live-demo.m3u8
curl -I http://localhost:8088/live/live-demo.flv
ffplay http://localhost:8088/live/live-demo.m3u8
```
排障:
* `m3u8 404`:推流后等待 10–20s 再试;检查 `docker exec sync-live-srs ls /usr/local/srs/objs/nginx/html/live/` 是否有切片。
* `candidate` 不通:容器内 `ip` 与浏览器不在同一网段时,`SRS_CANDIDATE` 改为宿主机公网/局域网 IP 后 `docker compose up -d --force-recreate`。
* HLS 跨域:已由 `srsHlsProxyHandler` 统一加 CORS,若直连 8080 需自行在 `srs.conf` 加 `crossdomain`(http_server 无此指令,建议走网关)。
---
## 6. 延时与选型建议
| 场景 | 推荐 | 理由 |
|------|------|------|
| 连麦/互动直播 | WHEP / Cloudflare Realtime | 200–500ms,可双向 |
| 单向大并发/回看友好 | HLS | 全端兼容,CDN 友好,成本最低 |
| 演示/对比 | WHEP + HLS 双出(默认) | 一路推流兼顾低延时与兼容性 |
> 默认即为双出:无需额外推流或配置,开箱获得两条观看链路。
---
## 7. 文件索引
* 服务端配置:`deploy/srs.conf`、`deploy/docker-compose.yml`、`internal/config/config.go`、`.env.example`
* 网关:`internal/server/proxy.go`(`srsProxyHandler` / `srsHlsProxyHandler`)、`internal/server/server.go`(`Handler` 路由)、`internal/server/gateway.go`、`internal/server/service.go`
* 前端:`internal/server/static/app.js`(`publishSRS`/`watchSRS`/`watchHLS`)、`internal/server/static/publish.html`、`internal/server/static/watch.html`
* 协议:`api/sync_live.proto`(`BackendKind` / `StreamTarget`)
---
*更新:2026-08 — 默认链路 PC→WHIP→SRS→HLS/WHEP/FLV 已启用并经网关反代,文档与代码同源。*