mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
test(auto-thumbnail): [PKG-AUTO-THUMB-03] verify first-seek diagnostic boundaries
This commit is contained in:
1 parent
2f130c58f1
commit
4fa5c68be9
8 files changed
+2280
-9
No files matched your search
@@ -150,6 +150,14 @@ correct Firefox control, so it is not a portable frame-readiness gate. The comma
|
||||
reports observations, not a passing acceptance suite, and never edits production
|
||||
bundles. See refactor's rendering-readiness record before repeating these options.
|
||||
|
||||
`yarn probe:auto-thumbnail-rendering --first-seek` separately compares initial
|
||||
loadeddata drawing without a seek, one/two animation-frame waits, delayed post-seek
|
||||
drawing and a completed forward/back seek. Native setter instrumentation records
|
||||
actual seek targets; the warm variant must reach one second before seeking zero.
|
||||
These variants still fail the unique-first-frame check on the recorded Windows
|
||||
WebKit host. They are diagnostic page-owned callbacks, not production fixes or
|
||||
cancellation code. See refactor's first-seek checkpoint before repeating them.
|
||||
|
||||
The frozen eight-second timeline has one unique purple first frame followed by
|
||||
red, black, blue and yellow sections. Its command and fingerprint are in
|
||||
`refactor/baselines/auto-thumbnail-timeline-media.json`. Reproduce into a new
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,56 @@
|
||||
# PKG-AUTO-THUMB-03: first-seek diagnostic checkpoint
|
||||
|
||||
Baseline: `2f130c58f14b1f3ff847568ae9cf144a75f450fe`. The task and
|
||||
AUTO-THUMB-PIXEL-01 remain open. This change adds diagnostic evidence, not a
|
||||
production workaround or package acceptance.
|
||||
|
||||
Earlier rendering/readiness experiments could not distinguish the first
|
||||
`currentTime = 0` seek from initial loadeddata presentation. The new
|
||||
`yarn probe:auto-thumbnail-rendering --first-seek` mode compares six Windows
|
||||
WebKit profiles: current code; first draw from loadeddata without a seek;
|
||||
that same draw after one/two animation frames; two animation frames after seek;
|
||||
and a completed forward seek to one second followed by a seek to zero.
|
||||
|
||||
Each code replacement must match exactly one known boundary. Native currentTime
|
||||
getter/setter instrumentation delegates to HTMLMediaElement and records every
|
||||
actual seek, including the warm-up seek and retries. It does not pretend that
|
||||
all variants seek five times. The sample retains its unique purple first frame,
|
||||
later red/black/blue/yellow cells and fixed SHA-256. Chromium/Firefox current-code
|
||||
controls (baseline and forward/back seek) retain native frame callbacks. There is no new production timeout,
|
||||
playback call, pixel substitution or test skip.
|
||||
|
||||
All six WebKit profiles completed five updates, but none obtained the required
|
||||
purple first frame. Omitting the zero seek therefore does not establish correct
|
||||
initial pixels. Animation-frame waits and warm forward/back seeking also cannot
|
||||
be adopted as fixes on this evidence. Both engine controls obtained the purple
|
||||
frame. This is a host/version-specific observation, not proof of a WebKit-wide
|
||||
or physical Safari defect.
|
||||
|
||||
The first exploratory cached script did not record the warm-up seek at its
|
||||
actual setter. Setter instrumentation then exposed an invalid warm-up comparison:
|
||||
a late zero-time event completed the first draw before any backward seek. The
|
||||
final probe waits for the one-second seek to complete before seeking zero, with
|
||||
at most three retries of a mismatched warm seek. Without those retries WebKit
|
||||
never completed the warm seek within the eight-second diagnostic window. The
|
||||
final observed targets start `[1, 1, 0]` in WebKit and `[1, 0]` in both controls;
|
||||
the probe checks that only warm retries precede zero. It was rerun;
|
||||
only its final report is the authoritative first-seek comparison. Code and media
|
||||
fingerprints, every actual seek and first-cell results are retained in
|
||||
`../baselines/auto-thumbnail-first-seek.json`; raw event/draw reports remain in
|
||||
the referenced cache. The ordinary rendering mode is also rerun because its
|
||||
instrumentation shares this change. A successful process exit means diagnostic
|
||||
capture completed, not that extraction passed pixel acceptance.
|
||||
|
||||
These in-memory variants are page-lifetime experiments; their RAF callbacks do
|
||||
not constitute production cancellation handling. Package sources, declarations,
|
||||
dist and default extraction remain unchanged. Existing native pixel assertions
|
||||
are neither removed nor weakened. Do not reintroduce these failed variants as
|
||||
untested suggestions. The next investigation needs a different decoded-frame
|
||||
signal or supported-device evidence; independent plugin/core combination tasks
|
||||
can proceed while this issue remains open.
|
||||
|
||||
Validation is scoped to the changed diagnostic: targeted lint, both actual
|
||||
browser probe modes, plan/risk consistency and risk-register tests. Full package
|
||||
and global tests are not rerun because no production or acceptance-test behavior
|
||||
changes. Revert this checkpoint to remove the extra probe mode and its records.
|
||||
Local checkpoint commit only; no task completion, push or publication.
|
||||
+1
-1
@@ -528,7 +528,7 @@
|
||||
- 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-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)
|
||||
- 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)
|
||||
- PKG-VTT-THUMB-01: [记录](baselines/vtt-thumbnail-release.json) [记录](baselines/vtt-thumbnail-contract.md) [记录](changes/2026-09-13-PKG-VTT-THUMB-01-contract.md)
|
||||
- PKG-VTT-THUMB-02: [记录](baselines/vtt-thumbnail-failures.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-02-failures.md)
|
||||
- PKG-VTT-THUMB-03: [记录](changes/2026-09-13-PKG-VTT-THUMB-03-resources.md) [记录](baselines/vtt-thumbnail-resources.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-03-parser.md) [记录](baselines/vtt-thumbnail-parser.json)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# 进度与证据
|
||||
|
||||
|
||||
## Auto Thumbnail 首次 seek 诊断检查点
|
||||
|
||||
SITE-04 的全包文档核对依赖尚未完成的 Auto Thumbnail/VAST 迁移,本轮先回到
|
||||
PKG-AUTO-THUMB-03。新增 --first-seek 诊断,保留唯一紫色首帧和真实 setter
|
||||
记录。最终十组中 WebKit 六组均完成抽帧但首帧错误;Chromium/Firefox 各两组
|
||||
控制正确。去掉首次 seek、延迟绘制与核实完成的前进/回零都不能作为修复。
|
||||
迟到零点事件使最初 warm 探针没有实际回零;已修正观测并保留无效/超时报告。
|
||||
普通十四组诊断也完成;生产源码、产物和像素断言未改。任务/风险仍开放,
|
||||
不是整包验收通过。见[检查点](changes/2026-09-14-PKG-AUTO-THUMB-03-first-seek.md)和[证据](baselines/auto-thumbnail-first-seek.json)。
|
||||
任务数保持 243 / 170 done。下一步推进已满足依赖的 VTT Thumbnail 新旧核心
|
||||
组合验收;SITE-04 保持 todo,不跳过其依赖。本地检查点提交,无推送/发布。
|
||||
|
||||
## SITE-03 桌面编辑器与生成流程完成
|
||||
|
||||
桌面自有 UI 拆为严格 TS 的 bootstrap、Monaco、运行会话、文件导入与设置模块;
|
||||
|
||||
+4
-2
@@ -4185,9 +4185,11 @@
|
||||
"test/auto-thumbnail-frames.test.js",
|
||||
"refactor/baselines/auto-thumbnail-frame-presentation.json",
|
||||
"refactor/changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md",
|
||||
"refactor/baselines/auto-thumbnail-rendering-readiness.json"
|
||||
"refactor/baselines/auto-thumbnail-rendering-readiness.json",
|
||||
"refactor/changes/2026-09-14-PKG-AUTO-THUMB-03-first-seek.md",
|
||||
"refactor/baselines/auto-thumbnail-first-seek.json"
|
||||
],
|
||||
"compatibleResolution": "Establish actual decoded-frame readiness for the existing timeline/grid and preserve silent independent extraction; do not waive colored pixel checks as unsupported media. Intrinsic-size hidden/opacity/clip/visible rendering crossed with current/readyState/first-loadeddata-event strategies still misses the unique first frame in all 12 Windows WebKit profiles. Correct Firefox frames retain totalVideoFrames=0, so that counter cannot be a portable readiness gate. Do not reintroduce these unvalidated workarounds.",
|
||||
"compatibleResolution": "Establish actual decoded-frame readiness for the existing timeline/grid and preserve silent independent extraction; do not waive colored pixel checks as unsupported media. Intrinsic-size hidden/opacity/clip/visible rendering crossed with current/readyState/first-loadeddata-event strategies still misses the unique first frame in all 12 Windows WebKit profiles. Correct Firefox frames retain totalVideoFrames=0, so that counter cannot be a portable readiness gate. Do not reintroduce these unvalidated workarounds. First-seek diagnostics additionally exclude drawing the first loadeddata frame without seek, one/two RAF waits, post-seek RAF waits and verified forward/back seeking on the recorded Windows WebKit host. The warm comparison first had an invalid stale-event completion, then a timeout; final bounded retries verify actual [1,1,0] seeking and still miss purple. Controls retain purple. These are exclusions, not production fixes.",
|
||||
"closureCriteria": "Candidate source and artifacts generate usable sampled pixels on native browser controls with lifecycle checks; supported Safari and core combinations remain explicit final gates.",
|
||||
"workspaceState": "The frames module now waits for native presentation and seek completion on capable browsers. Chromium/Firefox source/main/legacy assert all five cells including first purple frame, second red cell, legitimate black and presentation times. Callback-less Windows WebKit first two cells remain diagnostic, later cells asserted; physical Safari/core combinations remain open. 84 native lifecycle/pixel cases passed with explicit held native-frame vs Blob-fallback paths; 19 new Node cases fail 17 ways on prior checkpoint and all 52 current cases pass per candidate. Risk and task03 remain open."
|
||||
},
|
||||
|
||||
@@ -7,9 +7,11 @@ import { chromium, firefox, webkit } from '@playwright/test'
|
||||
|
||||
const bundle = fs.readFileSync('packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs', 'utf8')
|
||||
const bytes = fs.readFileSync('test/browser/media/auto-thumbnail-timeline.mp4')
|
||||
const firstSeek = process.argv.includes('--first-seek')
|
||||
assert(process.argv.slice(2).every(arg => arg === '--first-seek'), 'Unknown rendering probe argument')
|
||||
assert.equal(process.version.slice(1), fs.readFileSync('.node-version', 'utf8').trim())
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn probe:auto-thumbnail-rendering')
|
||||
const output = fs.mkdtempSync('refactor/.cache/auto-thumbnail-rendering-')
|
||||
const output = fs.mkdtempSync(`refactor/.cache/auto-thumbnail-${firstSeek ? 'first-seek' : 'rendering'}-`)
|
||||
console.log(`Rendering diagnostics: ${output}`)
|
||||
const fingerprint = data => createHash('sha256').update(data).digest('hex')
|
||||
assert.equal(fingerprint(bytes), JSON.parse(fs.readFileSync('refactor/baselines/auto-thumbnail-timeline-media.json')).file.sha256, 'The unique-frame diagnostic requires the frozen timeline fixture')
|
||||
@@ -32,6 +34,10 @@ const server = http.createServer((request, response) => {
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const styles = { hidden: 'visibility:hidden;', opacity: 'visibility:visible;opacity:0;', clip: 'visibility:visible;clip-path:inset(50%);', visible: 'visibility:visible;' }
|
||||
const results = []
|
||||
function replaceOnce(code, before, after) {
|
||||
assert.equal(code.split(before).length, 2, `Expected one probe boundary: ${before}`)
|
||||
return code.replace(before, after)
|
||||
}
|
||||
function purple([red, green, blue, alpha]) {
|
||||
return alpha === 255 && Math.min(red, blue) > 100 && Math.abs(red - blue) < 30 && green < 40
|
||||
}
|
||||
@@ -40,8 +46,10 @@ try {
|
||||
const browser = await engine.launch()
|
||||
try {
|
||||
const variants = engineName === 'webkit'
|
||||
? Object.entries(styles).flatMap(([style, css]) => ['current', 'ready-state', 'loaded-event'].map(readiness => ({ style, css, readiness })))
|
||||
: [{ style: 'hidden', css: styles.hidden, readiness: 'current' }]
|
||||
? firstSeek
|
||||
? ['current', 'loaded-no-seek', 'loaded-no-seek-raf', 'loaded-no-seek-two-raf', 'seek-two-raf', 'warm-backseek'].map(readiness => ({ style: 'hidden', css: styles.hidden, readiness }))
|
||||
: Object.entries(styles).flatMap(([style, css]) => ['current', 'ready-state', 'loaded-event'].map(readiness => ({ style, css, readiness })))
|
||||
: (firstSeek ? ['current', 'warm-backseek'] : ['current']).map(readiness => ({ style: 'hidden', css: styles.hidden, readiness }))
|
||||
for (const variant of variants) {
|
||||
const page = await browser.newPage()
|
||||
await page.goto(`http://127.0.0.1:${server.address().port}`)
|
||||
@@ -50,7 +58,22 @@ try {
|
||||
code = code.replace('const ready = !presentedFrames || video.readyState >= 2;', 'const ready = video.readyState >= 2;')
|
||||
if (variant.readiness === 'loaded-event')
|
||||
code = code.replace('let pending;', 'let pending; let firstFrame = true;').replace('const ready = !presentedFrames || video.readyState >= 2;', 'const ready = !firstFrame; firstFrame = false;')
|
||||
code = code.replace('video.currentTime = target;', '{ window.__thumbnailSeeks.push({target, loadedData: window.__thumbnailData, readyState: video.readyState}); video.currentTime = target; }')
|
||||
if (variant.readiness.startsWith('loaded-no-seek')) {
|
||||
code = replaceOnce(code, 'let pending;', 'let pending; let firstFrame = true;')
|
||||
code = replaceOnce(code, 'const ready = !presentedFrames || video.readyState >= 2;', 'const ready = !firstFrame;')
|
||||
const finish = variant.readiness === 'loaded-no-seek-two-raf'
|
||||
? 'requestAnimationFrame(() => requestAnimationFrame(() => finish()))'
|
||||
: variant.readiness === 'loaded-no-seek-raf'
|
||||
? 'requestAnimationFrame(() => finish())'
|
||||
: 'finish()'
|
||||
code = replaceOnce(code, 'current.started = true;', `current.started = true; if (firstFrame) { firstFrame = false; current.sought = true; current.presented = true; ${finish}; return; }`)
|
||||
}
|
||||
if (variant.readiness === 'seek-two-raf')
|
||||
code = replaceOnce(code, 'current.sought = true;\n finish();', 'current.sought = true; requestAnimationFrame(() => requestAnimationFrame(() => finish()));')
|
||||
if (variant.readiness === 'warm-backseek') {
|
||||
code = replaceOnce(code, 'let pending;', 'let pending; let warm = false; let warmRetries = 0;')
|
||||
code = replaceOnce(code, 'current.started = true;', 'if (!warm && target === 0) { warm = true; video.onseeked = job.guard(() => { if (!active() || video.seeking) return; if (Math.abs(video.currentTime - 1) >= 0.05) { if (++warmRetries > 3) throw new Error("Warm seek did not reach one second"); video.currentTime = 1; return; } video.onseeked = null; begin(); }); video.currentTime = 1; return; } current.started = true;')
|
||||
}
|
||||
const result = await page.evaluate(async (code) => {
|
||||
const draws = []
|
||||
const events = []
|
||||
@@ -67,6 +90,15 @@ try {
|
||||
const element = create(...args)
|
||||
if (args[0] === 'video') {
|
||||
videos.push(element)
|
||||
const time = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'currentTime')
|
||||
Object.defineProperty(element, 'currentTime', {
|
||||
configurable: true,
|
||||
get() { return time.get.call(this) },
|
||||
set(target) {
|
||||
window.__thumbnailSeeks.push({ target, loadedData: window.__thumbnailData, readyState: this.readyState })
|
||||
time.set.call(this, target)
|
||||
},
|
||||
})
|
||||
element.addEventListener('loadeddata', () => window.__thumbnailData++)
|
||||
for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'seeked', 'playing']) element.addEventListener(name, () => events.push({ name, time: element.currentTime, readyState: element.readyState, decoded: element.webkitDecodedFrameCount, total: element.getVideoPlaybackQuality?.().totalVideoFrames }))
|
||||
}
|
||||
@@ -118,6 +150,12 @@ try {
|
||||
console.warn = originalWarn
|
||||
}
|
||||
}, code)
|
||||
if (variant.readiness === 'warm-backseek' && result.outcome === 'completed') {
|
||||
const targets = result.seeks.map(seek => seek.target)
|
||||
assert.equal(targets[0], 1, 'Warm comparison must start with a forward seek')
|
||||
assert(targets.indexOf(0) > 0, 'Warm comparison must actually seek back before drawing')
|
||||
assert(targets.slice(0, targets.indexOf(0)).every(target => target === 1), 'Only bounded warm retries may precede the backward seek')
|
||||
}
|
||||
const firstFrameMatches = result.draws[0] ? purple(result.draws[0].rgba) : false
|
||||
const entry = { engine: engineName, browser: browser.version(), ...variant, codeSha256: fingerprint(code), firstFrameMatches, ...result }
|
||||
results.push(entry)
|
||||
@@ -131,5 +169,5 @@ try {
|
||||
finally {
|
||||
await new Promise(resolve => server.close(resolve))
|
||||
assert.equal(fs.readFileSync('packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs', 'utf8'), bundle)
|
||||
fs.writeFileSync(`${output}/report.json`, `${JSON.stringify({ capturedAt: new Date().toISOString(), environment: { node: process.version, platform: process.platform, arch: process.arch }, sourceSha256: fingerprint(bundle), mediaSha256: fingerprint(bytes), results, scope: 'Diagnostic capture, not acceptance. Four intrinsic-size rendering modes and three first-frame readiness strategies are compared in WebKit on the recorded host. Chromium/Firefox current-code controls retain native presentation callbacks. No production file is changed; missing or incorrect pixels remain explicit.' }, null, 2)}\n`)
|
||||
fs.writeFileSync(`${output}/report.json`, `${JSON.stringify({ capturedAt: new Date().toISOString(), environment: { node: process.version, platform: process.platform, arch: process.arch }, sourceSha256: fingerprint(bundle), mediaSha256: fingerprint(bytes), results, scope: firstSeek ? 'Diagnostic capture, not acceptance. WebKit baseline, first loadeddata drawing without seek, one/two animation-frame waits, two-frame post-seek wait and warm forward/back seek. Native currentTime setter instrumentation includes every actual seek. Chromium/Firefox current-code controls retain native presentation callbacks. Production files remain unchanged; pending diagnostic RAF callbacks are page-owned, not a production cancellation implementation.' : 'Diagnostic capture, not acceptance. Four intrinsic-size rendering modes and three first-frame readiness strategies are compared in WebKit on the recorded host. Chromium/Firefox current-code controls retain native presentation callbacks. No production file is changed; missing or incorrect pixels remain explicit.' }, null, 2)}\n`)
|
||||
}
|
||||
+3
-1
@@ -1799,7 +1799,9 @@
|
||||
"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"
|
||||
"baselines/auto-thumbnail-rendering-readiness.json",
|
||||
"changes/2026-09-14-PKG-AUTO-THUMB-03-first-seek.md",
|
||||
"baselines/auto-thumbnail-first-seek.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user