223 lines
9.3 KiB
Markdown
223 lines
9.3 KiB
Markdown
# 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 已启用并经网关反代,文档与代码同源。*
|