diff --git a/package.json b/package.json
index eec8a294f..6facece47 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs",
- "test:unit": "node --test test/danmuku-mask-failures.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js 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 test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
+ "test:unit": "node --test test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js 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 test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
"test": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
@@ -99,7 +99,8 @@
"test:asr-demo": "node refactor/scripts/asr-local-demo.mjs",
"test:chromecast": "node --test test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
"test:chromecast-types-package": "node refactor/scripts/chromecast-package-types.mjs",
- "test:danmuku-mask": "node --test test/danmuku-mask-failures.test.js refactor/scripts/danmuku-mask-contract.test.mjs"
+ "test:danmuku-mask": "node --test test/danmuku-mask-failures.test.js refactor/scripts/danmuku-mask-contract.test.mjs",
+ "test:danmuku": "node --test test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
diff --git a/refactor/baselines/danmuku-browser-validation.json b/refactor/baselines/danmuku-browser-validation.json
new file mode 100644
index 000000000..2cf848293
--- /dev/null
+++ b/refactor/baselines/danmuku-browser-validation.json
@@ -0,0 +1,6939 @@
+{
+ "schemaVersion": 1,
+ "task": "PKG-DANMUKU-02",
+ "status": "done",
+ "capturedAt": "2026-09-13T10:02:24.536Z",
+ "frozenSourceCommit": "b0cfbfe3a09a84a295a257e45e4577847588d1cb",
+ "controlled": {
+ "file": "refactor/baselines/danmuku-failures-validation.json",
+ "sha256": "6558273bbb57ed8d307d566be5ecd6806cd3eb03a597526e9e242fd67c73f123",
+ "tests": 42,
+ "parentReview": {
+ "file": "refactor/.cache/danmuku02-parent-controlled.log",
+ "sha256": "0ab4991d032e4160a5ff6fd388c170d877368a1c3283e58ce0511dbe273fa405"
+ }
+ },
+ "browsers": [
+ {
+ "file": "refactor/.cache/danmuku02-browser-three-final/report.json",
+ "sha256": "c0d1056af6b6dc382af4c6c1738ed5551db8f1b8de55028109051ccb23866e3c",
+ "workers": 3,
+ "expected": 48,
+ "unexpected": 0,
+ "skipped": 0,
+ "versions": [
+ "chromium: 153.0.8010.12",
+ "firefox: 155.0",
+ "webkit: 26.6"
+ ],
+ "observations": [
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.989479,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.942453,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 601.3364868164062,
+ "next": 601.3364868164062,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.632016,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.9111,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 295,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.955448,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.915711,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 602.8008422851562,
+ "next": 602.8008422851562,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.597092,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.91682,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 295,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.952409,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.930654,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 602.789306640625,
+ "next": 602.789306640625,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.601184,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.896709,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 295,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.965926,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.93251,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 604.2509765625,
+ "next": 604.2509765625,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.602008,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.89796,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 295,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "chromium",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.958332,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.957391,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 602.7999877929688,
+ "next": 602.7999877929688,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.657818,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.901681,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 294,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.958332,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.958332,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 643.63330078125,
+ "next": 643.63330078125,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.641169,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.938596,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 294,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.958332,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.958332,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 602.7999877929688,
+ "next": 602.7999877929688,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.636716,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.899656,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 294,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.990909,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0.961763,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 599.88330078125,
+ "next": 599.88330078125,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 3.761555,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.912036,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 294,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "firefox",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.5901241,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 9,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 1.2305143,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 618.5125122070312,
+ "next": 618.5125122070312,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 4.7285098,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 1.0015741,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 292,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 1.2708066,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 1.3726192,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 626.0374755859375,
+ "next": 626.0374755859375,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 4.4256368,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "published",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.5772691,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 292,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "published / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.0",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 1.1714412,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 6,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 1.4937955,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 616.0625,
+ "next": 616.0625,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 4.365894,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.4679923,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 292,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 main: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "modes",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": true,
+ "time": 0.6450449,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 5,
+ "visibleNodeCount": 4
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native Worker renders three modes with synchronous filter and asynchronous beforeVisible",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 4,
+ "stop": 1,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "density",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-dense-tracks",
+ "value": [
+ {
+ "id": "dense-0",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 0",
+ "x": 285,
+ "y": 10,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-1",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 1",
+ "x": 285,
+ "y": 33,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-2",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 2",
+ "x": 285,
+ "y": 56,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-3",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 3",
+ "x": 285,
+ "y": 79,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-4",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 4",
+ "x": 285,
+ "y": 102,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ },
+ {
+ "id": "dense-5",
+ "mode": "1",
+ "state": "stop",
+ "text": "TRACK 5",
+ "x": 285,
+ "y": 125,
+ "width": 70,
+ "height": 22.5,
+ "visibility": "visible",
+ "display": "block",
+ "transition": "",
+ "opacity": "1"
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 1.3254409,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 7,
+ "visibleNodeCount": 6
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: six simultaneous fixed comments preserve usable track geometry and input order",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 7,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 2,
+ "config": 1,
+ "loaded": 2,
+ "start": 2,
+ "visible": 6,
+ "stop": 2,
+ "destroy": 1
+ },
+ "gateCount": 7,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "playback",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-pool-and-rate",
+ "value": {
+ "frozen": {
+ "left": 618.1624755859375,
+ "next": 618.1624755859375,
+ "state": "stop"
+ },
+ "reused": {
+ "poolReused": true,
+ "visibleSameNode": false,
+ "original": {
+ "id": "",
+ "state": "wait",
+ "visibility": "hidden",
+ "text": "REUSED"
+ },
+ "extraNodes": [
+ {
+ "id": "",
+ "visibility": "hidden",
+ "text": "MOVING"
+ },
+ {
+ "id": "reused",
+ "state": "emit",
+ "visibility": "visible",
+ "text": "REUSED"
+ }
+ ],
+ "transition": "4s",
+ "rate": 2
+ },
+ "loadWithoutArgument": "replaces from original empty option; explicit array load appends"
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 4.788832,
+ "rate": 2,
+ "player": {
+ "x": 8,
+ "y": 8,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: native pause seek rate and public reset/load retain historical pool semantics",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 5,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 1,
+ "loaded": 3,
+ "start": 4,
+ "visible": 3,
+ "stop": 3,
+ "destroy": 1
+ },
+ "gateCount": 5,
+ "sendCount": 0,
+ "failures": [],
+ "retainedExternalSettings": [
+ 0
+ ]
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-browser-inputs",
+ "value": {
+ "core": "candidate",
+ "plugin": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "media": "/test/pattern.mp4",
+ "scenario": "settings",
+ "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked",
+ "heatmap": false
+ }
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-layout",
+ "value": [
+ {
+ "index": 0,
+ "playing": false,
+ "time": 0,
+ "rate": 1,
+ "player": {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0
+ },
+ "nodeCount": 0,
+ "visibleNodeCount": 0
+ },
+ {
+ "index": 1,
+ "playing": true,
+ "time": 0.5033361,
+ "rate": 1,
+ "player": {
+ "x": 8,
+ "y": 292,
+ "width": 640,
+ "height": 360
+ },
+ "nodeCount": 2,
+ "visibleNodeCount": 1
+ }
+ ]
+ },
+ {
+ "title": "candidate / Danmuku 5.3.0 legacy: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction",
+ "browser": "webkit",
+ "name": "danmuku-worker-and-lifecycle",
+ "value": {
+ "version": "5.4.1",
+ "workers": [
+ {
+ "urlScheme": "blob",
+ "replyCount": 0,
+ "terminated": 1,
+ "errors": []
+ },
+ {
+ "urlScheme": "blob",
+ "replyCount": 2,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "eventCounts": {
+ "reset": 4,
+ "config": 4,
+ "loaded": 2,
+ "stop": 2,
+ "destroy": 2,
+ "start": 2,
+ "visible": 1
+ },
+ "gateCount": 2,
+ "sendCount": 2,
+ "failures": [],
+ "retainedExternalSettings": [
+ 1,
+ 1
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "file": "refactor/.cache/danmuku02-browser-load-final/report.json",
+ "sha256": "11afd4c17f0efef2730e1daa018e0a68677dc8ae5569afe8b1bee22ab3365bc6",
+ "workers": 3,
+ "expected": 6,
+ "unexpected": 0,
+ "skipped": 0,
+ "versions": [
+ "chromium: 153.0.8010.12",
+ "firefox: 155.0",
+ "webkit: 26.6"
+ ],
+ "observations": [
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.049181,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 119,
+ "uniqueVisible": 119,
+ "nodeCount": 121,
+ "visibleNodeCount": 119,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 302,
+ "median": 16.699999999999818,
+ "p95": 16.700000000000728,
+ "max": 16.800000000000182
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 47,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 131.9000000357628,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5325,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/8325ad9e-b0c8-45f4-a532-95996384ceb4",
+ "posts": 4704,
+ "replies": 4703,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 121,
+ "rawFrameCount": 303,
+ "rawIntervalCount": 302
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/8325ad9e-b0c8-45f4-a532-95996384ceb4",
+ "posts": 4862,
+ "replies": 4862,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 119,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 47,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 131.9000000357628,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5325,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5475.400000035763,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.039338,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 119,
+ "uniqueVisible": 119,
+ "nodeCount": 121,
+ "visibleNodeCount": 119,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 301,
+ "median": 16.699999999999818,
+ "p95": 16.700000000000728,
+ "max": 16.800000000000182
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 94.5,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 192.89999997615814,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5376.199999988079,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/e785d7c6-105f-4431-9f6e-573b8fc7fdbb",
+ "posts": 4667,
+ "replies": 4666,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 121,
+ "rawFrameCount": 302,
+ "rawIntervalCount": 301
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/e785d7c6-105f-4431-9f6e-573b8fc7fdbb",
+ "posts": 4684,
+ "replies": 4684,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 119,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 94.5,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 192.89999997615814,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5376.199999988079,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5547.799999952316,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.163015,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 119,
+ "uniqueVisible": 119,
+ "nodeCount": 121,
+ "visibleNodeCount": 119,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 241,
+ "median": 16.680000000000064,
+ "p95": 50.03999999999951,
+ "max": 50.07999999999993
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 396,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 622,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 6189,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/360b9654-0272-43a7-aeb8-e7696622760c",
+ "posts": 4486,
+ "replies": 4485,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 121,
+ "rawFrameCount": 242,
+ "rawIntervalCount": 241
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/360b9654-0272-43a7-aeb8-e7696622760c",
+ "posts": 4578,
+ "replies": 4578,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 119,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 396,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 622,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 6189,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 6356,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.064198,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 129,
+ "uniqueVisible": 129,
+ "nodeCount": 131,
+ "visibleNodeCount": 129,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 244,
+ "median": 16.680000000000064,
+ "p95": 33.440000000000055,
+ "max": 66.72000000000025
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 355,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 486,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5917,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/d7d1c881-2850-436a-8d67-c3b786a16f2c",
+ "posts": 5001,
+ "replies": 5001,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 131,
+ "rawFrameCount": 245,
+ "rawIntervalCount": 244
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/d7d1c881-2850-436a-8d67-c3b786a16f2c",
+ "posts": 5001,
+ "replies": 5001,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 129,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 355,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 486,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5917,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 6040,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 4.9445888,
+ "video": {
+ "width": 640,
+ "height": 360,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 51,
+ "uniqueVisible": 51,
+ "nodeCount": 53,
+ "visibleNodeCount": 51,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 52,
+ "median": 106,
+ "p95": 140,
+ "max": 148
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 131,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 246,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 6483,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/2206fea6-9c83-4ed6-ae51-80498aac96dd",
+ "posts": 298,
+ "replies": 297,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 53,
+ "rawFrameCount": 53,
+ "rawIntervalCount": 52
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/2206fea6-9c83-4ed6-ae51-80498aac96dd",
+ "posts": 301,
+ "replies": 301,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 51,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 131,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 246,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 6483,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 6909,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 4.8138721,
+ "video": {
+ "width": 640,
+ "height": 360,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 55,
+ "uniqueVisible": 55,
+ "nodeCount": 57,
+ "visibleNodeCount": 55,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 53,
+ "median": 97,
+ "p95": 135,
+ "max": 141
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 134,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 251,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 6074,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/ae434b8e-620b-4d35-ba9e-7052fe76befc",
+ "posts": 314,
+ "replies": 313,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 57,
+ "rawFrameCount": 54,
+ "rawIntervalCount": 53
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/ae434b8e-620b-4d35-ba9e-7052fe76befc",
+ "posts": 321,
+ "replies": 320,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 55,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 134,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 251,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 6074,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 6460,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ }
+ ]
+ },
+ {
+ "file": "refactor/.cache/danmuku02-browser-load-serial/report.json",
+ "sha256": "635b7a7c0a19314edcf406c9bb8b0a778985d3c5d4f3e135eec688e5e5b95c57",
+ "workers": 1,
+ "expected": 6,
+ "unexpected": 0,
+ "skipped": 0,
+ "versions": [
+ "chromium: 153.0.8010.12",
+ "firefox: 155.0",
+ "webkit: 26.6"
+ ],
+ "observations": [
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.000722,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 119,
+ "uniqueVisible": 119,
+ "nodeCount": 121,
+ "visibleNodeCount": 119,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 299,
+ "median": 16.699999999999818,
+ "p95": 16.700000000000728,
+ "max": 16.800000000000182
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 44.80000001192093,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 120.80000001192093,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5225.100000023842,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/bcbfed39-b160-462f-8733-1803f14546df",
+ "posts": 7045,
+ "replies": 7045,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 121,
+ "rawFrameCount": 300,
+ "rawIntervalCount": 299
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/bcbfed39-b160-462f-8733-1803f14546df",
+ "posts": 7045,
+ "replies": 7045,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 119,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 44.80000001192093,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 120.80000001192093,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5225.100000023842,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5340.5,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.023261,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 119,
+ "uniqueVisible": 119,
+ "nodeCount": 121,
+ "visibleNodeCount": 119,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 300,
+ "median": 16.699999999999818,
+ "p95": 16.799999999999727,
+ "max": 16.800000000000182
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 33.19999998807907,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 109.89999997615814,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5263.600000023842,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/bba07862-2ac5-4c52-a4cb-399f145cb5b3",
+ "posts": 6329,
+ "replies": 6328,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 121,
+ "rawFrameCount": 301,
+ "rawIntervalCount": 300
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "chromium",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/bba07862-2ac5-4c52-a4cb-399f145cb5b3",
+ "posts": 6443,
+ "replies": 6443,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 119,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 33.19999998807907,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 109.89999997615814,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5263.600000023842,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5388.699999988079,
+ "memory": {
+ "usedJSHeapSize": 10000000,
+ "totalJSHeapSize": 10000000,
+ "jsHeapSizeLimit": 3760000000
+ }
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.040484,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 119,
+ "uniqueVisible": 119,
+ "nodeCount": 121,
+ "visibleNodeCount": 119,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 267,
+ "median": 16.660000000000082,
+ "p95": 33.340000000000146,
+ "max": 50.000000000000455
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 280,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 404,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5652,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/3520511d-55d1-4b6b-9097-b2ce1af67ade",
+ "posts": 6669,
+ "replies": 6668,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 121,
+ "rawFrameCount": 268,
+ "rawIntervalCount": 267
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/3520511d-55d1-4b6b-9097-b2ce1af67ade",
+ "posts": 6751,
+ "replies": 6751,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 119,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 280,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 404,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5652,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5774,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 5.056157,
+ "video": {
+ "width": 320,
+ "height": 180,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 130,
+ "uniqueVisible": 130,
+ "nodeCount": 132,
+ "visibleNodeCount": 130,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 268,
+ "median": 16.660000000000082,
+ "p95": 33.340000000000146,
+ "max": 50
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 61,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 137,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5342,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/f4cac3c1-264e-4af5-8b29-7d176d23fff2",
+ "posts": 6502,
+ "replies": 6502,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 132,
+ "rawFrameCount": 269,
+ "rawIntervalCount": 268
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "firefox",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/f4cac3c1-264e-4af5-8b29-7d176d23fff2",
+ "posts": 6502,
+ "replies": 6502,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 130,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 61,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 137,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5342,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5437,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "main",
+ "member": "package/dist/artplayer-plugin-danmuku.js",
+ "sha256": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 4.6056269,
+ "video": {
+ "width": 640,
+ "height": 360,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 51,
+ "uniqueVisible": 51,
+ "nodeCount": 53,
+ "visibleNodeCount": 51,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 63,
+ "median": 78,
+ "p95": 101,
+ "max": 122
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 60,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 163,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5618,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/d0900b47-b8eb-48ab-9c31-1ec51baf06f4",
+ "posts": 293,
+ "replies": 292,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 53,
+ "rawFrameCount": 64,
+ "rawIntervalCount": 63
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 main: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/d0900b47-b8eb-48ab-9c31-1ec51baf06f4",
+ "posts": 299,
+ "replies": 298,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 51,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 60,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 163,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5618,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5947,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-inputs",
+ "value": {
+ "core": "published",
+ "coreVersion": "5.4.0",
+ "pluginVersion": "5.3.0",
+ "format": "legacy",
+ "member": "package/dist/artplayer-plugin-danmuku.legacy.js",
+ "sha256": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
+ "comments": 300,
+ "modes": [
+ 0,
+ 1,
+ 2
+ ],
+ "scheduledTime": 0.5,
+ "heatmap": false,
+ "scope": "Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold."
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-sample",
+ "value": {
+ "coreVersion": "5.4.0",
+ "mediaTime": 4.6210889,
+ "video": {
+ "width": 640,
+ "height": 360,
+ "duration": 8
+ },
+ "loadedCount": 300,
+ "visibleEvents": 51,
+ "uniqueVisible": 51,
+ "nodeCount": 53,
+ "visibleNodeCount": 51,
+ "hiddenNodeCount": 2,
+ "frameSummary": {
+ "count": 61,
+ "median": 80,
+ "p95": 104,
+ "max": 119
+ },
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 74,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 171,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5617,
+ "memory": null
+ }
+ ],
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/a8a138d3-dcea-4520-b269-adbcd0b8df37",
+ "posts": 299,
+ "replies": 298,
+ "terminated": 0,
+ "errors": []
+ }
+ ],
+ "rawNodeCount": 53,
+ "rawFrameCount": 62,
+ "rawIntervalCount": 61
+ }
+ },
+ {
+ "title": "published core / Danmuku 5.3.0 legacy: 300-comment native load and frame/heap observation",
+ "browser": "webkit",
+ "name": "danmuku-load-final-observations",
+ "value": {
+ "destroyed": true,
+ "retainedPlayerRoots": 0,
+ "retainedExternalSettings": 1,
+ "workerCount": 1,
+ "workers": [
+ {
+ "url": "blob:http://127.0.0.1:8084/a8a138d3-dcea-4520-b269-adbcd0b8df37",
+ "posts": 301,
+ "replies": 301,
+ "terminated": 1,
+ "errors": []
+ }
+ ],
+ "loadedCounts": [
+ 0,
+ 300
+ ],
+ "visibleEvents": 51,
+ "heaps": [
+ {
+ "phase": "before-plugin-script",
+ "timestamp": 74,
+ "memory": null
+ },
+ {
+ "phase": "after-300-loaded",
+ "timestamp": 171,
+ "memory": null
+ },
+ {
+ "phase": "paused-after-four-media-seconds",
+ "timestamp": 5617,
+ "memory": null
+ },
+ {
+ "phase": "immediately-after-destroy",
+ "timestamp": 5925,
+ "memory": null
+ }
+ ],
+ "failures": [],
+ "destroyEvents": 1
+ }
+ }
+ ]
+ }
+ ],
+ "priorFailure": {
+ "file": "refactor/.cache/danmuku02-browser-chromium-initial/report.json",
+ "sha256": "411397683f17f97bcef6356ba48ed482ea54017db47ec2b88e981385e47ad8e1",
+ "passed": 7,
+ "failed": 9,
+ "fixtureCorrections": [
+ "FullscreenWeb moves the player into body; inspect actual controlsCenter/emitter ownership.",
+ "Pool nodes may be reused and then hidden by duplicate loops; inspect actual original-node text/use and record extra allocations, not assume final visible identity."
+ ],
+ "unresolvedProductionObservation": "Two simultaneous fixed comments occupied y=10 in the initial strict geometry test, with duplicate native start events and repeated worker IDs. The subsequent 48-case pass does not close the historical race; the strict overlap assertion remains active."
+ },
+ "inputs": [
+ {
+ "path": "playwright.config.js",
+ "algorithm": "sha256-lf",
+ "sha256": "df2384f5626252ec933725867e400ec22eb92bc5e6f04873059b32decf2f2d56"
+ },
+ {
+ "path": "refactor/baselines/danmuku-release.json",
+ "algorithm": "sha256-lf",
+ "sha256": "82a0ab2e87f108cbb1d09ce1b6647cfefc896d974da1cb60b2668fded18a4b2f"
+ },
+ {
+ "path": "test/browser/danmuku-baseline.spec.js",
+ "algorithm": "sha256-lf",
+ "sha256": "6d5d7d7342cab008612e6cdb1fc7b7a41a08f72eca6d602ec484904acfd51381"
+ },
+ {
+ "path": "test/browser/danmuku-load-baseline.spec.js",
+ "algorithm": "sha256-lf",
+ "sha256": "20890b1c4e415a33e82a2ac9e9b10701ca39f1b8dc31d514fcbf85e227646b4d"
+ },
+ {
+ "path": "test/browser/fixtures.js",
+ "algorithm": "sha256-lf",
+ "sha256": "313414b8958eb5b03a2da8595fa0bbf6bfdc01484373898c6cb7179545df175d"
+ },
+ {
+ "path": "test/browser/server.mjs",
+ "algorithm": "sha256-lf",
+ "sha256": "1406fac75eabc83b987e79ec7b5f97a948b27770bfd5f5290d7cb9155196f08f"
+ },
+ {
+ "path": "test/danmuku-failures.test.js",
+ "algorithm": "sha256-lf",
+ "sha256": "1d7a4fc988f75239bc33453fe4334c17ab34420b58ae26a33d7fc7471be8bf8f"
+ },
+ {
+ "path": "test/helpers/danmuku.js",
+ "algorithm": "sha256-lf",
+ "sha256": "4e4420d6fe7198bd91c7a444f3f82740991181c3a1b2cb8b91b1db6fc759046a"
+ }
+ ],
+ "limitations": [
+ "Only actual plugin 5.3.0 main/legacy native browser behavior; controlled source/legacy failures are separate.",
+ "The native core matrix is actual published 5.4.0 and candidate 5.4.1; no inferred continuous support range.",
+ "Load/heap/frame metrics are short-run observations, not a leak proof, performance threshold or long-term stability clearance.",
+ "Historical repeated-loop overlap remains open and can make the strict concurrency regression fail on later runs.",
+ "No real Bilibili service, CSP restriction matrix, physical Safari/mobile, Mask model or npm release readiness acceptance."
+ ],
+ "releaseReady": false,
+ "priorLoadFixtureFailure": {
+ "file": "refactor/.cache/danmuku02-browser-load-initial/report.json",
+ "sha256": "af6d8d0a08702fcd6fdc0c75c3f6fd88900e8f434857db9ba626b7890f883701",
+ "failed": 6,
+ "cause": "Test read non-existent art.paused; corrected to art.template.$video.paused. No production change."
+ },
+ "rawObservationPolicy": "Large layout/event/frame arrays remain in the hash-bound raw Playwright reports. This maintained summary retains counts, selected geometry, input identities and sample metrics."
+}
diff --git a/refactor/baselines/danmuku-failures-validation.json b/refactor/baselines/danmuku-failures-validation.json
new file mode 100644
index 000000000..686c966bc
--- /dev/null
+++ b/refactor/baselines/danmuku-failures-validation.json
@@ -0,0 +1,301 @@
+{
+ "schemaVersion": 1,
+ "task": "PKG-DANMUKU-02",
+ "scope": "Controlled historical failure subtask only; parent browser/clock/track/load baseline remains required",
+ "wholeTaskComplete": false,
+ "capturedAt": "2026-09-13T09:46:29.424Z",
+ "sourceCommit": "b0cfbfe3a09a84a295a257e45e4577847588d1cb",
+ "environment": {
+ "node": "v24.21.0",
+ "platform": "win32",
+ "architecture": "x64",
+ "executable": "refactor/.cache/toolchains/node-v24.21.0-win-x64/node.exe"
+ },
+ "inputs": {
+ "test/helpers/danmuku.js": {
+ "sha256LF": "4e4420d6fe7198bd91c7a444f3f82740991181c3a1b2cb8b91b1db6fc759046a"
+ },
+ "test/danmuku-failures.test.js": {
+ "sha256LF": "1d7a4fc988f75239bc33453fe4334c17ab34420b58ae26a33d7fc7471be8bf8f"
+ },
+ "refactor/baselines/danmuku-release.json": {
+ "sha256LF": "82a0ab2e87f108cbb1d09ce1b6647cfefc896d974da1cb60b2668fded18a4b2f"
+ },
+ "refactor/scripts/danmuku-contract.mjs": {
+ "sha256LF": "8ca0996605cfbcd732030708bd67ecedff5018d2b7436fa7247de7bbb39d7573"
+ },
+ "refactor/changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md": {
+ "sha256LF": "e4373ee55bcd1f96987d6a647663bef9d646e7caa30ba9abf5416202b3873169"
+ }
+ },
+ "tests": {
+ "command": "node --test test/danmuku-failures.test.js",
+ "exitCode": 0,
+ "passed": 42,
+ "failed": 0,
+ "skipped": 0,
+ "log": "refactor/.cache/danmuku02-controlled.log",
+ "logSha256": "5e17966b1cd19f045b1d74d2ac065271ddbaeec44b7c5b533d625f3bd5b05670",
+ "implementationCount": 3,
+ "scenariosPerImplementation": 14
+ },
+ "lint": {
+ "command": "node node_modules/eslint/bin/eslint.js test/helpers/danmuku.js test/danmuku-failures.test.js",
+ "exitCode": 0,
+ "log": "refactor/.cache/danmuku02-controlled-lint.log",
+ "logSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "execution": {
+ "source": "Immutable Git modules bundled only in memory; Worker import transport controlled, parser actual generated Blob executed in a VM",
+ "main": "7c8749288711ef97e80b6f9f00981170aa73f62a8ae25d6d663749a4aef62a8f",
+ "legacy": "62db96685c48b8573f0480cead6ac934b1d0382b9fdd0046d17d15d2549c56dc",
+ "validator": "Actual frozen published Artplayer 5.4.0 static validator",
+ "domAndClock": "Controlled, not a browser/layout/playback claim"
+ },
+ "isolatedProcesses": {
+ "directory": "refactor/.cache/danmuku02-controlled/run-9m9V8f",
+ "count": 15,
+ "rejectionProcesses": 9,
+ "rejectionFlags": "--unhandled-rejections=strict",
+ "observation": "uncaughtExceptionMonitor only; errors remain fatal with exit 1 and origin unhandledRejection",
+ "heatmapProcesses": 6,
+ "vmTimeoutMilliseconds": 100,
+ "processTimeoutMilliseconds": 3000,
+ "timeoutScope": "Expected VM timeout only for default sampling at width99; explicit sampling1 completes; outer process timeout fails the test",
+ "reports": [
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/frozen-source-beforeVisible-reject.json",
+ "sha256": "41748d818ad4b20b916140bec42b58687943dec7f1f7faa42e01ef7d57f76b42",
+ "implementation": "frozen-source",
+ "scenario": "beforeVisible-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"beforeVisible-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/frozen-source-fetch-reject.json",
+ "sha256": "8f05b57fe32605ff3233394304d7851760ed8335e7cb2cd4314e3399e54f6efa",
+ "implementation": "frozen-source",
+ "scenario": "fetch-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"fetch-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/frozen-source-heatmap-positive.json",
+ "sha256": "faf1edb2e96a800a7605db93e1266d3cb7304672609843f061ee09484251e841",
+ "implementation": "frozen-source",
+ "scenario": "heatmap-positive",
+ "status": 0,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "heatmap-ready\n{\"stage\":\"complete\"}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/frozen-source-heatmap-zero.json",
+ "sha256": "5d3f166e103cccd2e6ba6b81e6cbcf95454f11c2f15427b41763a73ff4099dd8",
+ "implementation": "frozen-source",
+ "scenario": "heatmap-zero",
+ "status": 0,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "heatmap-ready\n{\"stage\":\"bounded-timeout\",\"code\":\"ERR_SCRIPT_EXECUTION_TIMEOUT\"}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/frozen-source-text-reject.json",
+ "sha256": "261c3206b5180ff0e093b21a842e1bcd1dbcbccea637574c2a15416a75a706ba",
+ "implementation": "frozen-source",
+ "scenario": "text-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"text-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-legacy-beforeVisible-reject.json",
+ "sha256": "6f2c59251bcb37d029eac4e1edd68fb0bedd6499781f045a384e40e0bf2810ef",
+ "implementation": "published-5.3.0-legacy",
+ "scenario": "beforeVisible-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"beforeVisible-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-legacy-fetch-reject.json",
+ "sha256": "76e39a61d50e7a2722ae8fef16672764b91e28d6b8496b7bbd13cdf908418e13",
+ "implementation": "published-5.3.0-legacy",
+ "scenario": "fetch-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"fetch-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-legacy-heatmap-positive.json",
+ "sha256": "3b672dbb6af585b5f260ab3cf2ff294cf52026de192f64dcaa4202b4ae914dd3",
+ "implementation": "published-5.3.0-legacy",
+ "scenario": "heatmap-positive",
+ "status": 0,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "heatmap-ready\n{\"stage\":\"complete\"}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-legacy-heatmap-zero.json",
+ "sha256": "817e3f4f61eb3c5f19096e9a6cc7fe01d5fb4be6756e52ab305de2e51fef7485",
+ "implementation": "published-5.3.0-legacy",
+ "scenario": "heatmap-zero",
+ "status": 0,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "heatmap-ready\n{\"stage\":\"bounded-timeout\",\"code\":\"ERR_SCRIPT_EXECUTION_TIMEOUT\"}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-legacy-text-reject.json",
+ "sha256": "63c38503aad6fc9c0eafecc59e906c441827333d7fa102cae370511aadcd077b",
+ "implementation": "published-5.3.0-legacy",
+ "scenario": "text-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"text-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-main-beforeVisible-reject.json",
+ "sha256": "338d99844c2e05ac121beef206550a0f590c11e6b4caf3f702cce62c68d3c79d",
+ "implementation": "published-5.3.0-main",
+ "scenario": "beforeVisible-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"beforeVisible-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-main-fetch-reject.json",
+ "sha256": "97428c512f7eb613a1102532170b92a395f6845b83e03d4242084ffd69617a45",
+ "implementation": "published-5.3.0-main",
+ "scenario": "fetch-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"fetch-original\",\"settled\":false,\"errors\":0}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-main-heatmap-positive.json",
+ "sha256": "a44cbec41648ea681eb4745561e704596aa0c2e4bc4c4024084701434a0a0fe1",
+ "implementation": "published-5.3.0-main",
+ "scenario": "heatmap-positive",
+ "status": 0,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "heatmap-ready\n{\"stage\":\"complete\"}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-main-heatmap-zero.json",
+ "sha256": "5677249e3b1d26932b27ff8f2a8749c1c6e2d1e409ff090e6d3722ff0660255a",
+ "implementation": "published-5.3.0-main",
+ "scenario": "heatmap-zero",
+ "status": 0,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "heatmap-ready\n{\"stage\":\"bounded-timeout\",\"code\":\"ERR_SCRIPT_EXECUTION_TIMEOUT\"}\n"
+ },
+ {
+ "file": "refactor/.cache/danmuku02-controlled/run-9m9V8f/published-5.3.0-main-text-reject.json",
+ "sha256": "f56b3c1f0d07f269f92a23d7751fc18c6b0b881c5458ddf98a33cdfe1c2c5401",
+ "implementation": "published-5.3.0-main",
+ "scenario": "text-reject",
+ "status": 1,
+ "signal": null,
+ "errorCode": null,
+ "stdout": "{\"stage\":\"fatal\",\"origin\":\"unhandledRejection\",\"error\":\"text-original\",\"settled\":false,\"errors\":0}\n"
+ }
+ ]
+ },
+ "reproduced": [
+ {
+ "id": "DMK-FAIL-01",
+ "issue": "fetch rejection leaves public load pending and skips error event",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-02",
+ "issue": "response.text rejection leaves public load pending and skips error event",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-03",
+ "issue": "beforeVisible rejection becomes an ignored animation callback rejection",
+ "next": "PKG-DANMUKU-04"
+ },
+ {
+ "id": "DMK-FAIL-04",
+ "issue": "width99 default sampling0 does not advance heatmap loop; positive control succeeds",
+ "next": "PKG-DANMUKU-05"
+ },
+ {
+ "id": "DMK-FAIL-05",
+ "issue": "parser Worker error has no handler/settlement or destruction cleanup",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-06",
+ "issue": "actual generated parser ignores empty XML and load remains pending",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-07",
+ "issue": "successful parser does not revoke its Blob URL",
+ "next": "PKG-DANMUKU-05"
+ },
+ {
+ "id": "DMK-FAIL-08",
+ "issue": "parser completion can populate queue and emit loaded after destroy",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-09",
+ "issue": "old replacement load overwrites the newer completed queue",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-10",
+ "issue": "function input resolves and emits loaded after destroy",
+ "next": "PKG-DANMUKU-03"
+ },
+ {
+ "id": "DMK-FAIL-11",
+ "issue": "beforeVisible continuation allocates DOM and attempts terminated-worker post after destroy",
+ "next": "PKG-DANMUKU-04"
+ },
+ {
+ "id": "DMK-FAIL-12",
+ "issue": "already delivered Worker response continuation changes state after destroy",
+ "next": "PKG-DANMUKU-04"
+ },
+ {
+ "id": "DMK-FAIL-13",
+ "issue": "play/playing duplicate loops overwrite response handlers and strand an earlier Promise",
+ "next": "PKG-DANMUKU-04"
+ },
+ {
+ "id": "DMK-FAIL-14",
+ "issue": "pause/destroy leaves a previously scheduled duplicate frame",
+ "next": "PKG-DANMUKU-04"
+ }
+ ],
+ "limitations": [
+ "Historical defect observations must not become candidate normal expectations",
+ "No native browser or server started",
+ "No actual Bilibili/media request, native Worker/CSP, video clock or layout tested",
+ "Late Worker continuation test delivers before terminate and resumes after it; no post-terminate native event guarantee assumed",
+ "Real track/clock/load/performance and visual/browser evidence required for whole task",
+ "No production source/declaration/artifact edits and no publication readiness conclusion"
+ ],
+ "productionChanged": false,
+ "browserValidated": false,
+ "releaseReady": false
+}
diff --git a/refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md b/refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md
new file mode 100644
index 000000000..6c15e7b49
--- /dev/null
+++ b/refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md
@@ -0,0 +1,77 @@
+# PKG-DANMUKU-02 浏览器与负载基线
+
+本记录整合先前的[受控失败子项](2026-09-13-PKG-DANMUKU-02-controlled-failures.md):
+42项旧失败观察及父代理复跑通过,新增真实浏览器的行为、布局和负载记录。
+这里完成的是重构前基线,生产代码、类型及dist均未修改;没有把已复现缺陷当作
+候选必须保持的行为,也不代表弹幕能力或整包已经达到发布条件。
+
+## 实际浏览器范围
+
+使用真实npm Danmuku 5.3.0 main/legacy归档字节,每次核验SHA;真实已发布核心
+5.4.0及当前候选5.4.1。Chromium 153.0.8010.12、Firefox 155.0、Windows WebKit26.6,
+后者不是Safari真机。页面运行本地八秒视频、原生Blob Worker、实际DOM/设置操作。
+Worker子类只记录创建/消息/终止,不替换回复、算法、RAF或媒体时钟。外网被拒绝,
+同源Blob可用。共2核心×2插件格式×4场景×3引擎,48项通过、无跳过或未处理页面错误。
+
+- 三种弹幕模式、同步filter与异步beforeVisible,真实居中/边界布局与Worker工作。
+- 六条同时固定弹幕的顺序、严格非重叠几何与暂停状态。
+- 原生play/pause/seek、两倍播放及四秒CSS运动时长,reset节点复用和load替换/追加。
+- 实际设置面板、异步发送去重和锁定、外挂输入框在网页全屏时移入并回位,双实例
+ 销毁隔离;旧版外部设置DOM残留如实记录为后续清理问题。
+
+截图、原始几何、事件/Worker记录和运行输入在
+[机器报告](../baselines/danmuku-browser-validation.json)引用的不可变归档中。
+
+## 初轮失败及处置
+
+首轮Chromium为7通过、9失败,原spec、报告、截图和trace完整保留。
+八项来自两个错误测试假设:网页全屏会把播放器移到body,原容器选择器失效;
+旧版可能复用一个节点后又生成额外节点,不能要求最终可见节点必须是原节点。
+测试改为验证实际控件引用归属,以及原节点确实被再次使用,同时记录额外/隐藏节点。
+没有修改生产行为来满足测试。
+
+另一次是**实际轨道重叠**:dense0和dense1的y都为10,宽70、高22.5,并观察到
+两次start及重复Worker消息id。截图也显示两条文字重叠。DANMUKU-TRACK-01保持开放,
+与已受控复现的双RAF/请求覆盖问题一同由04/05/07修复验收。严格非重叠断言仍保留;
+后次48通过只能说明那一轮没有触发,不能说明竞态已修复,后续运行仍可能再次报红。
+
+负载首轮六项也保留:读取不存在的art.paused导致等待失败。改为原生
+art.template.$video.paused后重跑通过;这属于测试错误,不记录成生产缺陷。
+
+## 300条弹幕负载观察
+
+两种实际插件格式、已发布核心、三个引擎,共六个配置;各载入300个独立ID混合模式
+弹幕,确认完整队列、真实播放到至少四秒、可见输出、Worker工作和销毁。保存原始
+帧时间戳/间隔、实际DOM池与几何、可见ID及各阶段performance.memory。独立RAF
+观察者不更改插件时钟,但其自身也有测量开销。
+
+初次三worker运行全部通过。WebKit帧间隔较长,因此再以单worker串行复测六个配置,
+减少并行测试争用;两份报告均保留,不能混为相同条件或增加六种功能覆盖。
+初次并行样本的可见唯一ID为51–129,DOM节点53–131;300载入成功不代表300条都
+在短窗口显示。真实排队/轨道与显示窗口仍受当前调度器影响。
+
+串行六项也通过。该主机上的帧间隔中位数:Chromium两格式约16.7ms,Firefox约
+16.66ms,Windows WebKit为78/80ms;WebKit的p95为101/104ms。其样本可见51个ID、
+DOM53个节点;记录保留这个差异,不因测试通过就称流畅,也不外推为Safari设备结论。
+实际暂停时间约4.61–5.06秒,原始时间戳保留,后续比较须使用相同输入/观察方式。
+
+Chromium提供堆读数,Firefox/WebKit为空;没有强制GC、跨引擎堆值比较、短样本
+泄漏结论或凭空设置性能阈值。串行结果是后续同条件对照,07仍需更长负载、重复
+装卸和资源稳定性验收。真实设备/CSP、Bilibili网络、Mask模型和最终新旧组合仍归后续。
+
+## 重放、提交与下一步
+
+```sh
+yarn test:danmuku
+yarn exec playwright test test/browser/danmuku-baseline.spec.js --workers=3
+yarn exec playwright test test/browser/danmuku-load-baseline.spec.js --workers=1
+```
+
+固定Node24.21.0/Yarn1.22.22。受控测试接入test:unit,契约由test:baseline收集,
+浏览器文件由既有Playwright发现。没有安装依赖、修改锁文件或使用全局吞错处理。
+lint及任务/风险/契约索引校验随本次提交;本任务独立commit,无推送、标签或npm发布。
+
+03接着整理加载、解析与config,保留数组同步初始化顺序、内部链式返回身份、
+追加/替换区别、逐条await emit以及live option回调接收者;优先修复网络拒绝悬挂、
+空XML、解析Worker/Blob释放、过期替换、time0和函数配置更新。轨道Worker/RAF、
+设置/heatmap和完整TS公开类型分别由04/05/06接续。
diff --git a/refactor/changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md b/refactor/changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md
new file mode 100644
index 000000000..6c0d28174
--- /dev/null
+++ b/refactor/changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md
@@ -0,0 +1,88 @@
+# PKG-DANMUKU-02 受控失败基线子项
+
+本记录只交付 02 的受控测试部分,**不代表 PKG-DANMUKU-02 整体完成**。
+真实浏览器的时钟、轨道、密集/稀疏负载和可视结果由主任务另行验收。
+没有修改生产源码、声明、构建产物、共享元数据或 root scripts;没有启动服务或浏览器。
+
+## 冻结输入与测试方法
+
+复用 [01 发布基线](../baselines/danmuku-release.json) 与
+[不可变校验器](../scripts/danmuku-contract.mjs),执行三个实际实现:
+
+- Git `b0cfbfe3a09a84a295a257e45e4577847588d1cb` 的冻结源码,只在内存打包。
+- 真正 npm artplayer-plugin-danmuku@5.3.0 main 字节。
+- 同一发布版的 legacy 字节。
+
+[test/helpers/danmuku.js](../../test/helpers/danmuku.js) 提供受控 DOM、时钟、rAF、
+timer、fetch 和 Worker transport;配置验证使用实际发布核心 5.4.0 的 validator。
+[`danmukuHistorical()`](../../test/helpers/danmuku.js) 返回 `{name,code,coreCode}` 数组;
+`danmukuEnvironment(implementation, settings)` 控制请求、帧、回应和资源观察。
+其捕获的 Bilibili Blob 内容在独立 VM 中实际执行,XML 的空输入和正常解析不是手写替身。
+框架只模拟传输/布局,不把它说成原生 Worker 或真实 DOM/播放验证。
+
+[test/danmuku-failures.test.js](../../test/danmuku-failures.test.js) 每个实现有 14 个场景,
+共 42 项历史观察全部通过。测试中的“失败”指真实历史缺陷;这些断言不能用于要求候选
+保留缺陷。候选修复必须在后续任务中另加正确行为断言。
+
+## 已复现观察
+
+| 测试场景 | 精确结果 | 后续责任 |
+| --- | --- | --- |
+| DMK-FAIL-01 fetch rejection | fetch 原拒绝成为未处理拒绝;公开 load 不 settle,未发 :error | 03 |
+| DMK-FAIL-02 text rejection | response.text 原拒绝同样脱离公开 load/error 路径 | 03 |
+| DMK-FAIL-03 beforeVisible rejection | 异步 rAF 回调 Promise 被忽略,原错成为未处理拒绝;无 :error | 04 |
+| DMK-FAIL-04 narrow heatmap | offsetWidth=99 的默认 sampling=0 耗尽 VM 时间预算;显式 sampling=1 正常完成 | 05 |
+| DMK-FAIL-05 parser worker error | 未装错误处理;错误投递后 load 不 settle,destroy 也未终止这个 parser worker | 03/05 |
+| DMK-FAIL-06 empty XML | 实际 Blob parser 忽略空 XML 而不回消息;load 一直等待,parser worker 活着 | 03 |
+| DMK-FAIL-07 Blob URL | 真实 parser 成功后 worker terminate,但对应 URL 没有 revoke | 03/05 |
+| DMK-FAIL-08 late parser | destroy 时 parser worker 未终止;之后实际 parser 完成仍向 queue 添加并发 :loaded | 03/05 |
+| DMK-FAIL-09 out-of-order load | 两次无参数替换 load,第二次先完成,第一次晚完成将新队列覆盖为旧数据 | 03 |
+| DMK-FAIL-10 late function input | destroy 后异步输入继续入队并触发 :loaded | 03 |
+| DMK-FAIL-11 late beforeVisible | destroy 后用户 Promise 成功继续创建弹幕 DOM,尝试向已 terminate worker postMessage | 04/05 |
+| DMK-FAIL-12 late continuation | worker 消息在 destroy 前送达,await continuation 在 destroy 后将 wait 改成 ready | 04/05 |
+| DMK-FAIL-13 competing loops | play + playing 建两条循环;不同 id 的第二请求覆盖 onmessage,第一请求永不 settle | 04 |
+| DMK-FAIL-14 remaining frame | 双 start 后 pause/destroy 只取消最后一帧,另一已排定帧仍存在 | 04 |
+
+DMK-FAIL-12 不依赖浏览器在 terminate 后再派发 Worker 消息:测试先完成消息投递,
+同步 destroy,再让 Promise continuation 恢复;不会夸大原生 Worker 终止语义。
+DMK-FAIL-11 的证据是 DOM 分配与 postMessage 调用尝试,不宣称原生 worker 已终止后
+仍执行代码或真的显示弹幕。测试也确认 late worker 分支没有发新的 :visible。
+
+03 需要明确异步取消如何 settle 公开 load Promise、如何保持原错误通知/返回关系;
+04/05 需要单一帧循环、每请求关联、generation/销毁防护和 parser/Blob/设置的资源所有权。
+这只是后续设计边界,不在本任务提前实现或改变接口。
+
+## 进程隔离与可重复证据
+
+三类未处理拒绝各实现分别运行独立 Node 子进程,共 9 次,固定
+`--unhandled-rejections=strict`。只用 `uncaughtExceptionMonitor` 记录原错误、origin、
+公开 Promise settled 标记和 :error 数量,不注册吞掉错误的 unhandledRejection 或
+uncaughtException 处理器。实际进程 exit 1,monitor 记录 origin=unhandledRejection;
+不是把未处理拒绝隐藏到通过结果中。
+
+热力图另有 6 次独立进程:三个 sampling=0,三个 sampling=1 对照。内部 VM 100ms
+执行预算明确产生 `ERR_SCRIPT_EXECUTION_TIMEOUT`;对照完成且没有超时。外层进程
+还有 3000ms 最终保护,外层超时本身会使测试失败,不能被算作正常观察。
+整个重放不会把零步长循环交给真实浏览器页面。
+
+每次测试写独立 `refactor/.cache/danmuku02-controlled/run-*/`,保留 15 个子进程
+stdout/stderr/status JSON。最终目录、文件 SHA、脚本 SHA、命令与汇总见
+[danmuku-failures-validation.json](../baselines/danmuku-failures-validation.json)。
+
+```text
+node --test test/danmuku-failures.test.js
+node node_modules/eslint/bin/eslint.js test/helpers/danmuku.js test/danmuku-failures.test.js
+```
+
+实际 Node 24.21.0 / Windows,42 passed / 0 failed / 0 skipped;专属 lint 通过。
+没有新依赖、没有修改 Yarn lock。缺少归档缓存时,01 verifier 只向冻结 npm URL 请求,
+测试本身不会请求 Bilibili、媒体或任何识别服务。
+
+## 未完成范围与回退
+
+尚未覆盖原生 Worker/CSP、真实媒体时钟和 seek/倍率、实际测量的防重叠/轨道顺序、
+长时间负载、内存、设置触摸/旋转、mask/fullscreen/PiP/外部挂载、浏览器错误表现。
+这些内容仍是 PKG-DANMUKU-02 及后续组合验收的门槛。本报告不标包重构完成或 npm 可发布。
+
+此批只有历史测试与证据,可独立撤销,不影响发布包。整合代理将本子项与 02 的其他
+必要证据一起管理状态;不要仅依据这 42 个观察把整个任务标为 done。
diff --git a/refactor/plan.md b/refactor/plan.md
index 71e1b59dc..b070bad79 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 228 项,范围 22 个包及工作区/示例。
-状态:todo 73 / doing 15 / blocked 0 / done 140 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
+状态:todo 72 / doing 15 / blocked 0 / done 141 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -310,8 +310,8 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| PKG-DANMUKU-01 | artplayer-plugin-danmuku
核对弹幕全部公开契约 | BASE-05 | emit/load/config/hide/show/reset/mount、option/isHide/isStop/icons 与事件清单 | 源码、声明、发布包、Bilibili 输入和设置入口对照 | H | done |
-| PKG-DANMUKU-02 | artplayer-plugin-danmuku
建立弹幕算法与浏览器基线 | PKG-DANMUKU-01, ENG-05, ENG-08 | 密集/稀疏弹幕、过滤/异步输入、seek/倍率/重载测试 | 轨道选择、发射顺序、事件、内存和可视结果可重跑 | H | doing |
-| PKG-DANMUKU-03 | artplayer-plugin-danmuku
整理加载、解析与配置 | PKG-DANMUKU-02, CORE-08 | bilibili/input/parser/config 的分层与取消 | 旧格式、回调、过滤、追加/替换语义保持 | M | todo |
+| PKG-DANMUKU-02 | artplayer-plugin-danmuku
建立弹幕算法与浏览器基线 | PKG-DANMUKU-01, ENG-05, ENG-08 | 密集/稀疏弹幕、过滤/异步输入、seek/倍率/重载测试 | 轨道选择、发射顺序、事件、内存和可视结果可重跑 | H | done |
+| PKG-DANMUKU-03 | artplayer-plugin-danmuku
整理加载、解析与配置 | PKG-DANMUKU-02, CORE-08 | bilibili/input/parser/config 的分层与取消 | 旧格式、回调、过滤、追加/替换语义保持 | M | doing |
| PKG-DANMUKU-04 | artplayer-plugin-danmuku
整理时钟、队列与轨道调度 | PKG-DANMUKU-03, CORE-10 | danmuku 调度器和确定性时钟测试 | seek/暂停/倍率/长时间运行无顺序和碰撞回归 | H | todo |
| PKG-DANMUKU-05 | artplayer-plugin-danmuku
整理 DOM 渲染、设置、热力图与 worker | PKG-DANMUKU-04, CORE-14, CORE-18 | renderer/setting/heatmap/worker 职责及资源归属 | mount/icons/设置和 worker 协议保持,销毁无后台工作 | H | todo |
| PKG-DANMUKU-06 | artplayer-plugin-danmuku
迁移 TS 与公开声明 | PKG-DANMUKU-05, ENG-04, ENG-06 | 自有模块、worker 消息、option/item/result 类型 | 旧参数、扩展字段和事件消费通过,vendored 边界清楚 | H | todo |
@@ -554,6 +554,7 @@
- PKG-DPIP-04: [记录](changes/2026-09-12-PKG-DPIP-04-types.md) [记录](baselines/dpip-types-validation.json) [记录](dpip-validation.md)
- PKG-DPIP-05: [记录](changes/2026-09-12-PKG-DPIP-05-native-checkpoint.md) [记录](baselines/dpip-native-validation.json)
- PKG-DANMUKU-01: [记录](baselines/danmuku-release.json) [记录](baselines/danmuku-contract.md) [记录](baselines/danmuku-contract-validation.json) [记录](changes/2026-09-13-PKG-DANMUKU-01-contracts.md)
+- PKG-DANMUKU-02: [记录](changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md) [记录](baselines/danmuku-failures-validation.json) [记录](changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md) [记录](baselines/danmuku-browser-validation.json)
- PKG-CANVAS-01: [记录](changes/2026-09-12-PKG-CANVAS-01-contract.md) [记录](baselines/canvas-contract.md) [记录](baselines/canvas-release.json) [记录](canvas-validation.md) [记录](baselines/canvas-contract-validation.json)
- PKG-CANVAS-02: [记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) [记录](baselines/canvas-behavior-validation.json) [记录](canvas-validation.md)
- PKG-CANVAS-03: [记录](changes/2026-09-12-PKG-CANVAS-03-lifecycle.md) [记录](baselines/canvas-lifecycle-validation.json) [记录](canvas-validation.md)
diff --git a/refactor/progress.md b/refactor/progress.md
index 261b9e994..748f83e1d 100644
--- a/refactor/progress.md
+++ b/refactor/progress.md
@@ -1,5 +1,16 @@
# 进度与证据
+## PKG-DANMUKU-02 历史错误和真实浏览器基线完成
+
+42项受控旧缺陷、48项原生Worker/视频/布局/设置及6项300弹幕负载观察通过;所有
+初轮失败留档。纠正两个测试假设,保留严格非重叠断言;首次测到的实际轨道重叠
+仍为DANMUKU-TRACK-01开放问题,后次48通过不能替代修复。短时heap/帧间隔不是
+长期内存或性能验收。见[变更](changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md)与[机器证据](baselines/danmuku-browser-validation.json)。
+根test:unit接入受控测试,浏览器文件由已有Playwright流程收集。
+228项:141 done、15 doing、72 todo。03开始输入/解析/config
+实施;04调度/05DOM和07负载稳定性继续。无推送或发布。
+
+
## PKG-MASK-02 生命周期与错误基线完成
54项冻结历史测试及父代理复核通过,覆盖源码和两旧版本三格式的关键生命周期;
diff --git a/refactor/risk-table.md b/refactor/risk-table.md
index 880d884e2..1b9e9952a 100644
--- a/refactor/risk-table.md
+++ b/refactor/risk-table.md
@@ -232,6 +232,7 @@
| DANMUKU-CONFIG-01 | open / 已复现 | Danmuku JSON configuration comparison ignores a replacement filter function | PKG-DANMUKU-02, PKG-DANMUKU-03 |
| DANMUKU-CLEANUP-01 | open / 已复现 | Danmuku removes a different resize callback and retains the settings lock timer on destroy | PKG-DANMUKU-02, PKG-DANMUKU-05 |
| DANMUKU-TYPE-02 | open / 源码/产物事实 | Danmuku id, icons, Slider steps, heatmap points and partial configuration drift from declarations | PKG-DANMUKU-05, PKG-DANMUKU-06 |
-| DANMUKU-ASYNC-01 | open / 源码/产物事实 | Danmuku asynchronous input, worker replies and pending visibility have unverified failure and cancellation paths | PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05 |
-| DANMUKU-HEATMAP-01 | open / 源码/产物事实 | Danmuku heatmap sampling can become zero and points/instance ownership need browser validation | PKG-DANMUKU-02, PKG-DANMUKU-05 |
+| DANMUKU-ASYNC-01 | open / 已复现 | Danmuku asynchronous input, worker replies and pending visibility have unverified failure and cancellation paths | PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05 |
+| DANMUKU-HEATMAP-01 | open / 已复现 | Danmuku heatmap sampling can become zero and points/instance ownership need browser validation | PKG-DANMUKU-02, PKG-DANMUKU-05 |
| DANMUKU-SOURCE-01 | open / 已复现 | Danmuku registry Git associations differ from releases and static icon provenance remains incomplete | PKG-DANMUKU-09 |
+| DANMUKU-TRACK-01 | open / 已复现 | Native simultaneous fixed Danmuku comments can overlap with duplicate start loops and repeated Worker IDs | PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-07 |
diff --git a/refactor/risks.json b/refactor/risks.json
index c317b2d61..5379ed391 100644
--- a/refactor/risks.json
+++ b/refactor/risks.json
@@ -5082,7 +5082,9 @@
"evidence": [
"refactor/baselines/danmuku-contract.md",
"refactor/baselines/danmuku-contract-validation.json",
- "refactor/baselines/danmuku-release.json"
+ "refactor/baselines/danmuku-release.json",
+ "refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md",
+ "refactor/baselines/danmuku-browser-validation.json"
],
"compatibleResolution": "Track and release exact subscription and timer ownership across settings, renderer and instances.",
"closureCriteria": "Repeated lifecycle and pending settings actions leave no owned subscriptions, timers or late DOM writes."
@@ -5107,7 +5109,7 @@
{
"id": "DANMUKU-ASYNC-01",
"title": "Danmuku asynchronous input, worker replies and pending visibility have unverified failure and cancellation paths",
- "confirmation": "source-observed",
+ "confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-DANMUKU-02",
@@ -5118,7 +5120,11 @@
"evidence": [
"refactor/baselines/danmuku-contract.md",
"refactor/baselines/danmuku-contract-validation.json",
- "refactor/baselines/danmuku-release.json"
+ "refactor/baselines/danmuku-release.json",
+ "refactor/baselines/danmuku-failures-validation.json",
+ "test/danmuku-failures.test.js",
+ "refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md",
+ "refactor/baselines/danmuku-browser-validation.json"
],
"compatibleResolution": "Reproduce load ordering, Bilibili rejection and late worker/visibility paths before defining operation ownership.",
"closureCriteria": "Failures settle, obsolete results cannot alter new queues or destroyed instances, and worker resources are released with browser evidence."
@@ -5126,7 +5132,7 @@
{
"id": "DANMUKU-HEATMAP-01",
"title": "Danmuku heatmap sampling can become zero and points/instance ownership need browser validation",
- "confirmation": "source-observed",
+ "confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-DANMUKU-02",
@@ -5135,7 +5141,11 @@
"evidence": [
"refactor/baselines/danmuku-contract.md",
"refactor/baselines/danmuku-contract-validation.json",
- "refactor/baselines/danmuku-release.json"
+ "refactor/baselines/danmuku-release.json",
+ "refactor/baselines/danmuku-failures-validation.json",
+ "test/danmuku-failures.test.js",
+ "refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md",
+ "refactor/baselines/danmuku-browser-validation.json"
],
"compatibleResolution": "Use isolated timeout-protected reproduction for narrow dimensions, then define finite sampling and per-instance rendering.",
"closureCriteria": "Narrow widths terminate, points/config behavior is explicit and multiple SVG/control instances remain independent."
@@ -5155,6 +5165,25 @@
],
"compatibleResolution": "Use actual archive identity, preserve missing historical Git information and audit icon source/notice obligations.",
"closureCriteria": "Candidate tarball paths, complete module forms and icon/source provenance are explicitly reviewed; unresolved history is not invented."
+ },
+ {
+ "id": "DANMUKU-TRACK-01",
+ "title": "Native simultaneous fixed Danmuku comments can overlap with duplicate start loops and repeated Worker IDs",
+ "confirmation": "reproduced",
+ "status": "open",
+ "owners": [
+ "PKG-DANMUKU-04",
+ "PKG-DANMUKU-05",
+ "PKG-DANMUKU-07"
+ ],
+ "evidence": [
+ "refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md",
+ "refactor/baselines/danmuku-browser-validation.json",
+ "test/browser/danmuku-baseline.spec.js",
+ "test/danmuku-failures.test.js"
+ ],
+ "compatibleResolution": "Enforce one owned scheduler loop and request correlation before claiming simultaneous native anti-overlap stability; keep strict track geometry tests.",
+ "closureCriteria": "Deterministic duplicate-loop/worker regressions and repeated candidate native simultaneous-load geometry pass without extra nodes or stale state; a later passing old snapshot alone does not close the race."
}
]
}
diff --git a/refactor/tasks.json b/refactor/tasks.json
index 9e9ec450d..ad31220c4 100644
--- a/refactor/tasks.json
+++ b/refactor/tasks.json
@@ -3278,11 +3278,16 @@
"ENG-05",
"ENG-08"
],
- "status": "doing",
+ "status": "done",
"risk": "H",
"deliverable": "密集/稀疏弹幕、过滤/异步输入、seek/倍率/重载测试",
"acceptance": "轨道选择、发射顺序、事件、内存和可视结果可重跑",
- "evidence": []
+ "evidence": [
+ "changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md",
+ "baselines/danmuku-failures-validation.json",
+ "changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md",
+ "baselines/danmuku-browser-validation.json"
+ ]
},
{
"id": "PKG-DANMUKU-03",
@@ -3295,7 +3300,7 @@
"PKG-DANMUKU-02",
"CORE-08"
],
- "status": "todo",
+ "status": "doing",
"risk": "M",
"deliverable": "bilibili/input/parser/config 的分层与取消",
"acceptance": "旧格式、回调、过滤、追加/替换语义保持",
diff --git a/test/browser/danmuku-baseline.spec.js b/test/browser/danmuku-baseline.spec.js
new file mode 100644
index 000000000..7976fbe5e
--- /dev/null
+++ b/test/browser/danmuku-baseline.spec.js
@@ -0,0 +1,337 @@
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
+import { expect, test } from './fixtures.js'
+
+const baseline = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/danmuku-release.json', import.meta.url), 'utf8'))
+const release = baseline.release
+assert.equal(release.version, '5.3.0')
+const implementations = new Map()
+
+test.beforeAll(async () => {
+ const archive = await ensureArchive(release)
+ for (const format of ['main', 'legacy']) {
+ const member = `package/${release.manifest[format].replace(/^\.\//u, '')}`
+ const bytes = readMember(archive, member)
+ assert.equal(hash(bytes), release.files[member], `Published Danmuku ${format} bytes drifted`)
+ implementations.set(format, { member, sha256: hash(bytes), code: bytes.toString() })
+ }
+})
+
+async function setup(page, core, format, scenario, testInfo) {
+ const external = []
+ const origin = new URL(testInfo.project.use.baseURL).origin
+ await page.route('**/*', (route) => {
+ const url = new URL(route.request().url())
+ if (url.origin !== origin) {
+ external.push(url.href)
+ return route.abort()
+ }
+ // Same-origin blob URLs are actual inline Worker/media resources, not external requests.
+ return route.continue()
+ })
+ await page.goto(`/test/player.html?core=${core}`)
+ await page.evaluate(() => {
+ window.danmukuEvidence = { workers: [], events: [], gates: [], sends: [], failures: [] }
+ const NativeWorker = window.Worker
+ window.Worker = class ObservedNativeWorker extends NativeWorker {
+ constructor(url, options) {
+ super(url, options)
+ this.evidence = { url: String(url), replies: [], terminated: 0, errors: [] }
+ window.danmukuEvidence.workers.push(this.evidence)
+ this.addEventListener('message', ({ data }) => this.evidence.replies.push({ id: data.id, result: data.result ?? null }))
+ this.addEventListener('error', event => this.evidence.errors.push(event.message))
+ }
+
+ terminate() {
+ this.evidence.terminated++
+ return super.terminate()
+ }
+ }
+ window.danmukuPlayers = []
+ window.danmukuPlugins = []
+ window.danmukuNodes = []
+ window.danmukuVisibility = new Promise((resolve) => {
+ window.releaseDanmukuVisibility = resolve
+ })
+ window.danmukuSending = new Promise((resolve) => {
+ window.releaseDanmukuSending = resolve
+ })
+ })
+ const implementation = implementations.get(format)
+ await page.addScriptTag({ content: implementation.code })
+ await page.evaluate(({ scenario }) => {
+ const first = document.querySelector('.player')
+ first.id = 'danmuku-player-0'
+ first.style.cssText = 'width:640px;height:360px'
+ window.createDanmukuPlayer = (index) => {
+ const container = index === 0 ? first : document.body.appendChild(document.createElement('div'))
+ container.id = `danmuku-player-${index}`
+ container.style.cssText = 'width:640px;height:360px'
+ const mount = document.createElement('div')
+ mount.id = `danmuku-mount-${index}`
+ mount.style.cssText = 'width:640px;height:36px;background:#333'
+ container.after(mount)
+ const art = new window.Artplayer({ container, url: `/test/pattern.mp4?danmuku=${index}`, muted: true, mutex: false, fullscreenWeb: true })
+ window.danmukuPlayers[index] = art
+ for (const event of ['loaded', 'visible', 'start', 'stop', 'reset', 'destroy', 'config', 'error']) {
+ art.on(`artplayerPluginDanmuku:${event}`, (value) => {
+ window.danmukuEvidence.events.push({ index, event, time: art.currentTime, id: value?.id, text: value?.text, mode: value?.mode, count: Array.isArray(value) ? value.length : undefined })
+ if (event === 'error')
+ window.danmukuEvidence.failures.push(String(value))
+ })
+ }
+ const option = {
+ danmuku: [],
+ heatmap: false,
+ speed: 8,
+ margin: [10, 10],
+ fontSize: 20,
+ antiOverlap: true,
+ synchronousPlayback: true,
+ lockTime: 1,
+ ...(scenario === 'settings' ? { mount } : {}),
+ filter: danmu => danmu.text !== 'FILTERED',
+ beforeVisible: async (danmu) => {
+ window.danmukuEvidence.gates.push({ index, id: danmu.id, text: danmu.text })
+ if (danmu.text === 'GATED')
+ return window.danmukuVisibility
+ return danmu.text !== 'DENIED'
+ },
+ beforeEmit: async (danmu) => {
+ window.danmukuEvidence.sends.push({ index, text: danmu.text, mode: danmu.mode })
+ return scenario === 'settings' && index === 0 ? window.danmukuSending : true
+ },
+ }
+ art.plugins.add(window.artplayerPluginDanmuku(option))
+ window.danmukuPlugins[index] = art.plugins.artplayerPluginDanmuku
+ return art
+ }
+ window.art = window.createDanmukuPlayer(0)
+ document.querySelector('#play').onclick = () => window.danmukuPlayers[0].play()
+ document.querySelector('#pause').onclick = () => window.danmukuPlayers[0].pause()
+ }, { scenario })
+ await expect.poll(() => page.evaluate(() => window.art.isReady && window.art.template.$video.videoWidth > 0)).toBe(true)
+ await testInfo.attach('danmuku-browser-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin: release.name, version: release.version, format, member: implementation.member, sha256: implementation.sha256, integrity: release.integrity, media: '/test/pattern.mp4', scenario, worker: 'Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked', heatmap: false }) })
+ return external
+}
+
+async function geometry(page) {
+ return page.evaluate(() => (window.danmukuPlayers || []).map((art, index) => {
+ const player = art.template.$player.getBoundingClientRect()
+ return {
+ index,
+ playing: art.playing,
+ time: art.currentTime,
+ rate: art.playbackRate,
+ player: { x: player.x, y: player.y, width: player.width, height: player.height },
+ nodes: [...art.template.$danmuku.children].map((node) => {
+ const rect = node.getBoundingClientRect()
+ const style = getComputedStyle(node)
+ return { id: node.dataset.id, mode: node.dataset.mode, state: node.dataset.state, text: node.textContent, x: rect.x - player.x, y: rect.y - player.y, width: rect.width, height: rect.height, visibility: style.visibility, display: style.display, transition: node.style.transition, opacity: style.opacity }
+ }),
+ }
+ }))
+}
+
+const visibleIds = page => page.evaluate(() => [...new Set(window.danmukuEvidence.events.filter(item => item.event === 'visible' && item.id).map(item => item.id))])
+
+test.afterEach(async ({ page }, testInfo) => {
+ await testInfo.attach('danmuku-layout', { contentType: 'application/json', body: JSON.stringify(await geometry(page)) })
+ await testInfo.attach('danmuku-native-page', { contentType: 'image/png', body: await page.screenshot() })
+ await page.evaluate(() => {
+ for (const art of window.danmukuPlayers || []) {
+ if (!art.isDestroy)
+ art.destroy()
+ }
+ })
+ const evidence = await page.evaluate(() => ({ workers: [], failures: [], ...window.danmukuEvidence, version: window.Artplayer?.version, retainedExternalSettings: [...document.querySelectorAll('[id^="danmuku-mount-"]')].map(node => node.querySelectorAll('.artplayer-plugin-danmuku').length) }))
+ await testInfo.attach('danmuku-worker-and-lifecycle', { contentType: 'application/json', body: JSON.stringify(evidence) })
+ expect(evidence.failures).toEqual([])
+ for (const worker of evidence.workers) {
+ expect(worker.url).toMatch(/^blob:/u)
+ expect(worker.errors).toEqual([])
+ expect(worker.terminated).toBe(1)
+ }
+})
+
+for (const core of ['published', 'candidate']) {
+ for (const format of ['main', 'legacy']) {
+ const label = `${core} / Danmuku 5.3.0 ${format}`
+
+ test(`${label}: native Worker renders three modes with synchronous filter and asynchronous beforeVisible`, async ({ page }, testInfo) => {
+ const external = await setup(page, core, format, 'modes', testInfo)
+ await page.evaluate(async () => {
+ await window.danmukuPlugins[0].load([
+ { id: 'scroll', text: 'SCROLL', mode: 0, time: 0.5 },
+ { id: 'top', text: 'TOP', mode: 1, time: 0.5 },
+ { id: 'bottom', text: 'BOTTOM', mode: 2, time: 0.5 },
+ { id: 'filtered', text: 'FILTERED', mode: 1, time: 0.5 },
+ { id: 'denied', text: 'DENIED', mode: 1, time: 0.5 },
+ { id: 'gated', text: 'GATED', mode: 1, time: 0.5 },
+ ])
+ })
+ await page.click('#play')
+ await expect.poll(() => visibleIds(page)).toEqual(expect.arrayContaining(['scroll', 'top', 'bottom']))
+ await expect.poll(() => page.evaluate(() => window.danmukuEvidence.gates.some(item => item.id === 'gated'))).toBe(true)
+ expect(await visibleIds(page)).not.toContain('gated')
+ expect(await visibleIds(page)).not.toContain('filtered')
+ expect(await visibleIds(page)).not.toContain('denied')
+ expect(await page.evaluate(() => window.danmukuEvidence.gates.some(item => item.id === 'filtered'))).toBe(false)
+ await page.evaluate(() => window.releaseDanmukuVisibility(true))
+ await expect.poll(() => visibleIds(page)).toContain('gated')
+ const [layout] = await geometry(page)
+ for (const id of ['top', 'bottom']) {
+ const item = layout.nodes.find(node => node.id === id && node.visibility === 'visible')
+ expect(item).toBeTruthy()
+ expect(Math.abs(item.x + item.width / 2 - layout.player.width / 2)).toBeLessThanOrEqual(2)
+ expect(item.y).toBeGreaterThanOrEqual(9)
+ expect(item.y + item.height).toBeLessThanOrEqual(layout.player.height - 8)
+ }
+ expect(layout.nodes.find(node => node.id === 'top').y).toBeLessThan(layout.nodes.find(node => node.id === 'bottom').y)
+ expect(await page.evaluate(() => window.danmukuEvidence.workers[0].replies.length)).toBeGreaterThanOrEqual(4)
+ expect(external).toEqual([])
+ })
+
+ test(`${label}: six simultaneous fixed comments preserve usable track geometry and input order`, async ({ page }, testInfo) => {
+ const external = await setup(page, core, format, 'density', testInfo)
+ await page.evaluate(async () => {
+ await window.danmukuPlugins[0].load(Array.from({ length: 6 }, (_, index) => ({ id: `dense-${index}`, text: `TRACK ${index}`, mode: 1, time: 0.5 })))
+ })
+ await page.click('#play')
+ await expect.poll(() => visibleIds(page)).toEqual(expect.arrayContaining(Array.from({ length: 6 }, (_, index) => `dense-${index}`)))
+ await page.click('#pause')
+ await expect.poll(() => page.evaluate(() => window.danmukuPlugins[0].isStop)).toBe(true)
+ const [layout] = await geometry(page)
+ const rows = Array.from({ length: 6 }, (_, index) => layout.nodes.find(node => node.id === `dense-${index}` && node.visibility === 'visible'))
+ expect(rows.every(Boolean)).toBe(true)
+ for (let index = 0; index < rows.length; index++) {
+ expect(rows[index].y).toBeGreaterThanOrEqual(9)
+ expect(rows[index].y + rows[index].height).toBeLessThanOrEqual(layout.player.height - 8)
+ if (index)
+ expect(rows[index].y).toBeGreaterThanOrEqual(rows[index - 1].y + rows[index - 1].height - 1)
+ }
+ const order = await visibleIds(page)
+ expect(order).toEqual(Array.from({ length: 6 }, (_, index) => `dense-${index}`))
+ await testInfo.attach('danmuku-dense-tracks', { contentType: 'application/json', body: JSON.stringify(rows) })
+ expect(external).toEqual([])
+ })
+
+ test(`${label}: native pause seek rate and public reset/load retain historical pool semantics`, async ({ page }, testInfo) => {
+ const external = await setup(page, core, format, 'playback', testInfo)
+ await page.evaluate(async () => {
+ await window.danmukuPlugins[0].emit({ id: 'moving', text: 'MOVING', mode: 0, time: 0.5 })
+ })
+ await page.click('#play')
+ await expect.poll(() => visibleIds(page)).toContain('moving')
+ await page.click('#pause')
+ await expect.poll(() => page.evaluate(() => window.danmukuPlugins[0].isStop)).toBe(true)
+ const frozen = await page.evaluate(async () => {
+ const node = window.art.template.$danmuku.querySelector('[data-id="moving"]')
+ window.danmukuNodes = [node]
+ const left = node.getBoundingClientRect().left
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
+ return { left, next: node.getBoundingClientRect().left, state: node.dataset.state }
+ })
+ expect(frozen.state).toBe('stop')
+ expect(Math.abs(frozen.left - frozen.next)).toBeLessThanOrEqual(1)
+ await page.evaluate(async () => {
+ window.art.playbackRate = 2
+ const sought = new Promise(resolve => window.art.once('video:seeked', resolve))
+ window.art.currentTime = 2
+ await sought
+ })
+ expect(await page.evaluate(() => window.danmukuNodes[0].dataset.state)).toBe('stop')
+ await page.evaluate(async () => {
+ window.danmukuPlugins[0].reset()
+ await window.danmukuPlugins[0].emit({ id: 'reused', text: 'REUSED', mode: 0, time: window.art.currentTime + 0.5 })
+ })
+ await page.click('#play')
+ await expect.poll(() => visibleIds(page)).toContain('reused')
+ const reused = await page.evaluate(() => {
+ const node = window.art.template.$danmuku.querySelector('[data-id="reused"]')
+ const original = window.danmukuNodes[0]
+ const nodes = [...window.art.template.$danmuku.children]
+ return {
+ poolReused: original.textContent === 'REUSED' && nodes.includes(original),
+ visibleSameNode: node === original,
+ original: { id: original.dataset.id, state: original.dataset.state, visibility: getComputedStyle(original).visibility, text: original.textContent },
+ extraNodes: nodes.filter(item => item !== original).map(item => ({ id: item.dataset.id, state: item.dataset.state, visibility: getComputedStyle(item).visibility, text: item.textContent })),
+ transition: node.style.transitionDuration,
+ rate: window.art.playbackRate,
+ }
+ })
+ // Native initial evidence reused the pooled node but a competing old RAF
+ // loop left it hidden and allocated another visible node. Record both.
+ expect(reused.poolReused).toBe(true)
+ expect(reused.transition).toBe('4s')
+ expect(reused.rate).toBe(2)
+ await page.evaluate(async () => {
+ await window.danmukuPlugins[0].load([{ id: 'appended', text: 'APPENDED', mode: 1, time: window.art.currentTime + 0.5 }])
+ })
+ await expect.poll(() => visibleIds(page)).toContain('appended')
+ await page.click('#pause')
+ await page.evaluate(async () => {
+ await window.danmukuPlugins[0].load()
+ })
+ expect(await page.locator('#danmuku-player-0 .art-danmuku > div').count()).toBe(0)
+ await testInfo.attach('danmuku-pool-and-rate', { contentType: 'application/json', body: JSON.stringify({ frozen, reused, loadWithoutArgument: 'replaces from original empty option; explicit array load appends' }) })
+ expect(external).toEqual([])
+ })
+
+ test(`${label}: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction`, async ({ page }, testInfo) => {
+ const external = await setup(page, core, format, 'settings', testInfo)
+ const mount = page.locator('#danmuku-mount-0')
+ await expect(mount.locator('.artplayer-plugin-danmuku')).toHaveCount(1)
+ await mount.locator('.apd-config').hover()
+ await mount.locator('.apd-config-mode [data-mode="2"]').click()
+ expect(await page.evaluate(() => window.danmukuPlugins[0].option.modes)).toEqual([0, 1])
+ await mount.locator('.apd-style').hover()
+ await mount.locator('.apd-style-mode [data-mode="1"]').click()
+ expect(await page.evaluate(() => window.danmukuPlugins[0].option.mode)).toBe(1)
+ await mount.locator('.apd-input').fill('FIRST')
+ await mount.locator('.apd-send').click()
+ await expect.poll(() => page.evaluate(() => window.danmukuEvidence.sends.length)).toBe(1)
+ await mount.locator('.apd-input').press('Enter')
+ expect(await page.evaluate(() => window.danmukuEvidence.sends.length)).toBe(1)
+ await page.evaluate(() => window.releaseDanmukuSending(true))
+ await expect(mount.locator('.apd-input')).toHaveValue('')
+ await expect(mount.locator('.apd-send')).toHaveClass(/apd-lock/u)
+ await mount.locator('.apd-input').fill('LOCKED')
+ await mount.locator('.apd-input').press('Enter')
+ expect(await page.evaluate(() => window.danmukuEvidence.sends.length)).toBe(1)
+ await expect(mount.locator('.apd-send')).not.toHaveClass(/apd-lock/u)
+ await page.evaluate(() => {
+ window.danmukuEmitter = document.querySelector('#danmuku-mount-0 .artplayer-plugin-danmuku')
+ window.art.fullscreenWeb = true
+ })
+ // FULLSCREEN_WEB_IN_BODY can move the player out of its original container.
+ await expect.poll(() => page.evaluate(() => window.art.template.$controlsCenter.contains(window.danmukuEmitter))).toBe(true)
+ await expect(mount.locator('.artplayer-plugin-danmuku')).toHaveCount(0)
+ await page.evaluate(() => {
+ window.art.fullscreenWeb = false
+ })
+ await expect(mount.locator('.artplayer-plugin-danmuku')).toHaveCount(1)
+ expect(await page.evaluate(() => document.querySelector('#danmuku-mount-0').contains(window.danmukuEmitter))).toBe(true)
+ await page.evaluate(() => {
+ const art = window.createDanmukuPlayer(1)
+ const button = document.createElement('button')
+ button.id = 'play-second-danmuku'
+ button.textContent = 'Play second'
+ button.onclick = () => art.play()
+ document.body.appendChild(button)
+ })
+ await expect.poll(() => page.evaluate(() => window.danmukuPlayers[1].isReady)).toBe(true)
+ await page.evaluate(() => window.danmukuPlayers[0].destroy())
+ expect(await page.evaluate(() => window.danmukuEvidence.workers.map(worker => worker.terminated))).toEqual([1, 0])
+ // The released plugin does not dispose external Setting DOM; retain this observation.
+ await expect(mount.locator('.artplayer-plugin-danmuku')).toHaveCount(1)
+ await page.locator('#danmuku-mount-1 .apd-input').fill('SECOND')
+ await page.locator('#danmuku-mount-1 .apd-send').click()
+ await page.click('#play-second-danmuku')
+ await expect.poll(() => page.evaluate(() => window.danmukuEvidence.events.some(item => item.index === 1 && item.event === 'visible' && item.text === 'SECOND'))).toBe(true)
+ expect(await page.evaluate(() => window.danmukuPlayers[1].isDestroy)).toBe(false)
+ expect(external).toEqual([])
+ })
+ }
+}
diff --git a/test/browser/danmuku-load-baseline.spec.js b/test/browser/danmuku-load-baseline.spec.js
new file mode 100644
index 000000000..05a77b606
--- /dev/null
+++ b/test/browser/danmuku-load-baseline.spec.js
@@ -0,0 +1,186 @@
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
+import { expect, test } from './fixtures.js'
+
+const baseline = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/danmuku-release.json', import.meta.url), 'utf8'))
+const release = baseline.release
+const mediaSha256 = hash(fs.readFileSync(new URL('./media/pattern.mp4', import.meta.url)))
+const implementations = new Map()
+assert.equal(release.version, '5.3.0')
+
+test.beforeAll(async () => {
+ const archive = await ensureArchive(release)
+ for (const format of ['main', 'legacy']) {
+ const member = `package/${release.manifest[format].replace(/^\.\//u, '')}`
+ const bytes = readMember(archive, member)
+ assert.equal(hash(bytes), release.files[member])
+ implementations.set(format, { member, sha256: hash(bytes), code: bytes.toString() })
+ }
+})
+
+test.afterEach(async ({ page }, testInfo) => {
+ await page.evaluate(() => {
+ if (window.loadObserverFrame !== undefined)
+ cancelAnimationFrame(window.loadObserverFrame)
+ if (window.loadArt && !window.loadArt.isDestroy)
+ window.loadArt.destroy()
+ })
+ await testInfo.attach('danmuku-load-final-observations', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => ({
+ evidence: window.loadEvidence,
+ destroyed: window.loadArt?.isDestroy,
+ retainedPlayerRoots: document.querySelectorAll('#danmuku-load-player .art-video-player').length,
+ retainedExternalSettings: document.querySelectorAll('#danmuku-load-mount .artplayer-plugin-danmuku').length,
+ }))) })
+})
+
+for (const format of ['main', 'legacy']) {
+ test(`published core / Danmuku 5.3.0 ${format}: 300-comment native load and frame/heap observation`, async ({ page }, testInfo) => {
+ const external = []
+ const origin = new URL(testInfo.project.use.baseURL).origin
+ await page.route('**/*', (route) => {
+ const url = new URL(route.request().url())
+ if (url.origin !== origin) {
+ external.push(url.href)
+ return route.abort()
+ }
+ return route.continue()
+ })
+ await page.goto('/test/player.html?core=published')
+ await page.evaluate(() => {
+ window.loadEvidence = { workers: [], loaded: [], visible: [], frameTimes: [], filtered: 0, heaps: [], failures: [], destroyEvents: 0 }
+ window.readLoadHeap = (phase) => {
+ const memory = performance.memory
+ const snapshot = memory && [memory.usedJSHeapSize, memory.totalJSHeapSize, memory.jsHeapSizeLimit].every(Number.isFinite)
+ ? { usedJSHeapSize: memory.usedJSHeapSize, totalJSHeapSize: memory.totalJSHeapSize, jsHeapSizeLimit: memory.jsHeapSizeLimit }
+ : null
+ window.loadEvidence.heaps.push({ phase, timestamp: performance.now(), memory: snapshot })
+ }
+ window.readLoadHeap('before-plugin-script')
+ const NativeWorker = window.Worker
+ window.Worker = class ObservedLoadWorker extends NativeWorker {
+ constructor(url, options) {
+ super(url, options)
+ this.observation = { url: String(url), posts: 0, replies: 0, terminated: 0, errors: [] }
+ window.loadEvidence.workers.push(this.observation)
+ this.addEventListener('message', () => this.observation.replies++)
+ this.addEventListener('error', event => this.observation.errors.push(event.message))
+ }
+
+ postMessage(message, ...options) {
+ this.observation.posts++
+ return super.postMessage(message, ...options)
+ }
+
+ terminate() {
+ this.observation.terminated++
+ return super.terminate()
+ }
+ }
+ // Independent observer: never replace the plugin's RAF or mutate its clock.
+ function frame(timestamp) {
+ if (window.loadArt?.playing)
+ window.loadEvidence.frameTimes.push(timestamp)
+ window.loadObserverFrame = requestAnimationFrame(frame)
+ }
+ window.loadObserverFrame = requestAnimationFrame(frame)
+ })
+ const implementation = implementations.get(format)
+ await page.addScriptTag({ content: implementation.code })
+ await testInfo.attach('danmuku-load-inputs', { contentType: 'application/json', body: JSON.stringify({ core: 'published', coreVersion: await page.evaluate(() => window.Artplayer.version), pluginVersion: release.version, format, member: implementation.member, sha256: implementation.sha256, integrity: release.integrity, mediaSha256, comments: 300, modes: [0, 1, 2], scheduledTime: 0.5, heatmap: false, scope: 'Single bounded native sample; independent RAF observer and optional performance.memory readings. No forced GC, leak diagnosis, cross-browser heap equivalence or performance threshold.' }) })
+ await page.evaluate(async () => {
+ const container = document.querySelector('.player')
+ container.id = 'danmuku-load-player'
+ container.style.cssText = 'width:640px;height:360px'
+ const mount = document.createElement('div')
+ mount.id = 'danmuku-load-mount'
+ mount.style.cssText = 'width:640px;height:36px;background:#333'
+ container.after(mount)
+ const art = window.loadArt = new window.Artplayer({ container, url: '/test/pattern.mp4?case=danmuku-load-300', muted: true })
+ art.on('artplayerPluginDanmuku:loaded', queue => window.loadEvidence.loaded.push({ count: queue.length, ids: queue.map(item => item.id) }))
+ art.on('artplayerPluginDanmuku:visible', item => window.loadEvidence.visible.push({ id: item.id, mode: item.mode, mediaTime: art.currentTime, timestamp: performance.now() }))
+ art.on('artplayerPluginDanmuku:error', error => window.loadEvidence.failures.push(String(error)))
+ art.on('artplayerPluginDanmuku:destroy', () => window.loadEvidence.destroyEvents++)
+ art.plugins.add(window.artplayerPluginDanmuku({
+ danmuku: [],
+ mount,
+ heatmap: false,
+ speed: 8,
+ fontSize: 18,
+ margin: [10, 10],
+ antiOverlap: true,
+ filter(item) {
+ window.loadEvidence.filtered++
+ return item.id.startsWith('load-')
+ },
+ }))
+ const plugin = window.loadPlugin = art.plugins.artplayerPluginDanmuku
+ const input = Array.from({ length: 300 }, (_, index) => ({ id: `load-${String(index).padStart(3, '0')}`, text: `Load ${index}`, mode: index % 3, time: 0.5 }))
+ await plugin.load(input)
+ window.readLoadHeap('after-300-loaded')
+ document.querySelector('#play').onclick = () => art.play()
+ document.querySelector('#pause').onclick = () => art.pause()
+ })
+ await expect.poll(() => page.evaluate(() => window.loadArt.isReady && window.loadArt.template.$video.videoWidth > 0)).toBe(true)
+ const loaded = await page.evaluate(() => ({ latest: window.loadEvidence.loaded.at(-1), filtered: window.loadEvidence.filtered }))
+ expect(loaded.latest.count).toBe(300)
+ expect(new Set(loaded.latest.ids).size).toBe(300)
+ expect(loaded.filtered).toBe(300)
+ await page.click('#play')
+ // A state deadline, not a performance pass/fail budget or a fixed sleep.
+ await expect.poll(() => page.evaluate(() => window.loadArt.currentTime), { timeout: 15000 }).toBeGreaterThanOrEqual(4)
+ await page.click('#pause')
+ await expect.poll(() => page.evaluate(() => window.loadArt.template.$video.paused && window.loadPlugin.isStop)).toBe(true)
+ const sample = await page.evaluate(() => {
+ cancelAnimationFrame(window.loadObserverFrame)
+ window.loadObserverFrame = undefined
+ window.readLoadHeap('paused-after-four-media-seconds')
+ const art = window.loadArt
+ const rect = art.template.$player.getBoundingClientRect()
+ const nodes = [...art.template.$danmuku.children].map((node) => {
+ const position = node.getBoundingClientRect()
+ const style = getComputedStyle(node)
+ return { id: node.dataset.id, state: node.dataset.state, mode: node.dataset.mode, text: node.textContent, x: position.x - rect.x, y: position.y - rect.y, width: position.width, height: position.height, visibility: style.visibility, display: style.display }
+ })
+ const times = window.loadEvidence.frameTimes
+ const frameIntervals = times.slice(1).map((timestamp, index) => timestamp - times[index])
+ const sorted = [...frameIntervals].sort((a, b) => a - b)
+ return {
+ coreVersion: window.Artplayer.version,
+ mediaTime: art.currentTime,
+ video: { width: art.template.$video.videoWidth, height: art.template.$video.videoHeight, duration: art.duration },
+ loadedCount: window.loadEvidence.loaded.at(-1).count,
+ visibleEvents: window.loadEvidence.visible.length,
+ uniqueVisible: new Set(window.loadEvidence.visible.map(item => item.id)).size,
+ nodeCount: nodes.length,
+ visibleNodeCount: nodes.filter(node => node.visibility === 'visible' && node.display !== 'none').length,
+ hiddenNodeCount: nodes.filter(node => node.visibility === 'hidden' || node.display === 'none').length,
+ nodes,
+ frameTimes: times,
+ frameIntervals,
+ frameSummary: { count: frameIntervals.length, median: sorted[Math.floor(sorted.length / 2)] ?? null, p95: sorted[Math.floor(sorted.length * 0.95)] ?? null, max: sorted.at(-1) ?? null },
+ heaps: window.loadEvidence.heaps,
+ workers: window.loadEvidence.workers,
+ }
+ })
+ await testInfo.attach('danmuku-load-sample', { contentType: 'application/json', body: JSON.stringify(sample) })
+ await testInfo.attach('danmuku-load-screenshot', { contentType: 'image/png', body: await page.screenshot() })
+ expect(sample.mediaTime).toBeGreaterThanOrEqual(4)
+ expect(sample.uniqueVisible).toBeGreaterThan(0)
+ expect(sample.visibleNodeCount).toBeGreaterThan(0)
+ expect(sample.frameIntervals.length).toBeGreaterThan(1)
+ expect(sample.workers).toHaveLength(1)
+ expect(sample.workers[0].url).toMatch(/^blob:/u)
+ expect(sample.workers[0].posts).toBeGreaterThan(0)
+ expect(sample.workers[0].replies).toBeGreaterThan(0)
+ expect(sample.workers[0].errors).toEqual([])
+ await page.evaluate(() => {
+ window.loadArt.destroy()
+ window.readLoadHeap('immediately-after-destroy')
+ })
+ expect(await page.evaluate(() => window.loadArt.isDestroy)).toBe(true)
+ expect(await page.evaluate(() => window.loadEvidence.destroyEvents)).toBe(1)
+ expect(await page.evaluate(() => window.loadEvidence.workers[0].terminated)).toBe(1)
+ expect(external).toEqual([])
+ })
+}
diff --git a/test/danmuku-failures.test.js b/test/danmuku-failures.test.js
new file mode 100644
index 000000000..bdb29adb8
--- /dev/null
+++ b/test/danmuku-failures.test.js
@@ -0,0 +1,278 @@
+import assert from 'node:assert/strict'
+import { spawnSync } from 'node:child_process'
+import fs from 'node:fs'
+import path from 'node:path'
+import process from 'node:process'
+// eslint-disable-next-line test/no-import-node-test -- Historical failures are explicit Node observations.
+import test from 'node:test'
+import { fileURLToPath } from 'node:url'
+import { danmukuEnvironment, danmukuHistorical, deferred } from './helpers/danmuku.js'
+
+const implementations = await danmukuHistorical()
+const cache = fileURLToPath(new URL('../refactor/.cache/danmuku02-controlled/', import.meta.url))
+fs.mkdirSync(cache, { recursive: true })
+const evidenceDirectory = fs.mkdtempSync(path.join(cache, 'run-'))
+
+function isolated(implementation, scenario) {
+ const program = `
+ import fs from 'node:fs';
+ import vm from 'node:vm';
+ import { danmukuEnvironment } from './test/helpers/danmuku.js';
+ const implementation = JSON.parse(fs.readFileSync(0, 'utf8'));
+ const scenario = ${JSON.stringify(scenario)};
+ let settled = false;
+ let env;
+ process.on('uncaughtExceptionMonitor', (error, origin) => {
+ process.stdout.write(JSON.stringify({ stage: 'fatal', origin, error: error.message, settled, errors: env?.events.filter(e => e.name === 'artplayerPluginDanmuku:error').length }) + '\\n');
+ });
+ if (scenario === 'heatmap-zero' || scenario === 'heatmap-positive') {
+ env = danmukuEnvironment(implementation, { heatmapWidth: 99 });
+ env.factory({ danmuku: [], heatmap: scenario === 'heatmap-positive' ? { sampling: 1 } : true })(env.art);
+ const sandbox = vm.createContext({ run: () => env.art.emit('ready') });
+ process.stdout.write('heatmap-ready\\n');
+ try {
+ vm.runInContext('run()', sandbox, { timeout: 100 });
+ process.stdout.write(JSON.stringify({ stage: 'complete' }) + '\\n');
+ } catch (error) {
+ if (error.code !== 'ERR_SCRIPT_EXECUTION_TIMEOUT') throw error;
+ process.stdout.write(JSON.stringify({ stage: 'bounded-timeout', code: error.code }) + '\\n');
+ }
+ process.exit(0);
+ }
+ if (scenario === 'beforeVisible-reject') {
+ env = danmukuEnvironment(implementation);
+ const plugin = env.factory({ danmuku: [], beforeVisible: () => Promise.reject(new Error('beforeVisible-original')) })(env.art);
+ await plugin.emit({ text: 'visible', time: 10 });
+ env.art.playing = true;
+ env.art.emit('video:play');
+ // Browser rAF ignores a callback's returned Promise. Do not observe it here.
+ env.frame();
+ } else {
+ env = danmukuEnvironment(implementation, { fetch: async () => {
+ if (scenario === 'fetch-reject') throw new Error('fetch-original');
+ return { text: async () => { throw new Error('text-original'); } };
+ } });
+ const plugin = env.factory({ danmuku: [] })(env.art);
+ plugin.load('/controlled.xml').then(() => settled = true, () => settled = true);
+ }
+ setTimeout(() => process.stdout.write('unexpected-survival\\n'), 200);
+ `
+ const child = spawnSync(process.execPath, ['--unhandled-rejections=strict', '--input-type=module', '-e', program], {
+ cwd: fileURLToPath(new URL('../', import.meta.url)),
+ input: JSON.stringify(implementation),
+ encoding: 'utf8',
+ timeout: 3000,
+ maxBuffer: 2 * 1024 * 1024,
+ })
+ const evidence = { implementation: implementation.name, scenario, status: child.status, signal: child.signal, errorCode: child.error?.code ?? null, stdout: child.stdout, stderr: child.stderr }
+ fs.writeFileSync(path.join(evidenceDirectory, `${implementation.name}-${scenario}.json`), `${JSON.stringify(evidence, null, 2)}\n`)
+ assert.equal(child.error, undefined, `Isolated process infrastructure failed: ${child.error?.message}`)
+ return evidence
+}
+
+for (const implementation of implementations) {
+ for (const scenario of ['fetch-reject', 'text-reject']) {
+ test(`danmuku ${implementation.name}: historical ${scenario} crashes the isolated process while public load stays pending`, () => {
+ const evidence = isolated(implementation, scenario)
+ assert.equal(evidence.status, 1)
+ const record = JSON.parse(evidence.stdout.trim())
+ assert.equal(record.stage, 'fatal')
+ assert.equal(record.origin, 'unhandledRejection')
+ assert.equal(record.settled, false)
+ assert.equal(record.errors, 0)
+ assert.equal(record.error, scenario === 'fetch-reject' ? 'fetch-original' : 'text-original')
+ })
+ }
+
+ test(`danmuku ${implementation.name}: historical beforeVisible rejection is an unobserved animation callback Promise`, () => {
+ const evidence = isolated(implementation, 'beforeVisible-reject')
+ assert.equal(evidence.status, 1)
+ const record = JSON.parse(evidence.stdout.trim())
+ assert.equal(record.error, 'beforeVisible-original')
+ assert.equal(record.origin, 'unhandledRejection')
+ assert.equal(record.errors, 0)
+ })
+
+ test(`danmuku ${implementation.name}: historical heatmap width 99 blocks at default sampling zero but sampling 1 completes`, () => {
+ const zero = isolated(implementation, 'heatmap-zero')
+ const positive = isolated(implementation, 'heatmap-positive')
+ assert.equal(zero.status, 0)
+ assert.equal(positive.status, 0)
+ assert.match(zero.stdout, /heatmap-ready\n\{"stage":"bounded-timeout","code":"ERR_SCRIPT_EXECUTION_TIMEOUT"\}/u)
+ assert.match(positive.stdout, /heatmap-ready\n\{"stage":"complete"\}/u)
+ })
+
+ for (const event of ['error', 'empty-xml']) {
+ test(`danmuku ${implementation.name}: historical Bilibili ${event} leaves loading pending and parser worker alive`, async () => {
+ const env = danmukuEnvironment(implementation, event === 'empty-xml' ? { fetch: async () => ({ text: async () => '' }) } : {})
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ let settled = false
+ plugin.load('/controlled.xml').then(() => settled = true, () => settled = true)
+ await env.flush()
+ assert.equal(env.workers.length, 2)
+ const worker = env.workers[1]
+ assert.equal(worker.messages.length, 1)
+ if (event === 'error') {
+ assert.equal(worker.onerror, undefined)
+ worker.fail(new Error('parser worker failure'))
+ }
+ else {
+ assert.equal(worker.messages[0].message.xml, '')
+ // Execute the actual Blob, not a reimplementation of its empty-input branch.
+ await worker.execute(worker.messages[0].message)
+ }
+ await env.flush()
+ assert.equal(settled, false)
+ env.destroy()
+ assert.equal(worker.terminated, false, 'Parser worker is not owned by the Danmuku destroy path')
+ })
+ }
+
+ test(`danmuku ${implementation.name}: historical successful parser leaves its Blob URL allocated`, async () => {
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ const pending = plugin.load('/controlled.xml')
+ await env.flush()
+ const worker = env.workers[1]
+ await worker.execute(worker.messages[0].message)
+ const internal = await pending
+ assert.equal(internal.queue.length, 1)
+ assert.equal(internal.queue[0].text, 'xml')
+ assert.equal(worker.terminated, true)
+ assert.equal(env.urls.has(worker.url), true)
+ assert.equal(env.revoked.includes(worker.url), false)
+ env.destroy()
+ })
+
+ test(`danmuku ${implementation.name}: historical parser worker completion emits new data after player destruction`, async () => {
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ const pending = plugin.load('/controlled.xml')
+ await env.flush()
+ const worker = env.workers[1]
+ env.destroy()
+ const count = env.events.length
+ assert.equal(worker.terminated, false)
+ await worker.execute(worker.messages[0].message)
+ const internal = await pending
+ assert.equal(internal.queue[0].text, 'xml')
+ assert.deepEqual(env.events.slice(count).map(event => event.name), ['artplayerPluginDanmuku:loaded'])
+ assert.equal(worker.terminated, true)
+ })
+
+ test(`danmuku ${implementation.name}: historical overlapping replacement loads let the oldest completion overwrite newer data`, async () => {
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ const first = deferred()
+ const second = deferred()
+ const jobs = [first, second]
+ // Change an additional scalar because the historical function-only config bug is separate.
+ plugin.config({ danmuku: () => jobs.shift().promise, opacity: 0.9 })
+ const older = plugin.load()
+ const newer = plugin.load()
+ second.resolve([{ text: 'newer', time: 10 }])
+ const internal = await newer
+ assert.deepEqual(Array.from(internal.queue, item => item.text), ['newer'])
+ first.resolve([{ text: 'older', time: 10 }])
+ await older
+ assert.deepEqual(Array.from(internal.queue, item => item.text), ['older'])
+ env.destroy()
+ })
+
+ test(`danmuku ${implementation.name}: historical asynchronous input mutates queue and emits loaded after destroy`, async () => {
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ const input = deferred()
+ const pending = plugin.load(() => input.promise)
+ env.destroy()
+ const count = env.events.length
+ input.resolve([{ text: 'late', time: 10 }])
+ const internal = await pending
+ assert.deepEqual(Array.from(internal.queue, item => item.text), ['late'])
+ assert.deepEqual(env.events.slice(count).map(event => event.name), ['artplayerPluginDanmuku:loaded'])
+ })
+
+ test(`danmuku ${implementation.name}: historical pending beforeVisible allocates DOM and posts to a terminated worker after destroy`, async () => {
+ const visible = deferred()
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [], beforeVisible: () => visible.promise })(env.art)
+ await plugin.emit({ text: 'late visible', time: 10 })
+ env.art.playing = true
+ env.art.emit('video:play')
+ env.frame()
+ await env.flush()
+ assert.equal(env.art.template.$danmuku.children.length, 0)
+ env.destroy()
+ visible.resolve(true)
+ await env.flush()
+ assert.equal(env.art.template.$danmuku.children.length, 1)
+ assert.equal(env.workers[0].messages.length, 1)
+ assert.equal(env.workers[0].messages[0].afterTerminate, true)
+ assert.equal(env.events.filter(event => event.name === 'artplayerPluginDanmuku:visible').length, 0)
+ })
+
+ test(`danmuku ${implementation.name}: historical worker response continuation still mutates state after destroy`, async () => {
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ await plugin.emit({ text: 'pending position', time: 10 })
+ env.art.playing = true
+ env.art.emit('video:play')
+ const frame = env.frame()
+ await env.flush()
+ const worker = env.workers[0]
+ const { id } = worker.messages[0].message
+ const internal = plugin.show()
+ // Delivery starts before terminate; its awaiting rAF continuation runs afterward.
+ // This does not require a native Worker to dispatch a new event after terminate.
+ worker.deliver({ id, result: 10 })
+ env.destroy()
+ assert.equal(internal.queue[0].$state, 'wait')
+ await frame
+ assert.equal(internal.queue[0].$state, 'ready')
+ assert.equal(internal.queue[0].$ref, null)
+ assert.equal(env.frames.size, 0)
+ assert.equal(env.events.filter(event => event.name === 'artplayerPluginDanmuku:visible').length, 0)
+ })
+
+ test(`danmuku ${implementation.name}: historical play and playing schedule duplicate loops and overwrite pending worker response handlers`, async () => {
+ const env = danmukuEnvironment(implementation)
+ const plugin = env.factory({ danmuku: [] })(env.art)
+ await plugin.emit({ text: 'two loops', time: 10 })
+ env.art.playing = true
+ env.art.emit('video:play')
+ env.art.emit('video:playing')
+ assert.equal(env.frames.size, 2)
+ let firstSettled = false
+ env.frame().then(() => firstSettled = true)
+ await env.flush()
+ env.tick(1)
+ const second = env.frame()
+ await env.flush()
+ const worker = env.workers[0]
+ assert.equal(worker.messages.length, 2)
+ const firstId = worker.messages[0].message.id
+ const secondId = worker.messages[1].message.id
+ assert.notEqual(firstId, secondId)
+ worker.deliver({ id: firstId, result: 10 })
+ await env.flush()
+ assert.equal(firstSettled, false)
+ worker.deliver({ id: secondId, result: 10 })
+ await second
+ assert.equal(firstSettled, false)
+ assert.equal(env.events.filter(event => event.name === 'artplayerPluginDanmuku:visible').length, 1)
+ env.destroy()
+ })
+
+ test(`danmuku ${implementation.name}: historical duplicate start then pause cancels only the latest scheduled frame`, () => {
+ const env = danmukuEnvironment(implementation)
+ env.factory({ danmuku: [] })(env.art)
+ env.art.emit('video:play')
+ env.art.emit('video:playing')
+ env.art.emit('video:pause')
+ assert.equal(env.frames.size, 1)
+ env.destroy()
+ assert.equal(env.frames.size, 1)
+ })
+}
+
+process.stdout.write(`Danmuku controlled subprocess evidence: ${evidenceDirectory}\n`)
diff --git a/test/helpers/danmuku.js b/test/helpers/danmuku.js
new file mode 100644
index 000000000..da8c1b36c
--- /dev/null
+++ b/test/helpers/danmuku.js
@@ -0,0 +1,239 @@
+import assert from 'node:assert/strict'
+import { Buffer } from 'node:buffer'
+import fs from 'node:fs'
+import path from 'node:path'
+import vm from 'node:vm'
+import { build } from 'esbuild'
+import { verifyDanmukuContract } from '../../refactor/scripts/danmuku-contract.mjs'
+import { ensureArchive, readMember, refactorDir } from '../../refactor/scripts/releases.mjs'
+
+export function deferred() {
+ let resolve
+ let reject
+ const promise = new Promise((yes, no) => {
+ resolve = yes
+ reject = no
+ })
+ return { promise, resolve, reject }
+}
+
+export async function danmukuHistorical() {
+ const { baseline, archives, sources } = await verifyDanmukuContract()
+ const prefix = 'packages/artplayer-plugin-danmuku/'
+ const compiled = await build({ entryPoints: [`${prefix}src/index.js`], bundle: true, write: false, format: 'cjs', platform: 'browser', plugins: [{ name: 'frozen-danmuku-failures', setup(build) {
+ build.onResolve({ filter: /.*/ }, args => ({ path: path.posix.normalize(args.importer ? path.posix.join(path.posix.dirname(args.importer), args.path) : args.path), namespace: 'frozen' }))
+ build.onLoad({ filter: /.*/, namespace: 'frozen' }, (args) => {
+ if (args.path.includes('?worker'))
+ return { contents: 'export default Worker', loader: 'js' }
+ const file = args.path.split('?')[0]
+ const found = sources.get(file) ?? sources.get(`${file}.js`)
+ assert.notEqual(found, undefined, file)
+ return { contents: args.path.includes('?') ? `export default ${JSON.stringify(found)}` : found, loader: 'js' }
+ })
+ } }] })
+ const coreRelease = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8')).releases.find(item => item.name === 'artplayer')
+ const coreCode = readMember(await ensureArchive(coreRelease), 'package/dist/artplayer.js').toString()
+ return [
+ { name: 'frozen-source', code: compiled.outputFiles[0].text, coreCode },
+ ...['main', 'legacy'].map(field => ({ name: `published-5.3.0-${field}`, code: readMember(archives.get('5.3.0'), `package/${baseline.release.manifest[field].replace(/^\.\//u, '')}`).toString(), coreCode })),
+ ]
+}
+
+class Element {
+ constructor() {
+ this.style = {}
+ this.dataset = {}
+ this.children = []
+ this.nodes = new Map()
+ this.clientWidth = 640
+ this.clientHeight = 360
+ this.offsetWidth = 640
+ this.offsetHeight = 100
+ this.offsetTop = 0
+ this.className = ''
+ this.textContent = ''
+ this.value = ''
+ }
+
+ get [Symbol.toStringTag]() { return 'HTMLDivElement' }
+ appendChild(child) {
+ child.parentElement = this
+ this.children.push(child)
+ return child
+ }
+
+ querySelector(selector) {
+ if (!this.nodes.has(selector))
+ this.nodes.set(selector, new Element())
+ return this.nodes.get(selector)
+ }
+
+ getBoundingClientRect() { return { top: 0, left: 0, right: this.clientWidth, bottom: this.clientHeight, width: this.clientWidth, height: this.clientHeight } }
+ setAttribute(key, value) { this[key] = value }
+ addEventListener() {}
+}
+
+export function danmukuEnvironment(implementation, settings = {}) {
+ const coreModule = { exports: {} }
+ vm.runInNewContext(implementation.coreCode, { module: coreModule, exports: coreModule.exports }, { timeout: 1000 })
+ const events = []
+ const listeners = new Map()
+ const workers = []
+ const frames = new Map()
+ const timers = new Map()
+ const proxies = []
+ const controls = []
+ const requests = []
+ const urls = new Map()
+ const revoked = []
+ const consoleErrors = []
+ let id = 0
+ let now = 1000
+ const host = {
+ requestAnimationFrame(callback) {
+ frames.set(++id, callback)
+ return id
+ },
+ cancelAnimationFrame(id) { frames.delete(id) },
+ }
+ class Worker {
+ constructor(url) {
+ this.url = url
+ this.messages = []
+ this.listeners = new Map()
+ this.terminated = false
+ workers.push(this)
+ }
+
+ terminate() { this.terminated = true }
+ postMessage(message) { this.messages.push({ message, afterTerminate: this.terminated }) }
+ addEventListener(name, callback) { this.listeners.set(name, [...this.listeners.get(name) || [], callback]) }
+ deliver(data) { this.onmessage?.({ data }) }
+ async execute(message) {
+ const blob = urls.get(this.url)
+ assert(blob, 'Expected the parser actual Blob source')
+ const workerContext = vm.createContext({ postMessage: data => this.deliver(data) })
+ vm.runInContext(await blob.text(), workerContext, { timeout: 1000 })
+ workerContext.input = message
+ vm.runInContext('onmessage({ data: input })', workerContext, { timeout: 1000 })
+ }
+
+ fail(error) {
+ this.onerror?.(error)
+ for (const callback of this.listeners.get('error') || [])
+ callback(error)
+ }
+ }
+ class Clock extends Date {
+ static now() { return now }
+ }
+ const module = { exports: {} }
+ const context = vm.createContext({
+ window: host,
+ Worker,
+ Blob,
+ Date: Clock,
+ URL: {
+ createObjectURL(blob) {
+ const url = `blob:danmuku-${++id}`
+ urls.set(url, blob)
+ return url
+ },
+ revokeObjectURL(url) { revoked.push(url) },
+ },
+ atob: value => Buffer.from(value, 'base64').toString('binary'),
+ setTimeout(callback, delay) {
+ timers.set(++id, { callback, delay })
+ return id
+ },
+ clearTimeout(id) { timers.delete(id) },
+ fetch: async (url) => {
+ requests.push(url)
+ if (settings.fetch)
+ return settings.fetch(url)
+ return { text: async () => 'xml' }
+ },
+ console: { error: (...args) => consoleErrors.push(args) },
+ module,
+ exports: module.exports,
+ })
+ vm.runInContext(implementation.code, context, { timeout: 1000 })
+ const factory = typeof module.exports === 'function' ? module.exports : module.exports.default
+ const body = new Element()
+ context.document = { querySelector: selector => selector === '#external' ? body : null, createElement: () => new Element() }
+ const utils = {
+ clamp: (value, min, max) => Math.min(Math.max(value, min), max),
+ setStyle: (element, key, value) => element.style[key] = value,
+ setStyles: (element, style) => Object.assign(element.style, style),
+ errorHandle: (value, message) => {
+ if (!value)
+ throw new Error(message)
+ return value
+ },
+ createElement: () => new Element(),
+ query: (selector, element) => element.querySelector(selector),
+ append: (element, child) => element.appendChild(child),
+ tooltip() {},
+ inverseClass() {},
+ addClass() {},
+ removeClass() {},
+ }
+ const art = {
+ constructor: { utils, validator: coreModule.exports.validator },
+ template: { $danmuku: new Element(), $player: new Element(), $controlsCenter: new Element() },
+ currentTime: 10,
+ duration: 60,
+ played: 0,
+ option: { isLive: false },
+ width: 640,
+ playbackRate: 1,
+ playing: false,
+ plugins: {},
+ controls: { add(option) {
+ const element = new Element()
+ element.offsetWidth = settings.heatmapWidth ?? 640
+ controls.push({ option, element })
+ option.mounted?.(element)
+ return undefined
+ } },
+ on(name, callback) { listeners.set(name, [...listeners.get(name) || [], callback]) },
+ off(name, callback) { listeners.set(name, (listeners.get(name) || []).filter(item => item !== callback)) },
+ emit(name, ...args) {
+ events.push({ name, args })
+ for (const callback of listeners.get(name) || [])
+ callback(...args)
+ },
+ proxy(target, name, callback) { proxies.push({ target, name, callback }) },
+ }
+ return {
+ context,
+ factory,
+ art,
+ workers,
+ events,
+ listeners,
+ frames,
+ timers,
+ proxies,
+ controls,
+ requests,
+ urls,
+ revoked,
+ consoleErrors,
+ async flush() {
+ for (let step = 0; step < 12; step++)
+ await Promise.resolve()
+ },
+ frame(frameId = frames.keys().next().value) {
+ const callback = frames.get(frameId)
+ assert.equal(typeof callback, 'function', 'Expected one scheduled animation frame')
+ frames.delete(frameId)
+ return callback(now)
+ },
+ tick(milliseconds) { now += milliseconds },
+ destroy() {
+ art.isDestroy = true
+ art.emit('destroy')
+ },
+ }
+}