Go to file
noelorin c7f70970ac feat: POST+JWT SSE + permission visibility
Backend:
- SSE endpoints changed from GET to POST for JWT auth compliance
- Chat subscription moved to /api/room/{room}/chat/subscribe (avoids route conflict)
- handleAuthCheck now returns config:write permission (was missing from response)

Frontend:
- EventSource replaced with fetch POST + Authorization Bearer header
- Token persisted in localStorage, cleared on logout
- postSSE helper with exponential backoff reconnection
- rooms.tsx: SSE/loading gated on can('room:list'), not just isAuthed
- watch.tsx: room events SSE gated on can('room:watch'), chat SSE on can('room:chat')
- watch.tsx: subscribe button disabled when lacking room:subscribe
- permissions.ts: added isPublisher() helper
2026-08-23 23:55:23 +08:00
api feat(proto): add distribution model srs-hls/cf/cdn 2026-08-23 13:56:31 +08:00
cmd/server feat: 接入 sync-live 后端重构,前端全面对齐新协议与接口 2026-08-21 00:31:51 +08:00
data feat(auth): 基于 casbin 的登录与 RBAC 权限系统 2026-08-19 16:42:17 +08:00
deploy feat: 接入 sync-live 后端重构,前端全面对齐新协议与接口 2026-08-21 00:31:51 +08:00
docs feat: realtime rooms via SSE (global rooms/events) 2026-08-23 23:19:12 +08:00
gen feat(proto): add distribution model srs-hls/cf/cdn 2026-08-23 13:56:31 +08:00
internal feat: POST+JWT SSE + permission visibility 2026-08-23 23:55:23 +08:00
web feat: POST+JWT SSE + permission visibility 2026-08-23 23:55:23 +08:00
.env.example docs: update to SRS trunk + multi-distribution architecture 2026-08-23 15:19:46 +08:00
.gitignore refactor(manage): 对齐 GOSpeak 壳层拆分管理页 + 清理重复流程 2026-08-21 07:56:33 +08:00
Makefile feat: add runtime config, room distributor override, user management & UI polish 2026-08-23 19:48:32 +08:00
README.md docs: update to SRS trunk + multi-distribution architecture 2026-08-23 15:19:46 +08:00
buf.gen.yaml feat: live SFU fan-out demo (Cloudflare Realtime + SRS) 2026-08-17 15:14:51 +08:00
design.md feat: realtime rooms via SSE (global rooms/events) 2026-08-23 23:19:12 +08:00
go.mod chore: commit remaining worktree changes (room/playlist/docs/deps) 2026-08-23 16:18:28 +08:00
go.sum chore: commit remaining worktree changes (room/playlist/docs/deps) 2026-08-23 16:18:28 +08:00

README.md

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. 配置环境变量

复制示例并填写鉴权密钥(正式环境必填):

cp .env.example .env
# 必填:JWT_SECRET(与可选 SFU_TOKEN_SECRET)
# 可选:BOOTSTRAP_ADMIN_USER / BOOTSTRAP_ADMIN_PASS 冷启动首个管理员

未配置 JWT_SECRET 时服务将拒绝启动,避免以不安全默认密钥上线。

2. 起本地 SRS(主干,必须)

cd deploy && SRS_CANDIDATE=127.0.0.1 docker compose up -d srs

SRS 为唯一推流入口,未启动时推流将失败。

3. 起控制面

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://
  • 自动建表,重启后自动恢复房间拓扑
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

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/)

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。

主播外部推送 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 广播;同样受滑动窗口限流保护。