live-sfu-demo/README.md

152 lines
5.8 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 — 同步直播 · SFU 分流服务(对标 SyncTV)
> **对标 SyncTV**:SyncTV 是 *Sync + TV*(一起看),**SyncLive 是 *Sync + Live*(一起播·一起看直播)**。本项目是 SyncTV 理念在直播场景的姐妹实现:一路推流,多端同步扇出。
基于 **SRS 主干 + 多路分发** 的同步直播服务。控制面以 **protobuf / gRPC** 定义,媒体面 SRS 为唯一推流入口。
- 推流仅进入 SRS 主干(WHIP),由服务端按配置扇出到多路分发。
- 分发方式:**SRS HLS 直推**(remux 原地出 HLS/FLV)+ **Cloudflare Realtime SFU**(低延时)+ **第三方直播 CDN**(RTMP 转推)。
- 浏览器经服务端反向代理直连 SFU,凭证不出服务端。
- 默认 DB 使用嵌入式 libSQL (file) 持久化房间分发拓扑,零外部依赖。
- 前端为 **SolidJS + TanStack Router/Query + Ark UI + Tailwind v4** 单页应用,构建产物嵌入 Go 二进制。
## 目录结构
```
sync-live/
api/sync_live.proto # protobuf 控制面契约
gen/ # buf generate 产出(Go)
internal/
config/ # 运行参数 + 正式环境密钥校验
db/ # 嵌入式 libSQL 持久化
sfu/cloudflare/ # Cloudflare Realtime 分发
sfu/srs/ # SRS 主干
sfu/cdn/ # 第三方 CDN 分发
sfu/distributor.go # 分发抽象接口
server/ # gRPC 服务 + JSON 网关 + 媒体反代 + 房间拓扑 + 分发编排
static/ # 前端构建产物(由 web/ 构建生成,Go embed)
static/ # 前端构建产物(由 web/ 构建生成,Go embed)
auth/ # Casbin RBAC + JWT + 用户存储
cmd/server/main.go
deploy/ # docker-compose.yml + srs.conf
web/ # 前端工程(Solid + TanStack + Ark UI + Tailwind v4)
src/
vite.config.ts # 构建输出到 ../internal/server/static
```
## 运行
### 1. 配置环境变量
复制示例并填写鉴权密钥(正式环境必填):
```bash
cp .env.example .env
# 必填:JWT_SECRET(与可选 SFU_TOKEN_SECRET)
# 可选:BOOTSTRAP_ADMIN_USER / BOOTSTRAP_ADMIN_PASS 冷启动首个管理员
```
未配置 `JWT_SECRET` 时服务将拒绝启动,避免以不安全默认密钥上线。
### 2. 起本地 SRS(主干,必须)
```bash
cd deploy && SRS_CANDIDATE=127.0.0.1 docker compose up -d srs
```
SRS 为唯一推流入口,未启动时推流将失败。
### 3. 起控制面
```bash
go run ./cmd/server
# 首页: http://localhost:8088/
# 发布: http://localhost:8088/publish?room=<房间名>
# 观看: http://localhost:8088/watch?room=<房间名>
# 登录: http://localhost:8088/login
```
发布/订阅需先登录;房间名通过 URL `?room=` 指定,不依赖默认占位。
## 鉴权(Casbin RBAC + JWT)
控制面启用登录与 RBAC:
- 发布 / 订阅 / 房间管理需登录;`guest` 仅可读取配置与 SRS 切片流(HLS/FLV)。
- 用户存储于 `data/users.json`(首次启动为空,需通过 `BOOTSTRAP_ADMIN_*` 或注册创建首个管理员)。
- 登录后服务端下发 HttpOnly Cookie,前端同源请求自动携带。
角色:`admin` / `publisher` / `viewer` / `guest`,策略见 `internal/auth/policy.csv`。
## 持久化(嵌入式 libSQL)
房间「分发目标」拓扑(`stream_targets`)默认持久化到嵌入式 libSQL file,而非纯内存:
- DSN:`file:./data/sync-live.db?cache=shared&_journal_mode=WAL`(默认)
- 兼容 `DATABASE_URL` 覆盖;仅支持 file: / :memory:,拒绝远程 libsql://
- 自动建表,重启后自动恢复房间拓扑
```bash
curl -b cookie.txt http://localhost:8088/api/rooms | jq
```
## 运行(Cloudflare Realtime,主 SFU)
在 `.env` 填入 Cloudflare Realtime 凭证后启用主 SFU:
```
CF_APP_ID=xxxxxxxxxxxx
CF_APP_SECRET=xxxxxxxxxxxx
```
- 发布:服务端用 `CF_APP_SECRET` 创建 Cloudflare session,浏览器只交换 SDP(反代注入 Bearer)。
- 观看:服务端为观众创建 viewer session,并订阅发布者 session 的轨道。
Cloudflare 未配置时 UI 标注「未配置」,SRS 链路不受影响。
## protobuf / gRPC
```bash
export PATH="$HOME/go/bin:$PATH"
buf generate api
```
契约(`SyncLive` 服务):`GetConfig` / `ListRooms` / `Publish` / `Subscribe` / `StopStream` /
`WatchRoom`(服务端流式推送房间分发拓扑)以及 `Login` / `Register` / `GetMe` / `ListUsers` / `UpdateUserRole`。
gRPC 监听 `GRPC_PORT`(默认 9090),浏览器走同端口的 JSON 网关(`protojson`)。
## 前端开发(web/)
```bash
cd web
pnpm install
pnpm dev # http://localhost:5173 热更新
pnpm build # 构建进 ../internal/server/static(被 Go embed)
```
## 分流拓扑
```
publisher ──WHIP/tracks.new──▶ Cloudflare Realtime SFU ──▶ viewer(s)
└────WHIP────────────▶ SRS SFU ─┬─▶ WHEP viewer(s) (低延时 0.2-0.5s)
├─▶ HLS viewer(s) (全端兼容,/live/*.m3u8)
└─▶ FLV viewer(s)
```
> SRS 推流链路详解见 [`docs/streaming-pipeline.md`](docs/streaming-pipeline.md)。
## 主播外部推送 API(弹幕广播)
除观众可发送的 `POST /api/room/{room}/chat` 外,额外提供**仅主播可用**的独立推送端点:
```
POST /api/room/{room}/broadcast
Authorization: Bearer <publisher-or-admin jwt>
Content-Type: application/json
{ "message": "欢迎来到直播间", "color": "#4ea8ff" }
```
- 权限:需 `room:publish`(仅 `publisher` / `admin`),未登录返回 `401`,越权返回 `403`。
- 推送的弹幕自动标记 `host: true`,经同一 `chatHub` 广播;同样受滑动窗口限流保护。