test(hls): [PKG-HLS-SDK-01] compare SDK sequences and capture pre-destroy crash

This commit is contained in:
Harvey Zhao committed 2026-09-15 15:15:25 +08:00
1 parent ca967ea1a2
commit 2a38067911
9 files changed
+4327 -19

No files matched your search

@@ -145,6 +145,14 @@ later control passes. Diagnostic private SDK fields are read-only, version-speci
never read load-level getters after SDK destruction. See the 2026-09-15 switch-boundary
checkpoint in refactor for the reproduced empty-buffer failure and remaining attribution.
`--sequence 1.5.17,1.5.17,1.7.2` runs frozen SDK versions in separate contexts within
one Firefox browser instance. It cannot be combined with explicit `--version` or
`--iterations`; every result records and checks its actual SDK version. A first-case
direct-video 1.5.17 run also reported a page crash while awaiting the high-group
transition, before any explicit destroy. Keep playback and teardown evidence distinct;
neither the previous SDK nor ArtPlayer is a necessary precondition for that occurrence.
See the 2026-09-15 SDK sequence checkpoint for exact scope and remaining native diagnostics.
## Package file boundary
The package .npmignore excludes src and tsconfig.json. The implementation config
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,74 @@
# PKG-HLS-SDK-01 同浏览器实例的 SDK 顺序对照
来源 HEAD:`ca967ea1a2faeab3f5e23c612af31b41d22fd7f8`。任务保持 doing。
本轮扩展诊断工具并取得销毁前的崩溃信号,没有修改播放器、插件、公共声明、
依赖或分发文件。HLS-CRASH-01 和 HLS-PLAYBACK-01 均继续开放。
## 顺序对照
之前的集成失败发生于包含两个 SDK 的测试序列,单版本诊断不足以比较这个条件。
`hls-sdk-diagnostic.mjs --sequence` 现在能在同一个 Firefox browser 实例中依次
创建隔离 context,使用指定的固定 SDK。不能据此声称各 context 的原生内容进程
相同;也没有在一个播放器上直接替换 SDK 构造函数。
所有序列值必须在 hls-sdk-matrix.json 中,先验证归档成员哈希,再通过独立版本
URL 加载,初始化前检查页面 Hls.version。每项结果记录实际 version,报告记录
sequence/releases。旧 `--version --iterations` 方式继续支持,保留单版本 release
字段。两种 CLI 模式显式混用会报错,避免标签或执行次数与意图不符。最多50项。
| 对照 | SDK 顺序 | 宿主和传输 | 结果 |
| --- | --- | --- | --- |
| direct | 1.5.17 → 1.5.17 → 1.7.2 | 原生 video、普通 HTTP | 2通过,第一项崩溃 |
| published | 1.5.17 → 1.5.17 → 1.7.2 | 发布核心/候选插件、route、Worker观察器 | 3通过 |
| reverse | 1.7.2 → 1.5.17 | 原生 video、普通 HTTP | 2通过 |
| default | 1.7.2 一次,旧参数入口 | 原生 video、普通 HTTP | 1通过 |
均为 Windows Firefox155.0、真实 worker、同一冻结媒体夹具;没有替换 worker 输出。
前三组使用 selected 边界及销毁前快照;default 保留默认 switched 边界与关闭的
快照。direct/reverse 没有 ArtPlayer、插件、route、SDK logger 或 Worker观察器,
仍保留已有 Playwright trace 和事件记录,不能称为完全无插桩。
## 新的崩溃证据改变了什么
direct 第一项1.5.17在 high-group 阶段等待高度/音轨条件时收到 page-crash 信号,
时间为2026-09-15 07:05:48.727 UTC。还未执行 capture-before-destroy,也未调用
显式 destroy。它是该 browser 实例的第一项 SDK 用例,因此不能要求先运行其他
版本才会发生这次崩溃。完整报告及本项 trace 已独立保存并记录指纹。
此前已复现的销毁后崩溃不因此消失,但“只排查销毁顺序”已不充分。更新风险标题
及 workspaceState,保留旧证据和原关闭条件;没有将两次不同阶段的崩溃当作已
证明同一原生根因。后续成功的 context 也不证明浏览器状态完全无影响。
查阅本次信号前后各30秒的 Windows Application 1000/1001 事件没有匹配记录。
这仅说明该窄范围查询没有返回事件,不代表没有崩溃,也不是原生堆栈。当前仍缺
能够解释退出/异常的原生诊断。取得原生 dump 时可参考
[Mozilla Crash Reporter 文档](https://firefox-source-docs.mozilla.org/toolkit/crashreporter/crashreporter/index.html#environment-variables-affecting-crash-reporting)
的本地保存选项;本轮没有启用新报告环境、采集 dump 或上传崩溃数据。
本机 Playwright1.63.0 的 coreBundle 中默认 Firefox 与 BidiFirefox 是不同启动类,
不能把 Bidi 的 MOZ_CRASHREPORTER 默认环境误认为当前 Firefox 后端已应用。
安装目录未看到独立 crashreporter 文件,也不足以证明该二进制的所有 dump 能力。
下一步应先核实当前后端/二进制的原生取证方式,再处理 SDK/浏览器边界;不凭猜测
变更核心公开 destroy 顺序、强制无 worker 或宣称使用单一版本已修复问题。
## 验证与维护
```sh
node refactor/scripts/hls-sdk-diagnostic.mjs --sequence 1.5.17,1.5.17,1.7.2 --switch-boundary selected --capture-before-destroy
node refactor/scripts/hls-sdk-diagnostic.mjs --sequence 1.5.17,1.5.17,1.7.2 --host published --plugin --transport route --observe-workers --switch-boundary selected --capture-before-destroy
node refactor/scripts/hls-sdk-diagnostic.mjs --sequence 1.7.2,1.5.17 --switch-boundary selected --capture-before-destroy
node refactor/scripts/hls-sdk-diagnostic.mjs --version 1.7.2 --iterations 1
```
机器证据见 [hls-sdk-sequence-validation.json](../baselines/hls-sdk-sequence-validation.json):
8通过、1失败,无重试/跳过。三项 CLI 非法输入(冲突模式、未冻结版本、超长序列)
均在浏览器启动前拒绝;定向 lint、SDK来源单测和严格工具链通过。
Node24.21.0、Yarn1.22.22,未重新安装/构建浏览器或工作区包。
本轮没有更改集成验收用例,复用上一检查点的最终测试验证,不重复完整矩阵。
报告中的原始路径/哈希保留完整事件,已提交摘要只保留事件统计和末尾状态,
复用输入对象按哈希去重。不同 runnerSHA256 对应增加 CLI 冲突检查之前/之后;
序列运行逻辑相同,不能把最终源码哈希说成早期运行时已有的字节。
这是诊断进展,不是修复或发布验收。回退本提交恢复单版本 runner 和此前的风险
说明,不改运行时。不得把新的失败从汇总删除;后续处置仍需独立测试、记录和提交。
+1 -1
View File
@@ -569,7 +569,7 @@
- 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) [记录](changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md) [记录](baselines/hls-http-teardown-validation.json) [记录](changes/2026-09-15-PKG-HLS-SDK-01-switch-boundary.md) [记录](baselines/hls-switch-boundary-validation.json)
- PKG-HLS-SDK-01: [记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md) [记录](baselines/hls-sdk-validation.json) [记录](baselines/hls-sdk-diagnostics.json) [记录](changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md) [记录](baselines/hls-http-teardown-validation.json) [记录](changes/2026-09-15-PKG-HLS-SDK-01-switch-boundary.md) [记录](baselines/hls-switch-boundary-validation.json) [记录](changes/2026-09-15-PKG-HLS-SDK-01-sdk-sequence.md) [记录](baselines/hls-sdk-sequence-validation.json)
- PKG-DASH-01: [记录](changes/2026-09-12-PKG-DASH-01-contract.md) [记录](baselines/dash-control-contract.md) [记录](baselines/dash-control-release.json)
- PKG-DASH-02: [记录](changes/2026-09-12-PKG-DASH-02-tests.md) [记录](dash-validation.md) [记录](baselines/dash-validation.json)
- PKG-DASH-03: [记录](changes/2026-09-12-PKG-DASH-03-runtime.md) [记录](baselines/dash-runtime.json) [记录](dash-validation.md)
+13
View File
@@ -1,5 +1,18 @@
# 进度与证据
## PKG-HLS-SDK-01 SDK顺序与销毁前崩溃
新增固定SDK序列诊断入口,逐页核验版本并保存实际顺序。在同一个Firefox实例
依次运行两个SDK的对照中,直接原生video第一项1.5.17就在high-group阶段收到
页面崩溃信号,尚未执行destroy;此前SDK或核心/插件不是该次发生的必要前提。
这扩展了HLS-CRASH-01的证据范围,不能继续仅围绕销毁顺序排查。
四组共8通过1失败,原始报告/trace独立保留;三个CLI非法输入拒绝、lint、SDK
来源单测和工具链通过。窄范围Windows事件查询无匹配,仍缺原生根因证据。
见[记录](changes/2026-09-15-PKG-HLS-SDK-01-sdk-sequence.md)及
[证据](baselines/hls-sdk-sequence-validation.json)。未修改运行时、依赖或分发。
两项HLS风险及任务继续开放,总数203 done、21 doing、43 todo不变。
## PKG-HLS-SDK-01 分组切换与持续播放
再次复现 Firefox/Hls.js 1.7.2/发布核心分组切换失败:音轨已更新,视频仍90高、
+1 -1
View File
@@ -124,7 +124,7 @@
| 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-CRASH-01 | open / 已复现 | Firefox Hls 1.5.17 worker 播放切组及销毁路径出现页面崩溃信号 | 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 |
| AUDIO-LIFE-01 | resolved / 已复现 | Audio Track 销毁后 update 与媒体/订阅缺少终止守卫 | PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-05 |
| AUDIO-SYNC-01 | open / 已复现 | Audio Track 未同步原生暂停/结束,偏移边界与切源/缓冲仍待完整验证 | PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-05 |
+5 -3
View File
@@ -2799,7 +2799,7 @@
},
{
"id": "HLS-CRASH-01",
"title": "Firefox Hls 1.5.17 worker 销毁可在原生 video 与候选核心复现崩溃",
"title": "Firefox Hls 1.5.17 worker 播放切组及销毁路径出现页面崩溃信号",
"confirmation": "reproduced",
"status": "open",
"owners": [
@@ -2813,11 +2813,13 @@
"refactor/baselines/hls-sdk-diagnostics.json",
"refactor/scripts/hls-sdk-diagnostic.mjs",
"refactor/changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md",
"refactor/baselines/hls-http-teardown-validation.json"
"refactor/baselines/hls-http-teardown-validation.json",
"refactor/changes/2026-09-15-PKG-HLS-SDK-01-sdk-sequence.md",
"refactor/baselines/hls-sdk-sequence-validation.json"
],
"compatibleResolution": "直接原生 video(无 ArtPlayer/插件)和候选核心已有 page crashed;候选核心无观察器也复现。继续区分 SDK、Firefox/Playwright 与媒体销毁,不静默改核心 destroy 顺序或调用方 SDK 配置。",
"closureCriteria": "取得可解释的崩溃诊断或直接 SDK 对照;验证处置和新旧核心/worker/分组销毁矩阵,不能仅以再次通过关闭。",
"workspaceState": "2026-09-14 direct HTTP/no ArtPlayer/plugin/Worker observer:35 diagnostics,31 passed4 failed. Worker reset-first fails1/10 and1/5; no-worker10/10 and optional pre-destroy snapshot5/5 pass. SDK-first also fails2/5. Three phase-recorded crashes occur during final-state collection after destroy returned. Installed Firefox TargetRegistry observer receives oop-frameloader-crashed; no native stack/root cause established. Do not substitute no-worker/snapshot success or change core teardown order; risk remains open."
"workspaceState": "2026-09-14 direct HTTP/no ArtPlayer/plugin/Worker observer:35 diagnostics,31 passed4 failed. Worker reset-first fails1/10 and1/5; no-worker10/10 and optional pre-destroy snapshot5/5 pass. SDK-first also fails2/5. Three phase-recorded crashes occur during final-state collection after destroy returned. Installed Firefox TargetRegistry observer receives oop-frameloader-crashed; no native stack/root cause established. Do not substitute no-worker/snapshot success or change core teardown order; risk remains open. 2026-09-15 sequence: direct native video first 1.5.17 case reports page crash in high-group phase before explicit destroy; no earlier SDK case, ArtPlayer, plugin, route, SDK logger or Worker observer. Three ordered sequences plus one default invocation total 8 passed/1 failed. Native cause remains unproven; do not restrict investigation to teardown or merge distinct signals into a proven shared cause."
},
{
"id": "HLS-PLAYBACK-01",
+39 -13
View File
@@ -10,7 +10,7 @@ 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, captureBeforeDestroy = false, switchBoundary = 'switched', prefill = false } = {}) {
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, captureBeforeDestroy = false, switchBoundary = 'switched', prefill = false, sequence } = {}) {
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)
@@ -20,10 +20,13 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
assert(['switched', 'selected', 'immediate'].includes(switchBoundary))
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 versions = sequence || Array.from({ length: iterations }, () => version)
assert(Array.isArray(versions) && versions.length > 0 && versions.length <= 50)
const releases = [...new Set(versions)].map((version) => {
const release = matrix.releases.find(item => item.version === version)
assert(release, 'Use a frozen SDK version')
return release
})
const media = path.join(refactorDir, '../test/browser/media/hls')
const manifest = JSON.parse(fs.readFileSync(path.join(media, 'manifest.json')))
const files = new Map()
@@ -33,7 +36,11 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
files.set(`/${name}`, bytes)
}
files.set('/grouped.m3u8', fs.readFileSync(path.join(refactorDir, '../test/browser/fixtures/hls-grouped.m3u8')))
files.set('/hls.js', sdk)
for (const release of releases) {
const sdk = readMember(await ensureArchive(release), 'package/dist/hls.min.js')
assert.equal(hash(sdk), release.files['package/dist/hls.min.js'])
files.set(`/hls-${release.version}.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')
@@ -62,12 +69,12 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
try {
browser = await firefox.launch({ headless: true })
browserVersion = browser.version()
for (let iteration = 0; iteration < iterations; iteration++) {
for (let iteration = 0; iteration < versions.length; 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: [], phase: 'setup', crashes: [] }
const entry = { iteration, version: versions[iteration], status: 'running', errors: [], requests: [], phase: 'setup', crashes: [] }
results.push(entry)
page.on('pageerror', error => entry.errors.push(error.message))
page.on('crash', () => {
@@ -88,7 +95,8 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
await page.goto(origin)
if (workerObserver)
await page.evaluate(observeWorkers)
await page.addScriptTag({ url: `${origin}/hls.js` })
await page.addScriptTag({ url: `${origin}/hls-${entry.version}.js` })
assert.equal(await page.evaluate(() => window.Hls.version), entry.version)
if (host !== 'direct')
await page.addScriptTag({ url: `${origin}/artplayer.js` })
if (plugin)
@@ -280,21 +288,39 @@ export async function diagnose({ version = '1.7.2', iterations = 5, transport =
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 }))
console.log(JSON.stringify({ iteration, version: entry.version, 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, captureBeforeDestroy, switchBoundary, prefill, runnerSHA256, observerSHA256, browserVersion, release, inputs: Object.fromEntries([...files].map(([file, bytes]) => [file, hash(bytes)])), results }, null, 2)}\n`)
fs.writeFileSync(path.join(directory, 'report.json'), `${JSON.stringify({ version: sequence ? undefined : version, iterations: versions.length, sequence: versions, transport, teardown, worker, host, plugin, sdkLogs, workerObserver, captureBeforeDestroy, switchBoundary, prefill, runnerSHA256, observerSHA256, browserVersion, release: sequence ? undefined : releases[0], releases, 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' }, 'host': { type: 'string', default: 'direct' }, 'no-worker': { type: 'boolean', default: false }, 'plugin': { type: 'boolean', default: false }, 'sdk-logs': { type: 'boolean', default: false }, 'observe-workers': { type: 'boolean', default: false }, 'capture-before-destroy': { type: 'boolean', default: false }, 'switch-boundary': { type: 'string', default: 'switched' }, 'prefill': { 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'], captureBeforeDestroy: values['capture-before-destroy'], switchBoundary: values['switch-boundary'], prefill: values.prefill }).then((results) => {
const { values, tokens } = parseArgs({
tokens: true,
options: {
'version': { type: 'string', default: '1.7.2' },
'iterations': { type: 'string', default: '5' },
'sequence': { type: 'string' },
'transport': { type: 'string', default: 'http' },
'teardown': { type: 'string', default: 'reset-first' },
'host': { type: 'string', default: 'direct' },
'no-worker': { type: 'boolean', default: false },
'plugin': { type: 'boolean', default: false },
'sdk-logs': { type: 'boolean', default: false },
'observe-workers': { type: 'boolean', default: false },
'capture-before-destroy': { type: 'boolean', default: false },
'switch-boundary': { type: 'string', default: 'switched' },
'prefill': { type: 'boolean', default: false },
},
})
assert(!values.sequence || !tokens.some(token => token.kind === 'option' && ['version', 'iterations'].includes(token.name)), 'Use --sequence or --version/--iterations, not both')
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'], captureBeforeDestroy: values['capture-before-destroy'], switchBoundary: values['switch-boundary'], prefill: values.prefill, sequence: values.sequence?.split(',') }).then((results) => {
process.exitCode = results.some(result => result.status !== 'passed') ? 1 : 0
}).catch((error) => {
console.error(error)
+3 -1
View File
@@ -2151,7 +2151,9 @@
"changes/2026-09-14-PKG-HLS-SDK-01-http-teardown.md",
"baselines/hls-http-teardown-validation.json",
"changes/2026-09-15-PKG-HLS-SDK-01-switch-boundary.md",
"baselines/hls-switch-boundary-validation.json"
"baselines/hls-switch-boundary-validation.json",
"changes/2026-09-15-PKG-HLS-SDK-01-sdk-sequence.md",
"baselines/hls-sdk-sequence-validation.json"
]
},
{