Files
lxc-streamutils/docs/protocol.md
T
felis f79852d8e6 add account localization and switchable rooms
Centralize control and OBS copy in a shared TOML catalog, persist the selected locale per account, and broadcast language changes to component streams. Allow account owners to atomically switch their Bilibili room and restart the shared listener without changing component URLs.
2026-07-18 23:28:05 -07:00

129 lines
5.8 KiB
Markdown
Raw 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.
# 组件实时协议
当前协议版本为 `1`。后端的权威定义在 `domain.rs` 的
`ComponentMessage`,浏览器渲染器只依赖本文列出的稳定字段。
## 连接地址与认证
组件流地址:
```text
wss://danmaku.luoxingci.com/api/v1/components/<publicId>/stream
```
OBS 页面地址中的 token 位于 fragment:
```text
https://danmaku.luoxingci.com/obs/<publicId>#token=<component-token>
```
fragment 不会进入首次 HTTP 请求或 Nginx access
log。WebSocket 建立后,客户端必须在 8 秒内发送第一帧:
```json
{
"type": "authenticate",
"token": "component-token"
}
```
服务端验证 token 摘要、组件归属和 `events:subscribe` scope。认证成功后返回:
```json
{
"version": 1,
"type": "authenticated",
"componentId": "08d31d19-3e4b-4c11-9cf7-9bd786a39465",
"componentKind": "song_request",
"language": "zh-CN"
}
```
之后立即发送通用的 `component.settings.snapshot`,再发送该 kind 的状态快照和实时事件。弹幕姬还会发送
`overlay.settings.snapshot` 兼容帧。token 无效、被轮换或属于其他组件时,服务端以 policy
close 结束连接。
`language` 是组件所属账户的 locale。用户在控制台修改语言后,所有已连接组件会立即收到
`component.language.updated`,payload 为 `{ "language": "en-US" }`;新连接以认证帧为准。
设置快照中的 `themeId` 是稳定的主题标识,目前支持
`jade-scroll`。消费者应把未知主题降级为自身默认主题,不能因主题发布顺序不同而中断实时消息。
## 版本化事件信封
```json
{
"version": 1,
"id": "d56c1a28-a6f1-4ad9-b02f-4c22dc4d3c27",
"componentId": "08d31d19-3e4b-4c11-9cf7-9bd786a39465",
"sourceId": "c8087bb0-0dde-40eb-a43d-a9a2574c2717",
"occurredAt": "2026-07-15T20:10:30.125Z",
"roomId": "123456",
"type": "live.danmaku",
"payload": {
"viewer": { "uid": "42", "name": "观众" },
"text": "晚上好",
"segments": [{ "type": "text", "text": "晚上好" }]
}
}
```
`ownerId` 永远不会序列化到浏览器。消费者应按 `version` 和 `type`
分派,并忽略不认识的 payload 字段,从而允许兼容地增加元数据。 `sourceId`
标识所属账户唯一的直播监听连接,不表示组件单独绑定了一个直播源;同一账户不同组件收到的实时事件具有相同的
`sourceId`。
## 事件类型
| `type` | 主要 payload | 说明 |
| ----------------------------- | --------------------------------------------- | ---------------------- |
| `component.settings.snapshot` | `settings` | 所有组件的设置快照 |
| `component.settings.updated` | `settings` | 所有组件的设置更新 |
| `component.language.updated` | `language` | 账户语言实时更新 |
| `overlay.settings.snapshot` | `settings` | 认证后当前设置快照 |
| `overlay.settings.updated` | `settings` | 控制台保存后的实时设置 |
| `live.danmaku` | `viewer`, `text`, `segments` | 普通文字和表情分段 |
| `live.enter` | `viewer` | 进房事件 |
| `live.gift` | `viewer`, `gift`, `quantity`, `sourceEventId` | 一次可记账的礼物事件 |
| `live.gift.combo` | `viewer`, `gift`, `quantity`, `comboId` | 同一连击的视觉更新 |
| `live.superchat` | `viewer`, `message`, `price`, `sourceEventId` | 醒目留言 |
| `live.guard.buy` | `viewer`, `guardName`, `quantity`, `price` | 舰长购买 |
| `live.like` | `viewer` | 点赞 |
| `live.share` | `viewer` | 分享 |
| `live.unknown` | `command`, `metadata` | 有界且已清理的未知事件 |
## 点歌姬状态
`song_request` 认证后按顺序接收:
1. `song.queue.snapshot.begin`:包含 `snapshotId`、`revision`、当前歌曲和待唱总数。
2. 零到多个 `song.queue.snapshot.page`:每页最多 100 个待唱项。
3. `song.queue.snapshot.end`:提交该快照。
后续 `song.queue.changed` 携带连续 revision,`operation` 为 `added`、`promoted`、
`completed`、`cancelled` 或
`rating-updated`。客户端发现 revision 缺口必须重连取得新快照,不能猜测缺失队列状态。
礼物目录价格的原始单位是人民币的千分之一。`gift.totalPrice` 保留该整数单位,`gift.priceCny`
是供展示使用的人民币数值。
`live.gift` 与 `live.gift.combo` 不是两笔礼物。需要持久化计数的组件通常只消费
`live.gift`;连击事件用于更新同一张视觉卡片。
## 表情分段
`live.danmaku.payload.segments` 是判别联合:
- `text`:包含可直接显示的文字。
- `emoticon`:包含回退文字、图片 URL、可选尺寸、动态标记和整条大表情标记。
图片失败时客户端必须回退到 `text`,不能让一张失效图片破坏整条弹幕。
## 背压与重连
- 每个组件使用有界 `tokio::broadcast` channel。
- 慢客户端发生 `Lagged` 时跳过已经丢失的旧帧,继续接收新事件;实时展示不保证历史重放。
- 浏览器对非鉴权关闭使用指数退避重连,上限 12 秒。
- 鉴权失败不会自动重试,避免对已撤销 token 形成无限请求。
- 需要可靠业务处理的功能必须实现 `EventHandler` 并写入数据库,不能把 WebSocket 当作消息队列。