diff --git a/package.json b/package.json index 779e1db90..7f0816038 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ "test:danmuku-types-package": "node refactor/scripts/danmuku-package-types.mjs", "test:danmuku-mask-types-package": "node refactor/scripts/danmuku-mask-package-types.mjs", "test:auto-thumbnail-types": "node --test refactor/scripts/auto-thumbnail-types.test.mjs", - "test:auto-thumbnail-types-package": "node refactor/scripts/auto-thumbnail-package-types.mjs" + "test:auto-thumbnail-types-package": "node refactor/scripts/auto-thumbnail-package-types.mjs", + "probe:auto-thumbnail-rendering": "node refactor/scripts/auto-thumbnail-rendering-probe.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md b/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md index 3e54cbaf6..4501e06f6 100644 --- a/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md +++ b/packages/artplayer-plugin-auto-thumbnail/ARCHITECTURE.md @@ -139,6 +139,17 @@ close this risk based on the later-cell assertions or replace it with a nonblack test; legitimate black frames have opaque pixels. Frame timing, additional reentry/cleanup boundaries and resource budgets still need the remaining task03 work. +`yarn probe:auto-thumbnail-rendering` captures an isolated diagnostic matrix from +the current built ESM and the frozen timeline media. It compares intrinsic-size +hidden/transparent/clipped/visible elements with current readiness, a readyState +gate, and waiting for the first loadeddata event. The captured Windows WebKit +profiles all miss the unique first frame; the Chromium/Firefox controls retain it. +Seek records distinguish an already-ready element from a delivered loadeddata +event. A zero getVideoPlaybackQuality().totalVideoFrames is also observed in the +correct Firefox control, so it is not a portable frame-readiness gate. The command +reports observations, not a passing acceptance suite, and never edits production +bundles. See refactor's rendering-readiness record before repeating these options. + 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 diff --git a/refactor/baselines/auto-thumbnail-rendering-readiness.json b/refactor/baselines/auto-thumbnail-rendering-readiness.json new file mode 100644 index 000000000..53042955f --- /dev/null +++ b/refactor/baselines/auto-thumbnail-rendering-readiness.json @@ -0,0 +1,2715 @@ +{ + "task": "PKG-AUTO-THUMB-03", + "base": "f30c701d88b3c5210b7fa6ddb024b55eeeaf8206", + "outcome": "diagnostic-captured-fix-not-found", + "reportFile": "refactor/.cache/auto-thumbnail-rendering-l4FCqe/report.json", + "reportSha256": "a73f4f1e1774378f3d879b88cf0a0b6408372ccabb3acd5a9f9d9ae7ab652b0e", + "capturedAt": "2026-09-13T20:43:29.037Z", + "environment": { + "node": "v24.21.0", + "platform": "win32", + "arch": "x64" + }, + "summary": { + "webkitProfiles": 12, + "webkitCorrectFirstFrame": 0, + "chromiumControls": 1, + "firefoxControls": 1, + "correctControls": 2, + "allCompleted": true, + "productionChanged": false + }, + "inputs": [ + { + "file": "refactor/scripts/auto-thumbnail-rendering-probe.mjs", + "sha256": "cc94cd34582d5ac7ee734b0ec93e3d9328d9b60c510a81e5b6ed6f3d1eac067d" + }, + { + "file": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "6080dd90bde239515646899bdd8e71d17de24119523e4883025fbf39b1a6a302" + }, + { + "file": "test/browser/media/auto-thumbnail-timeline.mp4", + "sha256": "10370156cffada462d3ea00aae0caadd62752369cd6538cc55248b3ddd7bf994" + }, + { + "file": "refactor/baselines/auto-thumbnail-timeline-media.json", + "sha256": "1bee7645fbdb707679af7b4f8fb9df6e1cd064374d8d16acac1e5b1351725c7d" + } + ], + "supplementary": [ + { + "file": "refactor/.cache/auto-thumbnail-render-path-probe.json", + "sha256": "815a28c5fde5aff4d8e4d2caa2bce2887e46edf72d7087c3798b4f3cadf1ddc6", + "authority": "Intermediate diagnostic; final guarded instrumentation is in reportFile" + }, + { + "file": "refactor/.cache/auto-thumbnail-loaded-event-probe.json", + "sha256": "e845366a2ed40abdfa95807502371202ad988bb5a1fa55fde23d0e1c58f1c6b9", + "authority": "Intermediate diagnostic; final guarded instrumentation is in reportFile" + }, + { + "file": "refactor/.cache/auto-thumbnail-rendering-vdw14M/report.json", + "sha256": "5c3f83b88c3bc0474219e4b51abf1734c3f87d6ffd391d7701f2a46721be66d7", + "authority": "Intermediate diagnostic; final guarded instrumentation is in reportFile" + } + ], + "logs": [ + { + "file": "refactor/.cache/auto-thumb-rendering-verified.log", + "sha256": "f35655c5d6c48405de6a9fc2f4b758d8ba50990c50e27d32febca7f3b8b07326" + }, + { + "file": "refactor/.cache/auto-thumb-rendering-lint-final.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "profiles": [ + { + "engine": "webkit", + "browser": "26.6", + "style": "hidden", + "css": "visibility:hidden;", + "readiness": "current", + "codeSha256": "b5123da7dad7f9348d9f1773fccd13b5633bfa4b1dc4d65ca815de129f103afa", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.0024887, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.602398, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2015795, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8014029, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4006242, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0024887, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.0024887, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.602398, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2015795, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8014029, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4006242, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "hidden", + "css": "visibility:hidden;", + "readiness": "ready-state", + "codeSha256": "5aa06a69933e06c3559eaa1f232490d580420f88a6e3fe4ee84cf98db309258d", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.0017758, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6020256, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2007169, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8013909, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4035147, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0017758, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.0017758, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6020256, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2007169, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8013909, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4035147, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "hidden", + "css": "visibility:hidden;", + "readiness": "loaded-event", + "codeSha256": "d0d8666d39ce8f846dc40b695fe47bdea1006f77bb3118877bdeb73540c2f00a", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.0013745, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6023159, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2419148, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8010752, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4017794, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0013745, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.0013745, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6023159, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2419148, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8010752, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4017794, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "opacity", + "css": "visibility:visible;opacity:0;", + "readiness": "current", + "codeSha256": "ffce48e282cbd26961fa2ba668d5a8ad04acb21bb2cb9400f97aa8bf74bae7a9", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6020029, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.201545, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8007693, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4013641, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 2 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6020029, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.201545, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8007693, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4013641, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "opacity", + "css": "visibility:visible;opacity:0;", + "readiness": "ready-state", + "codeSha256": "8af6543461dc4187ca755625be868361df9b54dc40c7021ad140cf019a8d4ca5", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6013918, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2027911, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8023821, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4014574, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 2 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6013918, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2027911, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8023821, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4014574, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "opacity", + "css": "visibility:visible;opacity:0;", + "readiness": "loaded-event", + "codeSha256": "360d722861fded8298699773c3fc6c115644831be8f3c03679f26a77ea46a0fb", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.0015558, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6007096, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2019348, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8019057, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4016987, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0015558, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.0015558, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6007096, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2019348, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8019057, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4016987, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "clip", + "css": "visibility:visible;clip-path:inset(50%);", + "readiness": "current", + "codeSha256": "e93415bbc52e9bf9279d700837f3bf9679e01d1a259f0841753db62148947586", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.0015967, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6004785, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2014785, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8021848, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4004683, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0015967, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.0015967, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6004785, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2014785, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8021848, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4004683, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "clip", + "css": "visibility:visible;clip-path:inset(50%);", + "readiness": "ready-state", + "codeSha256": "4db668bd9e8ef86a86782557d39a305342e3fffd1c2bc8bbcea3eec6e29eeea0", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.001543, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6014475, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2015723, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8029334, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.401545, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.001543, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.001543, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6014475, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2015723, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8029334, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.401545, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "clip", + "css": "visibility:visible;clip-path:inset(50%);", + "readiness": "loaded-event", + "codeSha256": "d17c3f7a9145b2c8e3100ea336f62e7cc070abdad16beb31201867550055748a", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6013976, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2010535, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8023563, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4004394, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 1, + "readyState": 2 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0032293, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6013976, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2010535, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8023563, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4004394, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "visible", + "css": "visibility:visible;", + "readiness": "current", + "codeSha256": "a50edda152a464409a03fbbb348a71e64bbd9f4548e832647ea440787bd976ed", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.601877, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2012987, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8022308, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4018917, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0022813, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.601877, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2012987, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8022308, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4018917, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "visible", + "css": "visibility:visible;", + "readiness": "ready-state", + "codeSha256": "74f23023c20ac8cf505c872f0b02f04a7426a07826bd08c7adde0d49449e6827", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6027749, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.201647, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8011217, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4009517, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 2 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 2, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0015956, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6027749, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.201647, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8011217, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4009517, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "webkit", + "browser": "26.6", + "style": "visible", + "css": "visibility:visible;", + "readiness": "loaded-event", + "codeSha256": "84ef02be8d9fb21f60ba75480c049bdf92c5e771aa093dcffb500ef7589dbc86", + "firstFrameMatches": false, + "outcome": "completed", + "supportsFrames": false, + "draws": [ + { + "time": 0.0023941, + "rgba": [ + 0, + 0, + 0, + 0 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6008974, + "rgba": [ + 238, + 14, + 14, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2013486, + "rgba": [ + 16, + 16, + 16, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8018987, + "rgba": [ + 16, + 15, + 239, + 255 + ], + "readyState": 4, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4016328, + "rgba": [ + 235, + 236, + 12, + 255 + ], + "readyState": 3, + "decoded": 0, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0.0023941, + "readyState": 4, + "decoded": 0, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0.0023941, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6008974, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2013486, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8018987, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4016328, + "readyState": 3, + "decoded": 0, + "total": 0 + } + ] + }, + { + "engine": "chromium", + "browser": "153.0.8010.12", + "style": "hidden", + "css": "visibility:hidden;", + "readiness": "current", + "codeSha256": "b5123da7dad7f9348d9f1773fccd13b5633bfa4b1dc4d65ca815de129f103afa", + "firstFrameMatches": true, + "outcome": "completed", + "supportsFrames": true, + "draws": [ + { + "time": 0, + "rgba": [ + 127, + 0, + 126, + 255 + ], + "readyState": 4, + "decoded": 4, + "total": 4, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6, + "rgba": [ + 254, + 0, + 0, + 255 + ], + "readyState": 4, + "decoded": 26, + "total": 26, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2, + "rgba": [ + 0, + 0, + 0, + 255 + ], + "readyState": 4, + "decoded": 36, + "total": 36, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8, + "rgba": [ + 0, + 0, + 253, + 255 + ], + "readyState": 4, + "decoded": 64, + "total": 64, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4, + "rgba": [ + 255, + 255, + 2, + 255 + ], + "readyState": 4, + "decoded": 80, + "total": 80, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 0, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 4, + "decoded": 0, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 1, + "decoded": 0, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 1, + "decoded": 0, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 4, + "total": 4 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "decoded": 4, + "total": 4 + }, + { + "name": "seeked", + "time": 1.6, + "readyState": 4, + "decoded": 26, + "total": 26 + }, + { + "name": "seeked", + "time": 3.2, + "readyState": 4, + "decoded": 36, + "total": 36 + }, + { + "name": "seeked", + "time": 4.8, + "readyState": 4, + "decoded": 64, + "total": 64 + }, + { + "name": "seeked", + "time": 6.4, + "readyState": 4, + "decoded": 80, + "total": 80 + } + ] + }, + { + "engine": "firefox", + "browser": "155.0", + "style": "hidden", + "css": "visibility:hidden;", + "readiness": "current", + "codeSha256": "b5123da7dad7f9348d9f1773fccd13b5633bfa4b1dc4d65ca815de129f103afa", + "firstFrameMatches": true, + "outcome": "completed", + "supportsFrames": true, + "draws": [ + { + "time": 0, + "rgba": [ + 137, + 19, + 126, + 255 + ], + "readyState": 4, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 1.6, + "rgba": [ + 255, + 24, + 0, + 255 + ], + "readyState": 4, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 3.2, + "rgba": [ + 0, + 0, + 0, + 255 + ], + "readyState": 4, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 4.8, + "rgba": [ + 0, + 14, + 253, + 255 + ], + "readyState": 4, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + }, + { + "time": 6.4, + "rgba": [ + 255, + 241, + 2, + 255 + ], + "readyState": 4, + "total": 0, + "box": { + "width": 320, + "height": 180 + } + } + ], + "seeks": [ + { + "target": 0, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 1.6, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 3.2, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 4.8, + "loadedData": 1, + "readyState": 4 + }, + { + "target": 6.4, + "loadedData": 1, + "readyState": 4 + } + ], + "initialEvents": [ + { + "name": "loadedmetadata", + "time": 0, + "readyState": 1, + "total": 0 + }, + { + "name": "loadeddata", + "time": 0, + "readyState": 4, + "total": 0 + }, + { + "name": "canplay", + "time": 0, + "readyState": 4, + "total": 0 + }, + { + "name": "seeked", + "time": 0, + "readyState": 4, + "total": 0 + } + ], + "seekEvents": [ + { + "name": "seeked", + "time": 0, + "readyState": 4, + "total": 0 + }, + { + "name": "seeked", + "time": 1.6, + "readyState": 4, + "total": 0 + }, + { + "name": "seeked", + "time": 3.2, + "readyState": 4, + "total": 0 + }, + { + "name": "seeked", + "time": 4.8, + "readyState": 4, + "total": 0 + }, + { + "name": "seeked", + "time": 6.4, + "readyState": 4, + "total": 0 + } + ] + } + ], + "limitations": [ + "Diagnostic command success is not pixel acceptance.", + "No CSS/readiness/counter workaround is adopted.", + "Captured Windows WebKit behavior is not proof about all Safari versions or physical devices.", + "API/core integrations and complete historical type consumers remain separate tasks." + ] +} diff --git a/refactor/changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md b/refactor/changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md new file mode 100644 index 000000000..2224bdecf --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md @@ -0,0 +1,55 @@ +# PKG-AUTO-THUMB-03 渲染与首帧等待对照 + +状态仍为 doing。这是排除候选原因的检查点,没有修改生产抽帧算法,没有将 +AUTO-THUMB-PIXEL-01 或包迁移标为完成。起点为 +`f30c701d88b3c5210b7fa6ddb024b55eeeaf8206`。 + +已有 opacity 探针同时把 video 缩成 1px,不能单独回答完整尺寸隐藏方式的影响。 +本次保持实际 320×180 媒体尺寸,分别使用 visibility:hidden、opacity:0、 +clip-path:inset(50%)、完全可见元素,保留原始首帧时间 0 和渐进编码流程。 +运行输入是正式 ESM 的内存副本,改变 CSS 与等待条件只发生在诊断页面中。 + +第一轮只把无帧回调分支也改为检查 readyState>=2。事件证据显示 metadata +交付时往往已经是 readyState=4,因此这并不等于真正等到了 loadeddata 事件。 +第二轮明确让第一帧等待 loadeddata,并记录每次 seek 前累计交付的事件数; +后续帧沿用正常流程。最终脚本同时保留 current、ready-state、loaded-event +三种策略,避免后续 AI 把不同的等待语义混为一谈。 + +## 观察与下一步影响 + +- Windows WebKit 26.6 的 12 种组合均完成五次更新,但没有一组取得时间 0 + 应有的唯一紫色帧。初次 draw 可能是透明像素,也可能已是后续红色;全可见 + 元素也失败。因此不能靠“非黑/不透明”判断已经抽到正确首帧。 +- 四个 loaded-event 变体第一次 seek 前均已交付一次 loadeddata,仍不准确; + 它们实际执行了事件等待,不是仅根据 readyState 推测已等待。 +- Chromium 153.0.8010.12 与 Firefox 155.0 的当前代码对照均取得唯一紫色首帧; + 后续颜色与正常黑色内容也被保留。它们具有原生帧呈现回调,WebKit 此环境没有。 +- Firefox 对照画面正确时 totalVideoFrames 仍为 0;Windows WebKit 也为 0。 + 不能将该计数 >0 作为跨浏览器 fallback 的完成条件,否则会卡住正常提取。 +- 三个 WebKit 组合在请求 1.6 秒后又交付了约 0.002 秒的旧 seeked,触发已有 + 同目标重试,最终仍只产生五次 draw。记录保留这六次 seek,不能把采样数五 + 误当作原生事件和重试数也必须为五;这也不解决首帧内容错误。 +- 本次没有实现这些未经验证的 CSS/事件/计数修复;也没有删除或放宽现有像素 + 检查。后续需要从当前 WebKit 媒体路径或实际支持设备取得更直接证据,不能 + 继续把上述样式/readyState/loadeddata 的简单变体当作尚未尝试的新方案。 + +具体结果、事件次序、代码与媒体指纹见 +[验证记录](../baselines/auto-thumbnail-rendering-readiness.json)。首轮/事件补充 +缓存报告也保留指纹,最终十四组来自提交脚本,而不是用文字推测先前探针结果。 +这些是一次版本固定的观测,不能外推为所有 WebKit/Safari 版本或物理设备缺陷。 + +## 重跑与维护 + +```sh +yarn build artplayer-plugin-auto-thumbnail +yarn probe:auto-thumbnail-rendering +``` + +第二条只读取产物和固定媒体,启动 loopback 临时端口,依次启动三个测试浏览器, +输出到新的 `.cache/auto-thumbnail-rendering-*`。脚本检查精确可替换代码位置, +记录各变体 hash,结束时检查源产物没有变化。它是诊断采集命令,不加入默认 +通过测试列表,也不把成功退出解释为像素已通过。八秒是探针采集上限,不是 +新生产超时。使用现有 Playwright 依赖,没有增加或升级依赖、版本或发布产物。 + +本项只有诊断脚本、入口、说明和证据变更;不声称修复所有旧类型/首帧问题。 +回退该检查点会移除诊断入口和记录,不影响 07/08/09 的生产修复。 diff --git a/refactor/plan.md b/refactor/plan.md index 223e67670..3671aff68 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -517,7 +517,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) +- 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-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) diff --git a/refactor/progress.md b/refactor/progress.md index cc91a0b51..1e508a8fc 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,20 @@ # 进度与证据 +## PKG-AUTO-THUMB-03 渲染/等待诊断检查点(仍 doing) + +新增可重跑的 `yarn probe:auto-thumbnail-rendering`,用固定实际 ESM/时间轴媒体 +对照真实尺寸下四种可见性样式及 current/readyState/loadeddata 事件三种策略。 +最终 Windows WebKit 12 组均未取得唯一紫色首帧;Chromium/Firefox 两个对照正常。 +首个 loadeddata 的交付计数被直接记录,避免将 readyState>=2 误称为已等待事件。 +正确 Firefox 帧的 totalVideoFrames 仍为 0,不能用该计数 >0 构造通用等待条件。 +另外捕获三组旧 seeked 导致的同目标重试,保留全部事件,不把它算作额外采样。 +生产源码、dist 和普通像素断言均未改变;没有采用未通过验证的 CSS/等待修复。 +见[诊断记录](changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md)与 +[证据](baselines/auto-thumbnail-rendering-readiness.json)。脚本/lint/工具链及计划 +检查通过只说明诊断可运行,不代表首帧通过。PIXEL-01 和 03 保持未完成,计划 +仍为 237 项:160 done、15 doing、62 todo。后续不再重复这些已否定的简单策略, +继续定位当前媒体路径和补齐独立的历史消费验证;VAST 默认初始化选择仍待答复。 + ## PKG-AUTO-THUMB-09 历史 default 调用兼容完成 通过同一函数上的 default 自引用补齐 1.0.1 的 JavaScript 调用,继续支持 1.1.0 diff --git a/refactor/risks.json b/refactor/risks.json index 19cae8fb1..b4ae00bfa 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -4166,9 +4166,11 @@ "refactor/baselines/auto-thumbnail-hidden-renderer.json", "refactor/baselines/auto-thumbnail-timeline-media.json", "test/auto-thumbnail-frames.test.js", - "refactor/baselines/auto-thumbnail-frame-presentation.json" + "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" ], - "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.", + "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.", "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." }, diff --git a/refactor/scripts/auto-thumbnail-rendering-probe.mjs b/refactor/scripts/auto-thumbnail-rendering-probe.mjs new file mode 100644 index 000000000..14cf87996 --- /dev/null +++ b/refactor/scripts/auto-thumbnail-rendering-probe.mjs @@ -0,0 +1,135 @@ +import assert from 'node:assert/strict' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import http from 'node:http' +import process from 'node:process' +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') +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-') +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') +assert(bundle.includes('const ready = !presentedFrames || video.readyState >= 2;')) +assert(bundle.includes('visibility:hidden;')) +assert.equal(bundle.match(/let pending;/g)?.length, 1) +assert.equal(bundle.match(/video.currentTime = target;/g)?.length, 1) +const server = http.createServer((request, response) => { + if (request.url !== '/video.mp4') { + response.writeHead(200, { 'Content-Type': 'text/html' }) + response.end('Frame rendering probe') + return + } + const match = /^bytes=(\d+)-(\d*)$/.exec(request.headers.range || '') + const start = match ? Number(match[1]) : 0 + const end = match?.[2] ? Math.min(Number(match[2]), bytes.length - 1) : bytes.length - 1 + response.writeHead(match ? 206 : 200, { 'Content-Type': 'video/mp4', 'Content-Length': end - start + 1, 'Accept-Ranges': 'bytes', ...(match ? { 'Content-Range': `bytes ${start}-${end}/${bytes.length}` } : {}) }) + response.end(bytes.subarray(start, end + 1)) +}) +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 purple([red, green, blue, alpha]) { + return alpha === 255 && Math.min(red, blue) > 100 && Math.abs(red - blue) < 30 && green < 40 +} +try { + for (const [engineName, engine] of Object.entries({ webkit, chromium, firefox })) { + 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' }] + for (const variant of variants) { + const page = await browser.newPage() + await page.goto(`http://127.0.0.1:${server.address().port}`) + let code = bundle.replace('visibility:hidden;', variant.css) + if (variant.readiness === 'ready-state') + 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; }') + const result = await page.evaluate(async (code) => { + const draws = [] + const events = [] + const updates = [] + const warnings = [] + const videos = [] + window.__thumbnailData = 0 + window.__thumbnailSeeks = [] + const create = document.createElement.bind(document) + const draw = CanvasRenderingContext2D.prototype.drawImage + const originalWarn = console.warn + console.warn = (...args) => warnings.push(args.map(String)) + document.createElement = (...args) => { + const element = create(...args) + if (args[0] === 'video') { + videos.push(element) + 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 })) + } + return element + } + CanvasRenderingContext2D.prototype.drawImage = function (source, ...args) { + const result = draw.call(this, source, ...args) + if (source instanceof HTMLVideoElement) { + const [x, y, width, height] = args + draws.push({ time: source.currentTime, rgba: [...this.getImageData(x + Math.floor(width / 2), y + Math.floor(height / 2), 1, 1).data], readyState: source.readyState, decoded: source.webkitDecodedFrameCount, total: source.getVideoPlaybackQuality?.().totalVideoFrames, box: { width: source.getBoundingClientRect().width, height: source.getBoundingClientRect().height } }) + } + return result + } + const listeners = new Map() + let finish + const done = new Promise(resolve => finish = resolve) + const art = { + option: { url: '/video.mp4' }, + on(name, fn) { + if (!listeners.has(name)) + listeners.set(name, new Set()) + listeners.get(name).add(fn) + }, + off(name, fn) { listeners.get(name)?.delete(fn) }, + get thumbnails() { return updates.at(-1) }, + set thumbnails(value) { + updates.push(value) + if (updates.length === 5) + finish('completed') + }, + } + const emit = (name) => { + for (const fn of [...(listeners.get(name) || [])]) fn() + } + let timer + try { + const { default: factory } = await import(`data:text/javascript,${encodeURIComponent(code)}`) + await factory({ width: 80, number: 5 })(art) + timer = setTimeout(() => finish('timeout'), 8000) + emit('video:loadedmetadata') + const outcome = await done + return { outcome, draws, events, warnings, seeks: window.__thumbnailSeeks, supportsFrames: typeof videos[0]?.requestVideoFrameCallback === 'function' && typeof videos[0]?.cancelVideoFrameCallback === 'function' } + } + finally { + clearTimeout(timer) + emit('destroy') + document.createElement = create + CanvasRenderingContext2D.prototype.drawImage = draw + console.warn = originalWarn + } + }, code) + 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) + console.log(JSON.stringify({ engine: engineName, ...variant, outcome: result.outcome, firstFrameMatches, seeks: result.seeks, draws: result.draws.map(item => ({ time: item.time, rgba: item.rgba, total: item.total })) })) + await page.close() + } + } + finally { await browser.close() } + } +} +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`) +} diff --git a/refactor/tasks.json b/refactor/tasks.json index 0ecc9c19c..724d2b78d 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -1797,7 +1797,9 @@ "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" + "baselines/auto-thumbnail-frame-presentation.json", + "changes/2026-09-14-PKG-AUTO-THUMB-03-rendering-readiness.md", + "baselines/auto-thumbnail-rendering-readiness.json" ] }, { diff --git a/test/README.md b/test/README.md index d4ca0a5a6..501353b46 100644 --- a/test/README.md +++ b/test/README.md @@ -15,6 +15,13 @@ runtime types describe the alias without changing the historical root declaratio This does not decode video or close all older type-shape, native pixel, or device acceptance gaps. Candidate entry types use the same runtime files. +`yarn probe:auto-thumbnail-rendering` is an optional diagnosis command, separate +from passing test suites. It runs four intrinsic-size rendering modes and three +readiness strategies in Windows WebKit, plus Chromium/Firefox controls. It records +raw draw pixels, seek/event ordering, frame counters and exact code/media hashes +in a new cache directory. Completion of the command does not imply correct pixels: +the current WebKit first-frame discrepancy remains an open release/refactor risk. + `yarn test:danmuku-mask` runs candidate run cancellation/resource ownership plus frozen historical defects and public contracts. Candidate tests also run in `test:unit`; their controlled SDK, RAF and canvas hosts do not prove native model