# Danmuku 公共契约基线 PKG-DANMUKU-01;冻结提交 `b0cfbfe3a09a84a295a257e45e4577847588d1cb`。 实际输入及逐文件 SHA 见 [danmuku-release.json](danmuku-release.json),执行结果见 [danmuku-contract-validation.json](danmuku-contract-validation.json)。本次不改生产代码。 ## 发布依据与覆盖范围 2026-09-13 直接读取 [npm registry](https://registry.npmjs.org/artplayer-plugin-danmuku), HTTP 200,目录有 81 个稳定版,latest 为 5.3.0。冻结全部版本的发布时间、tarball、 integrity、gitHead;实际下载并校验全部 13 个 5.x 版,另加前两代末版 4.4.11、3.5.31, 共 15 个归档。其余 66 个版本只有目录证据,不能说其运行时已验证或全部兼容。 这是代表性历史覆盖,不是新最低支持版本,也不把旧 major 的产品行为自动带入候选。 当前包全部跟踪文件、12 个 SVG、三个 dist、声明、README、可运行 demo 和 VitePress 弹幕文档共 28 个输入冻结。最新归档中的 README、manifest、声明及三个 dist 与冻结 Git 文件逐字节相同;源码行为另通过冻结 Git 模块受控执行对照,不把可读源码等同于 历史打包过程已可重复构建。 | 归档版本 | registry gitHead 的插件版本 | 同提交核心版本 | 说明 | | --- | --- | --- | --- | | 3.5.31 | 3.5.31 | 3.5.31 | 历史 major 对照 | | 4.4.11 | 4.4.10 | 4.5.8 | manifest 版本不一致 | | 5.0.0 / 5.0.1 | 5.0.0 / 5.0.1 | 5.0.0 / 5.0.5 | 分别冻结 | | 5.1.0 / 5.1.1 | 5.1.0 / 5.1.0 | 5.1.2 / 5.1.5 | 5.1.1 版本不一致 | | 5.1.2 / 5.1.3 / 5.1.4 | 同归档版本 | 5.1.6 | 分别冻结 | | 5.1.5 / 5.1.6 | 5.1.4 / 5.1.6 | 5.2.2 | 5.1.5 版本不一致 | | 5.1.7 | 缺 gitHead | 未知 | 不推测来源提交 | | 5.1.8 | 5.1.7 | 5.2.4 | manifest 版本不一致 | | 5.2.0 | 5.1.9 | 5.3.0-beta.3 | 核心是提交关联预发布号 | | 5.3.0 | 5.2.1 | 5.3.1 | 不宣称核心 5.3.1 是 npm 稳定版 | 真实 manifest 是归档身份依据;提交关联不是最低核心支持声明。归档与关联 Git 路径 各成员的匹配/不匹配均在 JSON 中保存。registry 缺失的提交信息保持缺失。 未独立验证 npm 签名及发布者授权。5.1.7 归档的目录头没有尾随斜线;专属 verifier 用 tar 条目类型区分目录与普通文件,拒绝重复、越界和非普通文件,不改共享解包函数。 ## 分发与静态属性 | 版本 | 实际 CommonJS | manifest 分发入口 | 静态 icons | | --- | --- | --- | --- | | 3.5.31 | 直接函数 | main;未声明 types/module/legacy | 无 | | 4.4.11 | 对象.default | main/types | 无 | | 5.0.0–5.0.1 | 对象.default | main/types/legacy | 无 | | 5.1.0–5.1.6、5.1.8 | 对象.default | main/types/legacy;无 module/exports | 12 项 | | 5.1.7 | 对象.default | module 是 `.esm.js`;平铺 import/require/default exports | 12 项 | | 5.2.0 | 对象.default | `.mjs`、root/legacy 条件 exports;types 条件在后 | 12 项 | | 5.3.0 | 直接函数 | `.mjs`、root/legacy 条件 exports;types 条件在前 | 12 项 | main/legacy 共 28 个实际模块已在 Node VM 执行;三个 manifest ESM 的实际字节通过 data URL 动态 import 执行。此项只证明模块代码能求值,不等于在消费者目录通过 Node 包解析;真实安装、NodeNext/Node10、AMD/global/script、编辑器仍由 06/09 验证。 `artplayerPluginDanmuku.icons` 是可写、可配置、可枚举的对象值,顺序为: `$on/$off/$config/$style/$mode_0_off/$mode_0_on/$mode_1_off/$mode_1_on/` `$mode_2_off/$mode_2_on/$check_on/$check_off`。静态 icons 不是 getter;设置模板使用 模块内 SVG 字符串,没有读取工厂 icons 的后续改写。这一读码事实尚未验证热替换用法。 ## 最新版工厂与方法 `factory(option)(art)` 同步返回门面;字段按 `name/emit/load/config/hide/show/reset/` `mount/option/isHide/isStop` 顺序枚举,name 为 `artplayerPluginDanmuku`。 后三项是可枚举、可配置、没有 setter 的 getter,读取内部当前值。方法已 bind,可抽取调用。 工厂 option 没有默认对象;`{ danmuku: [] }` 是基准合法调用。初始化不等待异步载入完成。 | API | 最新真实行为 | 声明/兼容注意 | | --- | --- | --- | | `emit(danmu)` | async;验证并修改传入对象,filter 通过后同步入队;Promise 最终返回内部 Danmuku | 旧声明错误写同步 Result;不自动改旧根类型 | | `load()` | 读当前 option.danmuku,reset 后清空队列再逐条 emit | Promise 返回内部对象;不是门面 | | `load(target)` | 追加到现有队列,不自动改 option.danmuku | 包括数组、函数、同 realm Promise、XML URL | | `config(partial)` | 合并默认值、当前值与 partial,验证、钳位;变化比较用 JSON.stringify,单独更换函数会被忽略 | 旧类型要求完整 Option,实际 partial 有效 | | `hide/show` | 更新 isHide、层 opacity、option.visible,并发事件 | 返回内部对象;重复调用也发事件 | | `reset()` | 全队列回到 wait,回收已分配 DOM,再发 reset | 返回内部对象,不清空队列 | | `mount(target)` | selector 或元素;移动设置输入框并 reset 设置 UI | 返回 undefined;声明允许省略,但运行时无目标会抛错 | | `option` | 当前合并后的对象;config 可能更换其引用 | 声明可写,实际仅 getter | | `isHide/isStop` | 初始 false;show/hide 与 video start/stop 更新 | 不应解释为 video.paused 或 layer DOM 是否存在 | 内部 Danmuku 没有公开门面的 `name/mount`,但历史链式返回会将其暴露给调用方。 后续整理必须先评估返回身份,不能为了匹配旧错误声明静默返回门面。 `load(undefined)` 清空;其他显式 falsy 值通过 `target = argument || option.danmuku` 回退但不走清空分支;这只是读码事实,不把未声明参数纳入必须支持的接口。 ## 全部配置与弹幕项 | 配置 | 默认值/范围 | | --- | --- | | danmuku | `[]`;声明含数组、URL、返回 Promise 的函数、Promise;初始 Promise 被实际 validator 拒绝,load(Promise) 可用 | | speed / margin / opacity | `5`,钳位 1–10;`[10,'25%']`;`1`,钳位 0–1 | | color / mode / modes | `#FFFFFF`;`0`,钳位 0–2;`[0,1,2]`,显示模式由 dataset/CSS 控制 | | fontSize | `25`;数值或百分比,使用时钳位 12 到播放器高度 | | antiOverlap / synchronousPlayback | `true` / `false` | | mount / heatmap / width / points | controlsCenter / false / 512 / [] | | filter / beforeEmit / beforeVisible | 默认均返回 true;filter 同步,另外两个支持异步 | | visible / emitter | true / true | | maxLength / lockTime / theme | 200(1–1000)/ 5(1–60)/ dark | | OPACITY / FONT_SIZE / MARGIN / SPEED / COLOR | `{}` / `{}` / `{}` / `{}` / `[]`,设置组件再补默认滑块与颜色 | Danmu 声明成员为 `text/mode/color/time/border/style`;runtime 还验证可选 string `id`, 并传播额外字段。mode 只入队 0/1/2,空白 text 与 filter 拒绝项忽略。time 不存在或为 0 时写为 currentTime + 0.5;负数钳到 0。入队时额外生成 `$state/$index/$ref/$restTime/` `$lastStartTime`,loaded/visible 事件会暴露这些字段和对象引用。 直接 emit/load 调用 filter,以当前 option 为 this,不经过 beforeEmit。设置 UI 的 发送按钮/Enter 先 trim 输入,防锁定/重复提交,await beforeEmit;只有严格 true 继续, 加 border、删除 time、再调用 emit,清输入并锁定。beforeVisible 在准备显示前调用, 也以 option 为 this。三者不能合并成一个全局异步过滤器。 最新声明有六个命名类型 `Mode/Danmuku/Slider/Danmu/Option/Result`。 4.4.11/5.0.x 旧声明中 Danmuku 表示结果,而 5.1.x 起表示输入,旧配置还出现 useWorker/minWidth/maxWidth;不能将较早声明与最新声明拼成未经评估的根类型。 06 按已批准的 [统一类型规则](../type-compatibility-policy.md) 处理:最新 npm 根声明的 合法完整工厂赋值、Parameters/ReturnType、NodeNext 历史模块形态都需安装消费者证据; 准确异步类型可用独立 runtime 入口,较早冲突写迁移说明。本次没有改类型或声称编译矩阵通过。 ## 事件、DOM、设置与热力图 | 事件 | 参数/时机 | | --- | --- | | artplayerPluginDanmuku:config | 当前 option 引用;show/hide 在它之前发生 | | artplayerPluginDanmuku:loaded | 完成当前 load 后的实际 queue 引用;空数组初始化可在工厂返回前同步发生 | | artplayerPluginDanmuku:error | load 捕获的原错误,然后再次抛出;直接 emit 不自动发这个事件 | | artplayerPluginDanmuku:visible | worker 返回位置且允许显示后,实际内部 danmu | | artplayerPluginDanmuku:start / stop | 无参数,video:play/playing / pause/waiting 对应触发 | | artplayerPluginDanmuku:show / hide / reset | 无参数,对应方法更新状态之后 | | artplayerPluginDanmuku:destroy | stop、worker terminate 和解除部分监听之后 | | artplayerPluginDanmuku:points | 消费方发入的热力图数据事件,插件不主动 emit | 这些是十个输出事件和一个输入事件;不新增虚构的 `:emit` 事件。 [] 初始化的已复现顺序为 show → config → reset → loaded;非空/异步输入顺序和错误 重入仍需 02 扩展。Setting 还订阅 resize/fullscreen/fullscreenWeb/document:pointermove/ document:pointerup/show/hide。热力图订阅 ready/resize/loaded/timeupdate/setBar/points。 设置入口是 `new Setting` 创建的独立 `.artplayer-plugin-danmuku` 输入框和弹出面板, 不是核心 setting registry 的单个项目。内部使用 `.apd-*` 类、mode/color/state/id 数据, 外部挂载可在 fullscreen/fullscreenWeb 时移回控制栏;默认挂载在宽度低于 width 时移到 播放器底部。默认不透明度 0–100、字号 12–120;MARGIN 四档 `[10,'75%']/[10,'50%']/` `[10,'25%']/[10,10]`;SPEED 五档 10/7.5/5/2.5/1。滑块读取 step.hide,而声明写 show; MARGIN step.value 是数组,声明只有 number|string。 设置 style 在模块求值时插入 id=`artplayer-plugin-danmuku` 的全局 style;完整类名、 选择器、SVG 和样式源码已整体哈希冻结。视觉、CSS 覆盖、重复 style 与外部节点回收 未在本次 mock 中模拟,交 02/05/08 的真实页面验证。 heatmap=true/object 创建 name=`heatmap`、position=`top` 的控件。源码按 `[x,y]` 二元组 使用 points 事件数据;`option.points` 当前只存储,未被 heatmap update 消费,声明却为 `{time,value}[]`。初始化开启 heatmap 后 config 开关不会自动新增/移除控件。上面两项为 读码事实,尚未浏览器复现。窄尺寸下默认 sampling=floor(width/100) 可为 0;需 02 用 独立超时保护测试确认循环风险,不在 UI 主线程盲跑。 ## Bilibili XML 与 worker string 输入调用 fetch(url) → response.text(),没有显式 HTTP 状态判断。XML 用正则读 `...`,要求至少八个逗号分隔字段;模式 1/2/3→0、4→2、5→1,其他→0; trim 后按 quot/apos/lt/gt/amp 顺序解实体。输出 text/time/mode/fontSize/color/timestamp/ pool/userID/rowID;color 用十六进制字符串,不补齐六位,数字异常也不额外过滤。 优先生成 Blob worker,message 含 `{xml,id}`,结果 `{danmus,id}`,成功后 terminate; Worker 构建同步失败时 console.error 并退回相同 parser。正则和 fallback 已执行;真实 网络、Worker 消息和 CSP/Blob 能力未验证。源码用 async Promise executor,fetch/text 拒绝发生在 try 之外,且未 revokeObjectURL;空 XML/worker error/销毁竞态是待 02 复现项。 调度 worker 接口 `{type:'getDanmuTop',id,target,visibles,antiOverlap,clientWidth,clientHeight,marginBottom,marginTop}` → `{result,id}`,当前 Date.now 作为 id 并覆盖 onmessage;并发/旧回复仍待测试。 ## 已复现差异与后续责任 | 编号 | 证据级别 | 内容 | 后续任务 | | --- | --- | --- | --- | | DANMUKU-TYPE-01 | 运行时复现 + 逐字声明 | emit Promise、内部返回身份、getter、mount 省略、初始 Promise 和声明不一致 | 03/06 | | DANMUKU-INPUT-01 | 冻结源码和真实 5.3 main/legacy 复现 | time:0 被改为 current+0.5;输入对象被修改 | 02/03,分别评估修正和保留 | | DANMUKU-CONFIG-01 | 同上 | 单独 config({filter:新函数}) 被 JSON.stringify 比较判为未变化 | 02/03 | | DANMUKU-CLEANUP-01 | 同上 | resize 注册 this.resize,销毁 off(this.reset);设置锁定 timer 未清 | 02/05 | | DANMUKU-TYPE-02 | 读码/声明观察 | id/icons 缺失、Slider value/hide、points 类型与使用漂移、config partial | 05/06 | | DANMUKU-ASYNC-01 | 待复现 | Bilibili fetch/text/worker 错误、load 乱序、beforeVisible/worker 晚回调、重复 start | 02/03/04/05 | | DANMUKU-HEATMAP-01 | 待复现 | 小于 100px 时 sampling=0、事件 tuple/option.points、重复实例 SVG id | 02/05 | | DANMUKU-SOURCE-01 | 归档与 Git 核验 | gitHead 版本错位、5.1.7 无 gitHead、12 SVG 无单独来源授权证据 | 09 | 历史缺陷断言仅冻结观察,不要求候选保留。修复必须有独立候选正常断言与明确迁移影响。 包 README 只有简介/demo/license,主要公开说明来自冻结 VitePress 文档和 demo。 demo 使用本地 video.mp4/danmuku.xml,适合作为后续 8082/Monaco/Run 的本地输入。 本次不证明实际播放、密集弹幕碰撞/时钟、mask/全屏/PiP、真实 WebKit/设备或 npm 发布可行。 ## 重放 在固定 Node 24.21.0 / Yarn 1.22.22 与已安装根依赖下运行: ```text node refactor/scripts/danmuku-contract.mjs node --test refactor/scripts/danmuku-contract.test.mjs node node_modules/eslint/bin/eslint.js refactor/scripts/danmuku-contract.mjs refactor/scripts/danmuku-contract.test.mjs ``` 缺缓存时 verifier 仅从冻结 npm URL 下载并验证 SHA512/SHA256;不执行 registry 包脚本。 完整目录可再次读取 `https://registry.npmjs.org/artplayer-plugin-danmuku`,但返回的新目录 不应覆盖本次不可变基线。原始 registry body 的 SHA 保存在 JSON,原始响应在专属 cache。 测试使用真实发布核心 5.4.0 的 validator、受控 DOM/计时器/Worker,仅源码编译在内存中 完成,不生成或修改 dist/docs。仅访问 npm 元数据及归档,未请求媒体或 Bilibili 资源。