# 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 Content-Type: application/json { "message": "欢迎来到直播间", "color": "#4ea8ff" } ``` - 权限:需 `room:publish`(仅 `publisher` / `admin`),未登录返回 `401`,越权返回 `403`。 - 推送的弹幕自动标记 `host: true`,经同一 `chatHub` 广播;同样受滑动窗口限流保护。