166 lines
9.0 KiB
Markdown
166 lines
9.0 KiB
Markdown
# Go WebRTC Live
|
||
|
||
直播功能使用 Pion WebRTC,在现有 GVA WebSocket 和 JWT 用户体系上实现单主播、多观众的 SFU 转发。
|
||
|
||
## WebSocket
|
||
|
||
连接现有 WebSocket 地址(后端默认 `/ws`,生产反向代理地址为 `/api/ws`)。观看直播允许匿名握手;PC 登录后的 HttpOnly Cookie 会随同源握手自动发送,分享链接中不携带凭证。非浏览器客户端也可携带 GVA JWT:
|
||
|
||
```text
|
||
wss://host/ws?token=<x-token>
|
||
```
|
||
|
||
连接后发送:
|
||
|
||
```json
|
||
{"type":"Join","payload":{"role":"publisher","streamId":"room-1"}}
|
||
```
|
||
|
||
角色支持 `publisher`、`subscriber`、`observer`。`subscriber` 可匿名加入正在直播的房间;离线录播直接使用 HTTP 视频地址,不创建 WebRTC 观众连接。`publisher` 必须登录且只能发布自己绑定的启用直播间,分类也必须启用;允许从未开播状态开始推流。主播和观众完成 Join 后,浏览器发送 SDP offer:
|
||
|
||
```json
|
||
{"type":"Offer","payload":{"type":"offer","sdp":"v=0..."}}
|
||
```
|
||
|
||
服务端返回 `Answer`。主播使用发送方向的 transceiver,观众使用接收方向的 transceiver。媒体轨道由 Pion 在服务端复制给同一房间的所有观众。
|
||
|
||
其他消息:
|
||
|
||
- `Heartbeat` / `HeartbeatAck`
|
||
- `PublisherStatus`
|
||
- `NewProducer`
|
||
- `ProducerClosed`
|
||
- `NewSubscriber`
|
||
- `SubscriberLeft`
|
||
- `StreamListUpdate`
|
||
- `Kicked`
|
||
- `ChatMessage`:普通聊天广播
|
||
- `GiftMessage`:送礼成功后的礼物广播
|
||
|
||
发送聊天消息:
|
||
|
||
```json
|
||
{"type":"ChatSend","payload":{"content":"大家好"}}
|
||
```
|
||
|
||
聊天只允许已经以主播或观众身份加入该房间的登录用户发送,内容最多 50 个字符。消息只保存在当前服务进程内,每个房间最多 100 条;加入房间不会回放进入前的消息,开播、关播或服务重启都会清空该房间聊天。
|
||
|
||
主播和观众 45 秒没有心跳会被清理,直播房间最多 20 个;同一用户在同一房间重复观看时,旧连接会收到 `Kicked`。
|
||
|
||
## HTTP
|
||
|
||
公开列表接口:
|
||
|
||
```text
|
||
GET /live/streams
|
||
GET /live/streams/:streamId
|
||
GET /live/categories
|
||
GET /live/rooms?page=1&pageSize=20&categoryId=1&orderBy=viewer_count_desc
|
||
GET /live/rooms/:id
|
||
POST /live/rooms/:id/like
|
||
GET /live/rooms/:id/messages?limit=100
|
||
GET /live/gifts
|
||
GET /live/recharge/providers
|
||
GET /live/recharge/plans
|
||
```
|
||
|
||
`/live/rooms` 默认返回所有公开启用的房间,包括直播中、配置录播的离线房间、尚未开播的房间。停用房间、停用或已删除分类下的房间不公开。接口按在线人数倒序排列,再分页;支持 `categoryId`、`page`、`pageSize`、`orderBy`,显式传入 `status=live` 或 `status=offline` 时才限制直播状态。
|
||
|
||
`/live/categories` 的返回记录就是 PC 分类来源,客户端只额外增加“全部”,按分类 ID 筛选,不依赖固定分类名称。列表直接展示接口的 `viewerCountText`(如 `5.1k`、`1.0w`)。
|
||
|
||
`GET /live/rooms/:id` 同样允许访问公开的离线房间,并返回 `replayUrl`;未开播不再返回 `LIVE_ROOM_NOT_LIVE`。客户端在 `status=live` 时使用直播流或 WebRTC,离线时使用 `replayUrl`,未配置录播则显示封面和未开播状态。停用房间返回 `LIVE_ROOM_DISABLED`,停用分类返回 `LIVE_CATEGORY_DISABLED`。
|
||
|
||
聊天历史 HTTP 接口保留用于客户端契约兼容,但验证房间后始终返回空列表,不会向新进入用户泄露此前聊天。
|
||
|
||
PC 客户端账号接口:
|
||
|
||
```text
|
||
POST /live/client/auth/register
|
||
POST /live/client/auth/login
|
||
POST /live/client/auth/logout
|
||
GET /live/client/me
|
||
GET /live/client/balance
|
||
PUT /live/client/me
|
||
PUT /live/client/me/password
|
||
```
|
||
|
||
客户端账号使用系统的 `sys_users` 表。注册昵称可选,未填写时使用用户名,填写时限制为 2–16 个字符;新账号创建普通直播角色并初始化 100 钻石,历史账号缺少钱包时也补建 100 钻石。注册和修改密码都会返回新的 JWT 登录态。
|
||
|
||
登录后可调用:
|
||
|
||
```text
|
||
GET /live/rooms/:id/follow
|
||
POST /live/rooms/:id/follow
|
||
DELETE /live/rooms/:id/follow
|
||
POST /live/rooms/:id/gifts
|
||
POST /live/recharge/orders
|
||
GET /live/recharge/orders/:orderNo
|
||
GET /live/recharge/orders/:orderNo/status
|
||
```
|
||
|
||
点赞不要求登录,但仅允许正在直播的公开房间;服务端点赞数在当前进程的直播运行态中累加。PC 保留本次访问的点赞状态,不生成随机点赞。关注关系按直播用户和房间保存,登录后可查询、关注或取消关注。
|
||
|
||
创建充值订单直接接入 KRA 通用支付订单,请求使用支付模块的渠道标识:
|
||
|
||
```json
|
||
{
|
||
"planId": 1,
|
||
"provider": "wechat-v3",
|
||
"notifyUrl": "https://example.com/api/payment/callback/wechat-v3",
|
||
"returnUrl": "https://example.com/recharge/result"
|
||
}
|
||
```
|
||
|
||
充值档位、用户和钻石数量保存在直播业务订单中;渠道交易号、支付状态、回调、退款状态和事件流水统一保存在 KRA 的 `pay_orders` / `pay_events`。支付确认后由通用支付发货流程幂等增加钻石余额。
|
||
|
||
礼物数量允许 `1..9999`,礼物接口返回的 `tiers` 仅作为快捷数量。服务端会拒绝向未开播房间送礼;扣款、礼物流水和钱包余额在同一事务内完成,余额不会小于 0。余额不足时返回 `required`、`balance` 和 `missing`,供客户端打开充值流程;成功后通过 `GiftMessage` 广播给房间内在线用户。
|
||
|
||
PC 只展示接口返回的礼物,不补造礼物 ID、价格或随机聊天。赠送按钮在请求期间防止重复提交。充值只有确认 `creditedAt` 并刷新余额后才视为到账;余额足够时继续原先选择的礼物一次,仍不足则显示差额并允许继续充值。关闭充值弹窗、离开房间、切换账号或关播会取消尚未执行的赠送意图,迟到的响应不会重新打开弹窗或自动扣款。已提交支付的真实到账结果仍以服务端订单和钱包为准。
|
||
|
||
全站设置继续保存在 `live_settings`。字典用于稳定的枚举和下拉选项,不适合承载站点名称、公告、功能开关、兑换比例等需要类型校验和运行时读取的配置。
|
||
|
||
直播状态和在线连接是进程内状态,行为与旧项目一致;服务重启后房间自动清空。
|
||
|
||
## PC 与后台
|
||
|
||
- PC 观众页:`/#/live/:id`。
|
||
- PC 主播页:`/#/studio/:id`,使用房间绑定的主播账号登录。提供设备预览、摄像头和麦克风选择、音视频开关、开始与停止直播。
|
||
- 后台保存房间后立即展示可复制链接,房间列表也可复制主播或观众链接。默认 PC 在域名根路径,后台位于 `/admin/`;独立域名或开发端口使用后台构建变量 `VITE_LIVE_CLIENT_URL`。
|
||
- 链接不含 token。后台不再保留观众送礼页面;后台直播诊断和管理详情使用管理接口,不受房间是否正在直播的限制。
|
||
- 摄像头需要 HTTPS 或 localhost;播放和推流失败时显示错误并释放失效会话,后续可重新连接。
|
||
- API 返回的相对媒体路径默认经 `/api` 加载,已带 `/api` 的路径不会重复拼接;绝对 CDN 地址保持不变。独立文件服务可设置 PC 构建变量 `VITE_FILE_BASE`。
|
||
|
||
`frontend/` 是独立 Git 仓库,主仓库中的 `web/` 是管理后台。发布本次修复时需要分别更新 PC、后台与 Go 服务,不能只发布其中一个。
|
||
|
||
## 本地验收
|
||
|
||
`tmp/live_acceptance/` 包含可复跑的隔离接口验收程序,固定访问 `127.0.0.1:18080`,仅操作 `.tmp-live-acceptance/acceptance.db`。测试配置、账号、模拟支付密钥和报告都保存在被忽略的 `.tmp-live-acceptance/` 下,不写入生产配置,也不删除已有业务数据库。
|
||
|
||
在隔离服务和本地支付模拟服务运行后执行:
|
||
|
||
```text
|
||
go run ./tmp/live_acceptance verify
|
||
```
|
||
|
||
验收覆盖后台管理、注册登录、权限、双用户实时聊天、60 秒以上连接保活、送礼事务、余额不足、充值订单归属、签名回调与重复回调,并直接核对 SQLite 钱包、礼物流水、充值记录和不持久化的聊天表。支付模拟仅用于本地验证,不代表实际商户渠道或公网跨网络 WebRTC 已验收。
|
||
|
||
## 网络配置
|
||
|
||
默认使用 Pion 主机候选。本机开发无需额外配置;后端运行在 Docker、Kubernetes 或 NAT 后方时,推荐使用一个固定 UDP 端口并声明浏览器可访问的公网或局域网 IP:
|
||
|
||
```text
|
||
KRA_LIVE_PUBLIC_IP=203.0.113.10
|
||
KRA_LIVE_UDP_PORT=9504
|
||
```
|
||
|
||
容器必须同时发布同一个 UDP 端口,例如 `-p 9504:9504/udp`,主机防火墙和云安全组也必须放行 UDP 9504。`deploy/deploy.sh` 已自动发布该端口(默认 9504,与 9501-9503 顺延配套),并默认从 `live.fuhua91.com` 解析公网 IPv4;也可以在执行脚本前显式设置 `KRA_LIVE_PUBLIC_IP`。
|
||
|
||
若基础设施更适合端口范围,也可以不设置 `KRA_LIVE_UDP_PORT`,改用:
|
||
|
||
```text
|
||
KRA_LIVE_UDP_MIN_PORT=40000
|
||
KRA_LIVE_UDP_MAX_PORT=49999
|
||
```
|
||
|
||
端口范围模式同样需要逐一发布和放行整个 UDP 范围。跨复杂 NAT、企业网络或移动网络时,可额外配置 `KRA_LIVE_STUN_URL=stun:stun.example.com:3478`;需要强制中继时应部署 TURN 服务。
|