mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
test(mask): [PKG-MASK-05] checkpoint native model and historical-core coverage
This commit is contained in:
1 parent
aca4bc1cd2
commit
8abd49bc9c
14 files changed
+5486
-15
No files matched your search
@@ -162,8 +162,13 @@ Two registrations in the same controlled SDK/RAF environment also verify that
|
||||
stopping or destroying one leaves the other model, mask and scheduled frame owned
|
||||
by its original player. Native shared TensorFlow backend state remains unverified.
|
||||
|
||||
Native acceptance still needs actual local model loading, new/old cores, real
|
||||
Danmuku composition, pause/seek/source/layout changes, multiple players, failure
|
||||
recovery and post-destroy resources. The default unversioned solutionPath can
|
||||
`yarn test:browser test/browser/danmuku-mask-native.spec.js --workers=1` now covers
|
||||
actual local model loading, bitmap alpha and owned canvas cleanup, new/old cores,
|
||||
real Danmuku delivery after model readiness, pause/seek and CSS web fullscreen.
|
||||
Set ARTPLAYER_MASK_ARTIFACT to the legacy global build for the second format.
|
||||
Archive each report before another browser run. The startup timestamp diagnostic
|
||||
can still miss a row; do not interpret the post-ready assertions as losslessness.
|
||||
Native acceptance still needs source changes, multiple players, failure recovery,
|
||||
devices and internal GPU/WASM resources. The default unversioned solutionPath can
|
||||
load assets independently of Yarn's SDK resolution. PKG-MASK-05/06 retain the
|
||||
real combination and distribution/release gates after source/type migration.
|
||||
@@ -0,0 +1,484 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-MASK-05",
|
||||
"sharedWith": [
|
||||
"PKG-JASSUB-05"
|
||||
],
|
||||
"sourceCommit": "aca4bc1cd26f3327236cc9801b331c40ac93e385",
|
||||
"capturedAt": "2026-09-13T16:16:17.160Z",
|
||||
"associationScope": "These versions are the core workspace manifests at the Git commits associated with real Mask/JASSUB releases. Archive existence is not a supported-version range or browser validation. Stable 5.3.1 and prerelease 5.3.1-beta.1 are distinct identities.",
|
||||
"existingEvidence": [
|
||||
"refactor/baselines/asr-core.json records prior unavailable 5.3.1 and adjacent published 5.3.0; it is not substituted here",
|
||||
"refactor/baselines/vast-core.json records separate 5.1.7"
|
||||
],
|
||||
"associations": [
|
||||
{
|
||||
"baseline": "refactor/baselines/danmuku-mask-release.json",
|
||||
"plugin": "artplayer-plugin-danmuku-mask",
|
||||
"pluginVersion": "1.1.0",
|
||||
"commit": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
|
||||
"file": "packages/artplayer/package.json",
|
||||
"version": "5.3.1",
|
||||
"sha256LF": "862f3697876fe915dd0ada178da766ce823aae7563ed30b3e5b11b811202929c",
|
||||
"sourceManifest": {
|
||||
"name": "artplayer",
|
||||
"version": "5.3.1",
|
||||
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
|
||||
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.mjs",
|
||||
"require": "./dist/artplayer.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.legacy.js",
|
||||
"require": "./dist/artplayer.legacy.js"
|
||||
},
|
||||
"./i18n/*": {
|
||||
"types": "./types/i18n.d.ts",
|
||||
"import": "./dist/i18n/*.mjs",
|
||||
"require": "./dist/i18n/*.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer.js",
|
||||
"module": "./dist/artplayer.mjs",
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"i18n/*": [
|
||||
"types/i18n.d.ts"
|
||||
],
|
||||
"legacy": [
|
||||
"types/artplayer.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": "./dist/artplayer.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"option-validator": "^2.0.6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"baseline": "refactor/baselines/danmuku-mask-release.json",
|
||||
"plugin": "artplayer-plugin-danmuku-mask",
|
||||
"pluginVersion": "1.0.0",
|
||||
"commit": "b36f88db5f7fe0250285fd4f47229842231f402b",
|
||||
"file": "packages/artplayer/package.json",
|
||||
"version": "5.3.1-beta.1",
|
||||
"sha256LF": "e1a0a390ec10cd8c1c12006ac6515414ae98a964300dd3e9c288377def948497",
|
||||
"sourceManifest": {
|
||||
"name": "artplayer",
|
||||
"version": "5.3.1-beta.1",
|
||||
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
|
||||
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.mjs",
|
||||
"require": "./dist/artplayer.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.legacy.js",
|
||||
"require": "./dist/artplayer.legacy.js"
|
||||
},
|
||||
"./i18n/*": {
|
||||
"types": "./types/i18n.d.ts",
|
||||
"import": "./dist/i18n/*.mjs",
|
||||
"require": "./dist/i18n/*.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer.js",
|
||||
"module": "./dist/artplayer.mjs",
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"i18n/*": [
|
||||
"types/i18n.d.ts"
|
||||
],
|
||||
"legacy": [
|
||||
"types/artplayer.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": "./dist/artplayer.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"option-validator": "^2.0.6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"baseline": "refactor/baselines/jassub-release.json",
|
||||
"plugin": "artplayer-plugin-jassub",
|
||||
"pluginVersion": "1.1.0",
|
||||
"commit": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
|
||||
"file": "packages/artplayer/package.json",
|
||||
"version": "5.3.1",
|
||||
"sha256LF": "862f3697876fe915dd0ada178da766ce823aae7563ed30b3e5b11b811202929c",
|
||||
"scope": "Source-associated core version only, not verified supported core range.",
|
||||
"sourceManifest": {
|
||||
"name": "artplayer",
|
||||
"version": "5.3.1",
|
||||
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
|
||||
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.mjs",
|
||||
"require": "./dist/artplayer.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.legacy.js",
|
||||
"require": "./dist/artplayer.legacy.js"
|
||||
},
|
||||
"./i18n/*": {
|
||||
"types": "./types/i18n.d.ts",
|
||||
"import": "./dist/i18n/*.mjs",
|
||||
"require": "./dist/i18n/*.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer.js",
|
||||
"module": "./dist/artplayer.mjs",
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"i18n/*": [
|
||||
"types/i18n.d.ts"
|
||||
],
|
||||
"legacy": [
|
||||
"types/artplayer.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": "./dist/artplayer.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"option-validator": "^2.0.6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"baseline": "refactor/baselines/jassub-release.json",
|
||||
"plugin": "artplayer-plugin-jassub",
|
||||
"pluginVersion": "1.0.0",
|
||||
"commit": "b69d020c5a5d06e093c63f456ad7d0c7777b0b03",
|
||||
"file": "packages/artplayer/package.json",
|
||||
"version": "5.3.1-beta.1",
|
||||
"sha256LF": "e1a0a390ec10cd8c1c12006ac6515414ae98a964300dd3e9c288377def948497",
|
||||
"scope": "Source-associated core version only, not verified supported core range.",
|
||||
"sourceManifest": {
|
||||
"name": "artplayer",
|
||||
"version": "5.3.1-beta.1",
|
||||
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
|
||||
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.mjs",
|
||||
"require": "./dist/artplayer.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.legacy.js",
|
||||
"require": "./dist/artplayer.legacy.js"
|
||||
},
|
||||
"./i18n/*": {
|
||||
"types": "./types/i18n.d.ts",
|
||||
"import": "./dist/i18n/*.mjs",
|
||||
"require": "./dist/i18n/*.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer.js",
|
||||
"module": "./dist/artplayer.mjs",
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"i18n/*": [
|
||||
"types/i18n.d.ts"
|
||||
],
|
||||
"legacy": [
|
||||
"types/artplayer.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": "./dist/artplayer.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"option-validator": "^2.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"checks": [
|
||||
{
|
||||
"checkedAt": "2026-09-13T16:16:11.693Z",
|
||||
"url": "https://registry.npmjs.org/artplayer",
|
||||
"method": "GET",
|
||||
"status": 200,
|
||||
"bodySha256": "560fa27d689284c6e1743835b765e790613b0b11d7255ea0390eb0017a7e9748",
|
||||
"contentType": "application/json",
|
||||
"containsRequestedVersions": {
|
||||
"5.3.1": false,
|
||||
"5.3.1-beta.1": true
|
||||
},
|
||||
"nearbyVersions": [
|
||||
"5.2.0",
|
||||
"5.2.1",
|
||||
"5.2.2",
|
||||
"5.2.3",
|
||||
"5.2.4",
|
||||
"5.2.5",
|
||||
"5.3.0-beta.0",
|
||||
"5.3.0-beta.1",
|
||||
"5.3.0-beta.2",
|
||||
"5.3.0-beta.3",
|
||||
"5.3.0-beta.4",
|
||||
"5.3.0",
|
||||
"5.3.1-beta.0",
|
||||
"5.3.1-beta.1",
|
||||
"5.4.0"
|
||||
],
|
||||
"distTags": {
|
||||
"latest": "5.4.0",
|
||||
"beta": "5.3.1-beta.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"checkedAt": "2026-09-13T16:16:12.059Z",
|
||||
"url": "https://registry.npmjs.org/artplayer/5.3.1",
|
||||
"method": "GET",
|
||||
"status": 404,
|
||||
"bodySha256": "cb85b239f6d43e568f1a54eeac640ca05397450355d18994fe3719fed61dfd8b",
|
||||
"contentType": "application/json",
|
||||
"body": "\"version not found: 5.3.1\""
|
||||
},
|
||||
{
|
||||
"checkedAt": "2026-09-13T16:16:12.880Z",
|
||||
"url": "https://registry.npmjs.org/artplayer/-/artplayer-5.3.1.tgz",
|
||||
"method": "HEAD",
|
||||
"status": 404,
|
||||
"bodySha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"contentType": "application/json",
|
||||
"body": ""
|
||||
},
|
||||
{
|
||||
"checkedAt": "2026-09-13T16:16:13.152Z",
|
||||
"url": "https://registry.npmjs.org/artplayer/5.3.1-beta.1",
|
||||
"method": "GET",
|
||||
"status": 200,
|
||||
"bodySha256": "c239f203392461685268b388af53eb1ff4cd1f72c2fb7c776181302edb58c074",
|
||||
"contentType": "application/json"
|
||||
},
|
||||
{
|
||||
"checkedAt": "2026-09-13T16:16:16.650Z",
|
||||
"url": "https://registry.npmjs.org/artplayer/-/artplayer-5.3.1-beta.1.tgz",
|
||||
"method": "GET",
|
||||
"status": 200,
|
||||
"byteLength": 152307,
|
||||
"bodySha256": "335d4e042b92249aea5d9dfbc7ea4c9b5fd42c7c427b54c5d4b91ec6068a487e"
|
||||
}
|
||||
]
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"requestedVersion": "5.3.1",
|
||||
"status": "unavailable-in-npm-at-capture",
|
||||
"release": null,
|
||||
"archivePath": null,
|
||||
"note": "Source-associated workspace version only; no npm tarball or integrity is available. No substitute version is selected."
|
||||
},
|
||||
{
|
||||
"requestedVersion": "5.3.1-beta.1",
|
||||
"status": "archive-verified",
|
||||
"release": {
|
||||
"name": "artplayer",
|
||||
"version": "5.3.1-beta.1",
|
||||
"metadataUrl": "https://registry.npmjs.org/artplayer/5.3.1-beta.1",
|
||||
"tarball": "https://registry.npmjs.org/artplayer/-/artplayer-5.3.1-beta.1.tgz",
|
||||
"integrity": "sha512-btjbQwXPl86U/Apja0WrCUF+AXLPacI60eUGAeJTx3EMuUKrVaOFrmWrIeeyMUi5aIzMSXM++tQjlSMJVnmhuQ==",
|
||||
"sha256": "335d4e042b92249aea5d9dfbc7ea4c9b5fd42c7c427b54c5d4b91ec6068a487e",
|
||||
"registryShasum": "15eb5cd86016ff22702d1c4fe69c75293c026baf",
|
||||
"registryGitHead": "3f47356d2ce11a5ebf81fe4a5a30600f5e714d57",
|
||||
"files": {
|
||||
"package/README.md": "2070b94087839a80dc01616a3114cbe2d3a4950018188f433752880daf7843f8",
|
||||
"package/dist/artplayer.js": "e91e64228c57bb97300c709789d5557ab07b133cab307267b4a2a4df3083c861",
|
||||
"package/dist/artplayer.legacy.js": "da8180437fe7f65af62041ef95a5e6c40ef701560cf7d2619c2873bde38d20f0",
|
||||
"package/dist/artplayer.mjs": "04896904fb8ff3c4757bcd790a88f1517c65dba718b8b96ecca347a81694b7e8",
|
||||
"package/dist/i18n/ar.js": "44b94758cfc7c45179ee1a90ca730b91717fccb5a79d27afd691b61fdfdf51e0",
|
||||
"package/dist/i18n/ar.mjs": "5dd3a7183c81350786e95a5f0431de9c64937a16627c2a44042af7cb33d3d646",
|
||||
"package/dist/i18n/cs.js": "76353dbd1b97f52f3bcd7e71aa9117ca7d76d2b2cbf86426cf3dac64afd7abf2",
|
||||
"package/dist/i18n/cs.mjs": "aa6cb5eaa1ce78fd7d4033affb4f48cff4fedd773bf55f88ccaf3d76dd822cb1",
|
||||
"package/dist/i18n/es.js": "7e43252ba479d87c1edd5244a7c870126aae07c8791c365888fe0ba82dd6e85f",
|
||||
"package/dist/i18n/es.mjs": "7a0aa9698ddcb191fc4f62d1fcd07ee004dbb00053b2920cfcf5fa81f71ea71e",
|
||||
"package/dist/i18n/fa.js": "4b60d3d4f5d5e87686a3af179c9b671b4f432888c6f77ae92fb7025386a9910b",
|
||||
"package/dist/i18n/fa.mjs": "261a81e46d40a1bcad64419d4954036fb49e9fea86682a89aaba277bd52d3c54",
|
||||
"package/dist/i18n/fr.js": "410d9552ad387c191d168b6fff6d21538c57e74d07908ebab2c0dedc3577ed90",
|
||||
"package/dist/i18n/fr.mjs": "d8adfe0ba5eb2208a5241675a084dbd1c4d20d8ac2979fcbcb72bef18827fbfd",
|
||||
"package/dist/i18n/id.js": "4a8fc5d2393a5a19a8ba68429545073035c8803d9d3101b696fbc895d20b22b2",
|
||||
"package/dist/i18n/id.mjs": "b3ac8f8c203a7a64374ac64931d7eb3d912c2e71c4a604ddc4651229cca64c07",
|
||||
"package/dist/i18n/pl.js": "a7c421f0264cd054b8d289e553e77018c547417d8d437f24157ccb1a2cd8c4f6",
|
||||
"package/dist/i18n/pl.mjs": "c58ce3f06411e837c28dbea3d2a68f38045da58de3b73ae5457468a01d526884",
|
||||
"package/dist/i18n/ru.js": "5406ee8662a259af703a930c4e241874e12a811304f3e1d40dd00033e7ff0b52",
|
||||
"package/dist/i18n/ru.mjs": "c17c830da2189d4584c816907b6e8626d8606c7a20565b383c4b768be00e6600",
|
||||
"package/dist/i18n/tr.js": "f82cdbf5feefceff68f78466ca261e1ea1c4975c853543947fc91ffd8107ec46",
|
||||
"package/dist/i18n/tr.mjs": "683b768eeea905793f63bf483d9870af10183a718aba84d8ee794934a8f07b22",
|
||||
"package/dist/i18n/vi.js": "c4f0c224404ce73a807758661c9eaa46fb548eb4519a2ee11304396f268a6a0e",
|
||||
"package/dist/i18n/vi.mjs": "68d10c0bb6e7fe4d82817bca883f04f514d73301ff5dfddf02a0a1e3b2bfbc0a",
|
||||
"package/dist/i18n/zh-tw.js": "1241a6749c088192d1118f91ae36a894ecc198875d4b9bdfe2bbf617803e3806",
|
||||
"package/dist/i18n/zh-tw.mjs": "b28d13355759e2b47d0553617a01ad6b3af76d975681b569bad2e99e48d095ff",
|
||||
"package/package.json": "e1a0a390ec10cd8c1c12006ac6515414ae98a964300dd3e9c288377def948497",
|
||||
"package/types/artplayer.d.ts": "28cb09ddffb6591372ab01cbde8ff3c3c0db903eea58851dc6561375e740cc3c",
|
||||
"package/types/component.d.ts": "0118ec5dbb9aa48d735169c74bd65149b72c9f99db29dd635cd41ff69707b68d",
|
||||
"package/types/config.d.ts": "bb40a0ca9d200c15bba7ecba0103d4ce8216e428d05c17ec43288d298223bc68",
|
||||
"package/types/cssVar.d.ts": "2ff9827aa10bf326f60f6cda7556a94fdcabafba529185a1ceae46d865f33697",
|
||||
"package/types/events.d.ts": "afd56b6939bedecc070eecd2b7440d0903ef8d3107fe41d376bfdb9e0ae3362d",
|
||||
"package/types/i18n.d.ts": "01e7d7e7f775166eed6dbc6b86feb26306302fcbfe33fa01b7e363a14b3ef2b0",
|
||||
"package/types/icons.d.ts": "61ce1b0cb9bc7488263b644419b5fd5a13e72de1493c8113eee81d5174f1234b",
|
||||
"package/types/option.d.ts": "d689b55e3fbc0a9ca9c554b5c9f6c1ae5f1d81aed07c174a557c95633a18a276",
|
||||
"package/types/player.d.ts": "8eb3e3b5822139379ad09f6aad8a0da360fb049bd0ebf4540d128cdd1fb5e994",
|
||||
"package/types/quality.d.ts": "dcca8c0f5b5c25373f85825a274fc3ee8beeffd7c8b2a978468e039ff4946a9e",
|
||||
"package/types/setting.d.ts": "cc87ff5ccb87901e12d2f367ec2e872afd5c029b9f6d0a22d01d7d44a63dfb33",
|
||||
"package/types/subtitle.d.ts": "92f711ad0f7924e609ad4425cd26d19c73eb027cccddc6d89fcf9b8e334db065",
|
||||
"package/types/template.d.ts": "22d64dc2c68f8bd90247755a12bcd233343bb67537591ab08e34900917e1723d",
|
||||
"package/types/utils.d.ts": "65cbb66ab2f5d38085e0a1e5ebea57c320d16f566fb6dc8f8e2dac6cf096c004"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "artplayer",
|
||||
"version": "5.3.1-beta.1",
|
||||
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
|
||||
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.mjs",
|
||||
"require": "./dist/artplayer.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"import": "./dist/artplayer.legacy.js",
|
||||
"require": "./dist/artplayer.legacy.js"
|
||||
},
|
||||
"./i18n/*": {
|
||||
"types": "./types/i18n.d.ts",
|
||||
"import": "./dist/i18n/*.mjs",
|
||||
"require": "./dist/i18n/*.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer.js",
|
||||
"module": "./dist/artplayer.mjs",
|
||||
"types": "./types/artplayer.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"i18n/*": [
|
||||
"types/i18n.d.ts"
|
||||
],
|
||||
"legacy": [
|
||||
"types/artplayer.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": "./dist/artplayer.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"option-validator": "^2.0.6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"archivePath": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-5.3.1-beta.1.tgz",
|
||||
"archiveRelativePath": "refactor/.cache/releases/artplayer-5.3.1-beta.1.tgz",
|
||||
"cachedBefore": false,
|
||||
"memberCount": 41,
|
||||
"verification": [
|
||||
"registry metadata agrees with packument",
|
||||
"registry SHA-512 integrity",
|
||||
"registry SHA-1 shasum",
|
||||
"archive SHA-256",
|
||||
"all member SHA-256 hashes",
|
||||
"manifest identity and declared entrypoints"
|
||||
]
|
||||
}
|
||||
],
|
||||
"helperBoundary": "ensureArchive currently restricts versions to stable x.y.z. For the fixed beta identity this capture uses the same releases.mjs verifyIntegrity/archiveFiles/readMember primitives, registry-only download, expected registry SHA-512/SHA-1 verification before cache write, and normal cache filename. No shared helper was modified.",
|
||||
"validationLimits": [
|
||||
"No browser run",
|
||||
"No plugin/core combination acceptance",
|
||||
"No claim of all historical support"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,83 @@
|
||||
# PKG-MASK-05 真实模型与弹幕组合检查点
|
||||
|
||||
本次保持 doing,不增加完成任务数。生产源码、公开声明和构建产物没有修改。
|
||||
新增可重跑的真实 MediaPipe/WASM 测试,固定历史核心来源并增强发布归档工具。
|
||||
|
||||
## 实际覆盖
|
||||
|
||||
`test/browser/danmuku-mask-native.spec.js` 使用正常构建的 Mask 与 Danmuku、仓库内
|
||||
Steve Jobs 视频和 12 个固定模型资源,不替换 SDK、原生媒体时钟或 canvas 编码。
|
||||
候选核心、实际发布核心和实际 5.3.1-beta.1 核心各有两例,分别验证:
|
||||
|
||||
- 模型加载、连续原生输出、PNG 中同时存在透明和不透明像素;stop 后重启、
|
||||
destroy(false) 后画布清零,连续观察 500ms 没有新输出或 mask 复活。
|
||||
- 模型就绪后的时间戳弹幕显示;真实节点仍在原根层且有尺寸;暂停时媒体时钟
|
||||
停止,允许最多一次已在执行的输出;等待原生 seeked 后至少两次新输出,
|
||||
网页全屏进出保持根节点;隐藏/显示后另一条新弹幕正常投递。
|
||||
|
||||
独立审查发现只检查编码时的 currentTime 会误认 seek 前已启动的推理已更新,
|
||||
因此测试明确等待 seeked 并记录其输出计数。visible 事件也必须附有实际节点
|
||||
连接、尺寸与归属证据。截图可见人物区域遮住弹幕,但不是精确分割质量评分。
|
||||
自动夹具同时保存浏览器版本、资源指纹、媒体状态、请求和错误。
|
||||
|
||||
主产物与 legacy 各 18 项、合计 36 项通过,0 重试/跳过;精确环境见
|
||||
[机器记录](../baselines/danmuku-mask-native-validation.json)。浏览器是 Windows 下的
|
||||
Playwright Chromium/Firefox/WebKit;不是连接 Chrome、macOS Safari 或移动真机。
|
||||
无需重新构建生产文件:04 已确认这些产物与 03 的输出一致。
|
||||
|
||||
## 历史失败与尚未修复的现象
|
||||
|
||||
最初收集失败来自 Windows checkout 把 README 换为 CRLF。现在仅文档、声明及
|
||||
JSON 允许 LF 归一化比较,同时记录实际 SHA;执行 JS、WASM 和模型二进制仍需
|
||||
逐字节匹配固定基线,没有修改资源文件。
|
||||
|
||||
首次组合测试以固定 time=6 弹幕作为启动验收,六例中五例没有投递该条。
|
||||
后续 Chromium 诊断保留原条目和原 readys getter 的委托观测:发布核心一例
|
||||
首次采样为 6.202297,已经超过原 6±0.1 秒窗口;模型就绪后新增的未来条目正常。
|
||||
这是实际启动窗口遗漏,尚不足以断言具体哪段 CPU/SDK 操作造成它。首次报告在
|
||||
执行期间有另一处尚未到达的暂停断言编辑,因此只作为固定启动断言的失败证据,
|
||||
不作为全文件版本的最终验收。
|
||||
|
||||
最终用例把启动条目明确保留为诊断,成功断言覆盖模型就绪后的持续组合;
|
||||
最终主产物 9 个组合中 8 个、legacy 9 个组合中 9 个未显示启动条目,这一现象
|
||||
仍然存在;两轮各 9 个模型就绪后的组合断言均通过,不能混为启动验收通过。
|
||||
没有扩大原弹幕资格窗口、添加追补语义或把遗漏标为已修复。启动性能与采样
|
||||
影响保留在 MASK-SCHEDULING-01 / PKG-MASK-05,不能据此声称全时段零丢失。
|
||||
|
||||
## 历史核心与工具修改
|
||||
|
||||
[独立捕获记录](../baselines/danmuku-mask-historical-cores.json) 区分插件发布关联 Git
|
||||
manifest 的核心版本与实际 npm 产物。5.3.1 未找到发布归档:metadata、packument
|
||||
和 tarball 检查保留缺失事实;5.3.1-beta.1 则有实际 tarball、SHA-512/SHA-256、
|
||||
manifest 与入口校验。不能把 prerelease 或相邻 5.3.0 当作 5.3.1。
|
||||
|
||||
共享 releases helper 现在接受受限的 prerelease 标识,仍拒绝路径穿越并保留
|
||||
registry 源、归档完整性验证。新增危险版本反例与真实 beta 归档测试,共三项。
|
||||
浏览器 server 从该归档提取固定主入口,manifest 记录其实际身份与 SHA。
|
||||
原捕获记录中的 stable-only helper 限制是捕获时事实,本次才补齐支持。
|
||||
|
||||
## 重跑与剩余门槛
|
||||
|
||||
使用 .node-version 的 Node 与 Yarn 1.22.22:
|
||||
|
||||
```sh
|
||||
yarn test:browser test/browser/danmuku-mask-native.spec.js --workers=1
|
||||
node --test refactor/scripts/releases.test.mjs
|
||||
```
|
||||
|
||||
第二轮设置 `ARTPLAYER_MASK_ARTIFACT` 为
|
||||
`packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js`。
|
||||
每轮等待进程完成,保存 report.json/results 后再启动下一轮。当前加载方式是
|
||||
script global,不支持把 .mjs 传入该 override;真实 ESM/安装包验证仍属 06。
|
||||
|
||||
尚缺多实例共享原生后端、切源/网络失败恢复、实际推理 fallback、OS 全屏、
|
||||
设备矩阵、GPU/WASM 内部释放和持续资源测量。body-segmentation 1.0.2 的 dispose
|
||||
没有返回内部 close Promise,画布归零不能证明 GPU 已释放。默认远端未固定
|
||||
solutionPath 与许可/分发核对仍在 06;本次仅验证本地固定资源。
|
||||
|
||||
归档工具三项、定向 lint、plan/risk 检查与影响分析通过。共享测试 server/helper
|
||||
使影响分析保守要求全生态 CI;这不是本地全套或远端 CI 已通过的声明。原生报告
|
||||
无 pageerror/console error,媒体 abort/cache 请求诊断保留在机器记录中。
|
||||
|
||||
回退此检查点测试、server 路由与 prerelease 支持不影响生产接口。没有新增依赖、
|
||||
修改锁文件、推送、发布或远端 CI 验收。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 231 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 66 / doing 14 / blocked 0 / done 151 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 65 / doing 15 / blocked 0 / done 151 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -244,7 +244,7 @@
|
||||
| PKG-MASK-02 | artplayer-plugin-danmuku-mask<br>建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-MASK-03 | artplayer-plugin-danmuku-mask<br>整理内部职责与资源 | PKG-MASK-02, CORE-18, PKG-DANMUKU-07 | 模型加载/推理/画布输出分离,阻止重叠推理与过期写入 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-MASK-05 | artplayer-plugin-danmuku-mask<br>验证新旧核心和组合 | PKG-MASK-04, CORE-22 | 真实模型和 danmuku/seek/全屏组合,GPU 资源释放 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-MASK-05 | artplayer-plugin-danmuku-mask<br>验证新旧核心和组合 | PKG-MASK-04, CORE-22 | 真实模型和 danmuku/seek/全屏组合,GPU 资源释放 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
|
||||
| PKG-MASK-06 | artplayer-plugin-danmuku-mask<br>验证分发并同步文档 | PKG-MASK-05, ENG-07 | danmuku.mask.js、资源版本/许可、CPU fallback 和包体积证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-asr
|
||||
@@ -536,6 +536,7 @@
|
||||
- PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json)
|
||||
- PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json)
|
||||
- PKG-MASK-04: [记录](changes/2026-09-14-PKG-MASK-04-types.md) [记录](baselines/danmuku-mask-types-validation.json)
|
||||
- PKG-MASK-05: [记录](changes/2026-09-14-PKG-MASK-05-native-checkpoint.md) [记录](baselines/danmuku-mask-native-validation.json) [记录](baselines/danmuku-mask-historical-cores.json)
|
||||
- PKG-ASR-01: [记录](changes/2026-09-13-PKG-ASR-01-contracts.md) [记录](baselines/asr-contract.md) [记录](baselines/asr-release.json)
|
||||
- PKG-ASR-02: [记录](changes/2026-09-13-PKG-ASR-02-audio-baseline.md) [记录](baselines/asr-audio-validation.json)
|
||||
- PKG-ASR-03: [记录](changes/2026-09-13-PKG-ASR-03-audio-ownership.md) [记录](baselines/asr-ownership-validation.json)
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MASK-05 真实模型组合检查点(仍 doing)
|
||||
|
||||
main/legacy 各 18 项,三个浏览器引擎和三个核心组合共 36 项通过。实际模型、PNG
|
||||
透明像素、弹幕节点、暂停/seek/网页全屏和画布清理已有证据;启动窗口遗漏单独
|
||||
登记 MASK-SCHEDULING-01,GPU/WASM/设备与其他剩余门槛未关闭。
|
||||
[检查点](changes/2026-09-14-PKG-MASK-05-native-checkpoint.md)记录失败历史、独立审查、
|
||||
历史 beta 归档、重跑和限制;没有生产修改或新增 done。
|
||||
|
||||
## PKG-MASK-04 严格TS及公开类型完成
|
||||
|
||||
五个自有模块迁TS且运行产物保持MASK03字节;真实核心宿主、SDK声明桥接和私有资源
|
||||
|
||||
@@ -241,3 +241,4 @@
|
||||
| DANMUKU-HEATMAP-02 | resolved / 已复现 | Issue958 dense automatic heatmaps exceed the fixed Y domain and flatten across video | PKG-DANMUKU-10 |
|
||||
| DANMUKU-TIMING-01 | resolved / 已复现 | Hidden Worker wait is deducted from visible lifetime and can recycle a comment immediately after visible | PKG-DANMUKU-11 |
|
||||
| DANMUKU-SAMPLING-01 | resolved / 已复现 | Serial asynchronous placement stops native frame eligibility sampling and delays existing visible expiry | PKG-DANMUKU-12 |
|
||||
| MASK-SCHEDULING-01 | open / 已复现 | Native Mask startup can coincide with a missed narrow Danmuku timestamp window | PKG-MASK-05 |
|
||||
+25
-3
@@ -754,7 +754,9 @@
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
|
||||
"refactor/baselines/danmuku-mask-contract.md",
|
||||
"refactor/baselines/danmuku-mask-contract-validation.json",
|
||||
"refactor/baselines/danmuku-mask-release.json"
|
||||
"refactor/baselines/danmuku-mask-release.json",
|
||||
"refactor/baselines/danmuku-mask-native-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-MASK-05-native-checkpoint.md"
|
||||
],
|
||||
"compatibleResolution": "Npm dependency versions are separate from unversioned solutionPath CDN assets; keep option override, backend fallback, model result disposal and masks.",
|
||||
"closureCriteria": "固定 SDK/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。"
|
||||
@@ -4922,7 +4924,9 @@
|
||||
"test/danmuku-mask-failures.test.js",
|
||||
"refactor/changes/2026-09-13-PKG-MASK-03-lifecycle.md",
|
||||
"refactor/baselines/danmuku-mask-lifecycle-validation.json",
|
||||
"test/danmuku-mask-lifecycle.test.js"
|
||||
"test/danmuku-mask-lifecycle.test.js",
|
||||
"refactor/baselines/danmuku-mask-native-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-MASK-05-native-checkpoint.md"
|
||||
],
|
||||
"compatibleResolution": "Invalidate stale work while keeping restartable stop distinct from final destroy; release model and subscriptions. PKG-MASK-03 fixes controlled stale work, owned bitmaps and subscriptions; native SDK dispose completion is not exposed by body-segmentation 1.0.2 and remains a real-resource validation limit in PKG-MASK-05.",
|
||||
"closureCriteria": "Pending initialization/inference cannot write masks or schedule work after stop/destroy; errors settle and owned resources are released."
|
||||
@@ -5017,7 +5021,9 @@
|
||||
"test/danmuku-mask-failures.test.js",
|
||||
"refactor/changes/2026-09-13-PKG-MASK-03-lifecycle.md",
|
||||
"refactor/baselines/danmuku-mask-lifecycle-validation.json",
|
||||
"test/danmuku-mask-lifecycle.test.js"
|
||||
"test/danmuku-mask-lifecycle.test.js",
|
||||
"refactor/baselines/danmuku-mask-native-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-MASK-05-native-checkpoint.md"
|
||||
],
|
||||
"compatibleResolution": "Handle missing dependencies and frame/context failures without stale writes or uncontrolled loops while preserving plugin ordering.",
|
||||
"closureCriteria": "Missing Danmuku/context, CORS failure, source changes and ready/destroy timing have controlled and real browser evidence."
|
||||
@@ -5395,6 +5401,22 @@
|
||||
"refactor/changes/2026-09-13-PKG-DANMUKU-12-frame-sampling.md"
|
||||
],
|
||||
"resolutionRationale": "Controlled red/green tests prove continued observation and expiry with serial callback/Worker ownership, lifecycle clearing and ready priority. Three-format native media/RAF and existing two-core scheduling/lifetime tests pass. CPU-blocked unobserved timestamps and sustained load remain separately tracked."
|
||||
},
|
||||
{
|
||||
"id": "MASK-SCHEDULING-01",
|
||||
"title": "Native Mask startup can coincide with a missed narrow Danmuku timestamp window",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"PKG-MASK-05"
|
||||
],
|
||||
"evidence": [
|
||||
"test/browser/danmuku-mask-native.spec.js",
|
||||
"refactor/baselines/danmuku-mask-native-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-MASK-05-native-checkpoint.md"
|
||||
],
|
||||
"compatibleResolution": "Retain startup sampling evidence and distinguish it from post-model-ready delivery; investigate actual startup scheduling without silently adding catch-up semantics.",
|
||||
"closureCriteria": "Identify the startup scheduling cause and assess a compatible mitigation or explicitly reviewed limitation using actual model/media evidence; post-ready success alone cannot close this risk."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
export const refactorDir = fileURLToPath(new URL('../', import.meta.url))
|
||||
@@ -30,7 +32,7 @@ export function readMember(archive, member) {
|
||||
}
|
||||
|
||||
export async function ensureArchive(release) {
|
||||
assert(/^(?:@[a-z0-9]+(?:[.-][a-z0-9]+)*\/)?[a-z0-9]+(?:[.-][a-z0-9]+)*$/.test(release.name) && /^\d+\.\d+\.\d+$/.test(release.version), 'Invalid release identifier')
|
||||
assert(/^(?:@[a-z0-9]+(?:[.-][a-z0-9]+)*\/)?[a-z0-9]+(?:[.-][a-z0-9]+)*$/.test(release.name) && /^\d+\.\d+\.\d+(?:-[a-z0-9]+(?:[.-][a-z0-9]+)*)?$/i.test(release.version), 'Invalid release identifier')
|
||||
const archive = path.join(cacheDir, `${release.name.replace('/', '+')}-${release.version}.tgz`)
|
||||
if (!fs.existsSync(archive)) {
|
||||
const url = new URL(release.tarball)
|
||||
@@ -59,7 +61,8 @@ export async function verifyReleases() {
|
||||
assert.equal(manifest.name, release.name)
|
||||
assert.equal(manifest.version, release.version)
|
||||
for (const field of ['main', 'module', 'types', 'legacy']) {
|
||||
if (manifest[field]) assert(names.includes(`package/${manifest[field].replace(/^\.\//, '')}`), `Missing ${field}: ${release.name}`)
|
||||
if (manifest[field])
|
||||
assert(names.includes(`package/${manifest[field].replace(/^\.\//, '')}`), `Missing ${field}: ${release.name}`)
|
||||
}
|
||||
console.log(`Verified ${release.name}@${release.version}: SHA-512, SHA-256, ${names.length} files and manifest entrypoints`)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Archive verification uses the repository Node baseline runner.
|
||||
import { test } from 'node:test'
|
||||
import { ensureArchive, hash, verifyIntegrity } from './releases.mjs'
|
||||
|
||||
@@ -13,6 +16,15 @@ test('release integrity rejects corrupted bytes and inconsistent frozen digests'
|
||||
test('release cache rejects unsafe package and version path identifiers before I/O', async () => {
|
||||
for (const name of ['../hls.js', '.hls', 'hls..js', 'hls/js', 'hls\\js', 'hls.js/evil', '-hls', '@svta/../outside', '@../outside', '@svta/cml/request', '@svta\\cml-request', '@svta+cml-request', '@svta/', '@svta/.hidden'])
|
||||
await assert.rejects(ensureArchive({ name, version: '1.5.17' }), /Invalid release identifier/)
|
||||
for (const version of ['../1.5.17', '1.5.17/evil', 'latest'])
|
||||
for (const version of ['../1.5.17', '1.5.17/evil', 'latest', '5.3.1-beta/1', '5.3.1-beta\\1', '5.3.1-beta..1', '5.3.1-../outside'])
|
||||
await assert.rejects(ensureArchive({ name: 'hls.js', version }), /Invalid release identifier/)
|
||||
})
|
||||
|
||||
test('release cache accepts the fixed historical prerelease without replacing its identity', async () => {
|
||||
const baseline = JSON.parse(fs.readFileSync(new URL('../baselines/danmuku-mask-historical-cores.json', import.meta.url), 'utf8'))
|
||||
const { release } = baseline.entries.find(entry => entry.requestedVersion === '5.3.1-beta.1')
|
||||
assert.equal(release.version, '5.3.1-beta.1')
|
||||
const archive = await ensureArchive(release)
|
||||
assert(archive.endsWith('artplayer-5.3.1-beta.1.tgz'))
|
||||
verifyIntegrity(fs.readFileSync(archive), release)
|
||||
})
|
||||
+6
-2
@@ -2605,11 +2605,15 @@
|
||||
"PKG-MASK-04",
|
||||
"CORE-22"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "真实模型和 danmuku/seek/全屏组合,GPU 资源释放",
|
||||
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-MASK-05-native-checkpoint.md",
|
||||
"baselines/danmuku-mask-native-validation.json",
|
||||
"baselines/danmuku-mask-historical-cores.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MASK-06",
|
||||
|
||||
@@ -158,3 +158,14 @@ remain observations. No media/RAF clock is replaced or ready state seeded here.
|
||||
across commands and destroy(false), with actual video and placement Worker. It
|
||||
uses explicit ready rows for resource setup and a fixed SVG mask; it does not
|
||||
load the Mask SDK/model or provide timestamp-delivery evidence.
|
||||
|
||||
`danmuku-mask-native.spec.js` loads the actual built Mask/SDK and fixed local
|
||||
MediaPipe assets with native video. It verifies bitmap alpha, stop/restart/destroy,
|
||||
then actual Danmuku delivery after model readiness, pause, seek and CSS web
|
||||
fullscreen with candidate, published and actual 5.3.1-beta.1 cores. Set
|
||||
`ARTPLAYER_MASK_ARTIFACT` to a main/legacy global artifact; ESM is not supported by
|
||||
that override. Run formats sequentially and archive each completed report.
|
||||
The startup time=6 row remains diagnostic because initial model work can coincide
|
||||
with a missed narrow timestamp window. A passing post-ready combination is not
|
||||
proof of startup losslessness, private GPU/WASM closure, OS fullscreen or devices.
|
||||
See refactor/changes/2026-09-14-PKG-MASK-05-native-checkpoint.md for exact limits.
|
||||
@@ -0,0 +1,262 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import process from 'node:process'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const artifact = process.env.ARTPLAYER_MASK_ARTIFACT || 'packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js'
|
||||
const code = fs.readFileSync(artifact, 'utf8')
|
||||
const baseline = JSON.parse(fs.readFileSync('refactor/baselines/danmuku-mask-release.json', 'utf8'))
|
||||
const assets = baseline.sdk.assets.map(({ file, sha256 }) => {
|
||||
const bytes = fs.readFileSync(file)
|
||||
const actual = hash(bytes)
|
||||
// Git checkout may convert documentation/declaration newlines. Executed model
|
||||
// scripts and binaries must retain their exact fixed bytes.
|
||||
const normalizedLF = /\.(?:md|d\.ts|json)$/.test(file) && actual !== sha256
|
||||
assert.equal(normalizedLF ? hash(bytes.toString().replaceAll('\r\n', '\n')) : actual, sha256, `Model asset differs from fixed source: ${file}`)
|
||||
return { file, sha256: actual, baselineSha256: sha256, normalizedLF }
|
||||
})
|
||||
const media = 'docs/assets/sample/steve-jobs.mp4'
|
||||
const danmukuFile = 'packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js'
|
||||
const danmukuCode = fs.readFileSync(danmukuFile, 'utf8')
|
||||
|
||||
for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) {
|
||||
test(`Mask actual local MediaPipe model renders, stops and restarts with ${core} core`, async ({ page }, testInfo) => {
|
||||
test.setTimeout(90000) // Includes two actual model initializations and native video decoding.
|
||||
const external = []
|
||||
const resources = []
|
||||
const origin = new URL(testInfo.project.use.baseURL).origin
|
||||
await page.route('**/*', (route) => {
|
||||
if (new URL(route.request().url()).origin !== origin) {
|
||||
external.push(route.request().url())
|
||||
return route.abort()
|
||||
}
|
||||
return route.continue()
|
||||
})
|
||||
page.on('response', (response) => {
|
||||
if (response.url().includes('/assets/@mediapipe/'))
|
||||
resources.push({ url: response.url(), status: response.status() })
|
||||
})
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: code })
|
||||
await page.evaluate(() => {
|
||||
window.maskNative = { outputs: [], snapshots: [] }
|
||||
const encode = HTMLCanvasElement.prototype.toDataURL
|
||||
HTMLCanvasElement.prototype.toDataURL = function (...args) {
|
||||
const url = encode.apply(this, args)
|
||||
window.maskNative.outputs.push({ canvas: this, url, time: window.art?.currentTime })
|
||||
return url
|
||||
}
|
||||
window.createPlayer('/assets/sample/steve-jobs.mp4')
|
||||
window.art.plugins.add(window.artplayerPluginDanmukuMask({ solutionPath: '/assets/@mediapipe/selfie_segmentation' }))
|
||||
window.maskPlugin = window.art.plugins.artplayerPluginDanmukuMask
|
||||
window.maskLayer = window.art.template.$danmuku
|
||||
})
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.art.duration)).toBeGreaterThan(1)
|
||||
await page.evaluate(() => {
|
||||
window.art.currentTime = Math.min(5, window.art.duration / 2)
|
||||
})
|
||||
await page.locator('#play').click()
|
||||
await page.evaluate(() => window.maskPlugin.start())
|
||||
await expect.poll(() => page.evaluate(() => window.maskNative.outputs.length), { timeout: 30000 }).toBeGreaterThan(1)
|
||||
const first = await page.evaluate(async () => {
|
||||
const records = window.maskNative.outputs
|
||||
const output = records.at(-1)
|
||||
const image = new Image()
|
||||
image.src = output.url
|
||||
await image.decode()
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = image.naturalWidth
|
||||
canvas.height = image.naturalHeight
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(image, 0, 0)
|
||||
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data
|
||||
let transparent = 0
|
||||
let opaque = 0
|
||||
for (let i = 3; i < pixels.length; i += 4) {
|
||||
if (pixels[i] === 0)
|
||||
transparent++
|
||||
if (pixels[i] === 255)
|
||||
opaque++
|
||||
}
|
||||
const value = { time: window.art.currentTime, firstTime: records[0].time, lastTime: output.time, width: canvas.width, height: canvas.height, transparent, opaque, pixels: pixels.length / 4, maskImage: window.maskLayer.style.maskImage.slice(0, 45), outputs: records.length }
|
||||
window.maskNative.snapshots.push(value)
|
||||
return value
|
||||
})
|
||||
expect(first.width).toBeGreaterThan(0)
|
||||
expect(first.height).toBeGreaterThan(0)
|
||||
expect(first.lastTime).toBeGreaterThan(first.firstTime)
|
||||
expect(first.transparent).toBeGreaterThan(0)
|
||||
expect(first.opaque).toBeGreaterThan(0)
|
||||
expect(first.maskImage).toContain('data:image/png')
|
||||
await testInfo.attach('actual-model-page', { body: await page.screenshot(), contentType: 'image/png' })
|
||||
await page.evaluate(() => {
|
||||
window.maskPlugin.stop()
|
||||
window.stoppedOutputCount = window.maskNative.outputs.length
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.maskNative.outputs.every(({ canvas }) => canvas.width === 0 && canvas.height === 0))).toBe(true)
|
||||
expect(await page.evaluate(() => window.maskLayer.style.maskImage)).toBe('none')
|
||||
await page.waitForTimeout(500) // Bounded native observation after output cleanup.
|
||||
expect(await page.evaluate(() => window.maskNative.outputs.length)).toBe(await page.evaluate(() => window.stoppedOutputCount))
|
||||
expect(await page.evaluate(() => window.maskLayer.style.maskImage)).toBe('none')
|
||||
await page.evaluate(() => window.maskPlugin.start())
|
||||
await expect.poll(() => page.evaluate(() => window.maskNative.outputs.length > window.stoppedOutputCount), { timeout: 30000 }).toBe(true)
|
||||
await page.evaluate(() => {
|
||||
window.art.destroy(false)
|
||||
window.destroyedOutputCount = window.maskNative.outputs.length
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.maskNative.outputs.every(({ canvas }) => canvas.width === 0 && canvas.height === 0))).toBe(true)
|
||||
expect(await page.evaluate(() => window.maskLayer.style.maskImage)).toBe('none')
|
||||
await page.waitForTimeout(500)
|
||||
expect(await page.evaluate(() => window.maskNative.outputs.length)).toBe(await page.evaluate(() => window.destroyedOutputCount))
|
||||
expect(await page.evaluate(() => window.maskLayer.style.maskImage)).toBe('none')
|
||||
expect(external).toEqual([])
|
||||
expect(resources.some(item => item.url.endsWith('.wasm') && item.status === 200)).toBe(true)
|
||||
expect(resources.every(item => item.status === 200)).toBe(true)
|
||||
}
|
||||
finally {
|
||||
const state = await page.evaluate(() => ({ snapshots: window.maskNative?.snapshots, outputs: window.maskNative?.outputs.map(({ canvas, time }) => ({ width: canvas.width, height: canvas.height, time })), video: window.art ? { time: window.art.currentTime, duration: window.art.duration, paused: window.art.video.paused, size: [window.art.video.videoWidth, window.art.video.videoHeight] } : null, mask: window.maskLayer?.style.maskImage?.slice(0, 80) }))
|
||||
await testInfo.attach('actual-model-evidence', { body: JSON.stringify({ artifact, sha256: hash(code), core, assets, media: { file: media, sha256: hash(fs.readFileSync(media)) }, external, resources, state, limitation: 'Actual SDK/model, native video and output bitmap lifecycle. No assertion of private MediaPipe/GPU closure or Danmuku layout composition.' }, null, 2), contentType: 'application/json' })
|
||||
await page.evaluate(() => {
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test(`Mask actual model and Danmuku share the layer through pause, seek and web fullscreen with ${core} core`, async ({ page }, testInfo) => {
|
||||
test.setTimeout(90000)
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: danmukuCode })
|
||||
await page.addScriptTag({ content: code })
|
||||
const unexpected = []
|
||||
const origin = new URL(testInfo.project.use.baseURL).origin
|
||||
await page.route('**/*', (route) => {
|
||||
if (new URL(route.request().url()).origin !== origin) {
|
||||
unexpected.push(route.request().url())
|
||||
return route.abort()
|
||||
}
|
||||
return route.continue()
|
||||
})
|
||||
await page.evaluate(() => {
|
||||
window.maskCombination = { times: [], visible: [], snapshots: [], samples: [] }
|
||||
const encode = HTMLCanvasElement.prototype.toDataURL
|
||||
HTMLCanvasElement.prototype.toDataURL = function (...args) {
|
||||
const result = encode.apply(this, args)
|
||||
window.maskCombination.times.push(window.art?.currentTime)
|
||||
return result
|
||||
}
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/assets/sample/steve-jobs.mp4',
|
||||
muted: true,
|
||||
fullscreenWeb: true,
|
||||
plugins: [window.artplayerPluginDanmuku({ danmuku: [{ text: 'Native mask combination', time: 6, mode: 1 }], heatmap: false }), window.artplayerPluginDanmukuMask({ solutionPath: '/assets/@mediapipe/selfie_segmentation' })],
|
||||
})
|
||||
window.maskCombination.layer = window.art.template.$danmuku
|
||||
const owner = window.art.plugins.artplayerPluginDanmuku.config({})
|
||||
const ready = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(owner), 'readys').get
|
||||
Object.defineProperty(owner, 'readys', { configurable: true, get() {
|
||||
const rows = ready.call(this)
|
||||
window.maskCombination.samples.push({ time: window.art.currentTime, wall: performance.now(), rows: rows.map(row => ({ time: row.time, text: row.text })) })
|
||||
return rows
|
||||
} })
|
||||
window.art.on('artplayerPluginDanmuku:visible', (row) => {
|
||||
const node = row.$ref
|
||||
const rect = node?.getBoundingClientRect()
|
||||
window.maskCombination.visible.push({ text: row.text, connected: node?.isConnected, inLayer: node ? window.maskCombination.layer.contains(node) : false, width: rect?.width, height: rect?.height, display: node ? getComputedStyle(node).display : null })
|
||||
})
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
document.querySelector('#pause').onclick = () => window.art.pause()
|
||||
window.maskSnapshot = (phase) => {
|
||||
const layer = window.art.template.$danmuku
|
||||
const rect = layer.getBoundingClientRect()
|
||||
const snapshot = { phase, time: window.art.currentTime, outputs: window.maskCombination.times.length, sameLayer: layer === window.maskCombination.layer, mask: layer.style.maskImage.startsWith('url('), mode: getComputedStyle(layer).maskMode, width: rect.width, height: rect.height, fullscreenWeb: window.art.fullscreenWeb }
|
||||
window.maskCombination.snapshots.push(snapshot)
|
||||
return snapshot
|
||||
}
|
||||
})
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.art.duration)).toBeGreaterThan(20)
|
||||
await page.evaluate(() => {
|
||||
window.art.currentTime = 5
|
||||
})
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.maskCombination.times.length), { timeout: 30000 }).toBeGreaterThan(2)
|
||||
// Keep the startup row and sampling history as diagnostics: native model
|
||||
// initialization may block its narrow existing timestamp window. Exercise
|
||||
// the ongoing combination after the real model has become operational.
|
||||
await page.evaluate(async () => {
|
||||
const time = window.art.currentTime + 1
|
||||
window.maskCombination.scheduled = time
|
||||
await window.art.plugins.artplayerPluginDanmuku.load([{ text: 'Ready mask combination', time, mode: 1 }])
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.maskCombination.visible.map(row => row.text))).toContain('Ready mask combination')
|
||||
const row = await page.evaluate(() => window.maskCombination.visible.find(row => row.text === 'Ready mask combination'))
|
||||
expect(row.connected && row.inLayer).toBe(true)
|
||||
expect(row.width).toBeGreaterThan(0)
|
||||
expect(row.height).toBeGreaterThan(0)
|
||||
expect(row.display).not.toBe('none')
|
||||
const initial = await page.evaluate(() => window.maskSnapshot('playing'))
|
||||
expect(initial.mask).toBe(true)
|
||||
expect(initial.sameLayer).toBe(true)
|
||||
await page.locator('#pause').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.video.paused)).toBe(true)
|
||||
// Wait native frames for the already-running SDK frame to finish. Compare
|
||||
// its media timestamp, rather than forbidding one legitimate late encode.
|
||||
const paused = await page.evaluate(async () => {
|
||||
const time = window.art.currentTime
|
||||
const beforeOutputs = window.maskCombination.times.length
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
return { before: time, after: window.art.currentTime, extraOutputs: window.maskCombination.times.length - beforeOutputs, snapshot: window.maskSnapshot('paused') }
|
||||
})
|
||||
expect(Math.abs(paused.after - paused.before)).toBeLessThan(0.01)
|
||||
expect(paused.extraOutputs).toBeLessThanOrEqual(1)
|
||||
await page.evaluate(async () => {
|
||||
const seeked = new Promise(resolve => window.art.video.addEventListener('seeked', resolve, { once: true }))
|
||||
window.art.currentTime = 20
|
||||
window.art.fullscreenWeb = true
|
||||
await seeked
|
||||
window.maskCombination.seeked = { time: window.art.currentTime, outputs: window.maskCombination.times.length }
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThanOrEqual(19.9)
|
||||
const full = await page.evaluate(() => window.maskSnapshot('fullscreen-paused'))
|
||||
expect(full.sameLayer).toBe(true)
|
||||
expect(full.fullscreenWeb).toBe(true)
|
||||
expect(full.width).toBeGreaterThan(initial.width)
|
||||
await page.evaluate(() => window.art.play())
|
||||
await expect.poll(() => page.evaluate(() => window.maskCombination.times.length - window.maskCombination.seeked.outputs), { timeout: 30000 }).toBeGreaterThanOrEqual(2)
|
||||
expect(await page.evaluate(() => window.maskCombination.times.at(-1))).toBeGreaterThan(20)
|
||||
await page.evaluate(async () => {
|
||||
window.art.fullscreenWeb = false
|
||||
window.art.plugins.artplayerPluginDanmuku.hide()
|
||||
window.art.plugins.artplayerPluginDanmuku.show()
|
||||
await window.art.plugins.artplayerPluginDanmuku.load([{ text: 'After seek and visibility', time: window.art.currentTime + 1, mode: 1 }])
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.maskCombination.visible.map(row => row.text))).toContain('After seek and visibility')
|
||||
const afterRow = await page.evaluate(() => window.maskCombination.visible.find(row => row.text === 'After seek and visibility'))
|
||||
expect(afterRow.connected && afterRow.inLayer).toBe(true)
|
||||
expect(afterRow.width).toBeGreaterThan(0)
|
||||
expect(afterRow.height).toBeGreaterThan(0)
|
||||
expect(afterRow.display).not.toBe('none')
|
||||
const restored = await page.evaluate(() => window.maskSnapshot('restored'))
|
||||
expect(restored.sameLayer).toBe(true)
|
||||
expect(restored.mask).toBe(true)
|
||||
expect(restored.width).toBe(initial.width)
|
||||
expect(restored.mode).toBe('alpha')
|
||||
await testInfo.attach('actual-combination-page', { body: await page.screenshot(), contentType: 'image/png' })
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
expect(await page.evaluate(() => window.maskCombination.layer.style.maskImage)).toBe('none')
|
||||
expect(unexpected).toEqual([])
|
||||
}
|
||||
finally {
|
||||
const state = await page.evaluate(() => ({ times: window.maskCombination.times, visible: window.maskCombination.visible, snapshots: window.maskCombination.snapshots, scheduled: window.maskCombination.scheduled, samples: window.maskCombination.samples, seeked: window.maskCombination.seeked }))
|
||||
await testInfo.attach('actual-combination-evidence', { body: JSON.stringify({ core, mask: { file: artifact, sha256: hash(code) }, danmuku: { file: danmukuFile, sha256: hash(danmukuCode) }, unexpected, state, limitation: 'Actual model, native media, timestamp-delivered Danmuku and CSS web fullscreen. No OS fullscreen, GPU completion or device coverage claim.' }, null, 2), contentType: 'application/json' })
|
||||
await page.evaluate(() => {
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -88,6 +88,12 @@ add('/test/declaration-cues.vtt', fs.readFileSync(path.join(workspace, 'test/bro
|
||||
add('/test/thumbnail-grid.svg', fs.readFileSync(path.join(workspace, 'test/browser/media/thumbnail-grid.svg')), { kind: 'test-thumbnail-grid', file: 'test/browser/media/thumbnail-grid.svg' })
|
||||
add('/test/legacy-safe-area.js', fs.readFileSync(path.join(workspace, 'test/helpers/legacy-safe-area.js')), { kind: 'frozen-own-source', file: 'test/helpers/legacy-safe-area.js', commit: 'ccf77c4e' })
|
||||
add('/test/iframe-boundary-child.html', fs.readFileSync(path.join(workspace, 'test/browser/iframe-boundary-child.html')), { kind: 'iframe-fixture', file: 'test/browser/iframe-boundary-child.html' })
|
||||
const maskHistorical = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/danmuku-mask-historical-cores.json'), 'utf8'))
|
||||
const maskCore = maskHistorical.entries.find(entry => entry.requestedVersion === '5.3.1-beta.1').release
|
||||
const maskCoreMember = `package/${maskCore.manifest.main.replace(/^\.\//, '')}`
|
||||
const maskCoreBytes = readMember(await ensureArchive(maskCore), maskCoreMember)
|
||||
assert.equal(hash(maskCoreBytes), maskCore.files[maskCoreMember], 'Historical Mask core differs from frozen archive')
|
||||
add('/published-5.3.1-beta.1/artplayer.js', maskCoreBytes, { kind: 'npm-release', version: maskCore.version, integrity: maskCore.integrity, member: maskCoreMember })
|
||||
function send(req, res, bytes, filename) {
|
||||
res.setHeader('Content-Type', mime[path.extname(filename)] || 'application/octet-stream')
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
@@ -159,7 +165,7 @@ const server = http.createServer((req, res) => {
|
||||
if (url.pathname === '/test/player.html') {
|
||||
const core = url.searchParams.get('core') || 'candidate'
|
||||
const chapter = url.searchParams.get('chapter') || 'candidate'
|
||||
assert(['candidate', 'published', 'published-4.5.5', 'published-4.5.9', 'published-5.1.7', 'published-5.3.0'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
|
||||
assert(['candidate', 'published', 'published-4.5.5', 'published-4.5.9', 'published-5.1.7', 'published-5.3.0', 'published-5.3.1-beta.1'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
|
||||
const html = fs.readFileSync(path.join(workspace, 'test/browser/player.html'), 'utf8')
|
||||
.replace('__CORE__', core)
|
||||
.replace('__CHAPTER__', chapter)
|
||||
|
||||
Reference in new issue
Block a user