diff --git a/refactor/baselines/README.md b/refactor/baselines/README.md index 6e913ae81..99a228d2e 100644 --- a/refactor/baselines/README.md +++ b/refactor/baselines/README.md @@ -44,3 +44,7 @@ BASE-04 已在内置浏览器完成两轮 16 项 DOM/样式/实际输入断言 ## 分发、类型与 SSR 消费 BASE-05 的 [覆盖/复跑/历史诊断](consumer-coverage.md) 包含 18 项 Node runtime、16 项严格 TS 场景和浏览器两轮各 5 项 SSR 检查。[消费者](consumers.json)、[SSR](ssr.json)、[22 包资源](distribution.json) 分别记录已发布内容与工作区观察。8 项类型历史失败保持显式,不能计作候选通过。 + +## 性能和资源 + +BASE-06 保存两轮交错计时与独立资源诊断,见 [采样/阈值/范围](performance-coverage.md)、[原始报告](performance.json) 和 [压缩体积](sizes.json)。销毁后的延迟 resize/notice 工作已登记,不能把基本清理通过当成无内存泄漏证明;本任务没有优化收益声明。 diff --git a/refactor/baselines/performance-coverage.md b/refactor/baselines/performance-coverage.md new file mode 100644 index 000000000..0b761cc9a --- /dev/null +++ b/refactor/baselines/performance-coverage.md @@ -0,0 +1,73 @@ +# 性能与资源基线 + +BASE-06 使用固定发布 core 5.4.0 / chapter 1.1.0、本地 video.mp4 和 640x360 容器。两次独立页面重载,每次 12 个计时样本和 6 个资源探针;保留原始数据、预热标记和来源,见 [performance.json](performance.json)。没有修改生产实现,本任务不声称任何优化收益。 + +## 环境与采样 + +- Codex 内置浏览器,UA Chrome 152.0.0.0 / Windows,viewport 890x1272,devicePixelRatio=1,全程 visible,无 visibilitychange。 +- 浏览器报告 hardwareConcurrency=12、deviceMemory=32;这些是浏览器暴露值。测量后独立 Windows CIM 查询为 i5-12400F / 6 物理核心 / 12 逻辑线程 / 31.8 GiB 内存,记录在 hostContext,不将浏览器字段当硬件容量证明。 +- 发布 JS、夹具 LF 指纹和媒体 SHA-256 均绑定报告;固定本地服务返回 no-store,但未强制清空操作系统/解码器/连接缓存,不能称严格冷启动。其他应用负载、CPU 电源/温度和浏览器调度未锁定。 +- 每轮 core/chapter 交错执行,各 1 个预热、5 个计时样本;原始预热也保留。构造同步耗时、构造到 ready、play Promise 兑现、destroy 同步耗时分别测量。ready 通过 RAF 轮询已就绪状态,包含约一帧的观察量化和本地媒体初始化,不能称精确解码延迟。 +- 每个样本真实播放 currentTime 至少前进 0.15 秒,然后暂停、销毁;不通过固定 sleep 假装媒体就绪。没有测完整片段播放、FPS、 dropped frames、seek 热路径、首个呈现帧或下载耗时。 + +## 两轮计时结果 + +以下均为各 5 个非预热样本的中位数,单位 ms;完整 min/median/max 在报告中。样本量不足以报告有意义的尾部百分位或置信收益。 + +| 配置/轮次 | constructor | ready | play Promise | destroy | +| --- | ---: | ---: | ---: | ---: | +| core 第一轮 | 5.60 | 30.20 | 3.00 | 3.00 | +| chapter 第一轮 | 5.70 | 29.80 | 3.40 | 3.10 | +| core 第二轮 | 4.10 | 12.80 | 3.10 | 2.80 | +| chapter 第二轮 | 5.00 | 14.50 | 1.40 | 3.20 | + +代码完全相同,ready 中位数仍明显变化,说明一次快慢差异不足以证明优化。后续候选须在相同浏览器/设备/viewport/样本/配置中交错旧新运行,至少三组配对复测后评估,不拿这张旧表与不同机器/浏览器直接比较。 + +## 资源观察 + +计时样本不替换定时器。资源探针另行临时追踪 window timeout/interval/RAF,并显式启用 USE_RAF=true;默认计时保持发布默认 USE_RAF=false。探针保留原生回调 this、参数、句柄和取消行为,但任何 instrumentation 仍可能扰动时序,因此资源探针的耗时不纳入性能比较。 + +每轮资源 probe 运行 core/chapter 各三次:实际播放至至少两个 raf 事件、开启 notice、触发一个自定义代理事件,以及受控的 synthetic window resize 后立即 destroy。350ms 是销毁后观察窗口,不是等待媒体加载的固定延迟。 + +| 观察面 | 两轮实际结果 | 判断界限 | +| --- | --- | --- | +| 计时样本 DOM / 实例 / proxy 清理 Set | 每次挂载 core 113 节点、chapter 125 节点;57 个代理清理项;销毁后均为 0 | 数量是本配置的观察,不是必须永远固定的内部结构;Set 为内部账本,不等同全浏览器原生监听器统计 | +| video | 每次销毁后 paused=true、src 属性移除 | 未证明网络连接、解码器、GPU 内存即时释放 | +| 资源探针自定义代理 | 销毁前收到一次,销毁后再次 dispatch 不增加调用 | 真实 DOM 事件分发但为受控 synthetic 事件,不冒充用户输入 | +| RAF | 销毁前至少 2 次;销毁后 0 次 | 检查 opt-in USE_RAF 路径,不等于所有插件的动画循环通过 | +| pending resize / notice | 每次销毁后仍有 200ms timeout;它执行一次 resize,随后创建新的 2000ms notice timeout;350ms 后仍在记录中 | BASE-PERF-01,见下节;不是已经修复或永久泄漏证明 | + +两轮六组资源状态相同,errors/unhandled 与浏览器 error/warn 日志为空,最终 Artplayer.instances=0。探针先保存未清资源,再用原生取消函数清理其追踪到的剩余句柄并恢复 API;**此额外清理不能算作播放器自己的清理成功**。追踪器本身会临时持有回调/句柄,未进行 heap snapshot、强制 GC、detached-node 可达性或进程 RSS 测量,不能给出“无内存泄漏”结论。 + +### BASE-PERF-01:销毁后的 resize 工作 + +受控 window resize 在 destroy 前安排 debounce,destroy 清掉 DOM 代理、现有 Info/Notice timer 和 RAF,但没有取消该 debounce。晚到回调再次 emit resize,aspectRatio setter 又安排 notice timeout。当前源码 resizeInit、utils/time、aspectRatioMix 与发布行为一致;CORE-17 负责取消/晚到事件,关联 CORE-04 生命周期和 CORE-18 notice 防线。 + +后续应增加候选正向断言:销毁后没有该 resize 回调及新通知计时器,同时保持正常 resize 时序和公开快捷键。不要要求候选继续满足历史异常 profile,也不要只延长等待至 timer 自然结束来掩盖失去生命周期归属的问题。旧实现的现象仅在此有界窗口确认,不能直接断言永久保留内存。 + +## 包体积与审查阈值 + +[sizes.json](sizes.json) 分别保存 28 个真实发布 JS 文件和 85 个工作区 JS 文件的 raw/gzip9/Brotli6 字节数。工作区覆盖 21 库并匹配 BASE-05 的历史观察哈希,可能包含陈旧 dist,不能当作 21 包的真实 npm 版本。文档站没有库 bundle 入口,其站点体积由 SITE/ENG-08 单独测量。压缩使用固定 Node 24.21.0 及记录的 zlib/Brotli 版本,单文件独立压缩不是生产 HTTP 实际传输量或安装后大小。 + +| 发布主文件 | raw bytes | gzip level 9 | Brotli quality 6 | +| --- | ---: | ---: | ---: | +| core .js | 134037 | 35486 | 32533 | +| core .legacy.js | 135748 | 36021 | 33045 | +| core .mjs | 208826 | 43868 | 40443 | +| chapter .js / .legacy.js,各自 | 4973 | 1850 | 1653 | +| chapter .mjs | 7721 | 2126 | 1904 | + +阈值是初始审查规则,不是测得的收益承诺或单次自动发布裁决: + +- 同环境中位数增加超过 max(25%, 绝对容差) 才产生计时 review signal;constructor/destroy 容差 2ms,ready 50ms,play Promise 10ms。绝对容差防止毫秒级噪声因比例被夸大;ready 容差还考虑 RAF/媒体噪声。发布前 ENG-08/MOD-03 结合更多配对样本复核阈值。 +- 同一分发文件、相同压缩方法增加超过 max(5%, 1024 bytes) 必须解释。不同格式、总安装体积、不同来源不可混比;较小变化不豁免接口或依赖审查。 +- 新增可复现的销毁后监听响应、RAF、DOM/实例持续增长或未说明残留资源属于功能/生命周期阻断项,不能用计时改善抵消。BASE-PERF-01 作为已有问题有明确修复责任。 + +## 命令和维护入口 + +1. 运行 `node refactor/scripts/browser-server.mjs`,打开 `http://127.0.0.1:8083/fixtures/performance.html` 并点击 Run performance baseline。保持前台,采样期间不要同时构建/压缩或运行其他重负载。 +2. 显示 Saved 12 timing samples and 6 resource probes / errors 0 后,运行 `node refactor/scripts/performance.mjs --compare refactor/.cache/reports/performance.json`。比较会验证来源、媒体、前台状态及基本清理,再输出中位数、计时审查信号和资源 profile 差异;时间信号要求配对复测。 +3. 重载重复,保留每轮报告,不覆盖历史证据。`performance.mjs --check` 只校验已存报告,不重跑浏览器。`sizes.mjs --check` 会重算真实发布文件压缩大小,工作区部分仍为历史清单。 +4. `node --test refactor/scripts/performance.test.mjs` / `yarn test:baseline` 包含假播放、后台、缺样本、清理失败、设备不一致、重大变慢和体积遗漏负例。计时比较排除预热,并允许候选修正历史残留后报告明确差异。 + +performance.js 负责实际采样与有限资源追踪,performance.mjs 负责数据有效性、统计和比较,sizes.mjs 负责内容哈希与压缩。任务归属:BASE-06 提供首批基线;ENG-08 参数化候选与趋势/体积准入;MOD-03 核心性能、MOD-04 重型插件/proxy、CORE-17/04/18 处理已复现残留。未覆盖的字幕/worker/模型/网络/GPU/真机长期资源验证仍由对应包和发布复盘承担。 diff --git a/refactor/baselines/performance.json b/refactor/baselines/performance.json new file mode 100644 index 000000000..56056defc --- /dev/null +++ b/refactor/baselines/performance.json @@ -0,0 +1,1488 @@ +{ + "schemaVersion": 1, + "task": "BASE-06", + "sourceCommit": "c7a375e1", + "browserSurface": "Codex in-app browser", + "timingThresholds": { + "constructorMs": { + "relative": 0.25, + "absoluteMs": 2 + }, + "readyMs": { + "relative": 0.25, + "absoluteMs": 50 + }, + "playResolvedMs": { + "relative": 0.25, + "absoluteMs": 10 + }, + "destroyMs": { + "relative": 0.25, + "absoluteMs": 2 + } + }, + "runs": [ + { + "kind": "performance", + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", + "language": "zh-CN", + "hardwareConcurrency": 12, + "deviceMemory": 32, + "devicePixelRatio": 1, + "viewport": { + "width": 890, + "height": 1272 + }, + "visibility": "visible" + }, + "scripts": [ + "/releases/artplayer/dist/artplayer.js", + "/releases/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js", + "/fixtures/performance.js" + ], + "samples": [ + { + "configuration": "core", + "warmup": true, + "round": 0, + "constructorMs": 8.099999994039536, + "readyMs": 27.700000002980232, + "playResolvedMs": 5, + "playbackObservedMs": 234.5, + "mediaProgress": 0.16374, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 3.5999999940395355, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": true, + "round": 0, + "constructorMs": 7.5999999940395355, + "readyMs": 28.799999997019768, + "playResolvedMs": 2.0999999940395355, + "playbackObservedMs": 233.09999999403954, + "mediaProgress": 0.164939, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 1.5, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 1, + "constructorMs": 3, + "readyMs": 15.800000011920929, + "playResolvedMs": 3.2999999970197678, + "playbackObservedMs": 233, + "mediaProgress": 0.16683, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 2.800000011920929, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 1, + "constructorMs": 6.9000000059604645, + "readyMs": 30.700000002980232, + "playResolvedMs": 3.5999999940395355, + "playbackObservedMs": 233.29999999701977, + "mediaProgress": 0.161376, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3.4000000059604645, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 2, + "constructorMs": 5, + "readyMs": 12.400000005960464, + "playResolvedMs": 2.5, + "playbackObservedMs": 217.5, + "mediaProgress": 0.158314, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 5.799999997019768, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 2, + "constructorMs": 3.8999999910593033, + "readyMs": 27, + "playResolvedMs": 4.399999991059303, + "playbackObservedMs": 232.29999999701977, + "mediaProgress": 0.161005, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3.1000000089406967, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 3, + "constructorMs": 5.600000008940697, + "readyMs": 31.200000002980232, + "playResolvedMs": 4.0999999940395355, + "playbackObservedMs": 215.79999999701977, + "mediaProgress": 0.151102, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 3, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 3, + "constructorMs": 4.700000002980232, + "readyMs": 14.700000002980232, + "playResolvedMs": 1.9000000059604645, + "playbackObservedMs": 215.40000000596046, + "mediaProgress": 0.15072, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 4, + "constructorMs": 7, + "readyMs": 30.19999998807907, + "playResolvedMs": 3, + "playbackObservedMs": 233.30000001192093, + "mediaProgress": 0.161219, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 3, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 4, + "constructorMs": 7.5, + "readyMs": 29.799999997019768, + "playResolvedMs": 3.4000000059604645, + "playbackObservedMs": 233.70000000298023, + "mediaProgress": 0.170126, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3.1000000089406967, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 5, + "constructorMs": 6.200000002980232, + "readyMs": 31.099999994039536, + "playResolvedMs": 3, + "playbackObservedMs": 232.5, + "mediaProgress": 0.151235, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 3.199999988079071, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 5, + "constructorMs": 5.700000002980232, + "readyMs": 30.099999994039536, + "playResolvedMs": 2.9000000059604645, + "playbackObservedMs": 232.90000000596046, + "mediaProgress": 0.161492, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3.7000000029802322, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + } + ], + "resources": [ + { + "configuration": "core", + "round": 0, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 364.30000001192093, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "chapter", + "round": 0, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 354.3999999910593, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "core", + "round": 1, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 361.1000000089407, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "chapter", + "round": 1, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 358.8999999910593, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "core", + "round": 2, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 364.59999999403954, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "chapter", + "round": 2, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 350.20000000298023, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + } + ], + "errors": [], + "unhandled": [], + "visibilityChanges": [], + "remainingInstances": 0, + "capture": { + "capturedAt": "2026-09-10T12:39:24.007Z", + "releases": [ + { + "name": "artplayer", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js", + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f" + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0" + } + ], + "fixtureHashAlgorithm": "sha256-lf", + "fixtures": { + "performance.html": "0a039781773f0c90edf6cb9fbe11049d321ee971dae8ae0b8779707648775508", + "performance.js": "f72abbead56dc6499c5f08cebbd90fffc9fa9ca42d788f71ddcce31f70259a0f" + }, + "media": { + "video.mp4": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980" + } + } + }, + { + "kind": "performance", + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", + "language": "zh-CN", + "hardwareConcurrency": 12, + "deviceMemory": 32, + "devicePixelRatio": 1, + "viewport": { + "width": 890, + "height": 1272 + }, + "visibility": "visible" + }, + "scripts": [ + "/releases/artplayer/dist/artplayer.js", + "/releases/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js", + "/fixtures/performance.js" + ], + "samples": [ + { + "configuration": "core", + "warmup": true, + "round": 0, + "constructorMs": 10, + "readyMs": 42.8999999910593, + "playResolvedMs": 5.100000008940697, + "playbackObservedMs": 232.79999999701977, + "mediaProgress": 0.160365, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 1.2000000029802322, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": true, + "round": 0, + "constructorMs": 2.9000000059604645, + "readyMs": 15.900000005960464, + "playResolvedMs": 1.2999999970197678, + "playbackObservedMs": 216.79999999701977, + "mediaProgress": 0.1609, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 1, + "constructorMs": 4.0999999940395355, + "readyMs": 12.799999997019768, + "playResolvedMs": 3.1000000089406967, + "playbackObservedMs": 216.6000000089407, + "mediaProgress": 0.1511, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 1.7999999970197678, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 1, + "constructorMs": 3.1000000089406967, + "readyMs": 14.5, + "playResolvedMs": 1.9000000059604645, + "playbackObservedMs": 217.40000000596046, + "mediaProgress": 0.157789, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3.2000000029802322, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 2, + "constructorMs": 3.6000000089406967, + "readyMs": 12.600000008940697, + "playResolvedMs": 1.5999999940395355, + "playbackObservedMs": 217, + "mediaProgress": 0.150429, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 0.9000000059604645, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 2, + "constructorMs": 1.7000000029802322, + "readyMs": 15.700000002980232, + "playResolvedMs": 1.2999999970197678, + "playbackObservedMs": 217.5, + "mediaProgress": 0.161528, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 4, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 3, + "constructorMs": 3.6000000089406967, + "readyMs": 12, + "playResolvedMs": 2.5, + "playbackObservedMs": 217.29999999701977, + "mediaProgress": 0.153246, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 2.7999999970197678, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 3, + "constructorMs": 5.600000008940697, + "readyMs": 39.5, + "playResolvedMs": 1.2000000029802322, + "playbackObservedMs": 207.70000000298023, + "mediaProgress": 0.160302, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 2.5, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 4, + "constructorMs": 4.5999999940395355, + "readyMs": 14.400000005960464, + "playResolvedMs": 3.3999999910593033, + "playbackObservedMs": 232.69999998807907, + "mediaProgress": 0.164161, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 3.3999999910593033, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 4, + "constructorMs": 5, + "readyMs": 12.400000005960464, + "playResolvedMs": 2.5, + "playbackObservedMs": 217.5, + "mediaProgress": 0.158221, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 2.9000000059604645, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "core", + "warmup": false, + "round": 5, + "constructorMs": 5.5, + "readyMs": 30.30000001192093, + "playResolvedMs": 4.199999988079071, + "playbackObservedMs": 233, + "mediaProgress": 0.161452, + "videoWidth": 640, + "nodes": 113, + "proxyCleanupEntries": 57, + "destroyMs": 3.1000000089406967, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + }, + { + "configuration": "chapter", + "warmup": false, + "round": 5, + "constructorMs": 5.799999997019768, + "readyMs": 12.099999994039536, + "playResolvedMs": 1.4000000059604645, + "playbackObservedMs": 234.30000001192093, + "mediaProgress": 0.164744, + "videoWidth": 640, + "nodes": 125, + "proxyCleanupEntries": 57, + "destroyMs": 3.9000000059604645, + "afterDestroy": { + "nodes": 0, + "instances": 0, + "proxyCleanupEntries": 0, + "paused": true, + "sourceAttribute": null + } + } + ], + "resources": [ + { + "configuration": "core", + "round": 0, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 364.29999999701977, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "chapter", + "round": 0, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 350.8999999910593, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "core", + "round": 1, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 356.59999999403954, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "chapter", + "round": 1, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 357.5, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "core", + "round": 2, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 362.8999999910593, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + }, + { + "configuration": "chapter", + "round": 2, + "useRaf": true, + "before": { + "timers": [ + { + "kind": "timeout", + "delay": 1000 + }, + { + "kind": "raf", + "delay": null + }, + { + "kind": "timeout", + "delay": 2000 + }, + { + "kind": "timeout", + "delay": 200 + } + ], + "proxyCleanupEntries": 58, + "frames": 2, + "proxyCalls": 1 + }, + "immediatelyAfter": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "after": { + "observedMs": 364.5, + "timers": [ + { + "kind": "timeout", + "delay": 2000 + } + ], + "lateCallbacks": [ + { + "kind": "timeout", + "delay": 200 + } + ], + "lateResizeEvents": 1, + "framesAfterDestroy": 0, + "proxyCalls": 1, + "proxyCleanupEntries": 0, + "nodes": 0, + "instances": 0 + } + } + ], + "errors": [], + "unhandled": [], + "visibilityChanges": [], + "remainingInstances": 0, + "capture": { + "capturedAt": "2026-09-10T12:42:22.321Z", + "releases": [ + { + "name": "artplayer", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js", + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f" + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0" + } + ], + "fixtureHashAlgorithm": "sha256-lf", + "fixtures": { + "performance.html": "0a039781773f0c90edf6cb9fbe11049d321ee971dae8ae0b8779707648775508", + "performance.js": "f72abbead56dc6499c5f08cebbd90fffc9fa9ca42d788f71ddcce31f70259a0f" + }, + "media": { + "video.mp4": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980" + } + } + } + ], + "summaries": [ + { + "core": { + "constructorMs": { + "count": 5, + "min": 3, + "median": 5.600000008940697, + "max": 7 + }, + "readyMs": { + "count": 5, + "min": 12.400000005960464, + "median": 30.19999998807907, + "max": 31.200000002980232 + }, + "playResolvedMs": { + "count": 5, + "min": 2.5, + "median": 3, + "max": 4.0999999940395355 + }, + "destroyMs": { + "count": 5, + "min": 2.800000011920929, + "median": 3, + "max": 5.799999997019768 + } + }, + "chapter": { + "constructorMs": { + "count": 5, + "min": 3.8999999910593033, + "median": 5.700000002980232, + "max": 7.5 + }, + "readyMs": { + "count": 5, + "min": 14.700000002980232, + "median": 29.799999997019768, + "max": 30.700000002980232 + }, + "playResolvedMs": { + "count": 5, + "min": 1.9000000059604645, + "median": 3.4000000059604645, + "max": 4.399999991059303 + }, + "destroyMs": { + "count": 5, + "min": 3, + "median": 3.1000000089406967, + "max": 3.7000000029802322 + } + } + }, + { + "core": { + "constructorMs": { + "count": 5, + "min": 3.6000000089406967, + "median": 4.0999999940395355, + "max": 5.5 + }, + "readyMs": { + "count": 5, + "min": 12, + "median": 12.799999997019768, + "max": 30.30000001192093 + }, + "playResolvedMs": { + "count": 5, + "min": 1.5999999940395355, + "median": 3.1000000089406967, + "max": 4.199999988079071 + }, + "destroyMs": { + "count": 5, + "min": 0.9000000059604645, + "median": 2.7999999970197678, + "max": 3.3999999910593033 + } + }, + "chapter": { + "constructorMs": { + "count": 5, + "min": 1.7000000029802322, + "median": 5, + "max": 5.799999997019768 + }, + "readyMs": { + "count": 5, + "min": 12.099999994039536, + "median": 14.5, + "max": 39.5 + }, + "playResolvedMs": { + "count": 5, + "min": 1.2000000029802322, + "median": 1.4000000059604645, + "max": 2.5 + }, + "destroyMs": { + "count": 5, + "min": 2.5, + "median": 3.2000000029802322, + "max": 4 + } + } + } + ], + "verification": { + "comparison": { + "summary": { + "core": { + "constructorMs": { + "count": 5, + "min": 3.6000000089406967, + "median": 4.0999999940395355, + "max": 5.5 + }, + "readyMs": { + "count": 5, + "min": 12, + "median": 12.799999997019768, + "max": 30.30000001192093 + }, + "playResolvedMs": { + "count": 5, + "min": 1.5999999940395355, + "median": 3.1000000089406967, + "max": 4.199999988079071 + }, + "destroyMs": { + "count": 5, + "min": 0.9000000059604645, + "median": 2.7999999970197678, + "max": 3.3999999910593033 + } + }, + "chapter": { + "constructorMs": { + "count": 5, + "min": 1.7000000029802322, + "median": 5, + "max": 5.799999997019768 + }, + "readyMs": { + "count": 5, + "min": 12.099999994039536, + "median": 14.5, + "max": 39.5 + }, + "playResolvedMs": { + "count": 5, + "min": 1.2000000029802322, + "median": 1.4000000059604645, + "max": 2.5 + }, + "destroyMs": { + "count": 5, + "min": 2.5, + "median": 3.2000000029802322, + "max": 4 + } + } + }, + "reviewSignals": [], + "resourceProfileChanged": false + } + }, + "findings": [ + { + "id": "BASE-PERF-01", + "owner": "CORE-17", + "relatedOwners": [ + "CORE-04", + "CORE-18" + ], + "observation": "Pending resize debounce runs after destroy and schedules a new notice timer; observed for at least 350ms, not proof of a permanent leak." + } + ], + "hostContext": { + "source": "Windows CIM read after measurement; not a browser benchmark API", + "cpu": "12th Gen Intel(R) Core(TM) i5-12400F", + "physicalCores": 6, + "logicalProcessors": 12, + "memoryGiB": 31.8 + } +} diff --git a/refactor/baselines/sizes.json b/refactor/baselines/sizes.json new file mode 100644 index 000000000..03c3772bd --- /dev/null +++ b/refactor/baselines/sizes.json @@ -0,0 +1,1036 @@ +{ + "schemaVersion": 1, + "task": "BASE-06", + "sourceCommit": "c7a375e1", + "capturedAt": "2026-09-10T12:43:04.099Z", + "node": "v24.21.0", + "zlib": "1.3.2.1-motley-8002e91", + "brotli": "1.2.0", + "note": "Independent file compression: gzip level 9, Brotli quality 6. Not production HTTP transfer size. Workspace files match BASE-05 observed hashes and may be stale; do not treat them as verified npm releases. Documentation site has no library bundle entry.", + "reviewThreshold": { + "relative": 0.05, + "absoluteBytes": 1024, + "rule": "Review if same-file increase exceeds max(5 percent, 1024 bytes); not an optimization or automatic release verdict" + }, + "published": [ + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/artplayer.js", + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "raw": 134037, + "gzip9": 35486, + "brotli6": 32533 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/artplayer.legacy.js", + "sha256": "cef83f9dac8a343060329ea6c5d603f0e7811fd0fd6c1590ae1c47f9e0afc9bb", + "raw": 135748, + "gzip9": 36021, + "brotli6": 33045 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/artplayer.mjs", + "sha256": "a1800b37ac7549f52fa960fe28bc6e1c69ec18bd4bc9a7e2534aecc3df10eb3e", + "raw": 208826, + "gzip9": 43868, + "brotli6": 40443 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/ar.js", + "sha256": "61c43469ee95a00dc624583f418653e3dcbf48b24ae6a4c203ad986234852365", + "raw": 1742, + "gzip9": 868, + "brotli6": 823 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/ar.mjs", + "sha256": "68922fdc9f80d3321ea0654c8ff8f5875b6715103d2b14ffcf17f93738b39cf9", + "raw": 1686, + "gzip9": 749, + "brotli6": 689 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/cs.js", + "sha256": "ad7bbf8853204a6955ef84bfbd932cab3f141a7e73a8145f28ba7a5b2b83b7c0", + "raw": 1441, + "gzip9": 797, + "brotli6": 770 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/cs.mjs", + "sha256": "29a489be767fda9d46b59c9adbaad323f17a5e67daf7f7aa78b7ce8fb5b4d63c", + "raw": 1385, + "gzip9": 688, + "brotli6": 680 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/es.js", + "sha256": "aaa95cc9645b734c58fba06fdbcdda962c4cb3a0e7eb6206f06d6168208cd86b", + "raw": 1398, + "gzip9": 733, + "brotli6": 703 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/es.mjs", + "sha256": "331d2ef74da2eec8f3304fec0b5340c795d9c1eec24e08167c9a7b66bf30973b", + "raw": 1342, + "gzip9": 630, + "brotli6": 613 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/fa.js", + "sha256": "c998467261cbdfd2c3fb16c0ae4b2c48c409f42f41aea35ed94766f1e12819d5", + "raw": 1597, + "gzip9": 855, + "brotli6": 812 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/fa.mjs", + "sha256": "b4c98894b46975cebf5ea9088bcf0b5bdfd408bc3c82f4e2e9128133c7de2d4d", + "raw": 1541, + "gzip9": 737, + "brotli6": 687 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/fr.js", + "sha256": "6b06e16b1fb01c8695fb5c10b32a00421399180de5dc5d845ce17010f2fd3e66", + "raw": 1413, + "gzip9": 733, + "brotli6": 709 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/fr.mjs", + "sha256": "abe8e3548fa7304f302e2c74818a6176edee8c3b65e33cb6f62e96d130749ee5", + "raw": 1357, + "gzip9": 634, + "brotli6": 624 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/id.js", + "sha256": "a5832c4bfc03c7f16418bf4b324d9773895541dc823ece9114741794737aad74", + "raw": 1347, + "gzip9": 703, + "brotli6": 679 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/id.mjs", + "sha256": "a40a433be6ca1ac1ee1810fe805ee60784d349e59b9ad82ea80201d660db4e68", + "raw": 1291, + "gzip9": 599, + "brotli6": 599 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/pl.js", + "sha256": "2491487062bdfc91d60f17c6e9a83ee196c991216d15f525fe3536b98873da2c", + "raw": 1402, + "gzip9": 791, + "brotli6": 765 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/pl.mjs", + "sha256": "ad8ea2c03785edbfe1f35761bec21b8a04213f9fa88a794d850bb3973564930f", + "raw": 1346, + "gzip9": 680, + "brotli6": 678 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/ru.js", + "sha256": "2d3a9ee6533cb6fa961f0a24a25ce63cc76992fca17e1769b6fa75104f8a1529", + "raw": 1998, + "gzip9": 995, + "brotli6": 938 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/ru.mjs", + "sha256": "a89b8c36cfd7c181c2b31aa823deda3d7c37b56b2b81746338c52ea1c1d93352", + "raw": 1942, + "gzip9": 881, + "brotli6": 808 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/tr.js", + "sha256": "438b06a4c3f479a2901b0085eaa7bdba8eaef87bdb07ff3faa559c85403fe433", + "raw": 1342, + "gzip9": 754, + "brotli6": 725 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/tr.mjs", + "sha256": "cc96f9cbb9c1587d5cd8f25b0b9800678ad76e93de089ae42c38dee16e6fe8fa", + "raw": 1286, + "gzip9": 646, + "brotli6": 638 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/vi.js", + "sha256": "1ffbd62843a902ea2967eab46432d3fc548f5633e53eed104314b523cacc46a8", + "raw": 1489, + "gzip9": 824, + "brotli6": 787 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/vi.mjs", + "sha256": "7872ded84622bcda20e8c1d70c597c2ba071b50701900efa47cc4683ee17f1a2", + "raw": 1433, + "gzip9": 712, + "brotli6": 696 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/zh-tw.js", + "sha256": "2d43577f98456488196921e1ec721695a95e1b8c827077286b02ce3fb987e8ff", + "raw": 1255, + "gzip9": 820, + "brotli6": 751 + }, + { + "name": "artplayer", + "version": "5.4.0", + "path": "package/dist/i18n/zh-tw.mjs", + "sha256": "7ec638b4babe59d3ab1d3e64abdd74a96fefb260a50e2cd7240f8d50a1340925", + "raw": 1197, + "gzip9": 695, + "brotli6": 620 + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "path": "package/dist/artplayer-plugin-chapter.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0", + "raw": 4973, + "gzip9": 1850, + "brotli6": 1653 + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "path": "package/dist/artplayer-plugin-chapter.legacy.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0", + "raw": 4973, + "gzip9": 1850, + "brotli6": 1653 + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "path": "package/dist/artplayer-plugin-chapter.mjs", + "sha256": "61342a069b84e0731d44a2464d0dfe637d1514d0037c740371fc866c516d2209", + "raw": 7721, + "gzip9": 2126, + "brotli6": 1904 + } + ], + "workspaceObserved": [ + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/artplayer.js", + "sha256": "2ae05beeca573455af87b63259758f151f3c69e2a5553d6996cf1fcb161396b7", + "raw": 134753, + "gzip9": 35735, + "brotli6": 32757 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/artplayer.legacy.js", + "sha256": "3b85d82e59f0aaa61816ab66eb1a21735144a56310171b2ce571a00f2fb6f443", + "raw": 136473, + "gzip9": 36265, + "brotli6": 33246 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/artplayer.mjs", + "sha256": "f7df4dd5907dc58aaee1e456d7838e2ecfa352a74e219c08c318e80d98f94a0f", + "raw": 215072, + "gzip9": 44382, + "brotli6": 40944 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/ar.js", + "sha256": "61c43469ee95a00dc624583f418653e3dcbf48b24ae6a4c203ad986234852365", + "raw": 1742, + "gzip9": 868, + "brotli6": 823 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/ar.mjs", + "sha256": "fe7bf234e28e7c4405055051d230527ea5deb268168fe68bdc253cffd1af2703", + "raw": 1728, + "gzip9": 755, + "brotli6": 690 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/cs.js", + "sha256": "ad7bbf8853204a6955ef84bfbd932cab3f141a7e73a8145f28ba7a5b2b83b7c0", + "raw": 1441, + "gzip9": 797, + "brotli6": 770 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/cs.mjs", + "sha256": "a475449e19cffb67eb5762787752027bdc674f0d0d55fb10fbb5cd693dabe9da", + "raw": 1427, + "gzip9": 696, + "brotli6": 681 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/es.js", + "sha256": "aaa95cc9645b734c58fba06fdbcdda962c4cb3a0e7eb6206f06d6168208cd86b", + "raw": 1398, + "gzip9": 733, + "brotli6": 703 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/es.mjs", + "sha256": "f36b65a70247c580b9560ed5205a69916bf5d2b5c8bbbcaa41a4d015a0016272", + "raw": 1384, + "gzip9": 637, + "brotli6": 622 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/fa.js", + "sha256": "c998467261cbdfd2c3fb16c0ae4b2c48c409f42f41aea35ed94766f1e12819d5", + "raw": 1597, + "gzip9": 855, + "brotli6": 812 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/fa.mjs", + "sha256": "f44d249236e753606decc8b800f5f32888c6002d81975e1b1674f9a4c395cb8f", + "raw": 1583, + "gzip9": 745, + "brotli6": 690 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/fr.js", + "sha256": "6b06e16b1fb01c8695fb5c10b32a00421399180de5dc5d845ce17010f2fd3e66", + "raw": 1413, + "gzip9": 733, + "brotli6": 709 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/fr.mjs", + "sha256": "5b532775e7704e41f5ea5500d1d31d758f77b87c0d3e2e014f61ac3227cb3544", + "raw": 1399, + "gzip9": 642, + "brotli6": 634 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/id.js", + "sha256": "a5832c4bfc03c7f16418bf4b324d9773895541dc823ece9114741794737aad74", + "raw": 1347, + "gzip9": 703, + "brotli6": 679 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/id.mjs", + "sha256": "b18f86de99db2c5e71949f5c4e8e39130025b6d86e3dc61541fffd55826053d4", + "raw": 1333, + "gzip9": 607, + "brotli6": 600 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/pl.js", + "sha256": "2491487062bdfc91d60f17c6e9a83ee196c991216d15f525fe3536b98873da2c", + "raw": 1402, + "gzip9": 791, + "brotli6": 765 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/pl.mjs", + "sha256": "ba8f39c97ff5a4e5d28d62b91020c3b1f4844321eaad97a38d16beef58040be3", + "raw": 1388, + "gzip9": 688, + "brotli6": 677 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/ru.js", + "sha256": "2d3a9ee6533cb6fa961f0a24a25ce63cc76992fca17e1769b6fa75104f8a1529", + "raw": 1998, + "gzip9": 995, + "brotli6": 938 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/ru.mjs", + "sha256": "2e9ffc111f7d85170984d4e8a3d26b47eb4045a524696cc154dec11bd1495d96", + "raw": 1984, + "gzip9": 889, + "brotli6": 810 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/tr.js", + "sha256": "438b06a4c3f479a2901b0085eaa7bdba8eaef87bdb07ff3faa559c85403fe433", + "raw": 1342, + "gzip9": 754, + "brotli6": 725 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/tr.mjs", + "sha256": "a5a527bb40608558c1fe515e2a5a3552e6198eafbd79a2d10305f89cc3e7743f", + "raw": 1328, + "gzip9": 653, + "brotli6": 641 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/vi.js", + "sha256": "1ffbd62843a902ea2967eab46432d3fc548f5633e53eed104314b523cacc46a8", + "raw": 1489, + "gzip9": 824, + "brotli6": 787 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/vi.mjs", + "sha256": "06ac85d56d5d689d3db40e106d23cf06f6bdd867235b928613b9eb31ac3a51f8", + "raw": 1475, + "gzip9": 720, + "brotli6": 703 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/zh-tw.js", + "sha256": "2d43577f98456488196921e1ec721695a95e1b8c827077286b02ce3fb987e8ff", + "raw": 1255, + "gzip9": 820, + "brotli6": 751 + }, + { + "name": "artplayer", + "version": "5.4.1", + "path": "dist/i18n/zh-tw.mjs", + "sha256": "f6acdb6b64566abfa972bb2149ea7968f2f8c44c6d1cbc8813151f20d72c41bf", + "raw": 1239, + "gzip9": 705, + "brotli6": 619 + }, + { + "name": "artplayer-plugin-ads", + "version": "2.1.0", + "path": "dist/artplayer-plugin-ads.js", + "sha256": "ceb2d8a23a6735c161b31a4a427d8a757679aaf8a5a106ef40a6f047061f1df8", + "raw": 5620, + "gzip9": 1930, + "brotli6": 1734 + }, + { + "name": "artplayer-plugin-ads", + "version": "2.1.0", + "path": "dist/artplayer-plugin-ads.legacy.js", + "sha256": "1e10c3a6b51de18e54cd02dd342ab4010fc466de6257659cf1d2e087642aa9a3", + "raw": 5951, + "gzip9": 2090, + "brotli6": 1889 + }, + { + "name": "artplayer-plugin-ads", + "version": "2.1.0", + "path": "dist/artplayer-plugin-ads.mjs", + "sha256": "5d53a55bd6fcc64a01abe460a6e9161240ee23bc8b4cf160c22c602de03f6356", + "raw": 9577, + "gzip9": 2330, + "brotli6": 2119 + }, + { + "name": "artplayer-plugin-ambilight", + "version": "1.1.0", + "path": "dist/artplayer-plugin-ambilight.js", + "sha256": "8f32290c65a13b0a3026dee0045fef4125a9c9ac788ecb1675aef05f79172693", + "raw": 1844, + "gzip9": 1039, + "brotli6": 936 + }, + { + "name": "artplayer-plugin-ambilight", + "version": "1.1.0", + "path": "dist/artplayer-plugin-ambilight.legacy.js", + "sha256": "8f32290c65a13b0a3026dee0045fef4125a9c9ac788ecb1675aef05f79172693", + "raw": 1844, + "gzip9": 1039, + "brotli6": 936 + }, + { + "name": "artplayer-plugin-ambilight", + "version": "1.1.0", + "path": "dist/artplayer-plugin-ambilight.mjs", + "sha256": "b9b56fdec4a39b5d9dd2f839ddefbe5e01d74823d5879b75bb776825ed1b8140", + "raw": 3433, + "gzip9": 1212, + "brotli6": 1098 + }, + { + "name": "artplayer-plugin-asr", + "version": "2.1.0", + "path": "dist/artplayer-plugin-asr.js", + "sha256": "7f975b131a9ea207f88a157aafec778bc4906230500db9b30f5bbfbf327b251d", + "raw": 5064, + "gzip9": 2218, + "brotli6": 2041 + }, + { + "name": "artplayer-plugin-asr", + "version": "2.1.0", + "path": "dist/artplayer-plugin-asr.legacy.js", + "sha256": "8b9f07ce97a8ce2e4f29e41e79257be530fd7a76558369d34c3d7497ea606c33", + "raw": 5468, + "gzip9": 2344, + "brotli6": 2173 + }, + { + "name": "artplayer-plugin-asr", + "version": "2.1.0", + "path": "dist/artplayer-plugin-asr.mjs", + "sha256": "bf222a738fc7a8946769ebaf37c89d05968ec0d74fe1e6e5ec653e560cbb6eb8", + "raw": 9309, + "gzip9": 2808, + "brotli6": 2636 + }, + { + "name": "artplayer-plugin-audio-track", + "version": "1.1.0", + "path": "dist/artplayer-plugin-audio-track.js", + "sha256": "473f39f1d750f0722cca7e857c12c3c978ebe3c3b2352e325f1248318e508683", + "raw": 1471, + "gzip9": 676, + "brotli6": 620 + }, + { + "name": "artplayer-plugin-audio-track", + "version": "1.1.0", + "path": "dist/artplayer-plugin-audio-track.legacy.js", + "sha256": "d8dc74bce76112d7430c98e3165605e32fde5d3c603be4b496eac58f574576cb", + "raw": 1497, + "gzip9": 694, + "brotli6": 639 + }, + { + "name": "artplayer-plugin-audio-track", + "version": "1.1.0", + "path": "dist/artplayer-plugin-audio-track.mjs", + "sha256": "df9e2215d283450ba799545f38d26ed582dd245efe5abc31641bf88eb8d6faeb", + "raw": 2330, + "gzip9": 730, + "brotli6": 663 + }, + { + "name": "artplayer-plugin-auto-thumbnail", + "version": "1.1.0", + "path": "dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "ce1f1472bf02b836dd85b92eadf2f00786c02cde11a20ac2801136a0f675d12a", + "raw": 1299, + "gzip9": 734, + "brotli6": 661 + }, + { + "name": "artplayer-plugin-auto-thumbnail", + "version": "1.1.0", + "path": "dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "3561e51e0a5f294b56641bf0884a24a4a9e326582d2e99416a04f9526c7d0cc0", + "raw": 1949, + "gzip9": 1023, + "brotli6": 946 + }, + { + "name": "artplayer-plugin-auto-thumbnail", + "version": "1.1.0", + "path": "dist/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "c362a86f38b3515c5d53a368cd94602e4531e93c638ed325fc179ec8664607f9", + "raw": 1894, + "gzip9": 793, + "brotli6": 712 + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "path": "dist/artplayer-plugin-chapter.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0", + "raw": 4973, + "gzip9": 1850, + "brotli6": 1653 + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "path": "dist/artplayer-plugin-chapter.legacy.js", + "sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0", + "raw": 4973, + "gzip9": 1850, + "brotli6": 1653 + }, + { + "name": "artplayer-plugin-chapter", + "version": "1.1.0", + "path": "dist/artplayer-plugin-chapter.mjs", + "sha256": "226c9ab5fbf7ea8bb3d96bd73a1cec6f86eb054f369aafb686333b65e85edc0f", + "raw": 7880, + "gzip9": 2137, + "brotli6": 1921 + }, + { + "name": "artplayer-plugin-chromecast", + "version": "1.1.0", + "path": "dist/artplayer-plugin-chromecast.js", + "sha256": "4959661fc7f22eee3d32363bb0392a486c44499ec4a9c608ccfea55de29aaac6", + "raw": 3934, + "gzip9": 1851, + "brotli6": 1708 + }, + { + "name": "artplayer-plugin-chromecast", + "version": "1.1.0", + "path": "dist/artplayer-plugin-chromecast.legacy.js", + "sha256": "e2ba7aebec099884bb806f3caf77f9ff739ff9f236aefdfd0a4e69a8295c9d01", + "raw": 4472, + "gzip9": 2029, + "brotli6": 1870 + }, + { + "name": "artplayer-plugin-chromecast", + "version": "1.1.0", + "path": "dist/artplayer-plugin-chromecast.mjs", + "sha256": "0c96dad4cee48c353d91d19852243d566345cc7977e1e19ccbd9deba73190b8b", + "raw": 6709, + "gzip9": 2126, + "brotli6": 1970 + }, + { + "name": "artplayer-plugin-danmuku", + "version": "5.3.0", + "path": "dist/artplayer-plugin-danmuku.js", + "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f", + "raw": 52390, + "gzip9": 14529, + "brotli6": 13578 + }, + { + "name": "artplayer-plugin-danmuku", + "version": "5.3.0", + "path": "dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc", + "raw": 53263, + "gzip9": 14894, + "brotli6": 14001 + }, + { + "name": "artplayer-plugin-danmuku", + "version": "5.3.0", + "path": "dist/artplayer-plugin-danmuku.mjs", + "sha256": "f9e1c59adac3f399c022c5e5e6aef7c134e489862915141f25f931316e254f12", + "raw": 77263, + "gzip9": 18502, + "brotli6": 17068 + }, + { + "name": "artplayer-plugin-danmuku-mask", + "version": "1.1.0", + "path": "dist/artplayer-plugin-danmuku-mask.js", + "sha256": "07cebbfbf77f8ec4b2df6a77d7d7300345b86f2993e220ff14e1aae530e4fb31", + "raw": 904243, + "gzip9": 229221, + "brotli6": 201958 + }, + { + "name": "artplayer-plugin-danmuku-mask", + "version": "1.1.0", + "path": "dist/artplayer-plugin-danmuku-mask.legacy.js", + "sha256": "a678a3c0f6d3920bee3b66af9943b3612935028bff61c96677b8eaf8ef7cc8f0", + "raw": 907166, + "gzip9": 229714, + "brotli6": 202538 + }, + { + "name": "artplayer-plugin-danmuku-mask", + "version": "1.1.0", + "path": "dist/artplayer-plugin-danmuku-mask.mjs", + "sha256": "f07030990a8d6adbe6db0acdacbae9db6e6c33f0fb8126e09697184d0ef8396d", + "raw": 1777425, + "gzip9": 320210, + "brotli6": 277273 + }, + { + "name": "artplayer-plugin-dash-control", + "version": "1.1.0", + "path": "dist/artplayer-plugin-dash-control.js", + "sha256": "248aa1162ffb2480965f8e12d766ba6b7d2abfc2d80e65e8a6e8db6bdfa750c5", + "raw": 3450, + "gzip9": 1608, + "brotli6": 1504 + }, + { + "name": "artplayer-plugin-dash-control", + "version": "1.1.0", + "path": "dist/artplayer-plugin-dash-control.legacy.js", + "sha256": "248aa1162ffb2480965f8e12d766ba6b7d2abfc2d80e65e8a6e8db6bdfa750c5", + "raw": 3450, + "gzip9": 1608, + "brotli6": 1504 + }, + { + "name": "artplayer-plugin-dash-control", + "version": "1.1.0", + "path": "dist/artplayer-plugin-dash-control.mjs", + "sha256": "a29459436962e54efc3203d3264264291723593196461a6f116a684e53d7154f", + "raw": 6074, + "gzip9": 1893, + "brotli6": 1767 + }, + { + "name": "artplayer-plugin-document-pip", + "version": "1.1.0", + "path": "dist/artplayer-plugin-document-pip.js", + "sha256": "261d4ae84579c569ade1b1e7e91d51e3bd00c6efbea8c51a48b8283e0f4f3496", + "raw": 4624, + "gzip9": 1776, + "brotli6": 1593 + }, + { + "name": "artplayer-plugin-document-pip", + "version": "1.1.0", + "path": "dist/artplayer-plugin-document-pip.legacy.js", + "sha256": "8e36b658196dece391add21c249b735f3abfe4082fc968d3dbbbb1ae58a77b31", + "raw": 5356, + "gzip9": 2081, + "brotli6": 1901 + }, + { + "name": "artplayer-plugin-document-pip", + "version": "1.1.0", + "path": "dist/artplayer-plugin-document-pip.mjs", + "sha256": "ba1ae10a27740abc1c516470db8d03d45b88e3550180720bf3353864d629cca2", + "raw": 7106, + "gzip9": 2072, + "brotli6": 1885 + }, + { + "name": "artplayer-plugin-hls-control", + "version": "1.1.0", + "path": "dist/artplayer-plugin-hls-control.js", + "sha256": "7a6ce18a0b16f61f6149e0ea77efbb6e0503f997d39afe84271b59e366759e6d", + "raw": 3156, + "gzip9": 1439, + "brotli6": 1327 + }, + { + "name": "artplayer-plugin-hls-control", + "version": "1.1.0", + "path": "dist/artplayer-plugin-hls-control.legacy.js", + "sha256": "893a3de27f9ed02b4b61f03732aec03bfcaf50d02c9beb15cc8a5767ef9121a0", + "raw": 3182, + "gzip9": 1457, + "brotli6": 1346 + }, + { + "name": "artplayer-plugin-hls-control", + "version": "1.1.0", + "path": "dist/artplayer-plugin-hls-control.mjs", + "sha256": "5e665c994be0cd2c00538ac2e5f79e4fbda34083d4d04cccbe5d0047a9306f24", + "raw": 5158, + "gzip9": 1656, + "brotli6": 1532 + }, + { + "name": "artplayer-plugin-jassub", + "version": "1.1.0", + "path": "dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a", + "raw": 16231, + "gzip9": 5354, + "brotli6": 5116 + }, + { + "name": "artplayer-plugin-jassub", + "version": "1.1.0", + "path": "dist/artplayer-plugin-jassub.legacy.js", + "sha256": "0726c96bf0df2aab2662ac8973bb7676a0ca7f8e4cb806aa44cb3ec9de296e64", + "raw": 16947, + "gzip9": 5639, + "brotli6": 5399 + }, + { + "name": "artplayer-plugin-jassub", + "version": "1.1.0", + "path": "dist/artplayer-plugin-jassub.mjs", + "sha256": "00a39303dd1eba1d78cf164e38dad359dc026bf57a3595175c48f30eb7f8f322", + "raw": 34349, + "gzip9": 10048, + "brotli6": 9589 + }, + { + "name": "artplayer-plugin-multiple-subtitles", + "version": "1.2.0", + "path": "dist/artplayer-plugin-multiple-subtitles.js", + "sha256": "c1cb0b511da88347165cc855185de9450a44835bb34bb802281ccdb854ef603c", + "raw": 13433, + "gzip9": 4913, + "brotli6": 4721 + }, + { + "name": "artplayer-plugin-multiple-subtitles", + "version": "1.2.0", + "path": "dist/artplayer-plugin-multiple-subtitles.legacy.js", + "sha256": "0b17644827e5b199afe8293093c44bfda571821014ff5b087c7c918c630dcc18", + "raw": 14099, + "gzip9": 5198, + "brotli6": 4993 + }, + { + "name": "artplayer-plugin-multiple-subtitles", + "version": "1.2.0", + "path": "dist/artplayer-plugin-multiple-subtitles.mjs", + "sha256": "9c664c8410849d9ff0da1febf1b86357e326b879cb479c8df8d70a1847c97c90", + "raw": 27666, + "gzip9": 6211, + "brotli6": 5931 + }, + { + "name": "artplayer-plugin-vast", + "version": "1.2.0", + "path": "dist/artplayer-plugin-vast.js", + "sha256": "72842ae0b4a44e44a080f88783c0c9bcd0b572fa0e946d817b2c36d960a08bfe", + "raw": 23307, + "gzip9": 6935, + "brotli6": 6667 + }, + { + "name": "artplayer-plugin-vast", + "version": "1.2.0", + "path": "dist/artplayer-plugin-vast.legacy.js", + "sha256": "4b88cec8d12844d639cc7703f88703136a4fe586781d9939bf2e3a0c93b42b60", + "raw": 23571, + "gzip9": 7041, + "brotli6": 6770 + }, + { + "name": "artplayer-plugin-vast", + "version": "1.2.0", + "path": "dist/artplayer-plugin-vast.mjs", + "sha256": "5a66a4b40bbeaa47cb307f9a40935c1a0a73eee880b4df4a4580568df7162576", + "raw": 31443, + "gzip9": 7860, + "brotli6": 7544 + }, + { + "name": "artplayer-plugin-vtt-thumbnail", + "version": "1.1.0", + "path": "dist/artplayer-plugin-vtt-thumbnail.js", + "sha256": "2d8e22cbd191bdac5a6c711b641590c2f8a90ba1aae185dd0e02a00baf000f8d", + "raw": 2325, + "gzip9": 1228, + "brotli6": 1133 + }, + { + "name": "artplayer-plugin-vtt-thumbnail", + "version": "1.1.0", + "path": "dist/artplayer-plugin-vtt-thumbnail.legacy.js", + "sha256": "ecbd0abe12c537687fb058de552c328c71ced71d911e454d0e37887192e9a291", + "raw": 2594, + "gzip9": 1342, + "brotli6": 1244 + }, + { + "name": "artplayer-plugin-vtt-thumbnail", + "version": "1.1.0", + "path": "dist/artplayer-plugin-vtt-thumbnail.mjs", + "sha256": "1f3d0f2bcd68bf69a22fcc3023f1e6ffd4a154099597d899c317cd04a3c28d3b", + "raw": 4408, + "gzip9": 1542, + "brotli6": 1448 + }, + { + "name": "artplayer-proxy-canvas", + "version": "1.1.0", + "path": "dist/artplayer-proxy-canvas.js", + "sha256": "ef489570e13f402f1a60eb256ce3900a0021a3d05699c2ba15adfaaeff184d4c", + "raw": 1927, + "gzip9": 944, + "brotli6": 876 + }, + { + "name": "artplayer-proxy-canvas", + "version": "1.1.0", + "path": "dist/artplayer-proxy-canvas.legacy.js", + "sha256": "7d25f21170c06d067aeaa1c8e79b15f2c0130a27cff1b410532d6c4ef4d2d45f", + "raw": 2210, + "gzip9": 1071, + "brotli6": 1001 + }, + { + "name": "artplayer-proxy-canvas", + "version": "1.1.0", + "path": "dist/artplayer-proxy-canvas.mjs", + "sha256": "406eb15d0194fadfcb2e9cb02aeb1281f1b0c42e66911106a3307c755b48e24c", + "raw": 3973, + "gzip9": 1209, + "brotli6": 1119 + }, + { + "name": "artplayer-proxy-mediabunny", + "version": "1.2.0", + "path": "dist/artplayer-proxy-mediabunny.js", + "sha256": "7d088c0abe2f3c1e2857cb0547405ce1e5588630f28e484a64211fc675322bbd", + "raw": 370208, + "gzip9": 93700, + "brotli6": 85708 + }, + { + "name": "artplayer-proxy-mediabunny", + "version": "1.2.0", + "path": "dist/artplayer-proxy-mediabunny.legacy.js", + "sha256": "327da5f1be221af6887efb4175c1b92e4e0c28838cae8107acca652a76bddccb", + "raw": 390185, + "gzip9": 97578, + "brotli6": 88846 + }, + { + "name": "artplayer-proxy-mediabunny", + "version": "1.2.0", + "path": "dist/artplayer-proxy-mediabunny.mjs", + "sha256": "9dec2077358ccc7e085c5d511b3c507265e3ea764b7d497b87c0eba199236001", + "raw": 817627, + "gzip9": 145209, + "brotli6": 130741 + }, + { + "name": "artplayer-tool-iframe", + "version": "1.1.0", + "path": "dist/artplayer-tool-iframe.js", + "sha256": "2ec257c8055a9448d12af3fecf1fae8ba58c06f47e85072d02c386cd258b0c86", + "raw": 2878, + "gzip9": 1108, + "brotli6": 1005 + }, + { + "name": "artplayer-tool-iframe", + "version": "1.1.0", + "path": "dist/artplayer-tool-iframe.legacy.js", + "sha256": "c0755d79f316cda71f045da86749648f95faf47c09180d525fb04bb753576d5f", + "raw": 3108, + "gzip9": 1211, + "brotli6": 1107 + }, + { + "name": "artplayer-tool-iframe", + "version": "1.1.0", + "path": "dist/artplayer-tool-iframe.mjs", + "sha256": "138fcad1c66d7180728a209e99c600ca585f7d8be9f8cb757bfd6ec71b3f0bb7", + "raw": 4231, + "gzip9": 1210, + "brotli6": 1097 + }, + { + "name": "artplayer-tool-thumbnail", + "version": "4.4.0", + "path": "dist/artplayer-tool-thumbnail.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "raw": 5984, + "gzip9": 2331, + "brotli6": 2147 + }, + { + "name": "artplayer-tool-thumbnail", + "version": "4.4.0", + "path": "dist/artplayer-tool-thumbnail.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "raw": 5984, + "gzip9": 2331, + "brotli6": 2147 + }, + { + "name": "artplayer-tool-thumbnail", + "version": "4.4.0", + "path": "dist/artplayer-tool-thumbnail.mjs", + "sha256": "0610fc05d3f5b207280fffcd0024411b53964d669fb09241c464eeda88b10570", + "raw": 9293, + "gzip9": 2779, + "brotli6": 2597 + } + ] +} diff --git a/refactor/changes/2026-09-10-BASE-06-performance.md b/refactor/changes/2026-09-10-BASE-06-performance.md new file mode 100644 index 000000000..e03ca813a --- /dev/null +++ b/refactor/changes/2026-09-10-BASE-06-performance.md @@ -0,0 +1,19 @@ +# BASE-06:性能与资源基线 + +日期 2026-09-10;分支 codex/compatible-modernization;起点 c7a375e1。 + +## 实现及测量 + +新增 performance.html/js,使用固定发布核心/chapter 和本地视频。两次前台重载各收集 12 个交错计时样本(每配置 1 预热+5 样本),真实播放推进后销毁;分别测 constructor、ready、play Promise 和 destroy,不用固定延迟声称媒体已就绪。 + +资源诊断独立于计时,临时追踪定时器/RAF、开启 USE_RAF=true、触发代理事件和销毁前的 pending resize。两轮各 6 组结果一致:DOM/实例/代理账本清空、原代理不再响应、RAF 停止;同时复现 BASE-PERF-01,销毁后的 debounce 触发 resize 并重新创建 notice timer。追踪器记录后自己的额外取消操作没有计入播放器清理结果。 + +新增独立验证/统计/比较工具、阈值说明、28 个已发布 JS 与 85 个工作区 JS 的压缩体积清单(21 库)。真实发布来源与工作区陈旧产物分开。浏览器报告和测量后的 host CIM 参数均留存;不声称已测内存/GC/GPU 泄漏或获得性能收益。同代码两轮 ready 中位数从约 30ms 到 13-15ms,恰说明需要多组配对比较。 + +详细结果、ID、环境、范围、阈值和复现见 [性能台账](../baselines/performance-coverage.md)、[浏览器原始数据](../baselines/performance.json) 和 [体积数据](../baselines/sizes.json)。BASE-PERF-01 已分配 CORE-17,关联 CORE-04/18;ENG-08/MOD-03 接续候选比较与改进。 + +## 验证与接续 + +两轮 errors/unhandled 和浏览器 error/warn 为空;新增 3 项 Node 测试包含数据无效、后台/假播放、清理失败、忽略预热、重大变慢、不同设备与体积漏项负例,真实发布压缩结果可重算。完整 ci:check、计划/链接、语法和 diff 检查通过。没有修改生产代码、依赖、声明或分发产物,不用历史异常要求候选继续出错。 + +本任务独立本地提交,主题含 BASE-06;不推送/发布。撤销提交恢复上一个基线服务和测试入口;历史报告由 Git 保留。下一任务 BASE-07:整合差异、vendored/外部依赖与风险,随后继续工程基础和首个 TS 试点。 diff --git a/refactor/fixtures/performance.html b/refactor/fixtures/performance.html new file mode 100644 index 000000000..b1a33cc73 --- /dev/null +++ b/refactor/fixtures/performance.html @@ -0,0 +1,15 @@ + + + +ArtPlayer published performance baseline + +

Published performance and resource baseline

+

Keep this tab visible. Timing samples are separate from instrumented resource probes.

+ +

Ready

+
+

+
+
+
+
diff --git a/refactor/fixtures/performance.js b/refactor/fixtures/performance.js
new file mode 100644
index 000000000..8d70a6d39
--- /dev/null
+++ b/refactor/fixtures/performance.js
@@ -0,0 +1,166 @@
+/* global Artplayer, artplayerPluginChapter */
+(() => {
+  const container = document.querySelector('.player')
+  const status = document.querySelector('#status')
+  const now = () => performance.now()
+  const options = chapter => ({ container, url: '/assets/sample/video.mp4', muted: true, plugins: chapter ? [artplayerPluginChapter({ chapters: [{ start: 0, end: 10, title: 'First' }, { start: 10, end: Infinity, title: 'Second' }] })] : [] })
+  function waitFor(test, timeout = 12000) {
+    const started = now()
+    return new Promise((resolve, reject) => {
+      const poll = () => {
+        if (test()) resolve()
+        else if (now() - started > timeout) reject(new Error('State timeout'))
+        else requestAnimationFrame(poll)
+      }
+      poll()
+    })
+  }
+  function timers() {
+    const original = Object.fromEntries(['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'requestAnimationFrame', 'cancelAnimationFrame'].map(name => [name, window[name]]))
+    const active = new Map()
+    const late = []
+    let destroyed = false
+    const capture = (kind, callback, delay, args) => {
+      if (typeof callback !== 'function') throw new Error('Timer probe only supports function callbacks')
+      let handle
+      const wrapped = function (...values) {
+        if (kind !== 'interval') active.delete(`${kind}:${handle}`)
+        if (destroyed) late.push({ kind, delay })
+        return callback.apply(this, values)
+      }
+      handle = kind === 'raf' ? original.requestAnimationFrame.call(window, wrapped) : original[kind === 'timeout' ? 'setTimeout' : 'setInterval'].call(window, wrapped, delay, ...args)
+      active.set(`${kind}:${handle}`, { kind, delay })
+      return handle
+    }
+    window.setTimeout = (callback, delay = 0, ...args) => capture('timeout', callback, delay, args)
+    window.setInterval = (callback, delay = 0, ...args) => capture('interval', callback, delay, args)
+    window.requestAnimationFrame = callback => capture('raf', callback, null, [])
+    for (const clear of ['clearTimeout', 'clearInterval']) window[clear] = (handle) => {
+      active.delete(`timeout:${handle}`)
+      active.delete(`interval:${handle}`)
+      return original[clear].call(window, handle)
+    }
+    window.cancelAnimationFrame = (handle) => {
+      active.delete(`raf:${handle}`)
+      return original.cancelAnimationFrame.call(window, handle)
+    }
+    return {
+      snapshot: () => [...active.values()], late,
+      markDestroyed: () => { destroyed = true },
+      wait: ms => new Promise(resolve => original.setTimeout.call(window, resolve, ms)),
+      restore: () => {
+        // Probe-owned cleanup only after recording; never count it as library cleanup.
+        for (const key of active.keys()) {
+          const [kind, handle] = key.split(':')
+          original[kind === 'raf' ? 'cancelAnimationFrame' : kind === 'interval' ? 'clearInterval' : 'clearTimeout'].call(window, Number(handle))
+        }
+        Object.assign(window, original)
+        active.clear()
+      },
+    }
+  }
+  document.querySelector('#run').onclick = async () => {
+    document.querySelector('#run').disabled = true
+    const report = {
+      kind: 'performance',
+      environment: { userAgent: navigator.userAgent, language: navigator.language, hardwareConcurrency: navigator.hardwareConcurrency, deviceMemory: navigator.deviceMemory ?? null, devicePixelRatio, viewport: { width: innerWidth, height: innerHeight }, visibility: document.visibilityState },
+      scripts: [...document.scripts].map(script => new URL(script.src).pathname),
+      samples: [], resources: [], errors: [], unhandled: [], visibilityChanges: [],
+    }
+    const onError = event => report.errors.push(event.message)
+    const onUnhandled = event => report.unhandled.push(String(event.reason))
+    const onVisibility = () => report.visibilityChanges.push(document.visibilityState)
+    addEventListener('error', onError)
+    addEventListener('unhandledrejection', onUnhandled)
+    document.addEventListener('visibilitychange', onVisibility)
+    let art
+    const originalRaf = Artplayer.USE_RAF
+    try {
+      if (document.visibilityState !== 'visible') throw new Error('Keep benchmark tab visible')
+      if (originalRaf !== false) throw new Error('Unexpected published default USE_RAF')
+      // Interleave configurations, retaining one warm-up per configuration separately.
+      for (let round = 0; round < 6; round++) {
+        for (const chapter of [false, true]) {
+          status.textContent = `Timing ${round + 1}/6: ${chapter ? 'core + chapter' : 'core'}`
+          const started = now()
+          art = new Artplayer(options(chapter))
+          const constructorMs = now() - started
+          await waitFor(() => art.isReady)
+          const readyMs = now() - started
+          const playbackStarted = now()
+          await art.play()
+          const playResolvedMs = now() - playbackStarted
+          const startTime = art.currentTime
+          await waitFor(() => art.currentTime >= startTime + 0.15)
+          const playbackObservedMs = now() - playbackStarted
+          const mediaProgress = art.currentTime - startTime
+          art.pause()
+          const nodes = container.querySelectorAll('*').length
+          const proxyCleanupEntries = art.events.destroyEvents.size
+          const video = art.video
+          const videoWidth = video.videoWidth
+          const destroyStarted = now()
+          art.destroy()
+          const destroyMs = now() - destroyStarted
+          report.samples.push({ configuration: chapter ? 'chapter' : 'core', warmup: round === 0, round, constructorMs, readyMs, playResolvedMs, playbackObservedMs, mediaProgress, videoWidth, nodes, proxyCleanupEntries, destroyMs,
+            afterDestroy: { nodes: container.querySelectorAll('*').length, instances: Artplayer.instances.length, proxyCleanupEntries: art.events.destroyEvents.size, paused: video.paused, sourceAttribute: video.getAttribute('src') } })
+          art = null
+        }
+      }
+      // Instrumentation is deliberately excluded from the timing samples above.
+      for (let round = 0; round < 3; round++) {
+        for (const chapter of [false, true]) {
+          status.textContent = `Resources ${round + 1}/3: ${chapter ? 'core + chapter' : 'core'}`
+          const probe = timers()
+          let destroyed = false
+          let lateResizeEvents = 0
+          let proxyCalls = 0
+          let frames = 0
+          try {
+            Artplayer.USE_RAF = true
+            art = new Artplayer(options(chapter))
+            art.on('raf', () => frames++)
+            art.on('resize', () => { if (destroyed) lateResizeEvents++ })
+            art.proxy(document, 'art-baseline-resource', () => proxyCalls++)
+            await waitFor(() => art.isReady)
+            await art.play()
+            await waitFor(() => frames >= 2)
+            art.notice.show = 'Resource probe'
+            document.dispatchEvent(new Event('art-baseline-resource'))
+            // Controlled pending debounce before teardown, not a trusted user resize.
+            window.dispatchEvent(new Event('resize'))
+            const before = { timers: probe.snapshot(), proxyCleanupEntries: art.events.destroyEvents.size, frames, proxyCalls }
+            art.destroy()
+            destroyed = true
+            probe.markDestroyed()
+            const immediatelyAfter = probe.snapshot()
+            const atDestroyFrames = frames
+            document.dispatchEvent(new Event('art-baseline-resource'))
+            const waitStarted = now()
+            await probe.wait(350)
+            report.resources.push({ configuration: chapter ? 'chapter' : 'core', round, useRaf: true, before, immediatelyAfter,
+              after: { observedMs: now() - waitStarted, timers: probe.snapshot(), lateCallbacks: [...probe.late], lateResizeEvents, framesAfterDestroy: frames - atDestroyFrames, proxyCalls, proxyCleanupEntries: art.events.destroyEvents.size, nodes: container.querySelectorAll('*').length, instances: Artplayer.instances.length } })
+            art = null
+          }
+          finally {
+            if (art && Artplayer.instances.includes(art)) art.destroy()
+            probe.restore()
+            Artplayer.USE_RAF = originalRaf
+          }
+        }
+      }
+    }
+    catch (error) { report.errors.push(error.message) }
+    finally {
+      if (art && Artplayer.instances.includes(art)) art.destroy()
+      Artplayer.USE_RAF = originalRaf
+      removeEventListener('error', onError)
+      removeEventListener('unhandledrejection', onUnhandled)
+      document.removeEventListener('visibilitychange', onVisibility)
+    }
+    report.remainingInstances = Artplayer.instances.length
+    document.querySelector('#result').textContent = JSON.stringify(report, null, 2)
+    const response = await fetch('/reports/performance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })
+    status.textContent = response.ok ? `Saved ${report.samples.length} timing samples and ${report.resources.length} resource probes; errors ${report.errors.length}` : 'Save failed'
+  }
+})()
diff --git a/refactor/plan.md b/refactor/plan.md
index 54b8bf712..7b794ec4b 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -4,7 +4,7 @@
 
 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
 
-状态:todo 192 / doing 0 / blocked 0 / done 22 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
+状态:todo 191 / doing 0 / blocked 0 / done 23 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
 
 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
 
@@ -65,8 +65,8 @@
 | BASE-03 | workspace
捕获事件、异步和生命周期 | BASE-02 | ready/restart/destroy、Promise、切源/插件事件 trace;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 成功、失败、重入和多实例有断言,历史缺陷有标记;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | H | done | | BASE-04 | workspace
捕获 DOM、CSS 和官方 demo | BASE-02 | 核心模板/类名/变量、键盘/焦点/可访问名称基线及全 demo 路径映射;docs-browser-testing.md 中 HTML/编辑器的脚本来源与存储状态;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 核心/试点用户样式和输入用法已记录,其他 demo 运行状态与未知项明确;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | done | | BASE-05 | workspace
捕获包入口、资源和类型消费 | BASE-01 | 全包 manifest/资源路径清单;核心/试点的 UMD/AMD/ESM/CJS/legacy/i18n 和类型消费者基线;登记契约/版本/固定测试 ID/命令/报告与负责任务;区分库/npm 与站点实际分发类别,SSR import/模板/非浏览器构造错误分别捕获 | 核心/试点隔离消费可运行,其他包由各自契约任务验证,thumbnail tool 差异已登记 | H | done | -| BASE-06 | workspace
记录性能与资源基线 | BASE-03, BASE-04 | 固定媒体/设备测量、包体积、反复装卸资源报告 | 多次采样可重跑,指标和审查阈值确定,不承诺未经测量的收益;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | todo | -| BASE-07 | workspace
建立差异和风险台账 | BASE-03, BASE-05 | 销毁/切源/类型差异、vendored 许可和外部 SDK 清单 | 已确认与待复现分开,每项有负责任务及兼容处理路线;纳入 BASE-TYPE-01/02/03/04 和 BASE-DIST-01 的发布/声明/工作区证据 | H | todo | +| BASE-06 | workspace
记录性能与资源基线 | BASE-03, BASE-04 | 固定媒体/设备测量、包体积、反复装卸资源报告 | 多次采样可重跑,指标和审查阈值确定,不承诺未经测量的收益;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | done | +| BASE-07 | workspace
建立差异和风险台账 | BASE-03, BASE-05 | 销毁/切源/类型差异、vendored 许可和外部 SDK 清单 | 已确认与待复现分开,每项有负责任务及兼容处理路线;纳入 BASE-TYPE-01/02/03/04 和 BASE-DIST-01 的发布/声明/工作区证据;纳入 BASE-PERF-01 销毁后 resize/notice 延迟工作 | H | todo | | BASE-08 | workspace
建立消费者与真实环境验证矩阵 | BASE-01, BASE-05 | 每包版本/浏览器/codec/SDK/设备/样本、证据和负责验证任务的矩阵 | 格式兼容与运行能力分开,未知/缺环境不算通过,明确哪些检查阻止哪些批次发布 | H | todo | ## 2 工程保障 @@ -81,7 +81,7 @@ | ENG-05 | workspace
建立真实浏览器测试服务 | ENG-03, BASE-04, BASE-08 | Playwright projects、本地 Range/失败媒体服务;复用 docs 页面/样本的状态隔离、错误采集与候选资源映射 | Chromium/Firefox/WebKit 的基础播放 smoke 和报告可执行;以媒体状态断言判定通过,区分轻量用例与真实编辑器交互 | M | todo | | ENG-06 | workspace
支持按包非交互与 JS/TS 构建 | ENG-02, BASE-05 | build/dev 入口解析、指定包参数、原交互保留 | 三种产物、Less/SVG/worker 和本地 8082 demo 正常;保持 BASE-05 的 AMD 同时写入全局行为及 i18n/legacy 入口 | H | todo | | ENG-07 | workspace
建立 tarball 消费与产物检查 | ENG-04, ENG-06 | 隔离 npm 消费 fixtures、API/声明/入口差分 | 不借 workspace 源码通过,能识别缺文件与默认导出变化 | H | todo | -| ENG-08 | workspace
增加覆盖率、资源与性能报告 | ENG-03, ENG-05, BASE-06 | 覆盖率基线、资源清理断言、性能报告与阈值 | 关键生命周期分支有门槛,报告不靠无意义断言堆数量 | M | todo | +| ENG-08 | workspace
增加覆盖率、资源与性能报告 | ENG-03, ENG-05, BASE-06 | 覆盖率基线、资源清理断言、性能报告与阈值 | 关键生命周期分支有门槛,报告不靠无意义断言堆数量;将 BASE-06 的同环境多组配对、计时/压缩审查阈值与资源异常分开接入候选,不把历史现象冻结成正常要求 | M | todo | | ENG-09 | workspace
建立全包依赖影响和文档检查 | ENG-07, DOC-04 | 共享核心/构建影响映射、文档及每任务完成提交的 Git 审计接入;契约-支持版本-测试 ID-命令-候选/报告-任务的覆盖索引 | 核心变化触发必需生态检查;原有 DOC-01 至 04 基线例外明确,后续 done 任务不能缺失独立 commit;计划/已执行/缺证据分开,识别缺少验证归属的公开契约 | M | todo | | ENG-10 | workspace
建立历史失败分级和测试可靠性规则 | ENG-03, ENG-04, ENG-05, ENG-07, BASE-07 | 历史失败 ID/环境/旧版复现/负责修复任务、逐模块门槛、受控等待与 trace/retry 规则 | 不靠全局忽略或无理由 skip 隐藏问题,新增回归阻止交付,设备缺口和偶发失败单独可见 | M | todo | @@ -107,7 +107,7 @@ | CORE-01 | artplayer
迁移纯工具并冻结导出 | ENG-03, ENG-04, ENG-06, PILOT-01 | utils/time/property/format/file/error/subtitle 的分批 TS 迁移 | Artplayer.utils 导出/参数/绑定保持,纯逻辑与类型测试通过 | M | todo | | CORE-02 | artplayer
迁移内部 Emitter | CORE-01, BASE-03 | 带类型事件映射的原 Emitter 实现 | ctx、once/off、重入/异常和链式返回保持 | H | todo | | CORE-03 | artplayer
建立内部资源作用域 | CORE-02 | 监听/订阅/定时器/RAF/请求/URL 的资源登记 | 实例与操作作用域分离,单项清理失败不阻断其他资源 | H | todo | -| CORE-04 | artplayer
修复初始化与销毁生命周期 | CORE-03, ENG-05, BASE-07 | 构造失败回收、重复/重入 destroy、instances 修复 | 事件顺序、removeHtml、多实例和销毁中异步有真实回归;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-04/05 并增加候选正向回归 | H | todo | +| CORE-04 | artplayer
修复初始化与销毁生命周期 | CORE-03, ENG-05, BASE-07 | 构造失败回收、重复/重入 destroy、instances 修复 | 事件顺序、removeHtml、多实例和销毁中异步有真实回归;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-04/05 并增加候选正向回归;协调 BASE-PERF-01 销毁后定时器与回调归属 | H | todo | | CORE-05 | artplayer
区分输入配置和内部配置 | CORE-01, ENG-04 | Option/ResolvedOption、默认值与校验整理 | 配置合并/错误时机不变,JS 输入仍运行时校验 | M | todo | | CORE-06 | artplayer
建立内部媒体与宿主类型 | CORE-05 | 原生 video、canvas shim、UI host 类型 | 不更改公开 art.video 用法,不以 HTMLVideoElement 断言掩盖 shim | M | todo | | CORE-07 | artplayer
逐项协调公开声明差异 | CORE-02, CORE-06, BASE-07 | plugins/toggle/cue/setting/static 的旧新类型对照 | 每项有兼容重载/扩展或待决结论,不能删除旧类型样例;处理 BASE-TYPE-01/04,分别保留旧合法声明消费并验证真实返回 | H | todo | @@ -120,8 +120,8 @@ | CORE-14 | artplayer
拆分设置树、渲染和布局 | CORE-13 | setting model/selection/render/layout 的分批实现 | 不强行统一控件返回值,嵌套选择、默认高亮、更新清理通过;结合 BASE-DOM-02 验证窄容器设置定位 | H | todo | | CORE-15 | artplayer
迁移字幕获取、解析和渲染 | CORE-11, CORE-13 | subtitle 请求/track/DOM/URL 生命周期与类型 | 过期结果、偏移、cue 数组、原生全屏及错误路径通过 | H | todo | | CORE-16 | artplayer
迁移全屏、PiP、mini 与尺寸模式 | CORE-11, CORE-12 | 显示模式服务和原公开属性门面 | 模式互斥/恢复位置、手势权限、移动方向和退出失败通过 | H | todo | -| CORE-17 | artplayer
迁移键盘、手势、焦点和全局事件 | CORE-13, CORE-16 | events/hotkey 的明确 document 与资源归属 | 跨 document 重绑、触摸/键盘、快捷键及销毁无重复响应 | H | todo | -| CORE-18 | artplayer
迁移内置插件和提示模块 | CORE-14, CORE-16, CORE-17 | autoPlayback/autoOrientation/fastForward/lock/miniProgressBar 及 info/notice/loading/mask | 默认启用条件、storage 格式、定时器/动画清理通过 | M | todo | +| CORE-17 | artplayer
迁移键盘、手势、焦点和全局事件 | CORE-13, CORE-16 | events/hotkey 的明确 document 与资源归属 | 跨 document 重绑、触摸/键盘、快捷键及销毁无重复响应;修复 BASE-PERF-01 pending resize debounce 销毁后执行,并验证正常 resize 不变 | H | todo | +| CORE-18 | artplayer
迁移内置插件和提示模块 | CORE-14, CORE-16, CORE-17 | autoPlayback/autoOrientation/fastForward/lock/miniProgressBar 及 info/notice/loading/mask | 默认启用条件、storage 格式、定时器/动画清理通过;结合 BASE-PERF-01 防止销毁后重新安排 notice timer | M | todo | | CORE-19 | artplayer
整理进度、质量、缩略图和截图 | CORE-10, CORE-13, CORE-16 | 相关 player/control 功能与纯计算分离 | 截图跨域失败、缩略图布局、quality/进度边界保持 | M | todo | | CORE-20 | artplayer
收敛核心入口与依赖方向 | CORE-14, CORE-15, CORE-18, CORE-19 | Artplayer 门面、初始化依赖与最终目录 | 所有核心自有源码迁移,公开描述符/静态接口/事件差分通过 | H | todo | | CORE-21 | artplayer
生成核心声明并校验包内容 | CORE-20, ENG-07 | 由 TS 生成的既有入口声明与三种发布产物 | 内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过 | H | todo | @@ -375,7 +375,7 @@ | --- | --- | --- | --- | --- | --- | --- | | MOD-01 | workspace
Bun 固定版本干净安装试点 | ENG-09 | 独立目录的 Bun 安装与 Yarn 冻结基线对比,默认仍为用户选定的 Yarn | Node 测试仍通过;安装与资源一致才决定采用;不改 bundler;不自动替换默认 packageManager 或维护锁文件 | H | todo | | MOD-02 | workspace
整理剩余开发/构建脚本与插件模板 | ENG-06, SITE-03 | dev/build/utils/create-plugin 的 TS 与可测 CLI,模板同时提供旧 API | 旧脚本入口保留、新插件类型/测试/示例齐全,Lerna 改动单独取证 | M | todo | -| MOD-03 | workspace
测量并优化核心热路径 | CORE-22, ENG-08 | DOM 读写、进度更新、持久化、初始化的测量与改进 | 相同设备媒体多次比较,契约不变,收益及无效尝试记录 | M | todo | +| MOD-03 | workspace
测量并优化核心热路径 | CORE-22, ENG-08 | DOM 读写、进度更新、持久化、初始化的测量与改进 | 相同设备媒体多次比较,契约不变,收益及无效尝试记录;复用 BASE-06 的原始样本与测量限制,至少三组同环境旧新配对,不以单次变快宣称收益 | M | todo | | MOD-04 | workspace
测量并优化重型插件/proxy | PKG-DANMUKU-09, PKG-MASK-06, PKG-MB-10, ENG-08 | 帧/队列/推理/音画同步与资源长期运行比较 | 不改默认算法/阈值,性能改善有证据;无收益则保留旧实现 | M | todo | | MOD-05 | workspace
完成工具链与性能采用决策 | MOD-01, MOD-02, MOD-03, MOD-04 | 最终 runtime/packageManager/构建配置及性能台账 | 干净安装和全包检查通过;Bun 未采用有理由,不为状态强行切换 | M | todo | @@ -422,6 +422,7 @@ - BASE-03: [记录](changes/2026-09-10-BASE-03-lifecycle.md) [记录](baselines/lifecycle.json) [记录](baselines/lifecycle-coverage.md) - BASE-04: [记录](changes/2026-09-10-BASE-04-dom.md) [记录](baselines/dom.json) [记录](baselines/dom-coverage.md) [记录](baselines/demo-inventory.json) - BASE-05: [记录](changes/2026-09-10-BASE-05-consumers.md) [记录](baselines/consumer-coverage.md) [记录](baselines/consumers.json) [记录](baselines/ssr.json) [记录](baselines/distribution.json) +- BASE-06: [记录](changes/2026-09-10-BASE-06-performance.md) [记录](baselines/performance-coverage.md) [记录](baselines/performance.json) [记录](baselines/sizes.json) - ENG-01: [记录](changes/2026-09-10-ENG-01-reproducible-toolchain.md) [记录](baselines/toolchain-validation.json) - ENG-PM-01: [记录](changes/2026-09-10-ENG-PM-01-yarn-toolchain.md) [记录](baselines/yarn-validation.json) - ENG-02: [记录](changes/2026-09-10-ENG-02-readonly-ci.md) [记录](baselines/ci-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index a51d4bdfd..34bff26b0 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,11 @@ # 进度与证据 +## 当前实施:BASE-06 性能与资源基线完成 + +内置浏览器两轮各 12 个计时样本和 6 组资源探针已保存;真实播放及基本清理通过,同时复现销毁后 resize 防抖重新安排 notice timer,已分配 CORE-17/04/18。28 个发布 JS 与 85 个工作区 JS 的压缩体积独立记录。阈值只用于复测/审查,无生产优化或无内存泄漏结论。见 [BASE-06](changes/2026-09-10-BASE-06-performance.md)。 + +当前 214 项任务,23 完成、191 待办。BASE-05 已提交 c7a375e1;本轮独立提交 BASE-06。没有生产 TS 迁移、推送或发布;下一任务 BASE-07 差异/外部依赖风险台账。 + ## 当前实施:BASE-05 分发与类型消费者完成 固定发布包隔离消费的 18 项 Node runtime 检查通过;16 项 strict TS 场景中 8 项编译通过、8 项历史失败独立登记,未掩盖 NodeNext ESM 与 chapter 声明问题。内置浏览器两轮各 5 项 SSR 节点复用/媒体/清理检查通过。全包 manifest 与 118 个工作区 dist/types 资源已清点,thumbnail tool 和站点分发差异有后续责任。见 [BASE-05](changes/2026-09-10-BASE-05-consumers.md)。 diff --git a/refactor/scripts/browser-server.mjs b/refactor/scripts/browser-server.mjs index 9826c5870..0662b01fd 100644 --- a/refactor/scripts/browser-server.mjs +++ b/refactor/scripts/browser-server.mjs @@ -37,7 +37,7 @@ function sendBytes(req, res, bytes, filename) { const server = http.createServer(async (req, res) => { try { const url = new URL(req.url, `http://127.0.0.1:${port}`) - const reportName = { '/reports/api': 'api', '/reports/lifecycle': 'lifecycle', '/reports/dom': 'dom', '/reports/ssr': 'ssr' }[url.pathname] + const reportName = { '/reports/api': 'api', '/reports/lifecycle': 'lifecycle', '/reports/dom': 'dom', '/reports/ssr': 'ssr', '/reports/performance': 'performance' }[url.pathname] if (req.method === 'POST' && reportName) { assert.equal(req.headers.origin, `http://127.0.0.1:${port}`, 'Unexpected report origin') const chunks = [] @@ -62,7 +62,7 @@ const server = http.createServer(async (req, res) => { if (reportName === 'lifecycle') { report.capture.media = Object.fromEntries(['video.mp4', 'video2.mp4'].map(name => [name, hash(fs.readFileSync(path.join(root, 'docs/assets/sample', name)))])) } - if (['dom', 'ssr'].includes(reportName)) report.capture.media = { 'video.mp4': hash(fs.readFileSync(path.join(root, 'docs/assets/sample/video.mp4'))) } + if (['dom', 'ssr', 'performance'].includes(reportName)) report.capture.media = { 'video.mp4': hash(fs.readFileSync(path.join(root, 'docs/assets/sample/video.mp4'))) } const output = path.join(refactorDir, '.cache/reports') fs.mkdirSync(output, { recursive: true }) fs.writeFileSync(path.join(output, `${reportName}.json`), `${JSON.stringify(report, null, 2)}\n`) diff --git a/refactor/scripts/performance.mjs b/refactor/scripts/performance.mjs new file mode 100644 index 000000000..5f258045d --- /dev/null +++ b/refactor/scripts/performance.mjs @@ -0,0 +1,86 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { validatePublishedCapture } from './api.mjs' +import { hash, refactorDir } from './releases.mjs' + +export const timingThresholds = { + constructorMs: { relative: 0.25, absoluteMs: 2 }, + readyMs: { relative: 0.25, absoluteMs: 50 }, + playResolvedMs: { relative: 0.25, absoluteMs: 10 }, + destroyMs: { relative: 0.25, absoluteMs: 2 }, +} +export function validatePerformance(report) { + assert.equal(report.kind, 'performance') + validatePublishedCapture(report, 'performance') + assert.deepEqual(report.errors, []) + assert.deepEqual(report.unhandled, []) + assert.equal(report.environment.visibility, 'visible') + assert.deepEqual(report.visibilityChanges, [], 'Background timing is not comparable') + assert.equal(report.remainingInstances, 0) + assert.equal(report.capture.media['video.mp4'], hash(fs.readFileSync(path.resolve(refactorDir, '../docs/assets/sample/video.mp4')))) + assert.deepEqual(report.samples.map(({ configuration, round, warmup }) => ({ configuration, round, warmup })), Array.from({ length: 6 }, (_, round) => ['core', 'chapter'].map(configuration => ({ configuration, round, warmup: round === 0 }))).flat()) + for (const sample of report.samples) { + for (const key of [...Object.keys(timingThresholds), 'playbackObservedMs']) assert(Number.isFinite(sample[key]) && sample[key] >= 0, `Invalid ${key}`) + assert(sample.readyMs >= sample.constructorMs && sample.playbackObservedMs >= sample.playResolvedMs) + assert(Number.isFinite(sample.mediaProgress) && sample.mediaProgress >= 0.15 && Number.isFinite(sample.videoWidth) && sample.videoWidth > 0, 'Missing real playback progress') + assert(sample.nodes > 0 && sample.proxyCleanupEntries > 0) + assert.deepEqual(sample.afterDestroy, { nodes: 0, instances: 0, proxyCleanupEntries: 0, paused: true, sourceAttribute: null }, 'Basic lifecycle cleanup failed') + } + assert.deepEqual(report.resources.map(({ configuration, round }) => ({ configuration, round })), Array.from({ length: 3 }, (_, round) => ['core', 'chapter'].map(configuration => ({ configuration, round }))).flat()) + for (const resource of report.resources) { + assert.equal(resource.useRaf, true) + assert(resource.before.frames >= 2 && resource.before.proxyCleanupEntries > 0) + assert.equal(resource.before.proxyCalls, 1) + assert(resource.before.timers.some(timer => timer.kind === 'raf'), 'RAF probe was not active') + assert(resource.after.observedMs >= 350, 'Insufficient teardown observation') + for (const key of ['nodes', 'instances', 'proxyCleanupEntries', 'framesAfterDestroy']) assert.equal(resource.after[key], 0, `Resource cleanup failed: ${key}`) + assert.equal(resource.after.proxyCalls, 1, 'Removed proxy listener still responds') + // Late timeout work remains evidence, not a normal candidate cleanup requirement. + assert(Array.isArray(resource.after.timers) && Array.isArray(resource.after.lateCallbacks)) + } +} +export function summarizePerformance(report) { + return Object.fromEntries(['core', 'chapter'].map(configuration => { + const samples = report.samples.filter(sample => sample.configuration === configuration && !sample.warmup) + return [configuration, Object.fromEntries(Object.keys(timingThresholds).map(metric => { + const values = samples.map(sample => sample[metric]).sort((a, b) => a - b) + return [metric, { count: values.length, min: values[0], median: values[Math.floor(values.length / 2)], max: values.at(-1) }] + }))] + })) +} +export function resourceProfile(report) { + return report.resources.map(({ configuration, round, after }) => ({ configuration, round, timers: after.timers, lateCallbacks: after.lateCallbacks, lateResizeEvents: after.lateResizeEvents, framesAfterDestroy: after.framesAfterDestroy, proxyCalls: after.proxyCalls, nodes: after.nodes, instances: after.instances })) +} +export function comparePerformance(before, after) { + const cohort = report => ({ ...report.environment, visibility: undefined }) + assert.deepEqual(cohort(after), cohort(before), 'Different browser/viewport/hardware cohort; rerun paired samples') + const previous = summarizePerformance(before) + const current = summarizePerformance(after) + const reviewSignals = [] + for (const configuration of ['core', 'chapter']) { + for (const [metric, threshold] of Object.entries(timingThresholds)) { + const deltaMs = current[configuration][metric].median - previous[configuration][metric].median + const allowanceMs = Math.max(previous[configuration][metric].median * threshold.relative, threshold.absoluteMs) + if (deltaMs > allowanceMs) reviewSignals.push({ configuration, metric, deltaMs, allowanceMs }) + } + } + return { summary: current, reviewSignals, resourceProfileChanged: JSON.stringify(resourceProfile(after)) !== JSON.stringify(resourceProfile(before)) } +} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/performance.json'), 'utf8')) + for (const report of frozen.runs) validatePerformance(report) + const index = process.argv.indexOf('--compare') + if (index !== -1) { + assert(process.argv[index + 1], '--compare requires a report path') + const report = JSON.parse(fs.readFileSync(process.argv[index + 1], 'utf8')) + validatePerformance(report) + console.log(JSON.stringify(comparePerformance(frozen.runs[0], report), null, 2)) + console.log('Timing signals require repeat paired measurement and review, not automatic baseline replacement') + } + else { + assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --check or --compare ') + console.log(`Frozen performance evidence verified: ${frozen.runs.length} runs; no browser rerun or optimization claim`) + } +} diff --git a/refactor/scripts/performance.test.mjs b/refactor/scripts/performance.test.mjs new file mode 100644 index 000000000..2592f26a7 --- /dev/null +++ b/refactor/scripts/performance.test.mjs @@ -0,0 +1,60 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import test from 'node:test' +import { comparePerformance, summarizePerformance, validatePerformance } from './performance.mjs' +import { publishedSizes, validateSizes } from './sizes.mjs' +import { refactorDir } from './releases.mjs' + +const baseline = () => JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/performance.json'), 'utf8')) + +test('Performance evidence rejects background, fake playback and broken teardown', () => { + const frozen = baseline() + for (const report of frozen.runs) validatePerformance(report) + for (const mutate of [ + report => report.visibilityChanges.push('hidden'), + report => report.samples[2].mediaProgress = 0, + report => report.samples[2].afterDestroy.instances = 1, + report => report.resources[0].after.framesAfterDestroy = 1, + report => report.resources[0].after.observedMs = 20, + report => report.resources[0].after.proxyCalls = 2, + report => report.samples.pop(), + ]) { + const changed = structuredClone(frozen.runs[0]) + mutate(changed) + assert.throws(() => validatePerformance(changed)) + } +}) + +test('Performance comparison excludes warm-up, detects large regression and allows documented resource fixes', () => { + const report = baseline().runs[0] + const warmup = structuredClone(report) + warmup.samples[0].constructorMs = 1000 + assert.deepEqual(summarizePerformance(warmup), summarizePerformance(report)) + const slower = structuredClone(report) + for (const sample of slower.samples) { + sample.constructorMs += 10 + sample.readyMs += 10 + } + assert(comparePerformance(report, slower).reviewSignals.some(signal => signal.metric === 'constructorMs')) + const fixed = structuredClone(report) + for (const resource of fixed.resources) { + resource.after.timers = [] + resource.after.lateCallbacks = [] + resource.after.lateResizeEvents = 0 + } + validatePerformance(fixed) + assert.equal(comparePerformance(report, fixed).resourceProfileChanged, true) + const otherDevice = structuredClone(report) + otherDevice.environment.hardwareConcurrency++ + assert.throws(() => comparePerformance(report, otherDevice)) +}) + +test('Published compression sizes are reproducible and inventory cannot omit a library', async () => { + const report = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/sizes.json'), 'utf8')) + validateSizes(report) + assert.deepEqual(await publishedSizes(), report.published) + const missing = structuredClone(report) + missing.workspaceObserved = missing.workspaceObserved.filter(item => item.name !== 'artplayer-plugin-chapter') + assert.throws(() => validateSizes(missing)) +}) diff --git a/refactor/scripts/sizes.mjs b/refactor/scripts/sizes.mjs new file mode 100644 index 000000000..76d87e5ff --- /dev/null +++ b/refactor/scripts/sizes.mjs @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { brotliCompressSync, constants, gzipSync } from 'node:zlib' +import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs' + +const reportPath = path.join(refactorDir, 'baselines/sizes.json') +export function measureBytes(bytes) { + return { sha256: hash(bytes), raw: bytes.length, gzip9: gzipSync(bytes, { level: 9 }).length, brotli6: brotliCompressSync(bytes, { params: { [constants.BROTLI_PARAM_QUALITY]: 6 } }).length } +} +export async function publishedSizes() { + const releases = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8')).releases + const result = [] + for (const release of releases) { + const archive = await ensureArchive(release) + for (const member of Object.keys(release.files).filter(name => /\.(m?js)$/.test(name)).sort()) { + const sizes = measureBytes(readMember(archive, member)) + assert.equal(sizes.sha256, release.files[member]) + result.push({ name: release.name, version: release.version, path: member, ...sizes }) + } + } + return result +} +export function validateSizes(report) { + assert.equal(report.schemaVersion, 1) + for (const source of ['published', 'workspaceObserved']) { + for (const item of report[source]) { + assert(/^[a-f0-9]{64}$/.test(item.sha256)) + for (const key of ['raw', 'gzip9', 'brotli6']) assert(Number.isInteger(item[key]) && item[key] > 0, 'Invalid bundle size') + } + } + const packages = JSON.parse(fs.readFileSync(path.join(refactorDir, 'package-inventory.json'), 'utf8')).packages.filter(pkg => pkg.name !== 'artplayer-vitepress').map(pkg => pkg.name).sort() + assert.deepEqual([...new Set(report.workspaceObserved.map(item => item.name))].sort(), packages, 'Missing library size inventory') +} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const published = await publishedSizes() + if (process.argv.includes('--capture')) { + assert(!fs.existsSync(reportPath), 'Do not overwrite historical size measurements') + const distribution = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/distribution.json'), 'utf8')) + const workspaceObserved = distribution.packages.flatMap(pkg => pkg.resources.filter(resource => resource.path.startsWith('dist/') && /\.(m?js)$/.test(resource.path)).map(resource => { + const bytes = fs.readFileSync(path.resolve(refactorDir, '../packages', pkg.name, resource.path)) + assert.equal(hash(bytes), resource.sha256, 'Observed dist changed since BASE-05; record a new source snapshot') + return { name: pkg.name, version: pkg.manifest.version, path: resource.path, ...measureBytes(bytes) } + })) + const report = { schemaVersion: 1, task: 'BASE-06', sourceCommit: 'c7a375e1', capturedAt: new Date().toISOString(), node: process.version, zlib: process.versions.zlib, brotli: process.versions.brotli, + note: 'Independent file compression: gzip level 9, Brotli quality 6. Not production HTTP transfer size. Workspace files match BASE-05 observed hashes and may be stale; do not treat them as verified npm releases. Documentation site has no library bundle entry.', + reviewThreshold: { relative: 0.05, absoluteBytes: 1024, rule: 'Review if same-file increase exceeds max(5 percent, 1024 bytes); not an optimization or automatic release verdict' }, published, workspaceObserved } + validateSizes(report) + fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`) + } + else { + assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --capture or --check') + const frozen = JSON.parse(fs.readFileSync(reportPath, 'utf8')) + validateSizes(frozen) + assert.deepEqual(published, frozen.published, 'Published bundle sizes changed') + console.log(`Recomputed ${published.length} published JS sizes; workspace inventory remains historical`) + } +} diff --git a/refactor/tasks.json b/refactor/tasks.json index 2a4a2b68a..2977661a2 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -369,11 +369,16 @@ "BASE-03", "BASE-04" ], - "status": "todo", + "status": "done", "risk": "M", "deliverable": "固定媒体/设备测量、包体积、反复装卸资源报告", "acceptance": "多次采样可重跑,指标和审查阈值确定,不承诺未经测量的收益;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务", - "evidence": [] + "evidence": [ + "changes/2026-09-10-BASE-06-performance.md", + "baselines/performance-coverage.md", + "baselines/performance.json", + "baselines/sizes.json" + ] }, { "id": "BASE-07", @@ -389,7 +394,7 @@ "status": "todo", "risk": "H", "deliverable": "销毁/切源/类型差异、vendored 许可和外部 SDK 清单", - "acceptance": "已确认与待复现分开,每项有负责任务及兼容处理路线;纳入 BASE-TYPE-01/02/03/04 和 BASE-DIST-01 的发布/声明/工作区证据", + "acceptance": "已确认与待复现分开,每项有负责任务及兼容处理路线;纳入 BASE-TYPE-01/02/03/04 和 BASE-DIST-01 的发布/声明/工作区证据;纳入 BASE-PERF-01 销毁后 resize/notice 延迟工作", "evidence": [] }, { @@ -568,7 +573,7 @@ "status": "todo", "risk": "M", "deliverable": "覆盖率基线、资源清理断言、性能报告与阈值", - "acceptance": "关键生命周期分支有门槛,报告不靠无意义断言堆数量", + "acceptance": "关键生命周期分支有门槛,报告不靠无意义断言堆数量;将 BASE-06 的同环境多组配对、计时/压缩审查阈值与资源异常分开接入候选,不把历史现象冻结成正常要求", "evidence": [] }, { @@ -769,7 +774,7 @@ "status": "todo", "risk": "H", "deliverable": "构造失败回收、重复/重入 destroy、instances 修复", - "acceptance": "事件顺序、removeHtml、多实例和销毁中异步有真实回归;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-04/05 并增加候选正向回归", + "acceptance": "事件顺序、removeHtml、多实例和销毁中异步有真实回归;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-04/05 并增加候选正向回归;协调 BASE-PERF-01 销毁后定时器与回调归属", "evidence": [] }, { @@ -989,7 +994,7 @@ "status": "todo", "risk": "H", "deliverable": "events/hotkey 的明确 document 与资源归属", - "acceptance": "跨 document 重绑、触摸/键盘、快捷键及销毁无重复响应", + "acceptance": "跨 document 重绑、触摸/键盘、快捷键及销毁无重复响应;修复 BASE-PERF-01 pending resize debounce 销毁后执行,并验证正常 resize 不变", "evidence": [] }, { @@ -1007,7 +1012,7 @@ "status": "todo", "risk": "M", "deliverable": "autoPlayback/autoOrientation/fastForward/lock/miniProgressBar 及 info/notice/loading/mask", - "acceptance": "默认启用条件、storage 格式、定时器/动画清理通过", + "acceptance": "默认启用条件、storage 格式、定时器/动画清理通过;结合 BASE-PERF-01 防止销毁后重新安排 notice timer", "evidence": [] }, { @@ -3555,7 +3560,7 @@ "status": "todo", "risk": "M", "deliverable": "DOM 读写、进度更新、持久化、初始化的测量与改进", - "acceptance": "相同设备媒体多次比较,契约不变,收益及无效尝试记录", + "acceptance": "相同设备媒体多次比较,契约不变,收益及无效尝试记录;复用 BASE-06 的原始样本与测量限制,至少三组同环境旧新配对,不以单次变快宣称收益", "evidence": [] }, {