test(audio): [PKG-AUDIO-05] distinguish prefix startup from native starvation

This commit is contained in:
Harvey Zhao committed 2026-09-15 13:12:05 +08:00
1 parent bf56d09178
commit 3eb5500525
9 files changed
+4151 -11

No files matched your search

+12
View File
@@ -124,3 +124,15 @@ Chromium/Firefox 的八种组合都观测到推进与可信 waiting,Windows We
不支持缓冲;更不能为候选忽略 failing 用例。AUDIO-BUFFER-01 保持 open,Audio-05
仍 doing;需要其他可实际执行的原生环境/输入路径补齐。当前没有已安装 WSL 发行版或
可用 Docker 命令。物理设备、声音输出、自动播放策略和 Audio-06 的安装/demo 仍未验收。
### PKG-AUDIO-05 同文件字节前缀对照
后续[前缀诊断](changes/2026-09-15-PKG-AUDIO-05-prefix.md)保留相同 MP4/AAC、
媒体哈希和原有集成断言。原生 Windows WebKit 在更大的前缀下可以于尾部仍被扣住时
推进时钟,说明之前的“不推进”不是这两个格式一概不能渐进起播。四个完整模式
诊断仍未见可信 waiting;起播不能作为缓冲成功。保持 AUDIO-BUFFER-01 和任务05开放。
`media-gate-native.spec.js` 新增可选字节参数和放行前独立请求快照,并在恢复失败时
仍保存阶段证据。默认参数、观察时限和 `audio-buffering.spec.js` 均未改变。
明确比较原生时钟、readyState、buffered 与实际已发送字节,不只看最终请求的
sent(放行后该数值已变成完整正文)。复跑参数和原始失败限制见检查点。
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,64 @@
# PKG-AUDIO-05 字节前缀与原生缓冲诊断
接续 `bf56d091787bc32c1aacc84ac96904e0349bb0a8`,任务05仍为 doing。
不改播放器/插件、默认媒体、公开 API、事件、分发或依赖,不关闭 AUDIO-BUFFER-01。
## 取证与变化
旧诊断中,Windows WebKit 在 96 KiB 视频 / 32 KiB 音频前缀下不推进,放行后恢复。
本轮仅改变已发字节量,使用相同 `pattern.mp4` 和 `audio-tone.m4a`,原生对照没有
ArtPlayer 实例;早期独立探针连核心脚本也未加载。
- 六项独立起播探针:视频 98,304 字节不推进,188,653 和 251,537 字节推进;
音频 32,768 字节不推进,99,327 和 132,435 字节推进。所有尾部仍扣住,放行后
时钟前进。相同文件的结果不能解释为通用 codec 不支持渐进播放。
- 两项中间前缀探针:视频 131,072、音频 49,152 字节仍不推进/无可信 waiting。
这只是已测点,不推断精确启动阈值、内部缓冲策略或所有系统相同。
- 较大前缀探针增加九秒观察后,视频时钟曾达 9.319 秒而文件只有 8 秒,
ended=true;音频停在 5.611 秒且 readyState=4。均没有可信 waiting。
第一轮视频恢复断言因要求再推进而超时;随后在放行前写检查点并始终保留恢复
错误,没有把媒体结束误报成插件恢复失败,也没有用后来结果覆盖首次异常。
九秒窗口只用于上述一次独立诊断,不进入已提交测试。提交的原生测试保留原先
3 秒推进 / 7 秒 waiting 观察和原恢复断言。新增两个显式诊断变量:
```powershell
$env:ARTPLAYER_MEDIA_GATE_VIDEO_LIMIT = '188653'
$env:ARTPLAYER_MEDIA_GATE_AUDIO_LIMIT = '65536'
yarn test:browser media-gate-native.spec.js --project=webkit --workers=1
```
数值须为正整数且小于文件大小,由真实 HTTP gate 校验;结束诊断后清除这两个变量,
恢复原默认前缀。参数仅用于 native spec,不作用于 `audio-buffering.spec.js`。
在读取原生状态后、`gate.release()` 之前,测试现在拷贝 `requestsBeforeRelease` 并
立即附加 `native-gate-before-release`,避免后续 sent 数字被完整响应改写,也避免
恢复断言失败丢失前半段证据。最终 capability 附件放在 finally 中,包含已到达阶段,
不把缺少 after 当作恢复成功。增加 duration/ended/buffered,便于区分时钟和实际
响应状态。没有改变或删除音轨组合测试的可信 waiting、双时钟恢复与同步断言。
## 验证范围
机器结果见[前缀证据](../baselines/audio-prefix-validation.json)。固定 Node 24.21.0、
Yarn 1.22.22;Playwright Chromium 153.0.8010.12、Firefox 155.0、WebKit 26.6,win32。
独立报告/trace 输出保留在 `.cache/audio-prefix-*`,没有覆盖之前的缓冲失败。
较大前缀的四项 WebKit native 流式/完整部分 Range 诊断均正常退出:四项推进、
零项可信 waiting。视频放行前约 7.3 秒,流式音频约 5.6 秒,完整 Range 音频约
7.3 秒,readyState 均为 4;请求快照证明尾部尚未发送。它们只证明观察及放行后
时钟恢复,不是四项缓冲验收通过。
真实 HTTP gate 的三项单测和改动 spec 的只读 lint 通过。默认三引擎诊断的最终
统计及工作区检查在机器证据中记录;诊断通过数必须与 progressed/waiting 分开读。
本轮未重建无变化的生产产物、未重跑插件全矩阵、未验证实体 Safari、声音输出或远端 CI。
上游实现说明可帮助定位:[WebKit Windows 媒体后端](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/win/MediaPlayerPrivateMediaFoundation.cpp)
有独立的缓冲事件和时钟处理,
[Playwright 官方说明](https://github.com/microsoft/playwright/blob/main/docs/src/browsers.md)
要求区分各 OS 媒体能力。但本轮没有匹配所装 WebKit 二进制的完整源码和后端执行轨迹,
不能据当前上游文件宣布精确根因已找到。下一步需要对应原生后端或其他实际支持平台
的同输入缓冲证据;不在 ArtPlayer 中加入模拟 waiting、全局延时或放宽测试。
维护入口为 [audio-validation.md](../audio-validation.md) 和
[浏览器说明](../../test/browser/README.md)。本检查点可独立撤销以恢复旧观察方式,
生产运行字节不变;不推送、部署或发布。
+1 -1
View File
@@ -554,7 +554,7 @@
- PKG-AUDIO-02: [记录](changes/2026-09-12-PKG-AUDIO-02-tests.md) [记录](audio-validation.md) [记录](baselines/audio-validation.json)
- PKG-AUDIO-03: [记录](changes/2026-09-12-PKG-AUDIO-03-lifecycle.md) [记录](baselines/audio-lifecycle-validation.json)
- PKG-AUDIO-04: [记录](changes/2026-09-12-PKG-AUDIO-04-types.md) [记录](baselines/audio-types-validation.json)
- PKG-AUDIO-05: [记录](changes/2026-09-12-PKG-AUDIO-05-combinations.md) [记录](baselines/audio-combinations-first.json) [记录](baselines/audio-combinations-checkpoint.json) [记录](audio-validation.md)
- PKG-AUDIO-05: [记录](changes/2026-09-12-PKG-AUDIO-05-combinations.md) [记录](baselines/audio-combinations-first.json) [记录](baselines/audio-combinations-checkpoint.json) [记录](audio-validation.md) [记录](changes/2026-09-15-PKG-AUDIO-05-prefix.md) [记录](baselines/audio-prefix-validation.json)
- PKG-AUTO-THUMB-01: [记录](baselines/auto-thumbnail-release.json) [记录](baselines/auto-thumbnail-contract.md) [记录](changes/2026-09-13-PKG-AUTO-THUMB-01-contract.md) [记录](baselines/auto-thumbnail-contract-validation.json)
- PKG-AUTO-THUMB-02: [记录](baselines/auto-thumbnail-failures.md) [记录](baselines/auto-thumbnail-failures-validation.json) [记录](changes/2026-09-13-PKG-AUTO-THUMB-02-failures.md)
- PKG-AUTO-THUMB-03: [记录](changes/2026-09-13-PKG-AUTO-THUMB-03-lifecycle-checkpoint.md) [记录](baselines/auto-thumbnail-lifecycle-checkpoint.json) [记录](changes/2026-09-13-PKG-AUTO-THUMB-03-hidden-renderer.md) [记录](baselines/auto-thumbnail-hidden-renderer.json) [记录](baselines/auto-thumbnail-timeline-media.json) [记录](changes/2026-09-13-PKG-AUTO-THUMB-03-frame-presentation.md) [记录](baselines/auto-thumbnail-frame-presentation.json) [记录](changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md) [记录](baselines/auto-thumbnail-rendering-readiness.json) [记录](changes/2026-09-14-PKG-AUTO-THUMB-03-first-seek.md) [记录](baselines/auto-thumbnail-first-seek.json) [记录](changes/2026-09-14-PKG-AUTO-THUMB-03-native-decoder.md) [记录](baselines/auto-thumbnail-native-decoder.json)
+6
View File
@@ -1,5 +1,11 @@
# 进度与证据
## PKG-AUDIO-05 同文件前缀与原生缓冲检查点
仅改变同一 MP4/AAC 的 HTTP 放行前缀,Windows WebKit 能起播,但四项较大前缀诊断仍没有可信 waiting。默认三引擎 12 项诊断通过,实际 progressed/waiting 为 Chromium/Firefox 8/8、WebKit 0/4;诊断退出成功不算缓冲验收。新增参数只影响 native spec,放行前请求字节/媒体状态独立保存,恢复失败也保留观察。原插件集成断言、超时和运行字节均未改。见[检查点](changes/2026-09-15-PKG-AUDIO-05-prefix.md)及[证据](baselines/audio-prefix-validation.json)。
AUDIO-BUFFER-01 继续 open、任务05继续 doing;未以原生时钟代替实际画面/声音证据,未定位已安装媒体后端的精确根因。202/266 done、21 doing、43 todo 不变。接续需同输入的实际支持环境/后端证据,同时继续其他可实施任务;未推送或发布。
## REL-04 完成早期回退与主线同步演练
当前 HEAD 隔离重建后,七步连续安装/回退 237 项、五种状态三浏览器实播 15/15、iframe 更名恢复和 Git 冲突同步/中止均通过。旧站点恢复实现未改,本轮复核全部 538 文件、12 条 HTTP 路径和 6 项播放;未重复破坏/恢复,未验证远端部署。发布门槛负例及文件检查 34/34。见[验收](changes/2026-09-15-REL-04-acceptance.md)和[证据](baselines/rollback-acceptance-validation.json)。
+4 -2
View File
@@ -2976,11 +2976,13 @@
"refactor/baselines/audio-combinations-checkpoint.json",
"refactor/audio-validation.md",
"refactor/changes/2026-09-15-CI-01-subtitles-installed.md",
"refactor/baselines/ci-subtitles-installed-validation.json"
"refactor/baselines/ci-subtitles-installed-validation.json",
"refactor/changes/2026-09-15-PKG-AUDIO-05-prefix.md",
"refactor/baselines/audio-prefix-validation.json"
],
"compatibleResolution": "保留真实 waiting 断言;使用独立原生对照和其他实际支持环境/输入继续验证,不将没有播放推进的诊断当成缓冲通过,也不凭此改变插件 API 或否定真实 Safari 支持。",
"closureCriteria": "受影响的缓冲/恢复得到真实媒体证据;与原生能力和测试夹具差异分开,发布环境的剩余范围经显式处理。",
"workspaceState": "CI-01 九包安装扩展的首轮 336 项中,Windows WebKit 八项新旧音频受限媒体组合继续失败。七项未观察到目标可信 waiting,一项受限视频路径耗尽整体时间并在最终状态读取中报页面关闭。其他引擎均通过;保留原始失败,不以销毁后状态反推初始解码错误,不推断新的共同底层原因。"
"workspaceState": "CI-01 九包安装扩展的首轮 336 项中,Windows WebKit 八项新旧音频受限媒体组合继续失败。七项未观察到目标可信 waiting,一项受限视频路径耗尽整体时间并在最终状态读取中报页面关闭。其他引擎均通过;保留原始失败,不以销毁后状态反推初始解码错误,不推断新的共同底层原因。 Same-file prefix controls now show Windows WebKit can start before releasing the tail with larger prefixes (video188653/audio65536), but four committed native diagnostics still record no trusted waiting. Default three-engine diagnostics retain Chromium/Firefox8 progression+waiting and WebKit4 no progression/no waiting. Before-release request copies distinguish held bytes from final sent counters; no production fix or waiver."
},
{
"id": "CHAPTER-LAYOUT-01",
+3 -1
View File
@@ -1744,7 +1744,9 @@
"changes/2026-09-12-PKG-AUDIO-05-combinations.md",
"baselines/audio-combinations-first.json",
"baselines/audio-combinations-checkpoint.json",
"audio-validation.md"
"audio-validation.md",
"changes/2026-09-15-PKG-AUDIO-05-prefix.md",
"baselines/audio-prefix-validation.json"
]
},
{
+11
View File
@@ -230,6 +230,17 @@ playback after release; inspect its progressed/waiting flags before claiming buf
These capability observation timeouts do not relax the integration assertions.
Archive the report and results (including traces) before starting another browser suite.
For byte-prefix diagnostics, set `ARTPLAYER_MEDIA_GATE_VIDEO_LIMIT` and/or
`ARTPLAYER_MEDIA_GATE_AUDIO_LIMIT` to an integer byte count strictly between zero
and the corresponding file size. Unset them for the unchanged 96 KiB video / 32 KiB
audio defaults. These variables only affect the native diagnostic, never the audio
plugin integration test. `native-gate-before-release` retains the media state and
an immutable request snapshot before releasing the tail; `native-gate-capability`
also survives failures after that observation and records any successful recovery.
Check `progressed` and `waiting` independently. Larger prefixes can start Windows
WebKit playback without yielding a trusted waiting event; this does not prove the
integration buffering contract. See the audio prefix checkpoint in refactor.
Danmuku input, scheduler, resources and heatmap-density specs accept
`ARTPLAYER_DANMUKU_ARTIFACT` for a built main/legacy file; unset it to compile the
current JS/TS entry. Run each format sequentially and wait for the command's final
+12 -7
View File
@@ -1,4 +1,5 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { createMediaGate } from '../helpers/media-gate.js'
import { expect, test } from './fixtures.js'
@@ -8,8 +9,10 @@ for (const owner of ['video', 'audio']) {
test(`native ${owner} ${completeRanges ? 'complete ranges' : 'streamed response'} capability without ArtPlayer instances`, async ({ page }, testInfo) => {
const file = owner === 'video' ? 'pattern.mp4' : 'audio-tone.m4a'
const bytes = fs.readFileSync(new URL(`./media/${file}`, import.meta.url))
const limit = owner === 'video' ? 96 * 1024 : 32 * 1024
const configuredLimit = process.env[`ARTPLAYER_MEDIA_GATE_${owner.toUpperCase()}_LIMIT`]
const limit = configuredLimit === undefined ? (owner === 'video' ? 96 * 1024 : 32 * 1024) : Number(configuredLimit)
const gate = await createMediaGate(bytes, owner === 'video' ? 'video/mp4' : 'audio/mp4', limit, completeRanges)
const observation = { file, sha256: hash(bytes), bytes: bytes.length, limit, completeRanges, configuredLimit: configuredLimit ?? null, progressed: false, waiting: false }
try {
await page.goto('/test/player.html')
await page.evaluate(({ owner, url }) => {
@@ -25,29 +28,31 @@ for (const owner of ['video', 'audio']) {
}, { owner, url: gate.url })
await expect.poll(() => page.evaluate(() => window.nativeMedia.readyState >= 2 || Boolean(window.nativeMedia.error))).toBe(true)
await page.locator('#play').click()
let progressed = false
let waiting = false
try {
await page.waitForFunction(() => window.nativeMedia.currentTime > 0.3, null, { timeout: 3000 })
progressed = true
observation.progressed = true
await page.waitForFunction(() => window.nativeEvents.some(event => event.event === 'waiting' && event.trusted && event.time > 0.3 && event.readyState < 3), null, { timeout: 7000 })
waiting = true
observation.waiting = true
}
catch (error) {
if (error.name !== 'TimeoutError')
throw error
}
const before = await page.evaluate(() => ({ time: window.nativeMedia.currentTime, readyState: window.nativeMedia.readyState, paused: window.nativeMedia.paused, error: window.nativeMedia.error?.code || null, instances: window.Artplayer.instances.length, events: window.nativeEvents }))
const before = await page.evaluate(() => ({ time: window.nativeMedia.currentTime, duration: window.nativeMedia.duration, ended: window.nativeMedia.ended, readyState: window.nativeMedia.readyState, paused: window.nativeMedia.paused, error: window.nativeMedia.error?.code || null, instances: window.Artplayer.instances.length, events: window.nativeEvents, buffered: Array.from({ length: window.nativeMedia.buffered.length }, (_, index) => [window.nativeMedia.buffered.start(index), window.nativeMedia.buffered.end(index)]) }))
observation.before = before
observation.requestsBeforeRelease = structuredClone(gate.requests)
await testInfo.attach('native-gate-before-release', { contentType: 'application/json', body: JSON.stringify(observation) })
expect(before.instances).toBe(0)
expect(gate.blocked).toBeGreaterThan(0)
gate.release()
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.nativeMedia.currentTime)).toBeGreaterThan(before.time + 0.3)
expect(await page.evaluate(() => window.nativeMedia.error)).toBeNull()
await testInfo.attach('native-gate-capability', { contentType: 'application/json', body: JSON.stringify({ file, sha256: hash(bytes), limit, completeRanges, progressed, waiting, before, requests: gate.requests, after: await page.evaluate(() => ({ time: window.nativeMedia.currentTime, readyState: window.nativeMedia.readyState, paused: window.nativeMedia.paused })) }) })
observation.after = await page.evaluate(() => ({ time: window.nativeMedia.currentTime, readyState: window.nativeMedia.readyState, paused: window.nativeMedia.paused }))
}
finally {
try {
await testInfo.attach('native-gate-capability', { contentType: 'application/json', body: JSON.stringify({ ...observation, requests: gate.requests }) })
await page.evaluate(() => {
window.nativeMedia?.pause()
window.nativeMedia?.removeAttribute('src')