7.5 KiB
组件实时协议
当前协议版本为 1。后端的权威定义在 domain.rs 的
ComponentMessage,浏览器渲染器只依赖本文列出的稳定字段。
连接地址与认证
组件流地址:
wss://danmaku.luoxingci.com/api/v1/components/<publicId>/stream
OBS 页面地址中的 token 位于 fragment:
https://danmaku.luoxingci.com/obs/<publicId>#token=<component-token>
fragment 不会进入首次 HTTP 请求或 Nginx access log。WebSocket 建立后,客户端必须在 8 秒内发送第一帧:
{
"type": "authenticate",
"token": "component-token"
}
服务端验证 token 摘要、组件归属和 events:subscribe scope。认证成功后返回:
{
"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 结束连接。
componentId/publicId 标识组件实例,而不是组件类型。同一账户可以创建多个相同 componentKind
的实例;它们使用各自的设置、token 和 WebSocket 地址,客户端不能把同 kind 视为同一个订阅通道。
language 是组件所属账户的 locale。用户在控制台修改语言后,所有已连接组件会立即收到
component.language.updated,payload 为 { "language": "en-US" };新连接以认证帧为准。
设置快照中的 themeId 是稳定的主题标识。弹幕姬与点歌姬支持 jade-scroll 和
moonlit-water;礼物特效与大航海特效分别支持 jade-starfall 和 moonlit-water;礼物菜单支持
jade-banquet 和
moonlit-water。消费者应把未知主题降级为自身默认主题,不能因主题发布顺序不同而中断实时消息。
版本化事件信封
{
"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 认证后按顺序接收:
song.queue.snapshot.begin:包含snapshotId、revision、当前歌曲和待唱总数。- 零到多个
song.queue.snapshot.page:每页最多 100 个待唱项。 song.queue.snapshot.end:提交该快照。
后续 song.queue.changed 携带连续 revision,operation 为 added、promoted、
completed、cancelled 或
cleared。客户端发现 revision 缺口必须重连取得新快照,不能猜测缺失队列状态。 cleared
表示当前歌曲与全部待唱项已在同一事务中取消,客户端必须立即清空活动投影。
礼物目录价格的原始单位是人民币的千分之一。gift.totalPrice 保留该整数单位,gift.priceCny
是供展示使用的人民币数值。
live.gift 与 live.gift.combo 不是两笔礼物。需要持久化计数的组件通常只消费
live.gift;连击事件用于更新同一张视觉卡片。
gift_effect 只订阅 live.gift,不会收到 live.guard.buy。礼物档位由 sanitized settings 中的
highValueThreshold 和 featuredValueThreshold 决定;浏览器使用 gift.imageUrl 或
gift.animationUrl
作为流星主体,图片失效时必须使用本地星光占位。该组件不维护状态快照,重连后只展示新到达的实时事件。
guard_effect 只订阅
live.guard.buy。舰长、提督和总督事件进入该组件独立的广播通道;它不接收普通礼物或礼物连击,也不维护状态快照。
gift_effect 与 guard_effect
的浏览器渲染器分别维护有界 FIFO,并且每次只播放一个 active 特效。动画完成后按到达顺序推进下一项;这个队列仅用于 OBS 视觉节流,不是可靠业务队列,也不会在重连后恢复。
gift_menu 同样只消费一次性 live.gift 与 live.guard.buy。命中配置后投影为
gift-menu.triggered,payload 包含 itemIds、viewer 和
sourceEventId。一个特定礼物和一个同价电池规则可以同时命中多个菜单项;客户端应全部高亮,并滚动到第一个匹配项。未命中的投喂不会进入该组件通道。
表情分段
live.danmaku.payload.segments 是判别联合:
text:包含可直接显示的文字。emoticon:包含回退文字、图片 URL、可选尺寸、动态标记和整条大表情标记。
图片失败时客户端必须回退到 text,不能让一张失效图片破坏整条弹幕。
背压与重连
- 每个组件使用有界
tokio::broadcastchannel。 - 慢客户端发生
Lagged时跳过已经丢失的旧帧,继续接收新事件;实时展示不保证历史重放。 - 浏览器对非鉴权关闭使用指数退避重连,上限 12 秒。
- 鉴权失败不会自动重试,避免对已撤销 token 形成无限请求。
- 需要可靠业务处理的功能必须实现
EventHandler并写入数据库,不能把 WebSocket 当作消息队列。