# 直播 SFU 分流 Demo(Cloudflare Realtime · SRS) 参考 GOSpeak 技术栈的直播 SFU 分流演示。控制面用 **protobuf / gRPC** 定义,媒体面走 **Cloudflare Realtime**(主 SFU)与 **SRS**(本地对照)的 WebRTC 扇出。 - 一路推流 → SFU 扇出 → 多路拉流。 - 同一路发布可同时落到 **Cloudflare Realtime** 与 **SRS** 两条分发链路(分流)。 - 浏览器经服务端反向代理直连 SFU,凭证不出服务端。 - **默认 DB 使用 Turso 嵌入式 (libSQL file)**:房间分发拓扑持久化到本地文件。 ## 目录结构 ``` app/live-sfu-demo/ api/live_sfu.proto # protobuf 控制面契约 gen/ # buf generate 产出(Go) internal/ config/ # 运行参数(对齐 GOSpeak 的 env 布局) db/ # Turso 嵌入式 (libSQL file) 持久化 sfu/cloudflare/ # Cloudflare Realtime REST 客户端 + Provider sfu/srs/ # SRS Provider server/ # gRPC 服务 + JSON 网关 + SRS/CF 媒体反代 + 房间扇出状态 static/ # 浏览器 UI(发布 / 观看 / 分发面板) cmd/server/main.go deploy/ # docker-compose.yml + srs.conf ``` ## 运行(SRS 链路,开箱即跑) ```bash # 1) 起本地 SRS(WHIP/WHEP/HLS 三协议后端,见 docs/streaming-pipeline.md) cd app/live-sfu-demo/deploy && SRS_CANDIDATE=127.0.0.1 docker compose up -d srs # 2) 起 Demo 控制面(默认 Turso 嵌入式,无需额外配置) cd app/live-sfu-demo cp .env.example .env go run ./cmd/server # 3) 打开浏览器 # 发布: http://localhost:8088/publish?room=demo # 观看: http://localhost:8088/watch?room=demo ``` 两个标签页用同一房间名即可配对。发布页勾选的分发后端会在「分发状态」中实时显示。 ## 默认 DB:Turso 嵌入式 房间的“分发目标”拓扑(`stream_targets`)默认持久化到 **Turso 嵌入式**(`github.com/tursodatabase/go-libsql` 的 SQLite 兼容 file 模式),而非纯内存。 - **DSN**:`TURSO_DATABASE_URL=file:./data/live-sfu.db?cache=shared&_journal_mode=WAL`(默认) - 兼容 `DATABASE_URL` 覆盖 - 仅支持嵌入 file: / :memory:,拒绝 libsql:// 远程(嵌入适配专注单机持久化) - 自动建表 `(stream_targets, rooms)`,`SetMaxOpenConns(1)` 适配 SQLite 单写模型 - 重启后自动 `LoadAll` 恢复房间 ```bash # 默认即嵌入文件 TURSO_DATABASE_URL=file:./data/live-sfu.db?cache=shared&_journal_mode=WAL # 内存(测试) TURSO_DATABASE_URL=file::memory:?cache=shared # 验证持久化:发布后重启,/api/rooms 仍在 curl http://localhost:8088/api/rooms | jq sqlite3 data/live-sfu.db "select room, backend, stream from stream_targets;" ``` ## 运行(Cloudflare Realtime 链路,主 SFU) 在 `.env` 填入 Cloudflare Realtime 凭证后,`go run ./cmd/server` 即启用主 SFU: ``` CF_APP_ID=xxxxxxxxxxxx CF_APP_SECRET=xxxxxxxxxxxx ``` - 发布:服务端用 `CF_APP_SECRET` 创建 Cloudflare session,浏览器只交换 SDP(`/api/cf/...` 反代注入 Bearer)。 - 观看:服务端为观众创建 viewer session,并订阅发布者 session 的轨道(`location=remote`)。 Cloudflare 未配置时,UI 会标注「未配置」,SRS 链路不受影响。 ## protobuf / gRPC ```bash # 修改 api/live_sfu.proto 后重新生成 export PATH="$HOME/go/bin:$PATH" buf generate api ``` 契约(`LiveSFU` 服务):`GetConfig` / `ListRooms` / `Publish` / `Subscribe` / `StopStream` / `WatchRoom`(服务端流式推送房间分发拓扑)。gRPC 监听 `GRPC_PORT`(默认 9090),浏览器走同端口 的 JSON 网关(`protojson`)。 ## 文档 * 推流链路详解(PC → WHIP → SRS → HLS/WHEP/FLV):[`docs/streaming-pipeline.md`](docs/streaming-pipeline.md) ## 分流拓扑 ``` publisher ──WHIP/tracks.new──▶ Cloudflare Realtime SFU ──▶ viewer(s) └────WHIP────────────▶ SRS SFU ─┬─▶ WHEP viewer(s) (低延时 0.2-0.5s) ├─▶ HLS viewer(s) (PC→WHIP→SRS→HLS 延时 5-10s,全端兼容) └─▶ FLV viewer(s) 一路 WHIP 推流,SRS 自动 remux 三协议同出;房间分发目标由 WatchRoom 实时广播,观众任选后端拉流 ``` > 详见 [`docs/streaming-pipeline.md`](docs/streaming-pipeline.md) — 默认已开启 HLS,`Go` 网关反代 `/live/*.m3u8`,前端 iOS 原生 / hls.js 双兼容。