diff --git a/package.json b/package.json index 36fc168e3..d5ff76b98 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs", "test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs", "test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs", - "test:jassub": "node --test test/jassub.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs", + "test:jassub": "node --test test/jassub.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs refactor/scripts/jassub-failures.test.mjs", "check:ci": "node refactor/scripts/ci-workflow.mjs", "test:ci": "node --test test/ci-summary.test.js refactor/scripts/ci-workflow.test.mjs refactor/scripts/impact.test.mjs test/package-runtime.test.js", "test:package:runtime": "node scripts/package-runtime.mjs --canonical", diff --git a/packages/artplayer-plugin-jassub/ARCHITECTURE.md b/packages/artplayer-plugin-jassub/ARCHITECTURE.md index b48aa5089..5ec8746cf 100644 --- a/packages/artplayer-plugin-jassub/ARCHITECTURE.md +++ b/packages/artplayer-plugin-jassub/ARCHITECTURE.md @@ -42,5 +42,16 @@ asset comparisons and fails on any request error, without substituting cached ev Set ARTPLAYER_JASSUB_CANDIDATE=1 for current source behavior; ARTPLAYER_JASSUB_ARTIFACT can point at a main/legacy artifact. Tests use controlled DOM, Worker and SIMD detection, with actual vendor JavaScript. They do not render ASS or execute worker WASM. Real browser, failures, -repeated destroy and resource cleanup remain later steps. See +repeated destroy and resource cleanup remain later steps. Frozen failure tests now +cover repeated teardown, cross-parent setVideo, custom canvas, Worker construction +failure, stale video frames and incorrect fallback ratechange payloads. They assert +the old failures, not candidate fixes. PKG-JASSUB-03 owns adapter cleanup; 07 owns +separately tracked vendor lifecycle/clock changes and 05 verifies native behavior. + +`yarn test:browser test/browser/jassub-native.spec.js --workers=1` loads the actual +published wrapper, Worker, WASM and default font with offscreenRender=false. +Chromium/Firefox render and seek correctly; Windows WebKit's default frame-clock +path remains a recorded failure. The onDemandRender=false diagnostic does not +waive the default behavior gate. Native failure recovery, default offscreen mode, +full devices and sustained resource release remain unverified. See `../../refactor/baselines/jassub-contract.md` for precise contract and provenance evidence. diff --git a/refactor/baselines/jassub-failures-validation.json b/refactor/baselines/jassub-failures-validation.json new file mode 100644 index 000000000..70b85ddf0 --- /dev/null +++ b/refactor/baselines/jassub-failures-validation.json @@ -0,0 +1,3657 @@ +{ + "task": "PKG-JASSUB-02", + "sourceCommit": "0cf56b64d2711947ee7ea40a5a7f47fdb13e4521", + "node": "v24.21.0", + "platform": "win32", + "tests": { + "passed": 120, + "failed": 0, + "skipped": 0, + "historicalFailureAndContainment": 63, + "meaning": "Historical problem observations and existing protections, not candidate fixes.", + "log": { + "file": "refactor/.cache/jassub02-failures-final.log", + "sha256": "1c9650a5e2d118713f40f10ca62280b70e77a6de2af65901d890b7581b40531c" + } + }, + "sources": [ + { + "file": "test/jassub.test.js", + "sha256": "8c6fbb488912ac91eeb0beaf05b77cddbfc3c1d4cc663ab3196e237d689225c0" + }, + { + "file": "refactor/scripts/jassub-failures.test.mjs", + "sha256": "073f3fd419ff46177ffac091a7bcd6bfb3b1ee4f7da2cbf8991db9692880bc83" + }, + { + "file": "test/helpers/jassub.js", + "sha256": "16e7506ca140c61526e16bcc748c8f1d559f522950bebfbbbe1014317d7a283d" + }, + { + "file": "test/browser/jassub-native.spec.js", + "sha256": "5a048e6e2f851312f44e69474437b46a73c03210c4b4212b804e3b1437b9f0b0" + }, + { + "file": "test/browser/server.mjs", + "sha256": "8f7eed79508a1038f54d77d68d704e602499de3bf1d24c7aae8400caef6e560d" + } + ], + "observations": [ + { + "implementation": "published-1.1.0-main", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "published-1.1.0-main", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "published-1.1.0-main", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "published-1.1.0-main", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "published-1.1.0-main", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "published-1.1.0-main", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "published-1.1.0-main", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "published-1.1.0-main", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "published-1.1.0-main", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + }, + { + "implementation": "published-1.1.0-legacy", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "published-1.1.0-legacy", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "published-1.1.0-legacy", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "published-1.1.0-legacy", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "published-1.1.0-legacy", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "published-1.1.0-legacy", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "published-1.1.0-legacy", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "published-1.1.0-legacy", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "published-1.1.0-legacy", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + }, + { + "implementation": "published-1.0.0-main", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "published-1.0.0-main", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "published-1.0.0-main", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "published-1.0.0-main", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "published-1.0.0-main", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "published-1.0.0-main", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "published-1.0.0-main", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "published-1.0.0-main", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "published-1.0.0-main", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + }, + { + "implementation": "published-1.0.0-legacy", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "published-1.0.0-legacy", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "published-1.0.0-legacy", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "published-1.0.0-legacy", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "published-1.0.0-legacy", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "published-1.0.0-legacy", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "published-1.0.0-legacy", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "published-1.0.0-legacy", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "published-1.0.0-legacy", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + }, + { + "implementation": "frozen-workspace-source", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "frozen-workspace-source", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "frozen-workspace-source", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "frozen-workspace-source", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "frozen-workspace-source", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "frozen-workspace-source", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "frozen-workspace-source", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "frozen-workspace-source", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "frozen-workspace-source", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + }, + { + "implementation": "frozen-workspace-js", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "frozen-workspace-js", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "frozen-workspace-js", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "frozen-workspace-js", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "frozen-workspace-js", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "frozen-workspace-js", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "frozen-workspace-js", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "frozen-workspace-js", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "frozen-workspace-js", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "rate-event-payload", + "mediaRate": 1.5, + "forwardedType": "ratechange", + "directNumericRatePreserved": true + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "repeated-destroy", + "throws": true, + "workerTerminations": 1 + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "set-video-parent", + "workerTerminations": 0, + "listenersRetained": 7, + "originalContainerRetained": true + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "custom-canvas", + "workerTerminations": 0, + "listenersRetained": 7, + "hostDestroyHandlerRegistered": false, + "initSent": true + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "worker-construction-failure", + "originalErrorPreserved": true, + "insertedContainerRetained": true, + "listenersRetained": 7 + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "old-video-frame", + "replacementCurrentTime": 9, + "staleDemandTime": 123, + "replacementPendingCallbacks": 2, + "cancelledCallbacks": 0 + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "destroy-before-ready", + "workerTerminations": 1, + "listenersRetained": 0, + "postAfterTermination": [ + "init" + ], + "loadedSettledWithinFixtureFlush": false + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "worker-error", + "exactErrorForwarded": true, + "automaticallyTerminated": false, + "explicitHostCleanup": true + }, + { + "implementation": "frozen-workspace-legacy.js", + "case": "frame-after-destroy", + "activelyCancelled": false, + "callbackDoesNoWork": true, + "callbackRearms": false + } + ], + "reports": [ + { + "name": "first", + "file": "refactor/.cache/jassub02-native-first.json", + "sha256": "aaee75320045b5604c70b3c6aa2157c5edce930818c4855ae2cf45eabc362d80", + "stats": { + "startTime": "2026-09-13T16:45:55.577Z", + "duration": 64639.776, + "expected": 0, + "skipped": 0, + "unexpected": 3, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "chromium", + "status": "timedOut", + "retry": 0, + "errors": [ + "\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m", + "Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)", + "Error: page.evaluate: Target page, context or browser has been closed\n\n 125 | }\n 126 | finally {\n> 127 | const state = await page.evaluate(() => window.jassubNative)\n | ^\n 128 | await testInfo.attach('actual-jassub-evidence', { body: JSON.stringify({ core, release: { version: release.version, integrity: release.integrity, member, sha256: hash(code) }, subtitleSha256: hash(subtitles), resources, external, state, limitation: 'Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance.' }, null, 2), contentType: 'application/json' })\n 129 | await page.evaluate(() => {\n 130 | if (window.art && !window.art.isDestroy)\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:127:32" + ], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [ + "Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'." + ], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": null, + "screenshots": [] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published core", + "engine": "chromium", + "status": "timedOut", + "retry": 0, + "errors": [ + "\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m", + "Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)", + "Error: page.evaluate: Target page, context or browser has been closed\n\n 125 | }\n 126 | finally {\n> 127 | const state = await page.evaluate(() => window.jassubNative)\n | ^\n 128 | await testInfo.attach('actual-jassub-evidence', { body: JSON.stringify({ core, release: { version: release.version, integrity: release.integrity, member, sha256: hash(code) }, subtitleSha256: hash(subtitles), resources, external, state, limitation: 'Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance.' }, null, 2), contentType: 'application/json' })\n 129 | await page.evaluate(() => {\n 130 | if (window.art && !window.art.isDestroy)\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:127:32" + ], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [ + "Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'." + ], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": null, + "screenshots": [] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core", + "engine": "chromium", + "status": "timedOut", + "retry": 0, + "errors": [ + "\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m", + "Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)", + "Error: page.evaluate: Target page, context or browser has been closed\n\n 125 | }\n 126 | finally {\n> 127 | const state = await page.evaluate(() => window.jassubNative)\n | ^\n 128 | await testInfo.attach('actual-jassub-evidence', { body: JSON.stringify({ core, release: { version: release.version, integrity: release.integrity, member, sha256: hash(code) }, subtitleSha256: hash(subtitles), resources, external, state, limitation: 'Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance.' }, null, 2), contentType: 'application/json' })\n 129 | await page.evaluate(() => {\n 130 | if (window.art && !window.art.isDestroy)\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:127:32" + ], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [ + "Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'." + ], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": null, + "screenshots": [] + } + ] + }, + { + "name": "mime", + "file": "refactor/.cache/jassub02-native-mime.json", + "sha256": "888fcfe6554158ce71f376b0072ed029905c32ce90167ac64524ced8b928079d", + "stats": { + "startTime": "2026-09-13T16:48:39.077Z", + "duration": 7125.699, + "expected": 3, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": { + "core": "candidate", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.497676, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.014073, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.117091, + "connected": true + } + ], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "b14c548fa54a855a50dd491ff263052e44c081e366ad847e7caa9273dc7c6a0e" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published core", + "engine": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": { + "core": "published", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.503139, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.013048, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.114327, + "connected": true + } + ], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "ecfe0b2a8b5b3b4609b8a478db467f91c45fef9c57ec1ebda03e0500cf75fe1e" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core", + "engine": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": { + "core": "published-5.3.1-beta.1", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.523336, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.012859, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.101009, + "connected": true + } + ], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "187d1a46c31c16f80c027d779ca78c961f29a250f6bd49dc2ede1174e67cfbae" + } + ] + } + ] + }, + { + "name": "other", + "file": "refactor/.cache/jassub02-native-other.json", + "sha256": "f5a4a3ff6bde205cf0abc15b8d99e1c0375443cbcf8eb425f5591bf4a9f6c1ca", + "stats": { + "startTime": "2026-09-13T16:49:10.751Z", + "duration": 46204.432, + "expected": 3, + "skipped": 0, + "unexpected": 3, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "candidate", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.567509, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.000023, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040388, + "connected": true + } + ], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "184959dcee6e1aaecbecf12c760003455109f1b9b1e74dda4c4bd6226b2c39cf" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published core", + "engine": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + }, + "evidence": { + "core": "published", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.350295, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.00002, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040321, + "connected": true + } + ], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "8e128bda82ccf0bd36d19a1bd0ee8275590734805fabbd780f03b9e3a8ae617b" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core", + "engine": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "published-5.3.1-beta.1", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1 + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.570445, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.000021, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.161301, + "connected": true + } + ], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "220205864406f061da34aafee3c3be1eba23ec8d811f8fbe9c87f578f7e99f64" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m100\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 97 | }, subtitles)\n 98 | await page.locator('#play').click()\n> 99 | await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100)\n | ^\n 100 | const before = await page.evaluate(() => window.jassubPixels('before-seek'))\n 101 | expect(before.connected).toBe(true)\n 102 | await page.locator('#pause').click()\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:99:83" + ], + "browser": { + "version": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "candidate", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 0 + } + ], + "snapshots": [], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published core", + "engine": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m100\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 97 | }, subtitles)\n 98 | await page.locator('#play').click()\n> 99 | await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100)\n | ^\n 100 | const before = await page.evaluate(() => window.jassubPixels('before-seek'))\n 101 | expect(before.connected).toBe(true)\n 102 | await page.locator('#pause').click()\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:99:83" + ], + "browser": { + "version": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "published", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 0 + } + ], + "snapshots": [], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core", + "engine": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m100\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 97 | }, subtitles)\n 98 | await page.locator('#play').click()\n> 99 | await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100)\n | ^\n 100 | const before = await page.evaluate(() => window.jassubPixels('before-seek'))\n 101 | expect(before.connected).toBe(true)\n 102 | await page.locator('#pause').click()\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:99:83" + ], + "browser": { + "version": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "published-5.3.1-beta.1", + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 0 + } + ], + "snapshots": [], + "errors": [] + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [] + } + ] + }, + { + "name": "event-clock", + "file": "refactor/.cache/jassub02-native-event-clock.json", + "sha256": "a40530587be362c2b37abcfda70c52967b1b8b892a2aecb7b8c7565603ceaf65", + "stats": { + "startTime": "2026-09-13T16:51:44.674Z", + "duration": 6249.097000000001, + "expected": 1, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "webkit", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "candidate", + "onDemandRender": false, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "video" + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "canvas", + "width": 2560, + "height": 1440 + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "video" + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 2560, + "height": 1440 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 13152, + "signature": 1263898348, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.3775417, + "connected": true + }, + { + "phase": "after-seek", + "visible": 30818, + "signature": 827736580, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.0411875, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.2653959, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 119170, + "signature": 1826006850, + "width": 2560, + "height": 1440, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.2653959, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "ca726d3de3838bf7f2327a37846fd31aa5db04a6b70c274e06f7e4a3bea38deb" + } + ] + } + ] + }, + { + "name": "frame-clock", + "file": "refactor/.cache/jassub02-native-frame-clock.json", + "sha256": "12f747cffb9c950d6804bc795f6c5155ff98bb2f145b9942d6cd4f8eb0ca620f", + "stats": { + "startTime": "2026-09-13T16:52:08.322Z", + "duration": 11462.328, + "expected": 0, + "skipped": 0, + "unexpected": 1, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "webkit", + "status": "failed", + "retry": 0, + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m100\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 107 | }, { subContent: subtitles, onDemandRender })\n 108 | await page.locator('#play').click()\n> 109 | await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100)\n | ^\n 110 | const before = await page.evaluate(() => window.jassubPixels('before-seek'))\n 111 | expect(before.connected).toBe(true)\n 112 | await page.locator('#pause').click()\n at D:\\github\\ArtPlayer\\test\\browser\\jassub-native.spec.js:109:83" + ], + "browser": { + "version": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "candidate", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 0, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 1280, + "height": 720 + } + ] + } + ], + "snapshots": [], + "errors": [], + "lastPixel": { + "visible": 0, + "signature": 0, + "width": 1280, + "height": 720, + "cssWidth": 640, + "cssHeight": 360, + "time": 6.4992094, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [] + } + ] + }, + { + "name": "final", + "file": "refactor/.cache/jassub02-native-final.json", + "sha256": "acfc4af7f5384166dbc6d5434018c5f46e32c3d582395745a7bf390bea3cf026", + "stats": { + "startTime": "2026-09-13T16:54:43.778Z", + "duration": 14993.602, + "expected": 6, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": { + "core": "candidate", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "demand", + "time": 0.4 + }, + { + "target": "getColorSpace" + }, + { + "target": "demand", + "time": 0.44 + }, + { + "target": "demand", + "time": 0.48 + }, + { + "target": "demand", + "time": 0.52 + }, + { + "target": "demand", + "time": 0.56 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "verifyColorSpace" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.507385, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.013288, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.116258, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.116258, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "b14c548fa54a855a50dd491ff263052e44c081e366ad847e7caa9273dc7c6a0e" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published core", + "engine": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": { + "core": "published", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "demand", + "time": 0.4 + }, + { + "target": "getColorSpace" + }, + { + "target": "demand", + "time": 0.44 + }, + { + "target": "demand", + "time": 0.48 + }, + { + "target": "demand", + "time": 0.52 + }, + { + "target": "demand", + "time": 0.56 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "demand", + "time": 10.24 + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "verifyColorSpace" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.505015, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.014939, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.114424, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.114424, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "b14c548fa54a855a50dd491ff263052e44c081e366ad847e7caa9273dc7c6a0e" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core", + "engine": "chromium", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ] + }, + "evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "demand", + "time": 0.4 + }, + { + "target": "getColorSpace" + }, + { + "target": "demand", + "time": 0.44 + }, + { + "target": "demand", + "time": 0.48 + }, + { + "target": "demand", + "time": 0.52 + }, + { + "target": "demand", + "time": 0.56 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "demand", + "time": 10.24 + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "verifyColorSpace" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.505061, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.014032, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.101751, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.101751, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "187d1a46c31c16f80c027d779ca78c961f29a250f6bd49dc2ede1174e67cfbae" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with candidate core", + "engine": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "candidate", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "demand", + "time": 0.44 + }, + { + "target": "getColorSpace" + }, + { + "target": "demand", + "time": 0.48 + }, + { + "target": "demand", + "time": 0.52 + }, + { + "target": "demand", + "time": 0.56 + }, + { + "target": "demand", + "time": 0.6 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.5638, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.00002, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040412, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.040412, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "dcfe4e47ed962c6a6b98380878819a43be1581adb21722733b81fcac88897c2c" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published core", + "engine": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [] + }, + "evidence": { + "core": "published", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "demand", + "time": 0.44 + }, + { + "target": "getColorSpace" + }, + { + "target": "demand", + "time": 0.48 + }, + { + "target": "demand", + "time": 0.52 + }, + { + "target": "demand", + "time": 0.56 + }, + { + "target": "demand", + "time": 0.64 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "demand", + "time": 10.16 + }, + { + "target": "demand", + "time": 10.2 + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.532466, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.000023, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.161754, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 22298, + "signature": 2781790832, + "width": 1280, + "height": 720, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.161754, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "65fbdabc25f645a591068987a59b4b9ecb073738c87d7d83876a6b6b6a053c3e" + } + ] + }, + { + "title": "JASSUB published 1.1.0 renders actual WASM subtitles through seek and layout with published-5.3.1-beta.1 core", + "engine": "firefox", + "status": "passed", + "retry": 0, + "errors": [], + "browser": { + "version": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/assets/sample/steve-jobs.mp4", + "resourceType": "media", + "failure": { + "errorText": "NS_ERROR_PARSED_DATA_CACHED" + } + } + ] + }, + "evidence": { + "core": "published-5.3.1-beta.1", + "onDemandRender": true, + "release": { + "version": "1.1.0", + "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==", + "member": "package/dist/artplayer-plugin-jassub.js", + "sha256": "271d5284e5246fbd04575526510b3e381e5e60d414c030acecce92fcf2e9b10a" + }, + "subtitleSha256": "12994d1b6a07c45fd9776cb8709afc37725a8003bd90e4e829b7cc44fbadc0ec", + "resources": [ + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker.js", + "status": 200, + "contentType": "text/javascript" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/jassub-worker-modern.wasm", + "status": 200, + "contentType": "application/wasm" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + }, + { + "url": "http://127.0.0.1:8084/assets/jassub/default.woff2", + "status": 200, + "contentType": "font/woff2" + } + ], + "external": [], + "state": { + "workers": [ + { + "url": "/assets/jassub/jassub-worker.js", + "terminated": 1, + "sent": [ + { + "target": "init", + "width": 300, + "height": 150 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "canvas", + "width": 640, + "height": 360 + }, + { + "target": "demand", + "time": 0.44 + }, + { + "target": "getColorSpace" + }, + { + "target": "demand", + "time": 0.52 + }, + { + "target": "demand", + "time": 0.56 + }, + { + "target": "demand", + "time": 0.6 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "demand", + "time": 10 + }, + { + "target": "canvas", + "width": 1280, + "height": 720 + }, + { + "target": "demand", + "time": 10.04 + }, + { + "target": "demand", + "time": 10.08 + }, + { + "target": "demand", + "time": 10.12 + }, + { + "target": "destroy" + } + ], + "received": [ + { + "target": "ready" + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 0, + "width": 640, + "height": 360 + }, + { + "target": "verifyColorSpace" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 640, + "height": 360 + }, + { + "target": "unbusy" + }, + { + "target": "render", + "images": 2, + "width": 1280, + "height": 720 + }, + { + "target": "unbusy" + }, + { + "target": "unbusy" + } + ] + } + ], + "snapshots": [ + { + "phase": "before-seek", + "visible": 2676, + "signature": 1690974404, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 0.571282, + "connected": true + }, + { + "phase": "after-seek", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 640, + "cssHeight": 360, + "time": 10.000023, + "connected": true + }, + { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.045326, + "connected": true + } + ], + "errors": [], + "lastPixel": { + "phase": "fullscreen", + "visible": 6216, + "signature": 3526987101, + "width": 640, + "height": 360, + "cssWidth": 1280, + "cssHeight": 720, + "time": 10.045326, + "connected": true + } + }, + "limitation": "Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance." + }, + "screenshots": [ + { + "name": "actual-jassub-page", + "sha256": "832a7ac308c27290f7ec6484e502f593271e1c1580bd4fe2dc44ecfab48bc2d2" + } + ] + } + ] + } + ], + "limitations": [ + "WebKit default frame-clock tests remain failing; explicit event-clock diagnosis is separate.", + "Offscreen rendering is explicitly disabled for pixel readback.", + "No native failure recovery, CSP, ratechange, default OffscreenCanvas, devices or sustained heap evidence.", + "Controlled removeChild uses assertion errors; Event rate payload is not an actual browser structured-clone failure proof.", + "Historical vendor remains unchanged; candidate repair is later work." + ] +} diff --git a/refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md b/refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md new file mode 100644 index 000000000..bc4e03c96 --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md @@ -0,0 +1,72 @@ +# PKG-JASSUB-02 行为、错误与原生渲染基线 + +本步骤建立迁移前基线,生产源码、第三方文件和资源未修改。新增的历史失败 +断言证明问题存在,不能当作修复通过;后续 03/07 负责修复,05 完成组合验收。 + +## 受控行为 + +七份真实发布/冻结实现各九个失败或保护场景,共 63 项;既有工厂、入口和 +来源测试加上事件时钟与切换字幕轨道,`yarn test:jassub` 联合 120 项通过。 +实际运行 vendor JS,DOM/Worker/RVFC 为受控平台,以下六类问题均已复现: + +| 问题 | 具体结果与后续责任 | +| --- | --- | +| 直接 destroy 再宿主 destroy,或直接重复 destroy | 二次移除容器抛错;03 修宿主重复调用,07 保证公开实例重复销毁 | +| setVideo 切到其他父容器再 destroy | 从新视频父节点删除旧容器失败,Worker 未终止、七监听器保留;07 | +| 自定义 canvas | vendor 已创建 Worker,adapter 访问不存在的 _canvasParent.style 抛错,尚未注册宿主清理;03 | +| Worker 构造抛 SecurityError | 原错误保留,但先前创建的容器和七监听器遗留;07 | +| setVideo 后旧 RVFC 到达 | 实际已排队的旧回调以 time=123 驱动 currentTime=9 的新视频,再建立第二条回调链;07 | +| 事件模式 ratechange | 直接 setRate(2) 正常;媒体 ratechange 却将 Event 当作 rate 传给 Worker,未使用 playbackRate=1.5;07 | + +测试还保护已有正常行为:ready 前 destroy 会终止 Worker 并去监听器,虽然测试 +能观察到终止后仍尝试发 init;Worker error 原样分发,宿主销毁仍能清理; +destroy 后遗留的单个 RVFC 到达会退出且不再排队。不能将这些观察夸大成 +“终止后 Worker 仍实际执行”或“销毁后永远循环”。受控 removeChild 抛的是 +ERR_ASSERTION,不冒充原生 DOMException;rate 错误负载尚非真实 clone 异常证据。 + +正常事件用例验证时间偏移、playing/seeking 状态,以及同步 setTrackByUrl、 +setTrack、freeTrack 的实际 Worker 消息顺序。保留公开 instance 与返回值。 + +## 真实浏览器 + +新 `jassub-native.spec.js` 加载真实 npm 1.1.0 主产物、原生 Worker、本地固定 WASM +和 Liberation 字体,使用两条自编 ASS 字幕及实际视频。明确设置 +offscreenRender=false 以读取真实字幕 canvas 像素,其他默认路径未冒充验收。 +原生 Worker 子类只记录 postMessage/message/terminate,仍委托浏览器原实现。 +断言初始非透明像素、seek 后不同字幕像素、网页全屏尺寸和正常单次销毁。 + +Chromium/Firefox 各三个核心组合通过:候选、实际 npm 5.4.0、实际 5.3.1-beta.1。 +截图确认字幕已由真实 WASM 绘制。首次 Chromium 三例失败是测试服务器未提供 +application/wasm,补齐 MIME 后通过;没有改 SDK、替换编译器或放宽等待时间。 + +Windows WebKit 26.6 的三个默认 onDemandRender 场景均没有可见字幕。保留的 +进一步观测显示:资源 200、Worker ready,但媒体已到 6.4992094 秒仍只有 init/ +canvas 消息,没有 demand 请求;canvas 为零可见像素。显式设置旧选项 +onDemandRender=false 的候选核心诊断通过。这个诊断不能替代默认路径验收, +也不足以断言所有 Safari/设备不支持 RVFC。新增 JASSUB-NATIVE-FRAME-01,后续 +07/05 继续验证原因与兼容处理。默认 WebKit 用例保持失败,不加 skip、重试或 +扩大超时。02 的完成只表示已建立成功/失败基线,不表示全浏览器绿色。 + +## 维护与验收边界 + +```sh +yarn test:jassub +yarn test:browser test/browser/jassub-native.spec.js --workers=1 +``` + +历史失败测试进入既有 baseline glob 和 test:jassub。设置 +ARTPLAYER_JASSUB_FAILURE_REPORT 可输出详细受控观察;默认测试不写报告。 +原生诊断可设置 ARTPLAYER_JASSUB_ON_DEMAND=false,报告保存该选择。按不同运行 +分别归档 report/results,等待进程终态后再跑下一轮。机器报告包含成功、失败和 +诊断结果,见 [验证](../baselines/jassub-failures-validation.json)。 + +自有 adapter 修复与 vendor 方法修复必须分开。新增 PKG-JASSUB-07,在 03 之后、 +05 之前处理有证据的 vendor 生命周期/时钟问题;保留原文件及来源基线,任何 +实际补丁都必须单列差异与候选正例,不得把未修改 vendor 宣称为已修复。 +完整设备、默认 OffscreenCanvas、真实资源失败、CSP、倍率、长时播放/内存和 +VideoFrame/ResizeObserver 回收仍属后续验证。源码/类型/发布门槛全部保留。 + +本次新增测试及测试服务器 MIME 修正,定向 lint、计划和风险检查通过;完整 +浏览器流程仍有上述 WebKit 失败,不宣称 CI 或后续组合验收通过。 +没有新依赖、锁文件或生产产物变化。独立本地提交,不推送或发布;回退本步骤 +仅移除新基线/测试与 MIME 支持,生产行为保持原样。 diff --git a/refactor/plan.md b/refactor/plan.md index 131c17f7c..b371a788d 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -2,9 +2,9 @@ > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 -基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 231 项,范围 22 个包及工作区/示例。 +基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 232 项,范围 22 个包及工作区/示例。 -状态:todo 65 / doing 14 / blocked 0 / done 152 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 65 / doing 14 / blocked 0 / done 153 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -27,7 +27,7 @@ | artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-05, PKG-DASH-06 | | artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 | | artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 | -| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06 | +| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06, PKG-JASSUB-07 | | artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-07, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06 | | artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06 | | artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 | @@ -230,11 +230,12 @@ | ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 | | --- | --- | --- | --- | --- | --- | --- | | PKG-JASSUB-01 | artplayer-plugin-jassub
核对包契约与历史用法 | BASE-05 | 选项透传、result.instance、vendor 来源、worker/WASM/font 路径 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 VENDOR-04/05,核对 wrapper/worker/WASM/font 的独立来源与通知,保持资源路径和选项透传 | H | done | -| PKG-JASSUB-02 | artplayer-plugin-jassub
建立特有行为与错误测试 | PKG-JASSUB-01, ENG-03, ENG-05 | ASS 字体、时钟/seek/倍率/resize、加载失败和销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo | +| PKG-JASSUB-02 | artplayer-plugin-jassub
建立特有行为与错误测试 | PKG-JASSUB-01, ENG-03, ENG-05 | ASS 字体、时钟/seek/倍率/resize、加载失败和销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-JASSUB-03 | artplayer-plugin-jassub
整理内部职责与资源 | PKG-JASSUB-02, CORE-15, CORE-16 | 仅整理自有 adapter/销毁;保留第三方文件及来源 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo | | PKG-JASSUB-04 | artplayer-plugin-jassub
迁移自有源码和公开类型 | PKG-JASSUB-03, ENG-04, ENG-06, CORE-07 | JASSUB option/instance 的兼容类型包装,vendor JS 例外记录 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | -| PKG-JASSUB-05 | artplayer-plugin-jassub
验证新旧核心和组合 | PKG-JASSUB-04, CORE-22 | 真实 worker/WASM 字幕渲染与全屏、旧核心测试 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | +| PKG-JASSUB-05 | artplayer-plugin-jassub
验证新旧核心和组合 | PKG-JASSUB-04, CORE-22, PKG-JASSUB-07 | 真实 worker/WASM 字幕渲染与全屏、旧核心测试 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-JASSUB-06 | artplayer-plugin-jassub
验证分发并同步文档 | PKG-JASSUB-05, ENG-07 | jassub.js、外部资源路径、许可和离线失败记录;接续 VENDOR-04/05 完整组件/字体通知、LGPL 源码分发核对及六份未明确字体的分发处置 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录;来源身份已由01冻结,完整通知和未明确字体的发布处理必须闭环,不能以来源相同替代许可结论 | H | todo | +| PKG-JASSUB-07 | artplayer-plugin-jassub
修复 vendor 生命周期与字幕时钟 | PKG-JASSUB-03, PKG-JASSUB-02 | 独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界 | 保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过 | H | todo | ## 5 包迁移:artplayer-plugin-danmuku-mask @@ -532,6 +533,7 @@ - PKG-MULTI-SUB-04: [记录](baselines/multiple-subtitles-runtime-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md) [记录](baselines/multiple-subtitles-public-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md) [记录](type-compatibility-policy.md) [记录](baselines/multiple-subtitles-approved-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-approved-types.md) - PKG-MULTI-SUB-07: [记录](baselines/multiple-subtitles-timestamps.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md) - PKG-JASSUB-01: [记录](baselines/jassub-release.json) [记录](baselines/jassub-vendor.json) [记录](baselines/jassub-font-metadata.json) [记录](baselines/jassub-contract.md) [记录](baselines/jassub-contract-validation.json) [记录](changes/2026-09-13-PKG-JASSUB-01-baseline.md) [记录](baselines/jassub-provenance.json) [记录](baselines/jassub-provenance-validation.json) [记录](changes/2026-09-14-PKG-JASSUB-01-provenance.md) +- PKG-JASSUB-02: [记录](changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md) [记录](baselines/jassub-failures-validation.json) - PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md) - PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json) - PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 64199d837..7962784aa 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,14 @@ # 进度与证据 +## PKG-JASSUB-02 历史缺陷与原生渲染基线完成 + +联合 120 项通过,其中 63 项确认旧失败/已有保护;没有宣称修复。Chromium/Firefox +实际 npm 插件和 WASM/字体六个核心组合通过,WebKit 默认逐帧路径仍失败,显式 +事件时钟诊断单独通过。测试 server 的 WASM MIME 错误已修正并保存首次失败。 +[基线记录](changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md)明确新增六项风险; +03 处理自有 adapter,新增 07 专门修 vendor 生命周期/时钟且纳入 05 前置。生产文件 +未改,02 仅完成基线职责,全部兼容与发布门槛保留。 + ## PKG-JASSUB-01 来源及契约核对完成 两份 WASM 与 11 份字体的上游 blob 已精确匹配,nightly 对应源码、七子模块和 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index beeaccceb..c6f44f5a1 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -242,3 +242,9 @@ | DANMUKU-TIMING-01 | resolved / 已复现 | Hidden Worker wait is deducted from visible lifetime and can recycle a comment immediately after visible | PKG-DANMUKU-11 | | DANMUKU-SAMPLING-01 | resolved / 已复现 | Serial asynchronous placement stops native frame eligibility sampling and delays existing visible expiry | PKG-DANMUKU-12 | | MASK-SCHEDULING-01 | open / 已复现 | Native Mask startup can coincide with a missed narrow Danmuku timestamp window | PKG-MASK-05 | +| JASSUB-DESTROY-01 | open / 已复现 | Repeated destruction or cross-parent setVideo can throw before JASSUB cleanup | PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-07 | +| JASSUB-CANVAS-01 | open / 已复现 | Custom canvas registration throws after JASSUB Worker allocation | PKG-JASSUB-02, PKG-JASSUB-03 | +| JASSUB-CONSTRUCTION-01 | open / 已复现 | Worker construction failure leaves previously allocated JASSUB DOM and listeners | PKG-JASSUB-02, PKG-JASSUB-07 | +| JASSUB-VIDEO-FRAME-01 | open / 已复现 | Old video frame callbacks can drive replacement subtitle time and duplicate chains | PKG-JASSUB-02, PKG-JASSUB-07 | +| JASSUB-RATE-01 | open / 已复现 | Fallback ratechange sends an Event instead of numeric playbackRate | PKG-JASSUB-02, PKG-JASSUB-07 | +| JASSUB-NATIVE-FRAME-01 | open / 已复现 | Windows WebKit default frame-clock path does not produce subtitle demands in actual playback | PKG-JASSUB-02, PKG-JASSUB-07, PKG-JASSUB-05 | diff --git a/refactor/risks.json b/refactor/risks.json index 807f9c7f4..1942c4a1a 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -5425,6 +5425,104 @@ ], "compatibleResolution": "Retain startup sampling evidence and distinguish it from post-model-ready delivery; investigate actual startup scheduling without silently adding catch-up semantics.", "closureCriteria": "Identify the startup scheduling cause and assess a compatible mitigation or explicitly reviewed limitation using actual model/media evidence; post-ready success alone cannot close this risk." + }, + { + "id": "JASSUB-DESTROY-01", + "title": "Repeated destruction or cross-parent setVideo can throw before JASSUB cleanup", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "PKG-JASSUB-02", + "PKG-JASSUB-03", + "PKG-JASSUB-07" + ], + "evidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "refactor/baselines/jassub-failures-validation.json" + ], + "compatibleResolution": "Use actual resource ownership and tolerate direct instance teardown before host teardown.", + "closureCriteria": "Candidate direct/repeated/host teardown and cross-parent video replacement release container, Worker and listeners without throwing or removing user nodes." + }, + { + "id": "JASSUB-CANVAS-01", + "title": "Custom canvas registration throws after JASSUB Worker allocation", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "PKG-JASSUB-02", + "PKG-JASSUB-03" + ], + "evidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "refactor/baselines/jassub-failures-validation.json" + ], + "compatibleResolution": "Guard adapter-created parent styling; retain the supplied canvas and roll back failed registration.", + "closureCriteria": "Custom canvas registration succeeds and cleanup releases owned work while preserving caller DOM; public instance and option override behavior remain compatible." + }, + { + "id": "JASSUB-CONSTRUCTION-01", + "title": "Worker construction failure leaves previously allocated JASSUB DOM and listeners", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "PKG-JASSUB-02", + "PKG-JASSUB-07" + ], + "evidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "refactor/baselines/jassub-failures-validation.json" + ], + "compatibleResolution": "Roll back each construction phase and rethrow the original error without assuming new returned an instance.", + "closureCriteria": "Controlled and native construction failure do not retain owned DOM/listeners/callbacks; original error identity and successful construction remain intact." + }, + { + "id": "JASSUB-VIDEO-FRAME-01", + "title": "Old video frame callbacks can drive replacement subtitle time and duplicate chains", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "PKG-JASSUB-02", + "PKG-JASSUB-07" + ], + "evidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "refactor/baselines/jassub-failures-validation.json" + ], + "compatibleResolution": "Track video generation and callback ownership across setVideo and destroy.", + "closureCriteria": "Actually queued old callbacks cannot deliver stale mediaTime or rearm; replacement video owns one loop, with native evidence." + }, + { + "id": "JASSUB-RATE-01", + "title": "Fallback ratechange sends an Event instead of numeric playbackRate", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "PKG-JASSUB-02", + "PKG-JASSUB-07" + ], + "evidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "refactor/baselines/jassub-failures-validation.json" + ], + "compatibleResolution": "Adapt event subscription to read the current video rate while retaining direct setRate(number).", + "closureCriteria": "Real ratechange and direct calls send numeric values, preserve timing, and avoid stale listeners after source replacement." + }, + { + "id": "JASSUB-NATIVE-FRAME-01", + "title": "Windows WebKit default frame-clock path does not produce subtitle demands in actual playback", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "PKG-JASSUB-02", + "PKG-JASSUB-07", + "PKG-JASSUB-05" + ], + "evidence": [ + "refactor/changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "refactor/baselines/jassub-failures-validation.json" + ], + "compatibleResolution": "Investigate native frame delivery and establish a compatible capability response; explicit event-clock success is diagnostic only.", + "closureCriteria": "Default native behavior is verified or an explicit reviewed environment limitation is recorded; no silent global fallback or substitution of diagnostic mode for default acceptance." } ] } diff --git a/refactor/scripts/jassub-failures.test.mjs b/refactor/scripts/jassub-failures.test.mjs new file mode 100644 index 000000000..081d5a125 --- /dev/null +++ b/refactor/scripts/jassub-failures.test.mjs @@ -0,0 +1,239 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import process from 'node:process' +// eslint-disable-next-line test/no-import-node-test -- Frozen failure observations use the repository runner. +import test from 'node:test' +import { jassubEnvironment, jassubHistorical } from '../../test/helpers/jassub.js' + +const observations = [] +const implementations = await jassubHistorical() + +function environment(implementation) { + // Expose only VM platform constructors for controlled faults; vendor methods stay unchanged. + const code = `${implementation.code}\nObject.defineProperty(module.exports.default || module.exports, '__reviewPlatform', { value: { createCanvas: () => document.createElement('canvas'), setGlobal: (name, value) => { globalThis[name] = value }, ErrorEvent, Video: HTMLVideoElement } });` + const env = jassubEnvironment({ ...implementation, code }) + env.platform = env.factory.__reviewPlatform + return env +} + +function trackListeners(video) { + const listeners = new Map() + const add = video.addEventListener + const remove = video.removeEventListener + video.addEventListener = function (type, callback, ...options) { + if (!listeners.has(type)) + listeners.set(type, new Set()) + listeners.get(type).add(callback) + return add.call(this, type, callback, ...options) + } + video.removeEventListener = function (type, callback, ...options) { + listeners.get(type)?.delete(callback) + return remove.call(this, type, callback, ...options) + } + return () => [...listeners.values()].reduce((count, entries) => count + entries.size, 0) +} + +function frames(env) { + const pending = new Map() + const cancelled = [] + let next = 0 + env.platform.Video.prototype.requestVideoFrameCallback = function (callback) { + pending.set(++next, { video: this, callback }) + return next + } + env.platform.Video.prototype.cancelVideoFrameCallback = function (id) { + cancelled.push(id) + pending.delete(id) + } + return { + pending, + cancelled, + deliver(video, metadata) { + const entry = [...pending].find(([, row]) => row.video === video) + assert(entry, 'must deliver an actually queued frame callback') + const [id, row] = entry + pending.delete(id) + row.callback(0, metadata) + }, + } +} + +for (const implementation of implementations) { + test(`JASSUB failure baseline ${implementation.name}: fallback ratechange forwards the Event instead of the numeric media rate`, async () => { + const env = environment(implementation) + const { instance } = env.factory()(env.art) + await env.ready() + instance.setRate(2) + await env.flush() + assert.equal(env.workers[0].messages.at(-1).rate, 2) + env.art.video.playbackRate = 1.5 + const event = new Event('ratechange') + env.art.video.dispatchEvent(event) + await env.flush() + const message = env.workers[0].messages.at(-1) + assert.equal(message.target, 'video') + assert.equal(message.rate, event) + assert.notEqual(message.rate, env.art.video.playbackRate) + observations.push({ implementation: implementation.name, case: 'rate-event-payload', mediaRate: 1.5, forwardedType: message.rate.type, directNumericRatePreserved: true }) + instance.destroy() + }) + + test(`JASSUB failure baseline ${implementation.name}: direct destroy followed by art destroy throws on the removed container`, async () => { + const env = environment(implementation) + const active = trackListeners(env.art.video) + const { instance } = env.factory()(env.art) + await env.ready() + assert.equal(active(), 7) + instance.destroy() + assert.equal(active(), 0) + assert.equal(env.workers[0].terminated, 1) + assert.equal(env.parent.children.length, 1) + assert.throws(() => env.emit('destroy'), { code: 'ERR_ASSERTION' }) + assert.throws(() => instance.destroy(), { code: 'ERR_ASSERTION' }) + assert.equal(env.workers[0].terminated, 1) + observations.push({ implementation: implementation.name, case: 'repeated-destroy', throws: true, workerTerminations: 1 }) + }) + + test(`JASSUB failure baseline ${implementation.name}: setVideo across parents makes destroy throw before releasing the worker`, async () => { + const env = environment(implementation) + const { instance } = env.factory()(env.art) + await env.ready() + const secondParent = new env.parent.constructor() + const secondVideo = secondParent.appendChild(new env.platform.Video()) + const active = trackListeners(secondVideo) + instance.setVideo(secondVideo) + assert.equal(active(), 7) + assert.throws(() => instance.destroy(), { code: 'ERR_ASSERTION' }) + assert.notEqual(instance._destroyed, true) + assert.equal(env.workers[0].terminated, 0) + assert.equal(env.parent.children.length, 2) + assert.equal(active(), 7) + observations.push({ implementation: implementation.name, case: 'set-video-parent', workerTerminations: 0, listenersRetained: active(), originalContainerRetained: true }) + instance.setVideo(env.art.video) + instance.destroy() + }) + + test(`JASSUB failure baseline ${implementation.name}: a supplied canvas makes the adapter throw after allocating the worker`, async () => { + const env = environment(implementation) + const active = trackListeners(env.art.video) + const canvas = env.parent.appendChild(env.platform.createCanvas()) + assert.throws(() => env.factory({ canvas })(env.art), { name: 'TypeError', message: /reading 'style'/ }) + await env.flush() + assert.equal(env.workers.length, 1) + assert.equal(env.workers[0].terminated, 0) + assert.equal(env.workers[0].messages[0].target, 'init') + assert.equal(env.listeners.has('destroy'), false) + assert.equal(canvas.parentNode, env.parent) + assert.equal(active(), 7) + observations.push({ implementation: implementation.name, case: 'custom-canvas', workerTerminations: 0, listenersRetained: active(), hostDestroyHandlerRegistered: false, initSent: true }) + env.workers[0].terminate() + }) + + test(`JASSUB failure baseline ${implementation.name}: Worker construction failure leaves the already inserted container and video listeners`, async () => { + const env = environment(implementation) + const active = trackListeners(env.art.video) + const failure = new DOMException('Controlled Worker construction failure', 'SecurityError') + env.platform.setGlobal('Worker', class { + constructor() { throw failure } + }) + assert.throws(() => env.factory()(env.art), error => error === failure) + await env.flush() + assert.equal(env.workers.length, 0) + assert.equal(env.listeners.has('destroy'), false) + assert.equal(env.parent.children.length, 2) + assert.equal(env.parent.children[1].className, 'JASSUB') + assert.equal(active(), 7) + observations.push({ implementation: implementation.name, case: 'worker-construction-failure', originalErrorPreserved: true, insertedContainerRetained: true, listenersRetained: active() }) + }) + + test(`JASSUB failure baseline ${implementation.name}: a previous video's queued frame drives the replacement video and creates a second loop`, async () => { + const env = environment(implementation) + const clock = frames(env) + const oldVideo = env.art.video + const { instance } = env.factory()(env.art) + await env.ready() + assert.equal(clock.pending.size, 1) + const newVideo = env.createVideo() + newVideo.currentTime = 9 + instance.setVideo(newVideo) + await env.flush() + assert.equal(clock.pending.size, 2) + assert.equal(clock.cancelled.length, 0) + clock.deliver(oldVideo, { mediaTime: 123, width: 640, height: 360 }) + await env.flush() + assert.equal(env.workers[0].messages.filter(item => item.target === 'demand').at(-1).time, 123) + assert.equal([...clock.pending.values()].filter(row => row.video === newVideo).length, 2) + observations.push({ implementation: implementation.name, case: 'old-video-frame', replacementCurrentTime: newVideo.currentTime, staleDemandTime: 123, replacementPendingCallbacks: 2, cancelledCallbacks: 0 }) + instance.destroy() + }) + + test(`JASSUB containment baseline ${implementation.name}: destroy before ready terminates and detaches, but initialization continues after termination`, async () => { + const env = environment(implementation) + const active = trackListeners(env.art.video) + const { instance } = env.factory()(env.art) + const worker = env.workers[0] + const post = worker.postMessage + const afterTermination = [] + worker.postMessage = function (message) { + if (this.terminated) + afterTermination.push(message.target) + return post.call(this, message) + } + let settled = false + instance._loaded.then(() => { + settled = true + }) + instance.setTrack('[Script Info]') + instance.destroy() + await env.flush() + assert.equal(worker.terminated, 1) + assert.equal(env.parent.children.length, 1) + assert.equal(active(), 0) + assert.deepEqual(afterTermination, ['init']) + assert.equal(settled, false) + assert.equal(worker.messages.some(item => item.target === 'setTrack'), false) + observations.push({ implementation: implementation.name, case: 'destroy-before-ready', workerTerminations: 1, listenersRetained: 0, postAfterTermination: afterTermination, loadedSettledWithinFixtureFlush: settled }) + }) + + test(`JASSUB containment baseline ${implementation.name}: Worker errors reach the instance and explicit host destroy still releases resources`, async () => { + const env = environment(implementation) + const active = trackListeners(env.art.video) + const { instance } = env.factory()(env.art) + const failure = new Error('Controlled Worker script load failure') + const errors = [] + instance.addEventListener('error', event => errors.push(event.error)) + env.workers[0].onerror(new env.platform.ErrorEvent('error', { error: failure })) + await env.flush() + assert.deepEqual(errors, [failure]) + assert.equal(env.errors.at(-1), failure) + assert.equal(env.workers[0].terminated, 0) + assert.equal(active(), 7) + env.emit('destroy') + assert.equal(env.workers[0].terminated, 1) + assert.equal(env.parent.children.length, 1) + assert.equal(active(), 0) + observations.push({ implementation: implementation.name, case: 'worker-error', exactErrorForwarded: true, automaticallyTerminated: false, explicitHostCleanup: true }) + }) + + test(`JASSUB containment baseline ${implementation.name}: the outstanding frame after destroy does no work and does not rearm`, async () => { + const env = environment(implementation) + const clock = frames(env) + const { instance } = env.factory()(env.art) + await env.ready() + instance.destroy() + await env.flush() + assert.equal(clock.pending.size, 1) + assert.equal(clock.cancelled.length, 0) + const before = env.workers[0].messages.length + clock.deliver(env.art.video, { mediaTime: 123, width: 640, height: 360 }) + await env.flush() + assert.equal(env.workers[0].messages.length, before) + assert.equal(clock.pending.size, 0) + observations.push({ implementation: implementation.name, case: 'frame-after-destroy', activelyCancelled: false, callbackDoesNoWork: true, callbackRearms: false }) + }) +} + +test.after(() => { + if (process.env.ARTPLAYER_JASSUB_FAILURE_REPORT) + fs.writeFileSync(process.env.ARTPLAYER_JASSUB_FAILURE_REPORT, `${JSON.stringify(observations, null, 2)}\n`) +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index 4006817c0..a824ab884 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -2434,11 +2434,14 @@ "ENG-03", "ENG-05" ], - "status": "todo", + "status": "done", "risk": "H", "deliverable": "ASS 字体、时钟/seek/倍率/resize、加载失败和销毁", "acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言", - "evidence": [] + "evidence": [ + "changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md", + "baselines/jassub-failures-validation.json" + ] }, { "id": "PKG-JASSUB-03", @@ -2486,7 +2489,8 @@ ], "dependsOn": [ "PKG-JASSUB-04", - "CORE-22" + "CORE-22", + "PKG-JASSUB-07" ], "status": "todo", "risk": "H", @@ -2511,6 +2515,23 @@ "acceptance": "tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录;来源身份已由01冻结,完整通知和未明确字体的发布处理必须闭环,不能以来源相同替代许可结论", "evidence": [] }, + { + "id": "PKG-JASSUB-07", + "phase": "5 包迁移:artplayer-plugin-jassub", + "title": "修复 vendor 生命周期与字幕时钟", + "scope": [ + "artplayer-plugin-jassub" + ], + "dependsOn": [ + "PKG-JASSUB-03", + "PKG-JASSUB-02" + ], + "status": "todo", + "risk": "H", + "deliverable": "独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界", + "acceptance": "保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过", + "evidence": [] + }, { "id": "PKG-MASK-01", "phase": "5 包迁移:artplayer-plugin-danmuku-mask", diff --git a/test/browser/README.md b/test/browser/README.md index 10571dd0a..90a0aaa5c 100644 --- a/test/browser/README.md +++ b/test/browser/README.md @@ -169,3 +169,12 @@ The startup time=6 row remains diagnostic because initial model work can coincid with a missed narrow timestamp window. A passing post-ready combination is not proof of startup losslessness, private GPU/WASM closure, OS fullscreen or devices. See refactor/changes/2026-09-14-PKG-MASK-05-native-checkpoint.md for exact limits. + +`jassub-native.spec.js` runs actual published JASSUB 1.1.0, its native Worker, +fixed local WASM/font and authored ASS cues with offscreenRender=false. It checks +real canvas pixels before/after seek, CSS web fullscreen and normal destruction. +Windows WebKit currently fails the default frame-clock path; the explicitly +selected ARTPLAYER_JASSUB_ON_DEMAND=false diagnostic passes on the candidate core. +This is a retained historical failure, not a waived browser gate. The server must +serve WASM as application/wasm for native streaming compilation. Archive failed +and successful runs independently. See the PKG-JASSUB-02 change record. diff --git a/test/browser/jassub-native.spec.js b/test/browser/jassub-native.spec.js new file mode 100644 index 000000000..570ed4481 --- /dev/null +++ b/test/browser/jassub-native.spec.js @@ -0,0 +1,150 @@ +import fs from 'node:fs' +import process from 'node:process' +import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs' +import { expect, test } from './fixtures.js' + +const baseline = JSON.parse(fs.readFileSync('refactor/baselines/jassub-release.json', 'utf8')) +const release = baseline.release +const archive = await ensureArchive(release) +const member = `package/${release.manifest.main.replace(/^\.\//, '')}` +const code = readMember(archive, member).toString() +const onDemandRender = process.env.ARTPLAYER_JASSUB_ON_DEMAND !== 'false' +const subtitles = `[Script Info] +ScriptType: v4.00+ +PlayResX: 640 +PlayResY: 360 +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Liberation Sans,36,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,20,1 +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.00,0:00:04.00,Default,,0,0,0,,Before seek +Dialogue: 0,0:00:05.00,0:00:30.00,Default,,0,0,0,,After seek with actual WASM +` + +for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) { + test(`JASSUB published ${release.version} renders actual WASM subtitles through seek and layout with ${core} core`, async ({ page, context }, testInfo) => { + const resources = [] + const external = [] + const origin = new URL(testInfo.project.use.baseURL).origin + await context.route('**/*', (route) => { + if (new URL(route.request().url()).origin !== origin) { + external.push(route.request().url()) + return route.abort() + } + return route.continue() + }) + context.on('response', (response) => { + if (response.url().includes('/assets/jassub/')) + resources.push({ url: response.url(), status: response.status(), contentType: response.headers()['content-type'] }) + }) + await page.goto(`/test/player.html?core=${core}`) + await page.addScriptTag({ content: code }) + await page.evaluate(() => { + window.jassubNative = { workers: [], snapshots: [], errors: [] } + const NativeWorker = window.Worker + window.Worker = class extends NativeWorker { + constructor(...args) { + super(...args) + this.observation = { url: String(args[0]), terminated: 0, sent: [], received: [] } + window.jassubNative.workers.push(this.observation) + this.addEventListener('message', ({ data }) => this.observation.received.push({ target: data.target, images: data.images?.length, width: data.width, height: data.height })) + } + + postMessage(message, ...rest) { + this.observation.sent.push({ target: message.target, time: message.time, width: message.width, height: message.height }) + return super.postMessage(message, ...rest) + } + + terminate() { + this.observation.terminated++ + return super.terminate() + } + } + window.createPlayer('/assets/sample/steve-jobs.mp4') + }) + try { + await expect.poll(() => page.evaluate(() => window.art.duration)).toBeGreaterThan(30) + await page.locator('#play').click() + await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1) + await page.locator('#pause').click() + await page.evaluate(async ({ subContent, onDemandRender }) => { + const register = window.artplayerPluginJassub.default || window.artplayerPluginJassub + window.art.plugins.add(register({ + subContent, + workerUrl: '/assets/jassub/jassub-worker.js', + wasmUrl: '/assets/jassub/jassub-worker.wasm', + modernWasmUrl: '/assets/jassub/jassub-worker-modern.wasm', + availableFonts: { 'liberation sans': '/assets/jassub/default.woff2' }, + fonts: ['/assets/jassub/default.woff2'], + offscreenRender: false, + onDemandRender, + })) + window.jassub = window.art.plugins.artplayerPluginJassub.instance + window.jassub.addEventListener('error', event => window.jassubNative.errors.push(String(event.error))) + await new Promise((resolve, reject) => { + window.jassub.addEventListener('ready', resolve, { once: true }) + window.jassub.addEventListener('error', event => reject(event.error), { once: true }) + }) + window.jassubPixels = (phase) => { + const canvas = window.jassub._canvas + const rect = canvas.getBoundingClientRect() + const pixels = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data + let visible = 0 + let signature = 0 + for (let i = 3; i < pixels.length; i += 4) { + if (pixels[i] > 0) { + visible++ + signature = (signature + i * pixels[i]) >>> 0 + } + } + const state = { phase, visible, signature, width: canvas.width, height: canvas.height, cssWidth: rect.width, cssHeight: rect.height, time: window.art.currentTime, connected: canvas.isConnected } + window.jassubNative.lastPixel = state + if (phase) + window.jassubNative.snapshots.push(state) + return state + } + }, { subContent: subtitles, onDemandRender }) + await page.locator('#play').click() + await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100) + const before = await page.evaluate(() => window.jassubPixels('before-seek')) + expect(before.connected).toBe(true) + await page.locator('#pause').click() + await page.evaluate(async () => { + const seeked = new Promise(resolve => window.art.video.addEventListener('seeked', resolve, { once: true })) + window.art.currentTime = 10 + await seeked + }) + await page.locator('#play').click() + await expect.poll(() => page.evaluate(() => window.jassubPixels().signature)).not.toBe(before.signature) + await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100) + const after = await page.evaluate(() => window.jassubPixels('after-seek')) + expect(after.time).toBeGreaterThanOrEqual(10) + expect(after.signature).not.toBe(before.signature) + await page.evaluate(() => { + window.art.fullscreenWeb = true + }) + await expect.poll(() => page.evaluate(() => window.jassubPixels().cssWidth)).toBeGreaterThan(before.cssWidth) + await expect.poll(() => page.evaluate(() => window.jassubPixels().visible)).toBeGreaterThan(100) + await page.evaluate(() => window.jassubPixels('fullscreen')) + await testInfo.attach('actual-jassub-page', { body: await page.screenshot(), contentType: 'image/png' }) + await page.evaluate(() => window.art.destroy(false)) + expect(await page.evaluate(() => document.querySelectorAll('.JASSUB').length)).toBe(0) + expect(await page.evaluate(() => window.jassubNative.workers.map(worker => worker.terminated))).toEqual([1]) + expect(await page.evaluate(() => window.jassubNative.errors)).toEqual([]) + expect(external).toEqual([]) + expect(resources.some(item => item.url.endsWith('.wasm') && item.status === 200 && item.contentType === 'application/wasm')).toBe(true) + expect(resources.some(item => item.url.endsWith('.woff2') && item.status === 200)).toBe(true) + } + finally { + const state = await page.evaluate(() => window.jassubNative).catch(error => ({ unavailable: error.message })) + await testInfo.attach('actual-jassub-evidence', { body: JSON.stringify({ core, onDemandRender, release: { version: release.version, integrity: release.integrity, member, sha256: hash(code) }, subtitleSha256: hash(subtitles), resources, external, state, limitation: 'Actual published plugin, worker, WASM and local font with offscreenRender=false. No default offscreen path, failure recovery, repeated destruction or device acceptance.' }, null, 2), contentType: 'application/json' }) + if (!page.isClosed()) { + await page.evaluate(() => { + if (window.art && !window.art.isDestroy) + window.art.destroy() + }) + } + } + }) +} diff --git a/test/browser/server.mjs b/test/browser/server.mjs index eb88dc8e8..75378159b 100644 --- a/test/browser/server.mjs +++ b/test/browser/server.mjs @@ -83,7 +83,7 @@ add('/test/audio-tone.m4a', fs.readFileSync(path.join(workspace, 'test/browser/m add('/test/thumbnail-pattern.mp4', fs.readFileSync(path.join(workspace, 'test/browser/media/thumbnail-pattern.mp4')), { kind: 'generated-media', file: 'test/browser/media/thumbnail-pattern.mp4' }) add('/test/auto-thumbnail-timeline.mp4', fs.readFileSync(path.join(workspace, 'test/browser/media/auto-thumbnail-timeline.mp4')), { kind: 'generated-media', file: 'test/browser/media/auto-thumbnail-timeline.mp4' }) -const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.css': 'text/css', '.mp4': 'video/mp4', '.m4a': 'audio/mp4', '.webm': 'video/webm', '.vtt': 'text/vtt', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg' } +const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.css': 'text/css', '.mp4': 'video/mp4', '.m4a': 'audio/mp4', '.webm': 'video/webm', '.vtt': 'text/vtt', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.wasm': 'application/wasm', '.woff2': 'font/woff2' } add('/test/declaration-cues.vtt', fs.readFileSync(path.join(workspace, 'test/browser/media/declaration-cues.vtt')), { kind: 'test-subtitles', file: 'test/browser/media/declaration-cues.vtt' }) add('/test/thumbnail-grid.svg', fs.readFileSync(path.join(workspace, 'test/browser/media/thumbnail-grid.svg')), { kind: 'test-thumbnail-grid', file: 'test/browser/media/thumbnail-grid.svg' }) add('/test/legacy-safe-area.js', fs.readFileSync(path.join(workspace, 'test/helpers/legacy-safe-area.js')), { kind: 'frozen-own-source', file: 'test/helpers/legacy-safe-area.js', commit: 'ccf77c4e' }) diff --git a/test/jassub.test.js b/test/jassub.test.js index 15ad75d4c..5043b96ac 100644 --- a/test/jassub.test.js +++ b/test/jassub.test.js @@ -76,4 +76,30 @@ for (const implementation of implementations) { assert.equal(init.availableFonts['liberation sans'], './default.woff2') result.instance.destroy() }) + test(`JASSUB ${implementation.name}: fallback clock keeps offsets and synchronous track replacement preserves message order`, async () => { + const env = jassubEnvironment(implementation) + const { instance } = env.factory({ timeOffset: -0.25 })(env.art) + await env.ready() + env.art.video.currentTime = 12 + env.art.video.paused = false + env.art.video.dispatchEvent(new Event('playing')) + await env.flush() + const playing = env.workers[0].messages.at(-1) + assert.equal(playing.target, 'video') + assert.equal(playing.currentTime, 11.75) + assert.equal(playing.isPaused, false) + env.art.video.dispatchEvent(new Event('seeking')) + await env.flush() + assert.equal(env.workers[0].messages.at(-1).isPaused, true) + const start = env.workers[0].messages.length + assert.equal(instance.setTrackByUrl('/replacement.ass'), undefined) + assert.equal(instance.setTrack('[Script Info]\nTitle: Replacement'), undefined) + assert.equal(instance.freeTrack(), undefined) + await env.flush() + const messages = env.workers[0].messages.slice(start) + assert.deepEqual(messages.map(message => message.target), ['setTrackByUrl', 'setTrack', 'freeTrack']) + assert.equal(messages[0].url, '/replacement.ass') + assert.equal(messages[1].content, '[Script Info]\nTitle: Replacement') + instance.destroy() + }) }