# `danmaku_overlay` 弹幕姬 弹幕姬把一个租户直播源的互动事件投影为透明 OBS 消息墙。它是被动展示组件:不记账、不回复弹幕,也不把 WebSocket 当作持久化业务通道。账户注册时会创建一个初始实例;同一账户可以继续添加多个弹幕姬实例,为横屏、竖屏或其他 OBS 场景分别保存样式和 OBS 地址。所有实例共享账户直播监听,但事件投影和 WebSocket 广播仍按实例 ID 隔离。 ## 订阅事件 组件根据设置动态订阅: - 弹幕、进房、礼物与礼物连击、醒目留言、舰长、点赞、分享。 - 关闭类别后,后端不为该组件投影对应事件,前端也会进行一次兼容性过滤。 - 未归一化的 `live.unknown` 默认不进入弹幕姬。 ## 设置 | 字段 | 范围/单位 | 行为 | | ------------------------ | ----------- | ------------------------------------------ | | `themeId` | 主题 ID | 选择已安装的完整视觉主题 | | `fontFamily` | 字体 ID | `song`、`fang-song`、`kai`、`liyu-shoushu` | | `fontBrightness` | 70–180% | 只调整文字亮度 | | `viewerColor` | `#RRGGBB` | 可选的用户昵称颜色覆盖 | | `danmakuColor` | `#RRGGBB` | 可选的弹幕正文颜色覆盖 | | `fontScale` | 50–300% | 展开与收缩字号的统一比例 | | `decorationLineWeight` | 50–300% | 边框、分隔线与古风边缘粗细 | | `maxVisible` | 1–12 | 同时保留的消息卡数量 | | `expandNewDanmaku` | boolean | 新弹幕是否先展开并在超时后收缩 | | `collapseAfterSeconds` | 2–120 秒 | 最新卡从展开态切换到紧凑态 | | `unfoldDurationMs` | 200–5000 ms | 横向卷轴展开动画时间 | | `motionIntensity` | 0–100% | 卡片、流光与焦点动画强度 | | `particleCount` | 0–12 | 每卡星花粒子数量 | | `particleSpeed` | 25–300% | 粒子动画速度 | | `lowPerformanceMode` | boolean | 关闭高成本动态效果 | | `highValueThreshold` | 千分之一元 | 高价值礼物起点 | | `featuredValueThreshold` | 千分之一元 | 焦点礼物起点 | | `show*` | boolean | 控制各事件类别订阅 | 后端的 `OverlaySettings::sanitize` 是最终边界。控制台 range input 只改善交互,不能取代服务端校验。字体 ID 只映射到前端预先注册并由服务同域提供的 WOFF2 字体,不接受任意 CSS,也不依赖 OBS 设备安装的字体;`song`、`fang-song`、`kai` 与 `liyu-shoushu` 分别使用 Noto Serif SC、ZCOOL XiaoWei、LXGW WenKai 与漓雨手书。颜色覆盖同样只接受六位十六进制颜色;留空时使用主题色,切换主题后会自动采用新主题的配色。 ## 主题 主题同时封装配色、花纹素材、装饰变体、粒子组合与动画名称。当前内置主题为 `jade-scroll`(“青玉花卷”)与 `moonlit-water`(“静夜曲水”)。后者不绘制卡片底色、外框或消息分隔线,只保留青黛、米金衬线文字、少量花星微光以及组件上下共享的透明古风细边;控制台仍以选择框呈现,新增主题后无需改变设置交互或卡片队列。 增加主题时需要同步完成三处注册: 1. 在后端 `OverlayThemeId` 中增加稳定 ID,使未知主题无法进入数据库。 2. 在 `apps/overlay/src/themes/` 增加主题定义,并注册到 `overlayThemes`。 3. 提供主题使用的 CSS 动画和素材;不要在 `Card` 中加入按主题 ID 分支。 旧组件没有 `themeId` 时会自动使用 `jade-scroll`。WebSocket 收到未知 ID 时前端也会安全降级到该主题,以支持前后端滚动部署。 ## 卡片生命周期 1. 新事件追加在可视区域底部;启用 `expandNewDanmaku` 时,普通弹幕以卷轴动画横向展开,否则直接使用紧凑态。旧事件被向上顶出并裁切。 2. 用户名与内容在展开态分行显示,长内容完整换行。 3. 新事件到达或超时后,旧卡变成紧凑态;内容不会隐藏。 4. 紧凑态缩小字号并尽量压缩布局,但仍允许换行避免截断。 5. 超过 `maxVisible` 的最旧卡才会离开队列。 花纹由事件类型和事件 ID 的稳定 hash 选择。相邻卡会避开完全相同的款式;礼物连击更新沿用原卡片 key 和装饰,避免视觉跳动。每条普通弹幕右端还会显示主题着色的内置透明莲花图案;渲染时预留文字空间,不会遮挡昵称、正文或表情。 ## 礼物展示 - `live.gift` 创建礼物卡;`live.gift.combo` 使用 `comboId` 更新同一张卡。 - 元数据优先使用礼物目录中的静态图、GIF、币种和价格。 - GIF 加载失败时降级为静态图片;静态图失败时隐藏图片但保留名称、数量和用户。 - 普通、高价值和焦点礼物按 `totalPrice` 与两个阈值分级。 - `priceCny` 只用于人类可读展示;整数 `totalPrice` 用于精确分级。 ## 弹幕表情 文字和表情按 `segments` 顺序混排。表情 URL 来自消息本身或用户级表情目录补全;图片使用 `no-referrer`,失败后显示原始表情文字。整条大表情可通过 `standalone` 使用更合适的尺寸。 ## OBS 自适应 - 根背景完全透明,不存在 1920×1080 固定画布。 - `ResizeObserver` 根据浏览器源实际宽高选择 `narrow`、`short` 或 `standard`。 - 字号和间距使用 CSS custom properties 与 `clamp()`,OBS 自由缩放时不拉伸素材比例。 - 建议从 `360×600`、`440×760`、`600×1080` 或 `720×320` 开始测试。 - 低高度会减少视觉密度,但不会把仍在队列中的文字裁成省略号。 ## 测试页面 控制台的事件测试直接构造 canonical `LiveEvent`,使用当前账户、source 和 component 走同一套订阅、handler、projection 与 EventHub。测试事件标记为 `simulated=true`,不会发送到 Bilibili,也不会跨组件广播。