# Go WebRTC Live 直播功能使用 Pion WebRTC,在现有 GVA WebSocket 和 JWT 用户体系上实现单主播、多观众的 SFU 转发。 ## WebSocket 连接现有 WebSocket 地址(后端默认 `/ws`,生产反向代理地址为 `/api/ws`)。观看直播允许匿名握手;PC 登录后的 HttpOnly Cookie 会随同源握手自动发送,分享链接中不携带凭证。非浏览器客户端也可携带 GVA JWT: ```text wss://host/ws?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 服务。