test(danmuku-mask): [PKG-MASK-01] freeze published contracts and model provenance

This commit is contained in:
Harvey Zhao committed 2026-09-13 17:36:11 +08:00
1 parent 8ec9fbe83a
commit 7d44bce06f
13 files changed
+1138 -13

No files matched your search

@@ -0,0 +1,56 @@
{
"task": "PKG-MASK-01",
"sourceCommit": "b0cfbfe3a09a84a295a257e45e4577847588d1cb",
"node": "v24.21.0",
"releases": [
{
"version": "1.1.0",
"sha256": "96682e46cb140a84b164818dd118480d7be42b24afaab390027a25e52e8ae31c",
"integrity": "sha512-VBmmwtNLj0TKYz9lWbyQ7A8osQHdyDiPMazP9g1UZbLyw/rMJ3qRclMgszRpZHN9hod0dQl2iYSJDoaqu3hGOQ==",
"members": 6,
"registryGitHead": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
"sourceManifest": "1.0.1",
"coreAssociation": "5.3.1"
},
{
"version": "1.0.0",
"sha256": "8a88f52ae843bed1be01a2e60b82cf886d33582f990a5016d49b1409c3493c4b",
"integrity": "sha512-x5Kd6nfPYyXVUoy4xNq7EQ8PeVGOiWVpIT75RVOrGLMjFrBKvAYp0btcjSu9ZZN7aAkB9MtAyFcTB6hbkOX8uw==",
"members": 6,
"registryGitHead": "b36f88db5f7fe0250285fd4f47229842231f402b",
"sourceManifest": "1.0.0",
"coreAssociation": "5.3.1-beta.1"
}
],
"checks": {
"archiveVerifier": "passed",
"nodeTests": {
"passed": 6,
"failed": 0,
"skipped": 0
},
"actualExportProbes": 8,
"lint": "passed",
"diffCheck": "passed"
},
"evidence": [
{
"file": "refactor/.cache/danmuku-mask-contract-verify.log",
"sha256": "c36b2313c5d2fbabf6f7e6a4285ac905870f9764663a85df87693693e9648f73"
},
{
"file": "refactor/.cache/danmuku-mask-contract-tests.log",
"sha256": "a7dc7994625711f0256b16663c49406d734e0c4b12c7cfa689468be5deaf41cd"
}
],
"riskIds": [
"MASK-LIFETIME-01",
"MASK-START-01",
"MASK-MODEL-01",
"MASK-BACKEND-01",
"MASK-DOM-01",
"SDK-08",
"MASK-NOTICE-01"
],
"scope": "Published archive export probes without model startup; owned source uses controlled SDK stubs; installed adapter read-only and existing model assets hashed. No model download or inference, real browser or physical device, installation matrix or release acceptance."
}
+125
View File
@@ -0,0 +1,125 @@
# Danmuku Mask 历史契约与 SDK-08 证据
任务 PKG-MASK-01。依据 [真实归档](danmuku-mask-release.json) 与
[原始 npm 响应](danmuku-mask-registry.json),捕获时全部稳定版为 1.0.0、1.1.0。
冻结工作区提交为 `b0cfbfe3a09a84a295a257e45e4577847588d1cb`。
源码文本以 LF 归一化;tarball、成员、已有模型资源均按原始字节计算哈希。
## 发布来源与公开形状
两版各有六个实际归档成员:README、manifest、main/legacy/module 三种产物和声明。
四个 manifest 入口以及 root/legacy exports 目标全部存在。源码由 `.npmignore`
排除;不能声称 npm 包含源码。1.1.0 六个成员与冻结工作区对应文件完全一致。
| 项目 | npm 1.0.0 | npm 1.1.0 |
| --- | --- | --- |
| registry gitHead | `b36f88db5f7fe0250285fd4f47229842231f402b` | `daf133b22630b4a0eecfa3336bbddab0e9119d96` |
| gitHead 的插件 manifest | 1.0.0 | **1.0.1**,与发布版本不一致 |
| 同提交核心 manifest | 5.3.1-beta.1 | 5.3.1 |
| 实际 CommonJS main/legacy | 对象的 `.default` 工厂 | 直接工厂,无 `.default` |
| 实际 script main/legacy | 全局 `artplayerPluginDanmukuMask` 工厂 | 同名工厂 |
| module | `.mjs` 中 default export | `.mjs` 中 default export |
| 声明 | optional option、同步注册结果 | 与 1.0.0 字节一致 |
核心版本仅是 Git 关联,不是已经通过的兼容矩阵。main/legacy 的 CommonJS 与 script
导出已用实际归档字节在隔离 VM 验证,包括不启动模型的注册/stop;module 这里只做
成员与文本形状核实,尚未完成真实安装 ESM/NodeNext 消费。较早来源源码的额外
`window.artplayerPluginDanmukuMask = ...` 已在冻结工作区移除;归档全局仍保留。
公开工厂 `artplayerPluginDanmukuMask(option?)` 同步返回 registrar;registrar 同步
返回 `{ name: 'artplayerPluginDanmukuMask', start, stop }`。`start(): Promise<void>`,
`stop(): void`,两版声明均如此。Option/Result 未作为命名导出发布。后续类型工作应
保持最新完整工厂赋值和 NodeNext 旧模块形状,遵守统一类型政策;旧 CJS `.default`
调用是实际合法 JS,不能因为最新声明没有属性而删掉兼容路径。
## 源码参数与样式
参数在 registrar 调用时快照为内部 config,之后修改原 option 不会重读。默认值:
| 字段 | 默认值 / 合并规则 |
| --- | --- |
| solutionPath | `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation`,`||` |
| modelSelection | 1,`||`,传 0 仍为 1 |
| smoothSegmentation | true,仅 undefined 使用默认,false 保留 |
| minDetectionConfidence / minTrackingConfidence | 各 0.5,`||` |
| selfieMode / drawContour | 各 false,`||` |
| foregroundThreshold | 0.5,`||` |
| opacity | 1,`||` |
| maskBlurAmount | 3,`||` |
这些是**插件传给适配器的值**,不是所有参数都对模型有效。源码始终指定
`runtime: 'mediapipe'`、`modelType: 'general'`;先尝试 `tf.setBackend('webgl')`,
仅 rejection 时尝试 `cpu`。这不能证明 MediaPipe 自身推理切换成 CPU,也不能把
`setBackend` fulfilled false 视为已执行 fallback。
registrar 捕获 `art.template.$video/$danmuku`,订阅 ready→start、destroy→stop。
必须在 Danmuku 插件建立 `$danmuku` 后注册;demo 正是先 Danmuku 再 Mask。
start 建立 2D canvas 并设置 `$danmuku.style`:maskMode=alpha、maskSize=contain、
maskRepeat=no-repeat、backgroundSize=contain、backgroundRepeat=no-repeat。
每轮以视频固有尺寸推理,白色前景/黑色背景生成 binary mask,drawMask 后将 RGB
三个分量均 **>250** 的像素 alpha 置 0,以 canvas PNG data URL 写入 maskImage。
值等于 250 的边界不透明。stop 只写 maskImage=none 并取消所记住的一次 RAF;
其余样式不恢复。没有额外 public 事件或插件自建可见 DOM。
## SDK-08:锁文件、适配器和模型资源是不同来源
| 依赖 | manifest 范围 | 冻结 Yarn / 当前安装 |
| --- | --- | --- |
| @mediapipe/selfie_segmentation | ^0.1.1675465747 | 0.1.1675465747 |
| @tensorflow-models/body-segmentation | ^1.0.2 | 1.0.2 |
| @tensorflow/tfjs-backend-cpu | ^4.21.0 | 4.22.0 |
| @tensorflow/tfjs-backend-webgl | ^4.21.0 | 4.22.0 |
| @tensorflow/tfjs-converter | ^4.21.0 | 4.22.0 |
| @tensorflow/tfjs-core | ^4.21.0 | 4.22.0 |
范围、resolved、integrity、已安装 manifest 哈希、适配器关键文件哈希均在 JSON。
它们约束本仓库安装,**不约束消费者重新解析的依赖,也不约束默认 CDN URL**。
没有查询 CDN latest、下载远程模型或从 bundle 中猜出完整历史 TensorFlow 版本。
归档中出现 MediaPipe `VERSION` 字符串不等于整套 SDK/模型链已经认证。
body-segmentation 1.0.2 的
`dist/selfie_segmentation_mediapipe/segmenter.js` 已按已安装字节冻结。它以
`config.modelType` 映射 `general → modelSelection=0`、`landscape → 1`;不读取
插件额外传入的 `config.modelSelection/smoothSegmentation/minDetectionConfidence/
minTrackingConfidence/selfieMode`。构造时 selfieMode=false;segmentPeople 的
flipHorizontal 参数另有处理,而插件未传这个参数。后续不能直接把模型参数接通
当作等价重构;需先记录效果变化及兼容决策。
该 adapter 的 locateFile 去掉 solutionPath 尾部斜线再拼资源文件名。已安装
MediaPipe JS 选择 SIMD/普通 wasm loader、graph binarypb、general/landscape tflite,
loader 再加载 WASM 等资源。插件默认目录未带 `@version`,Yarn 固定 JS 依赖不能
固定这条额外下载链;自定义 solutionPath 必须继续保留。
demo 指向 `/assets/@mediapipe/selfie_segmentation`,注释要求从 node_modules 拷贝。
仓库已存在 **12 个文件**,package.json 为 0.1.1675465747;冻结 Git 中全部文件与
当前 node_modules 同名文件字节一致,包括两个 tflite、两个 wasm、loader JS、
binarypb、data、README/声明/manifest。未新增或执行这些模型;这是本地字节一致性,
不是对其获取链的独立真实性认证,也不是模型输出、离线网络闭包或 GPU 释放验证。
## 有依据的后续风险
| ID | 已确认事实与最小触发路径 | 归属 / 证据等级 |
| --- | --- | --- |
| MASK-LIFETIME-01 | start 等待初始化或一次推理期间 stop/destroy 不使异步工作失效;后续 continuation 仍能写 mask 并再排 RAF。没有 segmenter.dispose 或事件 off | 02 复现、03 修复;源码路径确认,尚非原生模型复现 |
| MASK-START-01 | start 无 pending/running 守卫;并发启动可重复初始化,已初始化的多次 start 可建立多条 RAF 链,只有一个 animationFrameId | 02/03;源码路径确认 |
| MASK-MODEL-01 | 插件声明/传参和已安装 adapter 实际消费不同,general 始终选择 0;零值受历史 OR 默认影响 | 02/04/05;适配器文本确认,默认/零值有受控源码测试 |
| MASK-BACKEND-01 | WebGL 仅 rejection 时 fallback;MediaPipe runtime 与 TF backend 不等同;createSegmenter rejection 被吞掉后仍能进入等待 RAF | 02/03/05;源码路径确认,真实降级未验证 |
| MASK-DOM-01 | 缺少 Danmuku、2D context 或视频可读帧时无完整前置保护;CORS getImageData 失败被 catch 后下一轮继续 | 02/03/05;需受控失败及真实浏览器复现 |
| SDK-08 | 默认无版本 CDN 与 Yarn 安装分离;本地资源一致不证明真实模型、设备、跨源、断网与资源释放 | 05/06;保持开放 |
| MASK-NOTICE-01 | 当前 SDK manifest 标 Apache-2.0;两版 plugin 归档只有六个文件,无独立第三方通知,抽查 module bundle 无 Apache/Google copyright 字面标记,顶部只有项目 MIT | 06;需核查完整嵌入来源和通知义务,不作已获授权/合规结论 |
## 重放与边界
使用固定 Node/Yarn 环境:
```sh
node refactor/scripts/danmuku-mask-contract.mjs
node --test refactor/scripts/danmuku-mask-contract.test.mjs
```
verifier 校验缓存 tarball SHA512/SHA256、全部成员/入口、原始 registry 快照、Git
来源关系、冻结 Yarn 解析与已安装 SDK/已有资产字节。缓存缺失时只从固定 npm
tarball 地址恢复插件发布包。测试六项:归档关系、两版声明、八个真实 main/legacy
CJS/script 导出探针、受控自有源码默认/零值/像素边界、SDK-08 证据。
导出探针执行 bundle 顶层注册但不启动模型;源码行为探针用 TF/MediaPipe 替身。
没有远程模型请求、真实推理、设备、浏览器播放、npm 安装矩阵或发布验证。
@@ -0,0 +1 @@
{"_id":"artplayer-plugin-danmuku-mask","_rev":"2-2b1cc3cc510e405aed54bd6ffbfeabb9","name":"artplayer-plugin-danmuku-mask","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"artplayer-plugin-danmuku-mask","version":"1.0.0","keywords":["html5","video","player"],"author":{"name":"Harvey Zack","email":"laozhaochaguan@gmail.com"},"license":"MIT","_id":"artplayer-plugin-danmuku-mask@1.0.0","maintainers":[{"name":"zhw2590582","email":"laozhaochaguan@gmail.com"}],"homepage":"https://artplayer.org","bugs":{"url":"https://github.com/zhw2590582/ArtPlayer/issues"},"dist":{"shasum":"b160cf47826f96556d22c7e41f60a7b5e4a878f0","tarball":"https://registry.npmjs.org/artplayer-plugin-danmuku-mask/-/artplayer-plugin-danmuku-mask-1.0.0.tgz","fileCount":6,"integrity":"sha512-x5Kd6nfPYyXVUoy4xNq7EQ8PeVGOiWVpIT75RVOrGLMjFrBKvAYp0btcjSu9ZZN7aAkB9MtAyFcTB6hbkOX8uw==","signatures":[{"sig":"MEQCIEwctUwtJoQITzQ2SGSDh7RVd9s2t/l4YB8DidHz+IFkAiBYw63HOgX0At7/Jq+uM1iQRMibtLKYwlV2k9RmIFYirA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":4285370},"main":"./dist/artplayer-plugin-danmuku-mask.js","types":"./types/artplayer-plugin-danmuku-mask.d.ts","legacy":"./dist/artplayer-plugin-danmuku-mask.legacy.js","module":"./dist/artplayer-plugin-danmuku-mask.mjs","exports":{".":{"types":"./types/artplayer-plugin-danmuku-mask.d.ts","import":"./dist/artplayer-plugin-danmuku-mask.mjs","require":"./dist/artplayer-plugin-danmuku-mask.js"},"./legacy":{"types":"./types/artplayer-plugin-danmuku-mask.d.ts","import":"./dist/artplayer-plugin-danmuku-mask.legacy.js","require":"./dist/artplayer-plugin-danmuku-mask.legacy.js"}},"gitHead":"b36f88db5f7fe0250285fd4f47229842231f402b","_npmUser":{"name":"zhw2590582","email":"laozhaochaguan@gmail.com"},"repository":{"url":"git+https://github.com/zhw2590582/ArtPlayer.git","type":"git"},"_npmVersion":"10.9.3","description":"Danmuku mask plugin for ArtPlayer","directories":{},"_nodeVersion":"22.18.0","browserslist":"last 1 Chrome version","dependencies":{"@tensorflow/tfjs-core":"^4.21.0","@tensorflow/tfjs-converter":"^4.21.0","@tensorflow/tfjs-backend-cpu":"^4.21.0","@mediapipe/selfie_segmentation":"^0.1.1675465747","@tensorflow/tfjs-backend-webgl":"^4.21.0","@tensorflow-models/body-segmentation":"^1.0.2"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/artplayer-plugin-danmuku-mask_1.0.0_1767516539621_0.37797476956956433","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"artplayer-plugin-danmuku-mask","version":"1.1.0","description":"Danmuku mask plugin for ArtPlayer","author":{"name":"Harvey Zhao","email":"laozhaochaguan@gmail.com"},"license":"MIT","homepage":"https://artplayer.org","repository":{"type":"git","url":"git+https://github.com/zhw2590582/ArtPlayer.git"},"bugs":{"url":"https://github.com/zhw2590582/ArtPlayer/issues"},"keywords":["html5","video","player"],"exports":{".":{"types":"./types/artplayer-plugin-danmuku-mask.d.ts","import":"./dist/artplayer-plugin-danmuku-mask.mjs","require":"./dist/artplayer-plugin-danmuku-mask.js"},"./legacy":{"types":"./types/artplayer-plugin-danmuku-mask.d.ts","import":"./dist/artplayer-plugin-danmuku-mask.legacy.js","require":"./dist/artplayer-plugin-danmuku-mask.legacy.js"}},"main":"./dist/artplayer-plugin-danmuku-mask.js","module":"./dist/artplayer-plugin-danmuku-mask.mjs","types":"./types/artplayer-plugin-danmuku-mask.d.ts","legacy":"./dist/artplayer-plugin-danmuku-mask.legacy.js","browserslist":"last 1 Chrome version","dependencies":{"@mediapipe/selfie_segmentation":"^0.1.1675465747","@tensorflow-models/body-segmentation":"^1.0.2","@tensorflow/tfjs-backend-cpu":"^4.21.0","@tensorflow/tfjs-backend-webgl":"^4.21.0","@tensorflow/tfjs-converter":"^4.21.0","@tensorflow/tfjs-core":"^4.21.0"},"gitHead":"daf133b22630b4a0eecfa3336bbddab0e9119d96","_id":"artplayer-plugin-danmuku-mask@1.1.0","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-VBmmwtNLj0TKYz9lWbyQ7A8osQHdyDiPMazP9g1UZbLyw/rMJ3qRclMgszRpZHN9hod0dQl2iYSJDoaqu3hGOQ==","shasum":"9b1d1854fa3a2c45ba34e92c57bd0b5ec519cfa7","tarball":"https://registry.npmjs.org/artplayer-plugin-danmuku-mask/-/artplayer-plugin-danmuku-mask-1.1.0.tgz","fileCount":6,"unpackedSize":3542013,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFvUHXO+0eQ+bKsYVjrDkahzjWuVjiOyFMeFIHaGTT+OAiBkRcdVV1UvzIbNE/8g2kmpFOl8ESRfNUxreScPcnHwMA=="}]},"_npmUser":{"name":"zhw2590582","email":"laozhaochaguan@gmail.com"},"directories":{},"maintainers":[{"name":"zhw2590582","email":"laozhaochaguan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/artplayer-plugin-danmuku-mask_1.1.0_1773325276855_0.6942467109165913"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-04T08:48:59.620Z","modified":"2026-03-12T14:21:17.145Z","1.0.0":"2026-01-04T08:48:59.809Z","1.1.0":"2026-03-12T14:21:17.027Z"},"bugs":{"url":"https://github.com/zhw2590582/ArtPlayer/issues"},"author":{"name":"Harvey Zhao","Line truncated
@@ -0,0 +1,504 @@
{
"schemaVersion": 1,
"registry": "https://registry.npmjs.org/artplayer-plugin-danmuku-mask",
"capturedAt": "2026-09-13T09:18:03.395Z",
"sourceCommit": "b0cfbfe3a09a84a295a257e45e4577847588d1cb",
"sourceNormalization": "Git source text normalized to LF; archive and binary asset members hashed as bytes.",
"source": {
"packages/artplayer-plugin-danmuku-mask/.npmignore": "46063058e98be55dba5cf5f33d379e73eca048f4126e7af75f8011bab7f09145",
"packages/artplayer-plugin-danmuku-mask/README.md": "31ecede166379454dfd030c73b1e83cfcffd2d7e3361abe624838f3db7314cae",
"packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js": "07cebbfbf77f8ec4b2df6a77d7d7300345b86f2993e220ff14e1aae530e4fb31",
"packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js": "a678a3c0f6d3920bee3b66af9943b3612935028bff61c96677b8eaf8ef7cc8f0",
"packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs": "48f7a449dc9555ab4060a578902b5431701f81b2f093a7cb0526042a8c417400",
"packages/artplayer-plugin-danmuku-mask/package.json": "b216164ada1b9baf4fcec3388036839c28686f864e3d2a706258af78e6633887",
"packages/artplayer-plugin-danmuku-mask/src/index.js": "2397900688f86bc74bc86a4e870f69478a651bd8df02bba543cdab832773fd6e",
"packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts": "bbb270d73fc791488ac306f55e8c78990d0d9eef0a9d70cdf6093dadd7dff4b2",
"docs/assets/example/danmuku.mask.js": "c0fe4f2d4cd60738583c4bfeebc24bc3365d8ca382e6f4ff86e1056f5a2729a8"
},
"registrySnapshot": {
"status": 200,
"bodySha256": "b4caa94b8179a6c6e6ff8f96ed7ec09a615df48afadd2cad66409cf176395820",
"distTags": {
"latest": "1.1.0"
},
"stableVersions": [
"1.0.0",
"1.1.0"
],
"prereleaseVersions": [],
"scope": "All stable versions listed in the live registry snapshot; registry signatures not independently verified."
},
"release": {
"name": "artplayer-plugin-danmuku-mask",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-danmuku-mask/-/artplayer-plugin-danmuku-mask-1.1.0.tgz",
"integrity": "sha512-VBmmwtNLj0TKYz9lWbyQ7A8osQHdyDiPMazP9g1UZbLyw/rMJ3qRclMgszRpZHN9hod0dQl2iYSJDoaqu3hGOQ==",
"sha256": "96682e46cb140a84b164818dd118480d7be42b24afaab390027a25e52e8ae31c",
"registryGitHead": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
"publishedAt": "2026-03-12T14:21:17.027Z",
"files": {
"package/README.md": "31ecede166379454dfd030c73b1e83cfcffd2d7e3361abe624838f3db7314cae",
"package/dist/artplayer-plugin-danmuku-mask.js": "07cebbfbf77f8ec4b2df6a77d7d7300345b86f2993e220ff14e1aae530e4fb31",
"package/dist/artplayer-plugin-danmuku-mask.legacy.js": "a678a3c0f6d3920bee3b66af9943b3612935028bff61c96677b8eaf8ef7cc8f0",
"package/dist/artplayer-plugin-danmuku-mask.mjs": "48f7a449dc9555ab4060a578902b5431701f81b2f093a7cb0526042a8c417400",
"package/package.json": "b216164ada1b9baf4fcec3388036839c28686f864e3d2a706258af78e6633887",
"package/types/artplayer-plugin-danmuku-mask.d.ts": "bbb270d73fc791488ac306f55e8c78990d0d9eef0a9d70cdf6093dadd7dff4b2"
},
"manifest": {
"name": "artplayer-plugin-danmuku-mask",
"version": "1.1.0",
"description": "Danmuku mask plugin for ArtPlayer",
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
"license": "MIT",
"homepage": "https://artplayer.org",
"repository": {
"type": "git",
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
},
"bugs": {
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
},
"keywords": [
"html5",
"video",
"player"
],
"exports": {
".": {
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"import": "./dist/artplayer-plugin-danmuku-mask.mjs",
"require": "./dist/artplayer-plugin-danmuku-mask.js"
},
"./legacy": {
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"import": "./dist/artplayer-plugin-danmuku-mask.legacy.js",
"require": "./dist/artplayer-plugin-danmuku-mask.legacy.js"
}
},
"main": "./dist/artplayer-plugin-danmuku-mask.js",
"module": "./dist/artplayer-plugin-danmuku-mask.mjs",
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"legacy": "./dist/artplayer-plugin-danmuku-mask.legacy.js",
"browserslist": "last 1 Chrome version",
"dependencies": {
"@mediapipe/selfie_segmentation": "^0.1.1675465747",
"@tensorflow-models/body-segmentation": "^1.0.2",
"@tensorflow/tfjs-backend-cpu": "^4.21.0",
"@tensorflow/tfjs-backend-webgl": "^4.21.0",
"@tensorflow/tfjs-converter": "^4.21.0",
"@tensorflow/tfjs-core": "^4.21.0"
}
},
"entrypoints": {
"main": "./dist/artplayer-plugin-danmuku-mask.js",
"module": "./dist/artplayer-plugin-danmuku-mask.mjs",
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"legacy": "./dist/artplayer-plugin-danmuku-mask.legacy.js"
},
"missingEntrypoints": [],
"registrySourceManifest": {
"commit": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
"file": "packages/artplayer-plugin-danmuku-mask/package.json",
"version": "1.0.1",
"sha256LF": "320f1762d2e062089d11e013fa2199dae36003e3500252900e47c593d16af265",
"versionMatchesRelease": false
},
"historicalCore": {
"commit": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
"file": "packages/artplayer/package.json",
"version": "5.3.1",
"sha256LF": "862f3697876fe915dd0ada178da766ce823aae7563ed30b3e5b11b811202929c"
},
"registrySourceComparison": [
{
"file": "packages/artplayer-plugin-danmuku-mask/.npmignore",
"sha256LF": "46063058e98be55dba5cf5f33d379e73eca048f4126e7af75f8011bab7f09145",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/README.md",
"sha256LF": "31ecede166379454dfd030c73b1e83cfcffd2d7e3361abe624838f3db7314cae",
"matchesArchiveBytes": true
},
{
"file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js",
"sha256LF": "9e4f16458fe4887d5850591ee6a8ed07b13ce819dd2d30d23610548772de5f63",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js",
"sha256LF": "871af38b439e1e49fad2bdc8e4f25a25bc9335ad023dee9ecfb66ddd326b81cb",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs",
"sha256LF": "b0669d5348fc21e3b1d6ec7d0baa95d822e27d79ee555c751ca7ab71e2b3ecb8",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/package.json",
"sha256LF": "320f1762d2e062089d11e013fa2199dae36003e3500252900e47c593d16af265",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/src/index.js",
"sha256LF": "2397900688f86bc74bc86a4e870f69478a651bd8df02bba543cdab832773fd6e",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts",
"sha256LF": "bbb270d73fc791488ac306f55e8c78990d0d9eef0a9d70cdf6093dadd7dff4b2",
"matchesArchiveBytes": true
}
]
},
"previous": [
{
"name": "artplayer-plugin-danmuku-mask",
"version": "1.0.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-danmuku-mask/-/artplayer-plugin-danmuku-mask-1.0.0.tgz",
"integrity": "sha512-x5Kd6nfPYyXVUoy4xNq7EQ8PeVGOiWVpIT75RVOrGLMjFrBKvAYp0btcjSu9ZZN7aAkB9MtAyFcTB6hbkOX8uw==",
"sha256": "8a88f52ae843bed1be01a2e60b82cf886d33582f990a5016d49b1409c3493c4b",
"registryGitHead": "b36f88db5f7fe0250285fd4f47229842231f402b",
"publishedAt": "2026-01-04T08:48:59.809Z",
"files": {
"package/README.md": "8c042edec31eb22fb6022bc5a94f3b896a877653141cc0a7819d853d4ae3add4",
"package/dist/artplayer-plugin-danmuku-mask.js": "940cc240fcdb208427cb25d54180f9b84e0bfa96b5a4d3ba68fa9fb2ad8ffa16",
"package/dist/artplayer-plugin-danmuku-mask.legacy.js": "49da39c368e036f894e623277ce837efa9b7524a040d8ae01e2245b3459c3c9f",
"package/dist/artplayer-plugin-danmuku-mask.mjs": "92ae895c45c3dd39110c3513eb5cb6d99b9249d96b37b7fd1a6437e97febdc8f",
"package/package.json": "c24d66328eff31d93789a24e6e198bc92126f62ce4dd6c8929f64e4458b3b02a",
"package/types/artplayer-plugin-danmuku-mask.d.ts": "bbb270d73fc791488ac306f55e8c78990d0d9eef0a9d70cdf6093dadd7dff4b2"
},
"manifest": {
"name": "artplayer-plugin-danmuku-mask",
"version": "1.0.0",
"description": "Danmuku mask plugin for ArtPlayer",
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
"license": "MIT",
"homepage": "https://artplayer.org",
"repository": {
"type": "git",
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
},
"bugs": {
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
},
"keywords": [
"html5",
"video",
"player"
],
"exports": {
".": {
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"import": "./dist/artplayer-plugin-danmuku-mask.mjs",
"require": "./dist/artplayer-plugin-danmuku-mask.js"
},
"./legacy": {
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"import": "./dist/artplayer-plugin-danmuku-mask.legacy.js",
"require": "./dist/artplayer-plugin-danmuku-mask.legacy.js"
}
},
"main": "./dist/artplayer-plugin-danmuku-mask.js",
"module": "./dist/artplayer-plugin-danmuku-mask.mjs",
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"legacy": "./dist/artplayer-plugin-danmuku-mask.legacy.js",
"browserslist": "last 1 Chrome version",
"dependencies": {
"@mediapipe/selfie_segmentation": "^0.1.1675465747",
"@tensorflow-models/body-segmentation": "^1.0.2",
"@tensorflow/tfjs-backend-cpu": "^4.21.0",
"@tensorflow/tfjs-backend-webgl": "^4.21.0",
"@tensorflow/tfjs-converter": "^4.21.0",
"@tensorflow/tfjs-core": "^4.21.0"
}
},
"entrypoints": {
"main": "./dist/artplayer-plugin-danmuku-mask.js",
"module": "./dist/artplayer-plugin-danmuku-mask.mjs",
"types": "./types/artplayer-plugin-danmuku-mask.d.ts",
"legacy": "./dist/artplayer-plugin-danmuku-mask.legacy.js"
},
"missingEntrypoints": [],
"registrySourceManifest": {
"commit": "b36f88db5f7fe0250285fd4f47229842231f402b",
"file": "packages/artplayer-plugin-danmuku-mask/package.json",
"version": "1.0.0",
"sha256LF": "c24d66328eff31d93789a24e6e198bc92126f62ce4dd6c8929f64e4458b3b02a",
"versionMatchesRelease": true
},
"historicalCore": {
"commit": "b36f88db5f7fe0250285fd4f47229842231f402b",
"file": "packages/artplayer/package.json",
"version": "5.3.1-beta.1",
"sha256LF": "e1a0a390ec10cd8c1c12006ac6515414ae98a964300dd3e9c288377def948497"
},
"registrySourceComparison": [
{
"file": "packages/artplayer-plugin-danmuku-mask/.npmignore",
"sha256LF": "19c9675ae41ddf5428d894c9edebf1800ffea8f9be5b7d77c01a3635116cd9e8",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/README.md",
"sha256LF": "8c042edec31eb22fb6022bc5a94f3b896a877653141cc0a7819d853d4ae3add4",
"matchesArchiveBytes": true
},
{
"file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js",
"sha256LF": "940cc240fcdb208427cb25d54180f9b84e0bfa96b5a4d3ba68fa9fb2ad8ffa16",
"matchesArchiveBytes": true
},
{
"file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js",
"sha256LF": "49da39c368e036f894e623277ce837efa9b7524a040d8ae01e2245b3459c3c9f",
"matchesArchiveBytes": true
},
{
"file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs",
"sha256LF": "92ae895c45c3dd39110c3513eb5cb6d99b9249d96b37b7fd1a6437e97febdc8f",
"matchesArchiveBytes": true
},
{
"file": "packages/artplayer-plugin-danmuku-mask/package.json",
"sha256LF": "c24d66328eff31d93789a24e6e198bc92126f62ce4dd6c8929f64e4458b3b02a",
"matchesArchiveBytes": true
},
{
"file": "packages/artplayer-plugin-danmuku-mask/src/index.js",
"sha256LF": "105a6ffa4476cc0af680d1ef90e9095b17f73e33ffe5e4b70f245c1c095c196a",
"matchesArchiveBytes": false
},
{
"file": "packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts",
"sha256LF": "bbb270d73fc791488ac306f55e8c78990d0d9eef0a9d70cdf6093dadd7dff4b2",
"matchesArchiveBytes": true
}
]
}
],
"sdk": {
"lock": {
"file": "yarn.lock",
"commit": "b0cfbfe3a09a84a295a257e45e4577847588d1cb",
"sha256LF": "6dfb6c5b0154bcd3adc279e12c4393db66a6ec52274002adf2d5ab93caa05d4b"
},
"dependencies": [
{
"name": "@mediapipe/selfie_segmentation",
"range": "^0.1.1675465747",
"key": "@mediapipe/selfie_segmentation@^0.1.1675465747",
"version": "0.1.1675465747",
"resolved": "https://registry.npmjs.org/@mediapipe/selfie_segmentation/-/selfie_segmentation-0.1.1675465747.tgz",
"integrity": "sha512-IxYxNhwE5VwOm52L1yoFWYLP7q9Pd+NJjzOC5tlepfvEGaY3o9hslhUrx9BgseqdfZtKSDtd/4NfCSMjNzQalA==",
"installedManifest": {
"file": "node_modules/@mediapipe/selfie_segmentation/package.json",
"sha256": "b514e7146be2fbceee0a4a6e8a3895d2df36e552338aad8045a38a12c1c8eff8",
"license": "Apache-2.0",
"peerDependencies": {}
}
},
{
"name": "@tensorflow-models/body-segmentation",
"range": "^1.0.2",
"key": "@tensorflow-models/body-segmentation@^1.0.2",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@tensorflow-models/body-segmentation/-/body-segmentation-1.0.2.tgz",
"integrity": "sha512-sbPiL8wpqfKqh01of6qguZzU9yLWOQDTwiPEIHFPA/EAjz5T51LKHKIySll+mmteRo/TxNED8pxd9VJ6q2r7kg==",
"installedManifest": {
"file": "node_modules/@tensorflow-models/body-segmentation/package.json",
"sha256": "53d2b6b38be7d4d4ad44fce80d7d9bc58cf4a666b2791d846324e369d011b3dd",
"license": "Apache-2.0",
"peerDependencies": {
"@mediapipe/selfie_segmentation": "~0.1.0",
"@tensorflow/tfjs-backend-webgl": "^4.9.0",
"@tensorflow/tfjs-converter": "^4.9.0",
"@tensorflow/tfjs-core": "^4.9.0"
}
}
},
{
"name": "@tensorflow/tfjs-backend-cpu",
"range": "^4.21.0",
"key": "@tensorflow/tfjs-backend-cpu@^4.21.0",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz",
"integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==",
"installedManifest": {
"file": "node_modules/@tensorflow/tfjs-backend-cpu/package.json",
"sha256": "9400f1a4ea5f144646b62c8b393f54ed53b3446a2827321ba6b462ac220f0859",
"license": "Apache-2.0",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
}
},
{
"name": "@tensorflow/tfjs-backend-webgl",
"range": "^4.21.0",
"key": "@tensorflow/tfjs-backend-webgl@^4.21.0",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz",
"integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==",
"installedManifest": {
"file": "node_modules/@tensorflow/tfjs-backend-webgl/package.json",
"sha256": "286282c479b83d0b00d71d4ec40071c76244ad6da85441e0efc7a1b51456bf2f",
"license": "Apache-2.0",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
}
},
{
"name": "@tensorflow/tfjs-converter",
"range": "^4.21.0",
"key": "@tensorflow/tfjs-converter@^4.21.0",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz",
"integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==",
"installedManifest": {
"file": "node_modules/@tensorflow/tfjs-converter/package.json",
"sha256": "b6a4f89058a5f4a5fdfb7fe0fe4cf3181023bdedcc80a374e0349c002ab068b5",
"license": "Apache-2.0",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
}
},
{
"name": "@tensorflow/tfjs-core",
"range": "^4.21.0",
"key": "@tensorflow/tfjs-core@^4.21.0",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz",
"integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==",
"installedManifest": {
"file": "node_modules/@tensorflow/tfjs-core/package.json",
"sha256": "d4b3c3f337f7ea6de582ac1320b7521abe970a9b11f4d486e6929b29ccdbd283",
"license": "Apache-2.0",
"peerDependencies": {}
}
}
],
"adapterFiles": {
"node_modules/@tensorflow-models/body-segmentation/dist/selfie_segmentation_mediapipe/segmenter.js": "a8d076647e9ba9a787fdf354e48de6ecf62d4d3f74c10505dc58ef975b63bb67",
"node_modules/@tensorflow-models/body-segmentation/dist/selfie_segmentation_mediapipe/types.d.ts": "c259e6d1f55c9d69ba61eaa8eb884461e30ef93557123a01172c9e8380576603",
"node_modules/@tensorflow-models/body-segmentation/dist/selfie_segmentation_mediapipe/segmenter_utils.js": "a6646102407f3ea0bd87e48d6a7c0c6d8fc64a179b7990deafac4b0ba3e4a413",
"node_modules/@tensorflow-models/body-segmentation/dist/selfie_segmentation_mediapipe/constants.js": "27b0ffba42da166433af40ab1d4f2a5eccc97edf416c5d2e2ec4a665a18e9f5d",
"node_modules/@tensorflow-models/body-segmentation/dist/create_segmenter.js": "8f1f9f9bb4d53ffb411e41123298d2053bd344955cf51bdec3eee39898e06ced",
"node_modules/@mediapipe/selfie_segmentation/selfie_segmentation.js": "a4720d2d2374ec9df3510461ff186a4af427fb5bd402375e135875a991467da3"
},
"assets": [
{
"file": "docs/assets/@mediapipe/selfie_segmentation/README.md",
"bytes": 379,
"sha256": "9dad28188ae54ea420e3d29c23360b040a0dfb88954b99a3e456b8a8c93cf2a1",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/README.md",
"installedSha256": "9dad28188ae54ea420e3d29c23360b040a0dfb88954b99a3e456b8a8c93cf2a1",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/index.d.ts",
"bytes": 2772,
"sha256": "46152cccb63a9249503f7b8f49dbc3cfbd2a2994eda2c9f0db32a2998e1879a9",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/index.d.ts",
"installedSha256": "46152cccb63a9249503f7b8f49dbc3cfbd2a2994eda2c9f0db32a2998e1879a9",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/package.json",
"bytes": 734,
"sha256": "b514e7146be2fbceee0a4a6e8a3895d2df36e552338aad8045a38a12c1c8eff8",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/package.json",
"installedSha256": "b514e7146be2fbceee0a4a6e8a3895d2df36e552338aad8045a38a12c1c8eff8",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation.binarypb",
"bytes": 362,
"sha256": "0007be42c754f111063c0121bba7104880b7c56277d3a9681cdcb17709236aee",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation.binarypb",
"installedSha256": "0007be42c754f111063c0121bba7104880b7c56277d3a9681cdcb17709236aee",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation.js",
"bytes": 44542,
"sha256": "a4720d2d2374ec9df3510461ff186a4af427fb5bd402375e135875a991467da3",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation.js",
"installedSha256": "a4720d2d2374ec9df3510461ff186a4af427fb5bd402375e135875a991467da3",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation.tflite",
"bytes": 249024,
"sha256": "8d13b7fae74af625c641226813616a2117bd6bca19eb3b75574621fc08557f27",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation.tflite",
"installedSha256": "8d13b7fae74af625c641226813616a2117bd6bca19eb3b75574621fc08557f27",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation_landscape.tflite",
"bytes": 249792,
"sha256": "4aafe6223bb8dac6fac8ca8ed56852870a33051ef3f6238822d282a109962894",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation_landscape.tflite",
"installedSha256": "4aafe6223bb8dac6fac8ca8ed56852870a33051ef3f6238822d282a109962894",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation_solution_simd_wasm_bin.data",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation_solution_simd_wasm_bin.data",
"installedSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation_solution_simd_wasm_bin.js",
"bytes": 276493,
"sha256": "9406e7e30843bdca189689d4bea413f3e468f71e381560e583ef12792d26d46a",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation_solution_simd_wasm_bin.js",
"installedSha256": "9406e7e30843bdca189689d4bea413f3e468f71e381560e583ef12792d26d46a",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation_solution_simd_wasm_bin.wasm",
"bytes": 5694839,
"sha256": "d93e73677ef1fe791ff5407083c0f9ced6d6050a705bd6c20823fc05919d8dc4",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation_solution_simd_wasm_bin.wasm",
"installedSha256": "d93e73677ef1fe791ff5407083c0f9ced6d6050a705bd6c20823fc05919d8dc4",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation_solution_wasm_bin.js",
"bytes": 276488,
"sha256": "ed59858c14c9db26d34abbf0afa9017e5c6583a6ad63dae63d2fe42e19abfea5",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation_solution_wasm_bin.js",
"installedSha256": "ed59858c14c9db26d34abbf0afa9017e5c6583a6ad63dae63d2fe42e19abfea5",
"matchesInstalled": true
},
{
"file": "docs/assets/@mediapipe/selfie_segmentation/selfie_segmentation_solution_wasm_bin.wasm",
"bytes": 5587523,
"sha256": "37665276fd8e38589a28b07ddec805266efb1b7a1a52fd36667ef11dff43a847",
"installedFile": "node_modules/@mediapipe/selfie_segmentation/selfie_segmentation_solution_wasm_bin.wasm",
"installedSha256": "37665276fd8e38589a28b07ddec805266efb1b7a1a52fd36667ef11dff43a847",
"matchesInstalled": true
}
],
"defaultSolutionPath": "https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation",
"demoSolutionPath": "/assets/@mediapipe/selfie_segmentation",
"scope": "Read-only installed SDK source and existing local assets. No SDK/model execution, remote model requests, GPU or device validation. Installed assets are compared with frozen Git bytes; not independently authenticated against a newly downloaded SDK archive."
},
"observations": {
"latestArchiveMatchesFrozenSourceFiles": [
"packages/artplayer-plugin-danmuku-mask/README.md",
"packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js",
"packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js",
"packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs",
"packages/artplayer-plugin-danmuku-mask/package.json",
"packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts"
]
}
}
@@ -0,0 +1,25 @@
# PKG-MASK-01 Danmuku Mask 契约基线
冻结提交 `b0cfbfe3a09a84a295a257e45e4577847588d1cb`,只新增契约与验证文件,
未改生产、依赖、root 脚本、生成产物或 demo。没有推送或发布。
实际 registry 全部稳定版本 1.0.0/1.1.0 的归档、SHA512/SHA256、完整六成员及
四种入口已冻结。1.1.0 六发布文件与冻结工作区相同;registry gitHead 的插件
manifest 却是 1.0.1,显式保留此差异。两版声明完全相同,真实 CJS main/legacy
分别是旧对象.default、新直接工厂;script 全局仍为同名工厂。
SDK-08 记录六个 Yarn 解析版本、已安装 adapter 字节,以及 demo 12 个既有资源与
已安装 MediaPipe 0.1.1675465747 的字节一致性。默认 solutionPath 仍是无版本 CDN,
没有被锁文件固定。已安装 adapter 以固定 general 选择模型 0,不消费插件额外传入
的若干模型选项;因此不能将声明可传参数解释为已验证效果。
记录 start/stop 异步过期、多重 RAF、backend fallback、DOM/CORS、模型参数和第三方
通知后续问题。详见 [契约](../baselines/danmuku-mask-contract.md) 和
[完整来源](../baselines/danmuku-mask-release.json)。这些问题未在本任务修复,也未被
笼统认定必须保留;由 02 复现、03/04 实施、05/06 做真实模型与分发验收。
验证:固定 Node 24.21.0,独立 verifier 通过;6 项 node:test 全部通过、无跳过。
专属脚本 ESLint 通过。导出探针使用实际 bundle;自有源码默认值/零值/像素探针
使用受控 SDK 替身。没有新增下载/执行模型、浏览器服务器或真实 GPU/设备验证。
父代理复核六项测试通过,整合任务/风险/SDK-08索引并创建独立本地提交。
新增测试由现有test:baseline自动收集;两个直接重放命令见契约文档,无需重复脚本别名。
+4 -3
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 228 项,范围 22 个包及工作区/示例。
状态:todo 75 / doing 16 / blocked 0 / done 137 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 74 / doing 16 / blocked 0 / done 138 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -240,8 +240,8 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| PKG-MASK-01 | artplayer-plugin-danmuku-mask<br>核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | doing |
| PKG-MASK-02 | artplayer-plugin-danmuku-mask<br>建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo |
| PKG-MASK-01 | artplayer-plugin-danmuku-mask<br>核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | done |
| PKG-MASK-02 | artplayer-plugin-danmuku-mask<br>建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | doing |
| PKG-MASK-03 | artplayer-plugin-danmuku-mask<br>整理内部职责与资源 | PKG-MASK-02, CORE-18, PKG-DANMUKU-07 | 模型加载/推理/画布输出分离,阻止重叠推理与过期写入 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-MASK-05 | artplayer-plugin-danmuku-mask<br>验证新旧核心和组合 | PKG-MASK-04, CORE-22 | 真实模型和 danmuku/seek/全屏组合,GPU 资源释放 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
@@ -529,6 +529,7 @@
- PKG-MULTI-SUB-04: [记录](baselines/multiple-subtitles-runtime-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md) [记录](baselines/multiple-subtitles-public-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md) [记录](type-compatibility-policy.md) [记录](baselines/multiple-subtitles-approved-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-approved-types.md)
- PKG-MULTI-SUB-07: [记录](baselines/multiple-subtitles-timestamps.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md)
- PKG-JASSUB-01: [记录](baselines/jassub-release.json) [记录](baselines/jassub-vendor.json) [记录](baselines/jassub-font-metadata.json) [记录](baselines/jassub-contract.md) [记录](baselines/jassub-contract-validation.json) [记录](changes/2026-09-13-PKG-JASSUB-01-baseline.md)
- PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md)
- PKG-ASR-01: [记录](changes/2026-09-13-PKG-ASR-01-contracts.md) [记录](baselines/asr-contract.md) [记录](baselines/asr-release.json)
- PKG-ASR-02: [记录](changes/2026-09-13-PKG-ASR-02-audio-baseline.md) [记录](baselines/asr-audio-validation.json)
- PKG-ASR-03: [记录](changes/2026-09-13-PKG-ASR-03-audio-ownership.md) [记录](baselines/asr-ownership-validation.json)
+10
View File
@@ -1,5 +1,15 @@
# 进度与证据
## PKG-MASK-01 弹幕遮罩契约完成
冻结全部两个真实稳定发布版、Git关联和导出差异;核对六个Yarn/SDK版本与12个已有
本地资源字节。6项测试及父代理复核通过。未固定CDN、无效模型参数、异步生命周期、
重复启动、backend与DOM/通知风险归入后续任务,SDK-08保持开放。
见[契约](baselines/danmuku-mask-contract.md)与[变更](changes/2026-09-13-PKG-MASK-01-contract.md)。
228项:138 done、16 doing、74 todo。02开始受控历史失败测试;
没有启动真实模型、GPU或远程投屏,没有推送或发布。
## PKG-CAST-04 公开类型与隔离安装完成
最新npm 1.1.0根类型保持,/runtime精确描述异步注册、回调与查询。17安装配置、
+7 -1
View File
@@ -40,7 +40,7 @@
| SDK-05 | open / 待取证 | webtorrent 实际集成验证范围 | EX-03 |
| SDK-06 | open / 待取证 | google-cast 实际集成验证范围 | PKG-CAST-01, PKG-CAST-05 |
| SDK-07 | open / 待取证 | ima-via-glomex 实际集成验证范围 | PKG-VAST-01, PKG-VAST-05 |
| SDK-08 | open / 待取证 | mediapipe-tensorflow 实际集成验证范围 | PKG-MASK-01, PKG-MASK-05 |
| SDK-08 | open / 源码/产物事实 | mediapipe-tensorflow 实际集成验证范围 | PKG-MASK-01, PKG-MASK-05, PKG-MASK-06 |
| SDK-09 | open / 待取证 | jassub-worker-wasm-fonts 实际集成验证范围 | PKG-JASSUB-01, PKG-JASSUB-05 |
| SDK-10 | open / 待取证 | mediabunny 实际集成验证范围 | PKG-MB-01, PKG-MB-09 |
| SDK-11 | open / 待取证 | asr-caller-service 实际集成验证范围 | PKG-ASR-01, PKG-ASR-05, EX-03 |
@@ -221,3 +221,9 @@
| CAST-LIFE-01 | resolved / 已复现 | Cast listeners and continuations survive player destruction and update another player icon | PKG-CAST-03, PKG-CAST-05 |
| CAST-STATE-01 | resolved / 已复现 | Cast terminal session states leave the normalized callback and icon connected | PKG-CAST-03, PKG-CAST-05 |
| CAST-ERROR-01 | resolved / 已复现 | Cast media loading rejection escapes on a detached Promise while the click has already fulfilled | PKG-CAST-03, PKG-CAST-05 |
| MASK-LIFETIME-01 | open / 源码/产物事实 | Mask asynchronous initialization and inference continue after stop or destroy | PKG-MASK-02, PKG-MASK-03 |
| MASK-START-01 | open / 源码/产物事实 | Concurrent Mask start calls can initialize multiple models and create independent RAF chains | PKG-MASK-02, PKG-MASK-03 |
| MASK-MODEL-01 | open / 源码/产物事实 | Published Mask options and installed MediaPipe adapter consume different model settings | PKG-MASK-02, PKG-MASK-04, PKG-MASK-05 |
| MASK-BACKEND-01 | open / 源码/产物事实 | Mask TensorFlow backend fallback does not establish MediaPipe fallback and failed initialization keeps scheduling | PKG-MASK-02, PKG-MASK-03, PKG-MASK-05 |
| MASK-DOM-01 | open / 源码/产物事实 | Mask registration and frame processing assume Danmuku DOM, readable frames and a 2D context | PKG-MASK-02, PKG-MASK-03, PKG-MASK-05 |
| MASK-NOTICE-01 | open / 源码/产物事实 | Published Mask bundles lack a separate third-party notice despite bundled SDK code | PKG-MASK-06 |
+111 -3
View File
@@ -743,14 +743,18 @@
{
"id": "SDK-08",
"title": "mediapipe-tensorflow 实际集成验证范围",
"confirmation": "unverified",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-01",
"PKG-MASK-05"
"PKG-MASK-05",
"PKG-MASK-06"
],
"evidence": [
"packages/artplayer-plugin-danmuku-mask/src/index.js"
"packages/artplayer-plugin-danmuku-mask/src/index.js",
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-contract-validation.json",
"refactor/baselines/danmuku-mask-release.json"
],
"compatibleResolution": "Npm dependency versions are separate from unversioned solutionPath CDN assets; keep option override, backend fallback, model result disposal and masks.",
"closureCriteria": "固定 SDK/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。"
@@ -4898,6 +4902,110 @@
"test/browser/chromecast-lifecycle.spec.js"
],
"resolutionRationale": "35 candidate source/main/legacy tests and 135 native browser DOM/core cases verify corrected behavior against controlled SDK boundaries. 93 frozen historical observations remain. Actual remote SDK and receivers remain SDK06/CAST05, not waived."
},
{
"id": "MASK-LIFETIME-01",
"title": "Mask asynchronous initialization and inference continue after stop or destroy",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-02",
"PKG-MASK-03"
],
"evidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-release.json",
"refactor/baselines/danmuku-mask-contract-validation.json"
],
"compatibleResolution": "Invalidate stale work while keeping restartable stop distinct from final destroy; release model and subscriptions.",
"closureCriteria": "Pending initialization/inference cannot write masks or schedule work after stop/destroy; errors settle and owned resources are released."
},
{
"id": "MASK-START-01",
"title": "Concurrent Mask start calls can initialize multiple models and create independent RAF chains",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-02",
"PKG-MASK-03"
],
"evidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-release.json",
"refactor/baselines/danmuku-mask-contract-validation.json"
],
"compatibleResolution": "Share pending initialization and own at most one inference loop per live registration.",
"closureCriteria": "Repeated and concurrent starts, stop then start, failures and multiple instances have deterministic resource and result assertions."
},
{
"id": "MASK-MODEL-01",
"title": "Published Mask options and installed MediaPipe adapter consume different model settings",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-02",
"PKG-MASK-04",
"PKG-MASK-05"
],
"evidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-release.json",
"refactor/baselines/danmuku-mask-contract-validation.json"
],
"compatibleResolution": "Freeze actual default/zero behavior and distinguish supported SDK configuration from inactive historical option fields before changing effects.",
"closureCriteria": "Effective model selection, zero values and configuration mapping have controlled and real model evidence, with explicit compatibility decisions for effect changes."
},
{
"id": "MASK-BACKEND-01",
"title": "Mask TensorFlow backend fallback does not establish MediaPipe fallback and failed initialization keeps scheduling",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-02",
"PKG-MASK-03",
"PKG-MASK-05"
],
"evidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-release.json",
"refactor/baselines/danmuku-mask-contract-validation.json"
],
"compatibleResolution": "Model backend outcomes explicitly, settle initialization failures and document the actual MediaPipe capability boundary.",
"closureCriteria": "Fulfilled false, rejection, CPU failure, createSegmenter failure and later retry are tested; real inference fallback is separately evidenced."
},
{
"id": "MASK-DOM-01",
"title": "Mask registration and frame processing assume Danmuku DOM, readable frames and a 2D context",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-02",
"PKG-MASK-03",
"PKG-MASK-05"
],
"evidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-release.json",
"refactor/baselines/danmuku-mask-contract-validation.json"
],
"compatibleResolution": "Handle missing dependencies and frame/context failures without stale writes or uncontrolled loops while preserving plugin ordering.",
"closureCriteria": "Missing Danmuku/context, CORS failure, source changes and ready/destroy timing have controlled and real browser evidence."
},
{
"id": "MASK-NOTICE-01",
"title": "Published Mask bundles lack a separate third-party notice despite bundled SDK code",
"confirmation": "source-observed",
"status": "open",
"owners": [
"PKG-MASK-06"
],
"evidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-release.json",
"refactor/baselines/danmuku-mask-contract-validation.json"
],
"compatibleResolution": "Trace included SDK assets and license notices against actual bundle sources; preserve required notices in candidate packages.",
"closureCriteria": "Document incorporated SDK provenance and applicable notices; inspect built and packed artifacts, without treating manifest license claims as full clearance."
}
]
}
+107
View File
@@ -0,0 +1,107 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import lockfile from '@yarnpkg/lockfile'
import { archiveFiles, ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
const workspace = path.dirname(refactorDir.replace(/[\\/]$/u, ''))
const prefix = 'packages/artplayer-plugin-danmuku-mask/'
function gitBytes(commit, file) {
return execFileSync('git', ['show', `${commit}:${file}`], { cwd: workspace, maxBuffer: 32 * 1024 * 1024 })
}
function gitText(commit, file) {
return gitBytes(commit, file).toString().replaceAll('\r\n', '\n')
}
export async function verifyDanmukuMaskContract() {
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/danmuku-mask-release.json'), 'utf8'))
const registryBytes = fs.readFileSync(path.join(refactorDir, 'baselines/danmuku-mask-registry.json'))
assert.equal(hash(registryBytes), baseline.registrySnapshot.bodySha256)
const registry = JSON.parse(registryBytes)
assert.deepEqual(registry['dist-tags'], baseline.registrySnapshot.distTags)
assert.deepEqual(Object.keys(registry.versions).filter(version => /^\d+\.\d+\.\d+$/u.test(version)), baseline.registrySnapshot.stableVersions)
const archives = new Map()
for (const release of [baseline.release, ...baseline.previous]) {
const metadata = registry.versions[release.version]
assert.equal(metadata.dist.integrity, release.integrity)
assert.equal(metadata.dist.tarball, release.tarball)
assert.equal(metadata.gitHead, release.registryGitHead)
const archive = await ensureArchive(release)
assert.deepEqual(archiveFiles(archive), Object.keys(release.files).sort())
for (const [file, expected] of Object.entries(release.files))
assert.equal(hash(readMember(archive, file)), expected, `${release.version}/${file}`)
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
assert.deepEqual(manifest, release.manifest)
assert.equal(manifest.version, release.version)
assert.equal(manifest.name, release.name)
for (const [field, target] of Object.entries(release.entrypoints)) {
assert.equal(manifest[field] ?? null, target)
if (target)
assert(Object.hasOwn(release.files, `package/${target.replace(/^\.\//u, '')}`), `${release.version}/${field}`)
}
function targetExists(target) {
if (typeof target === 'string')
assert(Object.hasOwn(release.files, `package/${target.replace(/^\.\//u, '')}`))
else if (target && typeof target === 'object')
Object.values(target).forEach(targetExists)
}
targetExists(manifest.exports)
for (const association of [release.registrySourceManifest, release.historicalCore]) {
assert.equal(association.commit, release.registryGitHead)
const text = gitText(association.commit, association.file)
assert.equal(hash(text), association.sha256LF)
assert.equal(JSON.parse(text).version, association.version)
}
assert.equal(release.registrySourceManifest.versionMatchesRelease, release.registrySourceManifest.version === release.version)
for (const comparison of release.registrySourceComparison) {
const text = gitText(release.registryGitHead, comparison.file)
assert.equal(hash(text), comparison.sha256LF)
assert.equal(comparison.matchesArchiveBytes, hash(text) === release.files[`package/${comparison.file.slice(prefix.length)}`])
}
archives.set(release.version, archive)
}
assert.deepEqual([...archives.keys()].sort(), [...baseline.registrySnapshot.stableVersions].sort())
const sources = new Map()
for (const [file, expected] of Object.entries(baseline.source)) {
const text = gitText(baseline.sourceCommit, file)
assert.equal(hash(text), expected, file)
sources.set(file, text)
}
for (const file of baseline.observations.latestArchiveMatchesFrozenSourceFiles)
assert.equal(hash(sources.get(file)), baseline.release.files[`package/${file.slice(prefix.length)}`])
const locked = gitText(baseline.sdk.lock.commit, baseline.sdk.lock.file)
assert.equal(hash(locked), baseline.sdk.lock.sha256LF)
const parsed = lockfile.parse(locked)
assert.equal(parsed.type, 'success')
const manifest = JSON.parse(sources.get(`${prefix}package.json`))
for (const dependency of baseline.sdk.dependencies) {
assert.equal(manifest.dependencies[dependency.name], dependency.range)
const entry = parsed.object[dependency.key]
for (const field of ['version', 'resolved', 'integrity'])
assert.equal(entry[field], dependency[field])
const installed = fs.readFileSync(path.join(workspace, dependency.installedManifest.file))
assert.equal(hash(installed), dependency.installedManifest.sha256)
assert.equal(JSON.parse(installed).version, dependency.version)
}
for (const [file, digest] of Object.entries(baseline.sdk.adapterFiles))
assert.equal(hash(fs.readFileSync(path.join(workspace, file))), digest, file)
for (const asset of baseline.sdk.assets) {
const bytes = gitBytes(baseline.sourceCommit, asset.file)
assert.equal(bytes.length, asset.bytes)
assert.equal(hash(bytes), asset.sha256)
const installed = fs.readFileSync(path.join(workspace, asset.installedFile))
assert.equal(hash(installed), asset.installedSha256)
assert.equal(asset.matchesInstalled, asset.sha256 === asset.installedSha256)
}
assert(sources.get(`${prefix}src/index.js`).includes(baseline.sdk.defaultSolutionPath))
assert(sources.get('docs/assets/example/danmuku.mask.js').includes(baseline.sdk.demoSolutionPath))
return { baseline, archives, sources }
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const { baseline, archives, sources } = await verifyDanmukuMaskContract()
console.log(JSON.stringify({ sourceCommit: baseline.sourceCommit, sourceFiles: sources.size, releases: [...archives.keys()], dependencies: baseline.sdk.dependencies.map(item => `${item.name}@${item.version}`), existingAssets: baseline.sdk.assets.length, validation: 'Archive SHA512/SHA256, complete entrypoints, Git associations, frozen Yarn versions, installed SDK source and existing asset bytes; no model execution' }, null, 2))
}
@@ -0,0 +1,170 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Frozen package contract verification.
import test from 'node:test'
import vm from 'node:vm'
import { transform } from 'esbuild'
import { verifyDanmukuMaskContract } from './danmuku-mask-contract.mjs'
import { readMember } from './releases.mjs'
const contract = await verifyDanmukuMaskContract()
const source = contract.sources.get('packages/artplayer-plugin-danmuku-mask/src/index.js')
async function controlledSource(option) {
const frames = new Map()
const configs = []
const calls = []
const listeners = new Map()
const style = {}
const pixels = new Uint8ClampedArray([255, 255, 255, 255, 250, 255, 255, 255])
let id = 0
const canvas = {
width: 0,
height: 0,
getContext: () => ({ getImageData: () => ({ data: pixels }), putImageData: value => calls.push(['pixels', [...value.data]]) }),
toDataURL: () => 'data:image/png;base64,controlled',
}
const segmenter = { segmentPeople: async () => [{}] }
const body = {
SupportedModels: { MediaPipeSelfieSegmentation: 'selfie' },
createSegmenter: async (model, config) => {
configs.push({ model, config: { ...config } })
return segmenter
},
toBinaryMask: async (...args) => {
calls.push(['mask', ...args])
return {}
},
drawMask: async (...args) => { calls.push(['draw', args[3], args[4]]) },
}
const tf = { setBackend: async value => calls.push(['backend', value]) }
const module = { exports: {} }
vm.runInNewContext((await transform(source, { format: 'cjs', target: 'es2020' })).code, {
module,
exports: module.exports,
require(name) {
if (name === '@tensorflow-models/body-segmentation')
return body
if (name === '@tensorflow/tfjs-core')
return tf
assert(['@tensorflow/tfjs-backend-webgl', '@tensorflow/tfjs-backend-cpu'].includes(name))
return {}
},
document: { createElement: (tag) => {
assert.equal(tag, 'canvas')
return canvas
} },
requestAnimationFrame: (callback) => {
frames.set(++id, callback)
return id
},
cancelAnimationFrame: id => frames.delete(id),
console,
}, { timeout: 1000 })
const art = { template: { $video: { paused: false, ended: false, videoWidth: 2, videoHeight: 1 }, $danmuku: { style } }, on: (name, callback) => listeners.set(name, callback) }
const result = module.exports.default(option)(art)
return { result, listeners, frames, configs, calls, style }
}
test('Danmuku Mask freezes all published stable archive members and their actual Git associations', () => {
assert.deepEqual([...contract.archives.keys()].sort(), ['1.0.0', '1.1.0'])
for (const release of [contract.baseline.release, ...contract.baseline.previous]) {
assert.equal(Object.keys(release.files).length, 6)
assert.deepEqual(release.missingEntrypoints, [])
}
assert.equal(contract.baseline.release.registrySourceManifest.version, '1.0.1')
assert.equal(contract.baseline.release.registrySourceManifest.versionMatchesRelease, false)
assert.equal(contract.baseline.observations.latestArchiveMatchesFrozenSourceFiles.length, 6)
})
test('Both published declarations retain optional options, synchronous registration, async start and void stop', () => {
const declaration = contract.sources.get('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts')
for (const archive of contract.archives.values())
assert.equal(readMember(archive, 'package/types/artplayer-plugin-danmuku-mask.d.ts').toString(), declaration)
assert(declaration.includes('(option?: Option) => (art: Artplayer) => Result'))
assert(declaration.includes('start: () => Promise<void>'))
assert(declaration.includes('stop: () => void'))
assert(declaration.includes('export default artplayerPluginDanmukuMask'))
})
test('Actual main and legacy exports preserve historical CommonJS and browser global forms without starting a model', () => {
for (const [version, archive] of contract.archives) {
for (const suffix of ['js', 'legacy.js']) {
const bytes = readMember(archive, `package/dist/artplayer-plugin-danmuku-mask.${suffix}`).toString()
for (const form of ['cjs', 'global']) {
const module = { exports: {} }
const environment = {
console,
fetch() { throw new Error('Network is forbidden in this export probe') },
}
environment.global = environment
environment.self = environment
if (form === 'cjs')
Object.assign(environment, { module, exports: module.exports })
else environment.window = environment
vm.runInNewContext(bytes, environment, { timeout: 5000 })
const value = form === 'cjs' ? module.exports : environment.artplayerPluginDanmukuMask
const oldCommonJs = version === '1.0.0' && form === 'cjs'
assert.equal(typeof value, oldCommonJs ? 'object' : 'function')
assert.equal(typeof value.default, oldCommonJs ? 'function' : 'undefined')
const factory = oldCommonJs ? value.default : value
const events = []
const style = {}
const result = factory()({
template: { $video: {}, $danmuku: { style } },
on: name => events.push(name),
})
assert.equal(result.name, 'artplayerPluginDanmukuMask')
assert.equal(typeof result.start, 'function')
assert.equal(typeof result.stop, 'function')
assert.equal(result.stop(), undefined)
assert.equal(style.maskImage, 'none')
assert.deepEqual(events, ['ready', 'destroy'])
}
}
}
})
test('Frozen owned source keeps its defaults, ready/destroy hooks and alpha mask pixel boundary without a real SDK', async () => {
const env = await controlledSource()
assert.equal(env.result.name, 'artplayerPluginDanmukuMask')
assert.deepEqual([...env.listeners.keys()], ['ready', 'destroy'])
const pending = env.result.start()
assert.equal(typeof pending.then, 'function')
await pending
await new Promise(resolve => setImmediate(resolve))
assert.deepEqual(env.configs, [{ model: 'selfie', config: { runtime: 'mediapipe', modelType: 'general', solutionPath: contract.baseline.sdk.defaultSolutionPath, modelSelection: 1, smoothSegmentation: true, minDetectionConfidence: 0.5, minTrackingConfidence: 0.5, selfieMode: false } }])
assert.deepEqual(env.style, { maskMode: 'alpha', maskSize: 'contain', maskRepeat: 'no-repeat', backgroundSize: 'contain', backgroundRepeat: 'no-repeat', maskImage: 'url(data:image/png;base64,controlled)' })
assert.deepEqual(env.calls.find(call => call[0] === 'draw'), ['draw', 1, 3])
assert.deepEqual(env.calls.find(call => call[0] === 'pixels'), ['pixels', [255, 255, 255, 0, 250, 255, 255, 255]])
assert.equal(env.frames.size, 1)
assert.equal(env.result.stop(), undefined)
assert.equal(env.frames.size, 0)
assert.equal(env.style.maskImage, 'none')
})
test('Frozen zero numeric options use historical OR defaults while false smoothSegmentation is retained', async () => {
const env = await controlledSource({ solutionPath: '/local-model', modelSelection: 0, smoothSegmentation: false, minDetectionConfidence: 0, minTrackingConfidence: 0, foregroundThreshold: 0, opacity: 0, maskBlurAmount: 0, drawContour: true, selfieMode: true })
await env.result.start()
await new Promise(resolve => setImmediate(resolve))
assert.deepEqual(env.configs[0].config, { runtime: 'mediapipe', modelType: 'general', solutionPath: '/local-model', modelSelection: 1, smoothSegmentation: false, minDetectionConfidence: 0.5, minTrackingConfidence: 0.5, selfieMode: true })
assert.deepEqual(env.calls.find(call => call[0] === 'draw'), ['draw', 1, 3])
const mask = env.calls.find(call => call[0] === 'mask')
assert.equal(mask[4], true)
assert.equal(mask[5], 0.5)
env.result.stop()
})
test('SDK-08 distinguishes the frozen Yarn adapter and local resource bytes from unversioned CDN resolution', () => {
assert.deepEqual(contract.baseline.sdk.dependencies.map(item => item.version), ['0.1.1675465747', '1.0.2', '4.22.0', '4.22.0', '4.22.0', '4.22.0'])
assert.equal(contract.baseline.sdk.assets.length, 12)
assert(contract.baseline.sdk.assets.every(asset => asset.matchesInstalled))
const adapter = fs.readFileSync('node_modules/@tensorflow-models/body-segmentation/dist/selfie_segmentation_mediapipe/segmenter.js', 'utf8')
assert.match(adapter, /switch \(config\.modelType\)/u)
assert.match(adapter, /case 'general':[\s\S]*?modelSelection = 0;/u)
for (const ignored of ['modelSelection', 'smoothSegmentation', 'minDetectionConfidence', 'minTrackingConfidence', 'selfieMode'])
assert.equal(adapter.includes(`config.${ignored}`), false, `Do not claim ${ignored} is consumed by this adapter`)
assert(adapter.includes('return solutionPath + "/" + path;'))
assert(adapter.includes('this.selfieSegmentationSolution.close();'))
assert.equal(source.includes('.dispose('), false, 'The historical plugin does not dispose its segmenter')
})
+9 -3
View File
@@ -2518,11 +2518,17 @@
"dependsOn": [
"BASE-05"
],
"status": "doing",
"status": "done",
"risk": "H",
"deliverable": "模型参数、start/stop、默认下载路径及 mask 样式",
"acceptance": "源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源",
"evidence": []
"evidence": [
"baselines/danmuku-mask-release.json",
"baselines/danmuku-mask-registry.json",
"baselines/danmuku-mask-contract.md",
"baselines/danmuku-mask-contract-validation.json",
"changes/2026-09-13-PKG-MASK-01-contract.md"
]
},
{
"id": "PKG-MASK-02",
@@ -2536,7 +2542,7 @@
"ENG-03",
"ENG-05"
],
"status": "todo",
"status": "doing",
"risk": "H",
"deliverable": "加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界",
"acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言",
+9 -3
View File
@@ -1224,14 +1224,20 @@
"riskId": "SDK-08",
"owners": [
"PKG-MASK-01",
"PKG-MASK-05"
"PKG-MASK-05",
"PKG-MASK-06"
],
"sourceFiles": [
"packages/artplayer-plugin-danmuku-mask/src/index.js"
],
"contract": "Npm dependency versions are separate from unversioned solutionPath CDN assets; keep option override, backend fallback, model result disposal and masks.",
"validationStatus": "Source integration observed; complete real SDK/service/device matrix not yet verified.",
"demoPathsSource": "refactor/baselines/demo-inventory.json"
"validationStatus": "MASK01 verified two actual releases, six frozen Yarn/installed SDK versions, adapter source hashes and 12 existing demo resource byte matches. The default solutionPath remains an unversioned CDN. Controlled export/default/pixel probes do not establish real models, fallback, browser/GPU cleanup or notice completeness; MASK05/06 remain open.",
"demoPathsSource": "refactor/baselines/demo-inventory.json",
"reviewEvidence": [
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-contract-validation.json",
"refactor/baselines/danmuku-mask-release.json"
]
},
{
"id": "jassub-worker-wasm-fonts",