test(hls): [PKG-HLS-SDK-01] checkpoint unresolved Firefox diagnostics

This commit is contained in:
Harvey Zhao committed 2026-09-12 01:42:47 +08:00
1 parent 2f9bee4ee0
commit 26b2983daf
18 files changed
+17776 -8

No files matched your search

File diff suppressed because it is too large. Load diff
+38
View File
@@ -0,0 +1,38 @@
{
"schemaVersion": 1,
"task": "PKG-HLS-SDK-01",
"capturedAt": "2026-09-11T16:52:02.281Z",
"metadataUrl": "https://registry.npmjs.org/hls.js/1.7.2",
"releases": [
{
"name": "hls.js",
"version": "1.5.17",
"tarball": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.17.tgz",
"integrity": "sha512-wA66nnYFvQa1o4DO/BFgLNRKnBTVXpNeldGRBJ2Y0SvFtdwvFKCbqa9zhHoZLoxHhZ+jYsj3aIBkWQQCPNOhMw==",
"sha256": "8f9e60001992f59dbfb1dbc397e145743c41bb6dde233ec8a7974013c8a031fe",
"files": {
"package/package.json": "f2172b9942a23ffb46edd4af815cf1ea161db088e7f63dc518d30e55c95d6ae9",
"package/LICENSE": "ca8773cf798c7ed997d4dd7c8e23c348699f8d5b7462636694cc14de6cda12db",
"package/dist/hls.min.js": "484054e8cd03d3f6d1781fb7f402bdc318d8a4c527f933a95c624e27cc9a9470",
"package/src/hls.ts": "b3d3f797669e89bc8d05d2de2aae243ae110811d6738948f173d789cc1ac1f2d",
"package/src/controller/audio-track-controller.ts": "ff34f3cb29b66aa5fcba87758bb20d129b052a053808c150ce779e0ceb1e4626"
}
},
{
"name": "hls.js",
"version": "1.7.2",
"tarball": "https://registry.npmjs.org/hls.js/-/hls.js-1.7.2.tgz",
"integrity": "sha512-CW/pPvSOFIRsosbwxrYaE9ERmpTo5fbTqL7wCvuCFlqW1Bmb1K5fXsY7yiH95rmlr4rVuA7UXHbM/cIXQ6AXwg==",
"sha256": "14311c46a7aea04a7ffc373653b11d019bb41df8a693a2459eb3115a262aef53",
"files": {
"package/package.json": "49918bb59328a44f4533dcfb5ecde80893db85072bd52b52721901b4ce239d81",
"package/LICENSE": "ca8773cf798c7ed997d4dd7c8e23c348699f8d5b7462636694cc14de6cda12db",
"package/dist/hls.min.js": "afcde07437ec84b072fe8782e772ceb5046eac751b2719f73ae0d83d763bc3f5",
"package/dist/hls.d.ts": "9571b0113a7103feb8e36e9fbc04c3baba07b5ab3e5c51166b50552ebbbf17f2",
"package/src/hls.ts": "e86f0bb70e845814aa2e3a4ff6a9f1f658d1c98fbdc738b02f623c9ddcf6c72c",
"package/src/controller/audio-track-controller.ts": "f7ec55e90cc2194e64432db1336d75d0309f6cddf8b55d7eac45b0ebba0517b1"
}
}
],
"note": "Fixed integration test inputs only. Latest tag observed as 1.7.2; no range or minimum SDK compatibility claim and no runtime dependency."
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,100 @@
# PKG-HLS-SDK-01:真实 worker 与分组轨道验证(进行中)
本子任务来自 PKG-HLS-05,负责可独立取证的桌面 SDK worker、分组轨道与重绑定。
父任务增加对子任务的依赖,仍要求真实设备/SDK 证据,不因拆分而降低完成条件。
当前子任务保持 doing,尚无完成提交;HLS-CRASH-01 未解释,不能继承为正常通过。
## 已实现
- 固定 Hls.js 1.5.17 和核实时 npm latest 为 1.7.2 的两个归档。SHA-512、SHA-256、
选定源/运行/许可文件均核验;历史冻结对象保持相同。版本元数据 URL 及获取时间见
baselines/hls-sdk-matrix.json。新版本来源为 https://registry.npmjs.org/hls.js/1.7.2,
上游变更核对 https://github.com/video-dev/hls.js/releases,不执行浮动 latest 测试。
- test/browser/hls-sdk.spec.js 使用原生 Worker 子类观察 init/transmuxComplete/error/
terminate,所有操作委托实际 Worker。视频帧、媒体时钟和 enableWorker 保持启用共同验证,
防止只设置开关或发生 SDK main-thread fallback 后仍冒充 worker 通过。
- 新旧核心与插件四组合覆盖两个固定 SDK、真实播放/seek、换成无音轨来源和 worker 清理。
候选插件另覆盖实际 detach/reattach、两档位不同音轨组、重编号/偏好保持和 UI 选择。
Commentary 复用 English 正弦样本,只验证轨道拓扑,不声称真实语音或音频语言识别。
- 新增 baseline runner 用例核验 SDK 归档、Apache-2.0 许可和未引入运行时依赖;现有
ci:check 纳入它。生产代码/构建产物保持 HLS-04 提交 2f9bee4e,未因 SDK 测试升级生产依赖。
## 已执行与未解释失败
完整本地 CI 537 项通过(496 单元、11 工程、30 基线),254 个生产 TS 文件严格检查通过。
首轮 Chromium 18 项通过。随后 main/legacy 完整矩阵各 38 passed、16 skipped。
强化新实例 worker 启用/错误断言后,一次 main 运行出现 37 passed、16 skipped、1 failed:
Firefox、Hls 1.5.17、候选核心在高低音轨组切换后的 destroy 附近出现 Target crashed。
错误发生在实际 destroy 返回后读取 worker 终止状态,无法从已关闭页面获取诊断状态。
目前没有证据将其归因于插件、核心、Hls、浏览器或内存压力,不称为“环境偶发已解决”。
随后同样源码的 legacy 完整矩阵和 main 完整复核各 38 passed、16 skipped;
Firefox 两个 SDK × 新旧核心分组场景各重复三次,共 12 passed。这些结果不关闭首次失败。
另进行 DEBUG=pw:browser + trace=on 的五次重复诊断,保留独立报告/trace 目录。
该轮 20 项中 19 通过、1 失败,未复现首次崩溃,但出现另一种失败:Firefox、Hls 1.7.2、
旧核心的组切换后音轨组已更新、视频仍为 90P,7 秒未达到 180P。SDK 有非致命 aborted
记录,未收到对应 LEVEL_SWITCHED=1。此问题另记 HLS-PLAYBACK-01,不混称崩溃已在旧核心复现。
有实际 SDK/浏览器状态及完整 trace,后续需直接 Hls 对照与时序取证;不扩大等待来掩盖。
首次失败报告/错误栈保留;首次 trace 所在共享结果目录被后续套件覆盖,不能声称 trace 仍在。
后续诊断复制完整 results 至专用 cache 路径,避免再丢失。
冻结的当前进度见 baselines/hls-sdk-validation.json,状态 partial-unresolved-crash。
其中记录首次失败、后续重复、完整矩阵、输入指纹及原生 worker 消息计数,不能只引用绿灯摘要。
浏览器报告中的 skipped 仅为 Windows WebKit 无 MSE;每个 SDK 的能力/失败清理实际执行。
## 剩余与接续
HLS-CRASH-01/HLS-PLAYBACK-01:继续保留失败并追查 Firefox 销毁崩溃和独立切组卡住;
定位前不完成本子任务。
适当的下一步是浏览器诊断日志/崩溃证据与直接 Hls+原生 video 对照,避免靠反复重跑宣布修复。
父 HLS-05 还包括 Safari/native、真机和完整 SDK/网络范围;HLS-06 包含例子/隔离安装分发。
本轮 1.7.2 只提供运行时验证,公开 SDK 类型消费仍以 HLS-04 的实际 1.5.17 为已验证范围。
没有将两个 SDK 点版本称为整个区间或新的最低版本。SDK-01/HLS-ENV-01 继续 open。
当前任务可提交明确标注 checkpoint 的诊断进度,但状态仍为 doing,不计入完成数。
满足原验收后仍须独立完成提交;checkpoint 不代表任一风险已关闭。
没有 push/tag/publish/merge。全项目目标继续。
## 后续诊断入口与当前判断
新增 refactor/scripts/hls-sdk-diagnostic.mjs:固定 SDK、确定性媒体和本地随机端口服务器,
可选择普通 HTTP/Playwright route、直接原生 video/发布核心/候选核心、是否安装候选插件、
SDK 调试日志、worker 开关/观察器以及直接 video 的销毁顺序。日志包含 SDK/媒体事件、
暂停/缓冲区/时钟与最终状态;每次运行使用专用目录保存每轮 trace 和报告。
直接模式不加载 ArtPlayer/插件,报告检查其 global 为 undefined;三种宿主的输入字节均记录。
此工具用于对照取证,不以诊断重复通过替代原场景或发布验收。
```sh
node refactor/scripts/hls-sdk-diagnostic.mjs --version 1.7.2 --iterations 10 --transport route
node refactor/scripts/hls-sdk-diagnostic.mjs --version 1.7.2 --iterations 10 --transport route --host published --plugin --observe-workers
node refactor/scripts/hls-sdk-diagnostic.mjs --version 1.5.17 --iterations 5 --transport route --host candidate --plugin --observe-workers
```
共享 test/helpers/worker-observer.js 保留原生 Worker 操作;grouped playlist 移到
test/browser/fixtures/hls-grouped.m3u8,内容与先前内嵌字符串相同,避免两套样本漂移。
原集成测试补充 paused、readyState、buffered 和 SDK 事件当时的媒体状态,保持原断言及超时。
移动 helper 后的一次完整 main 矩阵仍为 38 passed、16 skipped,trace 独立保存。
初始直接 HTTP/route 及更接近真实切轨完成顺序的 direct 对照各十次通过;
旧核心初始化在异步 customType 前暴露 video 尚未赋值,一轮新诊断脚本因此自身失败,
已改为动态 getter 与就绪条件,这不是播放器缺陷,不能计入 SDK 通过/失败归因。
其后带 SDK 日志的三宿主并行各 15 次通过,关闭 SDK 日志/采用相同条件轮询各十次通过,
加入相同 Worker 观察器后各十次通过。没有证明任一宿主/日志/路由/观察器是原因。
1.5.17 同样安排直接、旧核心、候选核心观察器对照各五次,结果与版本/指纹另存
baselines/hls-sdk-diagnostics.json;旧失败文件 hls-sdk-validation.json 不覆盖。
1.5.17 对照确实再次捕获崩溃:直接原生 video 与候选核心均出现 page crashed;
直接模式没有加载 ArtPlayer/插件(输入文件和 runner 分支可核对)。候选核心关闭 Worker
观察器后也出现两次崩溃,所以既不能只归因于核心,也不能只归因于 Worker 观察器。
直接模式关闭观察器、以及先销毁 SDK 再清理 video 的有限对照通过;尚不足以确定修复策略。
这些诊断还暴露早期 runner 的计数问题:操作断言完成后,最后状态读取失败或稍后 crash
没有改写 passed。现在状态/trace/关闭错误及 pageerror/crash 都参与最终结果,结束前再次核对。
冻结汇总保留原 recordedStatus,并按原始 errors/state.error 重算 effective status,
不是通过删除失败来刷新统计。全部有效诊断共 184 passed、6 failed;另有前述十条脚本自身
初始化失败单列排除。此处通过数不是发布矩阵通过数,未解释失败仍阻止本子任务完成。
目前不能从这些有限对照推出“已修复”或具体的“上游缺陷”。两风险保留 open,本子任务保持 doing。
下一轮优先比较非插桩/非路由的 1.5.17 worker 销毁与禁用 worker 控制,并保留最后销毁前
状态或原生崩溃证据。没有依据提前更改核心公开 destroy 顺序或消费者拥有的 SDK 配置。
继续其他无依赖的包迁移时,HLS 的原生/设备/压力与复盘门槛仍必须回来完成。
+23
View File
@@ -76,3 +76,26 @@ TS 4.3/5.9 严格校验;真实 Monaco 验证推断、错误提示及编译后
MediaCapabilitiesDecodingInfo;仅 SDK 的消费者也出现相同两个诊断。插件无新增诊断,
不注入虚假 DOM 定义或打开 skipLibCheck。独立插件五组类型模式均要求零诊断。
PKG-HLS-05/06 继续完整 SDK、浏览器、示例及安装包验收,SDK-01/HLS-ENV-01 仍开放。
## PKG-HLS-SDK-01 桌面 SDK 扩展
本子任务作为 PKG-HLS-05 前置单独交付,不降低父任务的设备/SDK 验收条件。
test/browser/hls-sdk.spec.js 使用冻结的 Hls.js 1.5.17/1.7.2 和真实 native Worker:
监听 init/transmuxComplete 消息、错误与 terminate,并且同时验证视频帧、时间推进和销毁。
只有 enableWorker=true 或仅创建 Worker 不算通过;实际转封装结果和清理是断言的一部分。
这些探针委托原生操作,不注入模拟转封装输出。SDK 仍为测试归档,不进入生产依赖。
worker 切源/清理覆盖两个 SDK 与新旧核心/插件四组合;候选插件另测 detach/reattach、
两档位不同音轨组、组内重新编号和实际 SDK 切轨完成事件、鼠标选择与 UI 清理。
高档组有 French/English/Commentary 三项、低档组有 English/French 两项,顺序有意不同。
音频是确定性正弦样本,Commentary 复用 English 样本,不冒充真实语音或音频语言识别。
版本来源、Apache-2.0 许可及成员哈希见 baselines/hls-sdk-matrix.json;新增版本来自
[npm 固定 1.7.2 元数据](https://registry.npmjs.org/hls.js/1.7.2),
[上游发布记录](https://github.com/video-dev/hls.js/releases) 仅用于核对版本变化。
这里不测试所有中间版本或外部服务器,也没有将 1.7.2 的声明改动等同于类型矩阵全部通过。
实际 TypeScript SDK 声明消费仍只有 PKG-HLS-04 明确记录的 1.5.17 范围。
本子任务暂未完成:一次 Firefox 分组切换后的销毁出现 Target crashed,HLS-CRASH-01
仍待定位。后续完整矩阵和重复通过不能抹去首次失败;详见
[进行中记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md)。
+6 -4
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 215 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 216 项,范围 22 个包及工作区/示例。
状态:todo 150 / doing 0 / blocked 0 / done 65 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 150 / doing 1 / blocked 0 / done 65 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -26,7 +26,7 @@
| artplayer-plugin-danmuku-mask | 1.1.0 | PKG-MASK-01, PKG-MASK-02, PKG-MASK-03, PKG-MASK-04, PKG-MASK-05, PKG-MASK-06 |
| artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-05, PKG-DASH-06 |
| artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 |
| artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-05, PKG-HLS-06 |
| artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 |
| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06 |
| artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06 |
| artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06 |
@@ -192,7 +192,8 @@
| PKG-HLS-02 | artplayer-plugin-hls-control<br>建立特有行为与错误测试 | PKG-HLS-01, ENG-03, ENG-05 | Auto/手动实际 level、音轨、去重/过滤和拓扑变化 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-HLS-03 | artplayer-plugin-hls-control<br>整理内部职责与资源 | PKG-HLS-02, CORE-11, CORE-14 | 映射计算与 selector 更新/删除、Hls 事件订阅分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-HLS-04 | artplayer-plugin-hls-control<br>迁移自有源码和公开类型 | PKG-HLS-03, ENG-04, ENG-06, CORE-07 | HLS 能力适配类型、回调上下文和旧 getName 参数 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-HLS-05 | artplayer-plugin-hls-control<br>验证新旧核心和组合 | PKG-HLS-04, CORE-22 | 本地多码率 HLS、换成无轨道来源、旧核心和最终核心 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-HLS-SDK-01 | artplayer-plugin-hls-control<br>验证真实 SDK worker 与分组轨道组合 | PKG-HLS-04, CORE-22 | 固定历史及当前 SDK 归档、真实 worker/多音轨组、外部选择与重绑定浏览器证据 | 受支持桌面 MSE 引擎与新旧核心通过;无 worker 回退伪通过,设备缺口仍由 PKG-HLS-05 保持未完成 | H | doing |
| PKG-HLS-05 | artplayer-plugin-hls-control<br>验证新旧核心和组合 | PKG-HLS-04, CORE-22, PKG-HLS-SDK-01 | 本地多码率 HLS、换成无轨道来源、旧核心和最终核心 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-HLS-06 | artplayer-plugin-hls-control<br>验证分发并同步文档 | PKG-HLS-05, ENG-07 | hls.control.js、依赖范围与回退记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
## 5 包迁移:artplayer-plugin-dash-control
@@ -469,3 +470,4 @@
- PKG-HLS-02: [记录](hls-validation.md) [记录](baselines/hls-sdk.json) [记录](baselines/hls-validation.json) [记录](changes/2026-09-11-PKG-HLS-02-tests.md)
- PKG-HLS-03: [记录](changes/2026-09-12-PKG-HLS-03-modules.md) [记录](baselines/hls-modules-validation.json) [记录](hls-validation.md)
- PKG-HLS-04: [记录](changes/2026-09-12-PKG-HLS-04-types.md) [记录](baselines/hls-types-validation.json) [记录](hls-validation.md)
- PKG-HLS-SDK-01: [记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md) [记录](baselines/hls-sdk-validation.json) [记录](baselines/hls-sdk-diagnostics.json)
+29
View File
@@ -1,5 +1,34 @@
# 进度与证据
## HLS 诊断 checkpoint:已缩小范围,尚未关闭风险
独立诊断 runner 已加入直接 Hls、发布核心、候选核心对照,以及 HTTP/route、worker/观察器、
SDK 日志和直接 video 销毁顺序选项。1.5.17 的直接原生 video 对照也收到了 page crashed;
候选核心关闭观察器仍复现,因此不是仅靠替换核心或移除观察器就能解释的问题。
有效诊断重算为 184 passed、6 failed,另十条初期 runner 初始化失败独立排除;
早期遗漏的末尾状态读取失败已纳入失败统计,没有删除原始证据。
见 [诊断记录](baselines/hls-sdk-diagnostics.json) 与 [进行中说明](changes/2026-09-12-PKG-HLS-SDK-01-integration.md)。
本次保存明确标注 checkpoint 的进度提交,PKG-HLS-SDK-01 继续 doing,HLS-CRASH-01/
HLS-PLAYBACK-01 继续 open,完成数仍为 65。完整 CI 537 项通过,最终 main 集成矩阵
38 通过、16 无 MSE 跳过;这些通过结果不覆盖已保留的六次诊断失败。
未修改生产源码、公开 API 或 destroy 顺序。可继续独立生态包迁移,HLS 的调查与发布门槛仍须完成。
## 当前实施:PKG-HLS-SDK-01 桌面 SDK 组合与崩溃取证
从 HLS-05 拆出可独立验证的 worker/分组轨道子任务,父任务仍保留全部设备门槛。
Hls 1.5.17/1.7.2 的实际 worker、切源清理、分组音轨和 detach/reattach 测试已加入。
完整 CI 537 项、254 个生产 TS 文件通过;main/legacy 完整复核各 38 通过、16 WebKit 无 MSE 跳过。
一次 Firefox 在候选核心 + 1.5.17 分组切换后的销毁阶段 Target crashed,尚未归因;
后续 12 项重复与完整复核通过不构成关闭证据。HLS-CRASH-01 保持 open,子任务仍 doing。
带 trace 的 20 项重复另有 1 项旧核心 + Hls 1.7.2 切组后画面未切到 180P,
登记 HLS-PLAYBACK-01;它不是同一种崩溃,完整 trace 已单独保存。
见 [进行中记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md) 与
[部分证据](baselines/hls-sdk-validation.json)。带浏览器诊断和 trace 的重复另存专用目录;
接续调查崩溃及直接 SDK 对照。当前 216 项、65 done、1 doing、150 todo;HLS-04 已独立提交
2f9bee4e,本子任务尚未完成/提交,没有发布放行。
## 最新完成:PKG-HLS-04 严格 TS 与公开类型
HLS 五个自有模块已迁移严格 TS;公开声明保留旧 object formatter、Parameters 配置提取、
+2
View File
@@ -124,3 +124,5 @@
| HLS-LIFE-01 | resolved / 已复现 | 过期 HLS 选择回调与销毁后 update 仍工作 | PKG-HLS-03 |
| HLS-AUTO-01 | resolved / 已复现 | 真实 ABR 播放的 Auto 标签在 update 后丢失 | PKG-HLS-03, PKG-HLS-05 |
| HLS-ENV-01 | open / 已复现 | Windows WebKit 缺失 MSE,HLS 播放矩阵尚缺 Safari 证据 | PKG-HLS-05, REL-09, REVIEW-02 |
| HLS-CRASH-01 | open / 已复现 | Firefox Hls 1.5.17 worker 销毁可在原生 video 与候选核心复现崩溃 | PKG-HLS-SDK-01, PKG-HLS-05, REVIEW-02 |
| HLS-PLAYBACK-01 | open / 已复现 | Firefox 旧核心与 Hls 1.7.2 切组后视频档位未完成切换 | PKG-HLS-SDK-01, PKG-HLS-05, REVIEW-02 |
+37
View File
@@ -2606,6 +2606,43 @@
],
"compatibleResolution": "显式验证宿主不支持,保留 14 skipped,不作为播放通过;补有 MSE 的 Safari/macOS/iOS 环境。",
"closureCriteria": "实际 Safari/MSE/native 路径证据齐全,不能仅凭 Windows 能力检查关闭。"
},
{
"id": "HLS-CRASH-01",
"title": "Firefox Hls 1.5.17 worker 销毁可在原生 video 与候选核心复现崩溃",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-HLS-SDK-01",
"PKG-HLS-05",
"REVIEW-02"
],
"evidence": [
"test/browser/hls-sdk.spec.js",
"refactor/changes/2026-09-12-PKG-HLS-SDK-01-integration.md",
"refactor/baselines/hls-sdk-diagnostics.json",
"refactor/scripts/hls-sdk-diagnostic.mjs"
],
"compatibleResolution": "直接原生 video(无 ArtPlayer/插件)和候选核心已有 page crashed;候选核心无观察器也复现。继续区分 SDK、Firefox/Playwright 与媒体销毁,不静默改核心 destroy 顺序或调用方 SDK 配置。",
"closureCriteria": "取得可解释的崩溃诊断或直接 SDK 对照;验证处置和新旧核心/worker/分组销毁矩阵,不能仅以再次通过关闭。"
},
{
"id": "HLS-PLAYBACK-01",
"title": "Firefox 旧核心与 Hls 1.7.2 切组后视频档位未完成切换",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-HLS-SDK-01",
"PKG-HLS-05",
"REVIEW-02"
],
"evidence": [
"test/browser/hls-sdk.spec.js",
"refactor/changes/2026-09-12-PKG-HLS-SDK-01-integration.md",
"refactor/baselines/hls-sdk-validation.json"
],
"compatibleResolution": "区分 SDK、测试媒体/时序和宿主问题,保留真实档位/解码断言及 trace;不扩大等待或只验证菜单。",
"closureCriteria": "直接 Hls 与新旧核心对照定位切换停滞,验证处置和正常/连续组切换,不能以重复通过关闭。"
}
]
}
+232
View File
@@ -0,0 +1,232 @@
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
import fs from 'node:fs'
import http from 'node:http'
import path from 'node:path'
import process from 'node:process'
import { parseArgs } from 'node:util'
import { expect, firefox } from '@playwright/test'
import { observeWorkers } from '../../test/helpers/worker-observer.js'
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
// Direct SDK control: no ArtPlayer or plugin is loaded, and no worker output is substituted.
export async function diagnose({ version = '1.7.2', iterations = 5, transport = 'http', teardown = 'reset-first', worker = true, host = 'direct', plugin = false, sdkLogs = false, workerObserver = false } = {}) {
const runnerSHA256 = hash(fs.readFileSync(new URL(import.meta.url)))
const observerSHA256 = hash(fs.readFileSync(new URL('../../test/helpers/worker-observer.js', import.meta.url)))
assert(Number.isInteger(iterations) && iterations > 0 && iterations <= 50)
assert(['http', 'route'].includes(transport))
assert(['reset-first', 'sdk-first'].includes(teardown))
assert(['direct', 'published', 'candidate'].includes(host))
assert(!plugin || host !== 'direct', 'Plugin comparison requires an ArtPlayer host')
const matrix = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/hls-sdk-matrix.json')))
const release = matrix.releases.find(item => item.version === version)
assert(release, 'Use a frozen SDK version')
const sdk = readMember(await ensureArchive(release), 'package/dist/hls.min.js')
assert.equal(hash(sdk), release.files['package/dist/hls.min.js'])
const media = path.join(refactorDir, '../test/browser/media/hls')
const manifest = JSON.parse(fs.readFileSync(path.join(media, 'manifest.json')))
const files = new Map()
for (const [name, expected] of Object.entries(manifest.files)) {
const bytes = fs.readFileSync(path.join(media, name))
assert.equal(hash(bytes), expected.sha256)
files.set(`/${name}`, bytes)
}
files.set('/grouped.m3u8', fs.readFileSync(path.join(refactorDir, '../test/browser/fixtures/hls-grouped.m3u8')))
files.set('/hls.js', sdk)
if (host === 'published') {
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json')))
const core = baseline.releases.find(item => item.name === 'artplayer')
const bytes = readMember(await ensureArchive(core), 'package/dist/artplayer.js')
assert.equal(hash(bytes), core.files['package/dist/artplayer.js'])
files.set('/artplayer.js', bytes)
}
if (host === 'candidate')
files.set('/artplayer.js', fs.readFileSync(path.join(refactorDir, '../packages/artplayer/dist/artplayer.js')))
if (plugin)
files.set('/plugin.js', fs.readFileSync(path.join(refactorDir, '../packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.js')))
files.set('/', Buffer.from('<!doctype html><title>Hls host diagnostic</title><div class="player" style="width:640px;height:360px"></div>'))
const contentType = name => name.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : name.endsWith('.mpegts') ? 'video/mp2t' : name.endsWith('.js') ? 'text/javascript' : 'text/html'
const server = http.createServer((req, res) => {
const name = new URL(req.url, 'http://localhost').pathname
const bytes = files.get(name)
res.writeHead(bytes ? 200 : 404, { 'Content-Type': contentType(name), 'Cache-Control': 'no-store' })
res.end(bytes || 'Missing diagnostic fixture')
})
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
const origin = `http://127.0.0.1:${server.address().port}`
const directory = fs.mkdtempSync(path.join(refactorDir, '.cache/hls-direct-'))
const results = []
let browser
let browserVersion
try {
browser = await firefox.launch({ headless: true })
browserVersion = browser.version()
for (let iteration = 0; iteration < iterations; iteration++) {
const context = await browser.newContext()
await context.tracing.start({ screenshots: true, snapshots: true, sources: true })
const page = await context.newPage()
const wait = predicate => expect.poll(() => page.evaluate(predicate), { timeout: 7000 }).toBe(true)
const entry = { iteration, status: 'running', errors: [], requests: [] }
results.push(entry)
page.on('pageerror', error => entry.errors.push(error.message))
page.on('crash', () => entry.errors.push('page crashed'))
page.on('requestfailed', request => entry.requests.push({ url: request.url(), error: request.failure() }))
if (transport === 'route') {
await page.route('**/*.m3u8', route => serveRoute(route))
await page.route('**/*.mpegts', route => serveRoute(route))
}
async function serveRoute(route) {
const name = new URL(route.request().url()).pathname
const bytes = files.get(name)
await route.fulfill({ status: bytes ? 200 : 404, body: bytes || 'Missing fixture', contentType: contentType(name) })
}
try {
await page.goto(origin)
if (workerObserver)
await page.evaluate(observeWorkers)
await page.addScriptTag({ url: `${origin}/hls.js` })
if (host !== 'direct')
await page.addScriptTag({ url: `${origin}/artplayer.js` })
if (plugin)
await page.addScriptTag({ url: `${origin}/plugin.js` })
await page.evaluate(({ worker, teardown, host, plugin, sdkLogs }) => {
let video
let hls
let art
const records = []
const buffer = ranges => Array.from({ length: ranges.length }, (_, index) => [ranges.start(index), ranges.end(index)])
const state = () => ({ time: video.currentTime, readyState: video.readyState, height: video.videoHeight, paused: video.paused, buffered: buffer(video.buffered), seekable: buffer(video.seekable) })
const record = (event, data) => {
records.push({ at: performance.now(), event, data, media: state() })
if (records.length > 400)
records.shift()
}
const logger = Object.fromEntries(['trace', 'debug', 'log', 'warn', 'info', 'error'].map(level => [level, (...args) => record(`log:${level}`, args.map(String))]))
function attach(element) {
video = element
hls = new window.Hls({ enableWorker: worker, startLevel: 0, debug: sdkLogs ? logger : false })
for (const key of ['ERROR', 'AUDIO_TRACKS_UPDATED', 'AUDIO_TRACK_SWITCHED', 'LEVEL_SWITCHED', 'FRAG_BUFFERED', 'BUFFER_FLUSHED']) {
hls.on(window.Hls.Events[key], (_, data) => record(key, { id: data.id, level: data.level, fatal: data.fatal, details: data.details, frag: data.frag && { type: data.frag.type, sn: data.frag.sn, level: data.frag.level }, tracks: data.audioTracks?.map(track => ({ id: track.id, name: track.name, group: track.groupId })) }))
}
for (const name of ['waiting', 'stalled', 'seeking', 'seeked', 'playing', 'pause', 'ended', 'loadedmetadata', 'canplay', 'error'])
video.addEventListener(name, () => record(name))
hls.loadSource('/grouped.m3u8')
hls.attachMedia(video)
return hls
}
if (host === 'direct') {
video = document.createElement('video')
video.muted = true
video.controls = true
video.width = 640
video.height = 360
document.querySelector('.player').append(video)
attach(video)
}
else {
art = new window.Artplayer({
container: '.player',
url: '/grouped.m3u8',
muted: true,
setting: true,
plugins: plugin ? [window.artplayerPluginHlsControl({ quality: { control: true, setting: true }, audio: { control: true, setting: true } })] : [],
customType: { m3u8(video, _url, player) {
player.hls = attach(video)
} },
})
art.on('destroy', () => hls.destroy())
}
window.direct = {
get hls() { return hls },
get video() { return video },
records,
state,
destroy() {
record('destroy:start', teardown)
if (art) {
art.destroy()
}
else {
if (teardown === 'reset-first') {
video.removeAttribute('src')
video.load()
video.remove()
}
hls.destroy()
if (teardown === 'sdk-first') {
video.removeAttribute('src')
video.load()
video.remove()
}
}
record('destroy:end')
},
}
}, { worker, teardown, host, plugin, sdkLogs })
await wait(() => window.direct.video?.readyState >= 3)
await page.evaluate(() => window.direct.video.play())
await wait(() => window.direct.video.currentTime > 0.3)
await page.evaluate(() => {
window.direct.hls.currentLevel = 0
})
await wait(() => window.direct.hls.audioTracks.length === 2 && window.direct.hls.audioTracks.every(track => track.groupId === 'low'))
await page.evaluate(() => {
window.direct.mark = performance.now()
window.direct.hls.audioTrack = 1
})
await wait(() => window.direct.records.some(item => item.at >= window.direct.mark && item.event === 'AUDIO_TRACK_SWITCHED' && item.data.id === 1))
await page.evaluate(() => {
window.direct.hls.currentLevel = 1
})
await wait(() => window.direct.video.videoHeight === 180 && window.direct.hls.audioTrack === 0)
await page.evaluate(() => {
window.direct.hls.audioTrack = 2
})
await wait(() => window.direct.records.some(item => item.event === 'AUDIO_TRACK_SWITCHED' && item.data.id === 2))
await page.evaluate(() => {
window.direct.hls.currentLevel = 0
})
await wait(() => window.direct.hls.audioTracks.length === 2)
await page.evaluate(() => window.direct.destroy())
await page.evaluate(() => window.direct.state())
assert.deepEqual(entry.errors, [])
entry.status = 'passed'
}
catch (error) {
entry.status = 'failed'
entry.failure = error.message
await page.screenshot({ path: path.join(directory, `${iteration}.png`) }).catch(() => {})
}
finally {
entry.state = await page.evaluate(() => ({ coreLoaded: typeof window.Artplayer, pluginLoaded: typeof window.artplayerPluginHlsControl, workers: window.workerEvidence, media: window.direct?.state(), events: window.direct?.records, sdk: { version: window.Hls?.version, level: window.direct?.hls.currentLevel, audioTrack: window.direct?.hls.audioTrack } })).catch(error => ({ error: error.message }))
await context.tracing.stop({ path: path.join(directory, `${iteration}.zip`) }).catch((error) => {
entry.traceError = error.message
})
await context.close().catch((error) => {
entry.closeError = error.message
})
if (entry.state.error || entry.errors.length || entry.traceError || entry.closeError) {
entry.status = 'failed'
entry.failure ||= entry.state.error || entry.traceError || entry.closeError || entry.errors.join('; ')
}
}
console.log(JSON.stringify({ iteration, status: entry.status, failure: entry.failure, media: entry.state.media }))
}
}
finally {
await browser?.close()
server.closeAllConnections()
await new Promise(resolve => server.close(resolve))
fs.writeFileSync(path.join(directory, 'report.json'), `${JSON.stringify({ version, iterations, transport, teardown, worker, host, plugin, sdkLogs, workerObserver, runnerSHA256, observerSHA256, browserVersion, release, inputs: Object.fromEntries([...files].map(([file, bytes]) => [file, hash(bytes)])), results }, null, 2)}\n`)
console.log(`Diagnostic report: ${directory}`)
}
return results
}
const { values } = parseArgs({ options: { 'version': { type: 'string', default: '1.7.2' }, 'iterations': { type: 'string', default: '5' }, 'transport': { type: 'string', default: 'http' }, 'teardown': { type: 'string', default: 'reset-first' }, 'no-worker': { type: 'boolean', default: false }, 'host': { type: 'string', default: 'direct' }, 'plugin': { type: 'boolean', default: false }, 'sdk-logs': { type: 'boolean', default: false }, 'observe-workers': { type: 'boolean', default: false } } })
diagnose({ version: values.version, iterations: Number(values.iterations), transport: values.transport, teardown: values.teardown, worker: !values['no-worker'], host: values.host, plugin: values.plugin, sdkLogs: values['sdk-logs'], workerObserver: values['observe-workers'] }).then((results) => {
process.exitCode = results.some(result => result.status !== 'passed') ? 1 : 0
}).catch((error) => {
console.error(error)
process.exitCode = 1
})
+25
View File
@@ -0,0 +1,25 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Verify immutable integration inputs in the baseline runner.
import test from 'node:test'
import { ensureArchive, hash, readMember } from './releases.mjs'
test('HLS SDK integration matrix keeps historical provenance and verified standalone worker builds', async () => {
const matrix = JSON.parse(fs.readFileSync(new URL('../baselines/hls-sdk-matrix.json', import.meta.url)))
const historical = JSON.parse(fs.readFileSync(new URL('../baselines/hls-sdk.json', import.meta.url)))
assert.deepEqual(matrix.releases.map(release => release.version), ['1.5.17', '1.7.2'])
assert.deepEqual(matrix.releases[0], historical.release, 'Do not rewrite the historical SDK baseline')
for (const release of matrix.releases) {
const archive = await ensureArchive(release)
for (const [member, expected] of Object.entries(release.files))
assert.equal(hash(readMember(archive, member)), expected, `${release.version} ${member}`)
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
assert.equal(manifest.name, 'hls.js')
assert.equal(manifest.version, release.version)
assert.equal(manifest.license, 'Apache-2.0')
assert(release.files['package/dist/hls.min.js'])
assert(release.files['package/LICENSE'])
}
const plugin = JSON.parse(fs.readFileSync(new URL('../../packages/artplayer-plugin-hls-control/package.json', import.meta.url)))
assert(!plugin.dependencies?.['hls.js'], 'Integration testing must not impose a new runtime SDK dependency')
})
+23 -1
View File
@@ -1860,6 +1860,27 @@
"hls-validation.md"
]
},
{
"id": "PKG-HLS-SDK-01",
"phase": "5 包迁移:artplayer-plugin-hls-control",
"title": "验证真实 SDK worker 与分组轨道组合",
"scope": [
"artplayer-plugin-hls-control"
],
"dependsOn": [
"PKG-HLS-04",
"CORE-22"
],
"status": "doing",
"risk": "H",
"deliverable": "固定历史及当前 SDK 归档、真实 worker/多音轨组、外部选择与重绑定浏览器证据",
"acceptance": "受支持桌面 MSE 引擎与新旧核心通过;无 worker 回退伪通过,设备缺口仍由 PKG-HLS-05 保持未完成",
"evidence": [
"changes/2026-09-12-PKG-HLS-SDK-01-integration.md",
"baselines/hls-sdk-validation.json",
"baselines/hls-sdk-diagnostics.json"
]
},
{
"id": "PKG-HLS-05",
"phase": "5 包迁移:artplayer-plugin-hls-control",
@@ -1869,7 +1890,8 @@
],
"dependsOn": [
"PKG-HLS-04",
"CORE-22"
"CORE-22",
"PKG-HLS-SDK-01"
],
"status": "todo",
"risk": "H",
+1 -1
View File
@@ -1034,7 +1034,7 @@
"docs/assets/example/hls.control.js"
],
"contract": "Caller supplies art.hls; homepage demo loads hls.js 1.5.17. Preserve quality/audio selectors and old core integration.",
"validationStatus": "PKG-HLS-03 verifies actual Hls.js 1.5.17 on Chromium/Firefox with source/published core and modern/legacy candidate builds. Auto/topology/lifecycle defects fixed; Windows WebKit has no MSE. Safari/native, detach/reattach, worker, grouped tracks, versions and isolated installation remain HLS-05/06 and EX-03.",
"validationStatus": "PKG-HLS-SDK-01 tests pinned Hls.js 1.5.17/1.7.2 with native workers, grouped audio, replacement and detach/reattach across published/current core on desktop MSE engines. Inputs remain test-only archives. Windows WebKit lacks MSE; Safari/native, physical devices, external networks, full SDK types/version range and isolated installation remain HLS-05/06 and EX-03. One Firefox grouped teardown target crash remains unexplained (HLS-CRASH-01); passing subsequent runs are not closure evidence and SDK task remains doing.",
"demoPathsSource": "refactor/baselines/demo-inventory.json"
},
{