mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
test(canvas): [PKG-CANVAS-05] checkpoint installed native picture-in-picture
This commit is contained in:
1 parent
c3967bcbf4
commit
37ebb7a034
21 files changed
+2552
-16
No files matched your search
@@ -140,7 +140,7 @@ jobs:
|
||||
# These extra installed packages are required by the browser helpers.
|
||||
# Other ecosystem installation/SDK gates remain tracked by CI-01.
|
||||
run: |
|
||||
yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas 2>&1 | tee refactor/.cache/ci/browser-packages.log
|
||||
yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip 2>&1 | tee refactor/.cache/ci/browser-packages.log
|
||||
node --input-type=module -e 'import fs from "node:fs"; const { output } = JSON.parse(fs.readFileSync("refactor/.cache/packages/latest.json")); fs.appendFileSync(process.env.GITHUB_ENV, `ARTPLAYER_BROWSER_ARTIFACTS=${output}/browser-artifacts.json\n`);'
|
||||
- name: Run all source browser checks
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
@@ -89,3 +89,9 @@ plugin 1.1.0; candidate core's event-document focus checks fix this independentl
|
||||
Candidate web fullscreen stays in the current ownerDocument body and restores
|
||||
its captured placement. This does not prove native fullscreen availability inside
|
||||
PiP, background throttling or physical-device support; PKG-DPIP-05 stays open.
|
||||
|
||||
`canvas-dpip.spec.js` additionally checks native Canvas video/track adoption,
|
||||
subtitle seeking and all three close/destroy endings. Its installed mode requires
|
||||
the verified five-package map, including Document PiP; the helper rejects missing
|
||||
maps, stale inputs and conflicting source/artifact overrides. This is integration
|
||||
evidence, not full package release acceptance or physical-device evidence.
|
||||
@@ -106,3 +106,10 @@ single-track ownership, ordinary Canvas child insertion/removal and escaped inse
|
||||
after destroy on old/new cores. It also belongs to the installed browser scope;
|
||||
keep its bytes and native engine provenance with the lifecycle report.
|
||||
See `refactor/canvas-validation.md` and the task evidence for actual outcomes and limitations.
|
||||
|
||||
`canvas-dpip.spec.js` follows the same Canvas, backing video and native subtitle
|
||||
track into actual Document PiP windows. It covers playback/draw advancement,
|
||||
paused subtitle seeking and close/native-close/destroy resource ownership. The
|
||||
installed scope requires Document PiP's own verified package bytes as well; a
|
||||
source helper may not fill a missing installed package. Unsupported native APIs
|
||||
are recorded separately and never treated as native playback evidence.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
# PKG-CANVAS-05 Canvas + 原生 Document PiP 检查点
|
||||
|
||||
进行中。新增 canvas-dpip.spec.js 验证实际 requestWindow、Canvas/底层 video/字幕
|
||||
轨道跨文档归属、持续绘制与时间推进、暂停寻址字幕、插件关闭/原生关闭/销毁。
|
||||
使用真实本地媒体和 VTT,RAF 观察器转发原生调度,不替换 PiP API 或媒体状态。
|
||||
不支持的引擎只记录能力缺口,不计作原生窗口播放通过。
|
||||
|
||||
首轮为源码组合,18 项成功退出包含 Chromium/Firefox 12 项真实窗口播放与
|
||||
WebKit 6 项 API 不可用记录。初版明确拒绝安装 map,避免把源码 Document PiP
|
||||
混称为已安装包;报告保留在 canvas-dpip-source-report。
|
||||
|
||||
## 安装与 CI 范围扩展
|
||||
|
||||
package-check 通过已有 dpip-contract 核对发布文件,将 Document PiP 加入显式
|
||||
可选包注册表。现在五包经过源码快照、三格式构建、pack、仓库外离线安装和冻结
|
||||
重装,再校验全部文件摘要。core/chapter 的36运行时、5旧类型+8精确类型模式
|
||||
仍是原来的两包范围;不把它们当作 Document PiP 的完整消费者验收。
|
||||
|
||||
dpipCandidate 现在优先核验显式安装 map,缺文件/缺包/旧输入报错,不再回退到
|
||||
源码;冻结 workspace 与独立 ARTPLAYER_DPIP_ARTIFACT 覆盖也不能混用。这个
|
||||
错误已有旧红新绿:此前不存在的安装map被静默忽略,Node测试3通过1失败;
|
||||
修正后包/runner/CI检查共46通过,包含缺 Document PiP 安装步骤的工作流反例。
|
||||
|
||||
installed scope 和 CI 准备步骤增加第五包及 canvas-dpip.spec.js,仍保留完整
|
||||
source suite、不吞失败、两份独立报告及原消费者执行顺序。main/legacy 的实际
|
||||
浏览器结果与输入摘要见[证据](../baselines/canvas-dpip-validation.json):每种格式
|
||||
均为51项成功退出,其中12项原生PiP、6项API不可用、33项Canvas字幕/生命周期与
|
||||
Ambilight组合。原生窗口覆盖六种 core/ending 组合各在 Chromium/Firefox 执行,
|
||||
不把 WebKit 的能力分支计为原生播放。这里不代表远端 CI 已运行。
|
||||
严格工具 TS、专项/根 lint(0错误/1既有warning)、actionlint 和固定工具链通过。
|
||||
未新增依赖,也没有更改浏览器重试、超时或媒体断言;新测试初版样式诊断已修复。
|
||||
|
||||
## 维护与限制
|
||||
|
||||
Canvas、Document PiP 的架构说明和测试/CI入口已同步。本次没有生产运行时修改,
|
||||
不修改已有全局源码/安装性能门槛。重跑时先提供含五包的安装map,再运行 README
|
||||
所列命令;source 对照仍独立存在。报告中的 API 不可用分支不是设备支持通过。
|
||||
|
||||
PKG-CANVAS-05 仍 doing:实际Safari/移动设备、后台节流和完整生态组合没有闭环。
|
||||
DPIP-05/06、Canvas-06 及全量远端 CI/发布任务仍开放。作为本任务检查点单独本地
|
||||
提交,提交审计通过后接续;没有推送、部署或npm发布。回退需一并还原五包CI配置、
|
||||
加载器、测试与文档,不能只删除安装准备步骤而保留依赖它的新scope。
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
浏览器CI现在分别执行 `yarn test:browser:source` 和
|
||||
`yarn test:browser:installed`。前者清除继承的安装map并保留全部spec;后者要求已
|
||||
核验的core/chapter/Ambilight/Canvas安装,执行明确列出的installed子集。两步不
|
||||
核验的core/chapter/Ambilight/Canvas/Document PiP安装,执行明确列出的installed子集。两步不
|
||||
吞失败,普通失败后仍保存另一范围的证据,分别上传browser-source/browser-installed。
|
||||
入口与报告规则见[维护说明](../scripts/browser-validation/README.md)。source全量
|
||||
耗时/60分钟预算、其余插件安装矩阵与远端运行仍待CI-01/CI-04,不计为已通过。
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 259 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 50 / doing 19 / blocked 0 / done 190 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 49 / doing 20 / blocked 0 / done 190 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
| PKG-CANVAS-02 | artplayer-proxy-canvas<br>建立特有行为与错误测试 | PKG-CANVAS-01, ENG-03, ENG-05 | ready/loadeddata/canplay、play/seek、回调、resize 与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-CANVAS-03 | artplayer-proxy-canvas<br>整理内部职责与资源 | PKG-CANVAS-02, CORE-06, CORE-11, CORE-16 | video adapter/原 canvas 方法/RAF 绘制/事件订阅分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-CANVAS-04 | artplayer-proxy-canvas<br>迁移自有源码和公开类型 | PKG-CANVAS-03, ENG-04, ENG-06, CORE-07 | canvas 与媒体能力的精确组合类型,保持 Result | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-CANVAS-05 | artplayer-proxy-canvas<br>验证新旧核心和组合 | PKG-CANVAS-04, CORE-22, PKG-DPIP-04, PKG-FACTORY-01, PKG-CANVAS-SUBTITLE-01 | 真实 video/canvas 绘制、字幕和 document PiP 恢复 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-CANVAS-05 | artplayer-proxy-canvas<br>验证新旧核心和组合 | PKG-CANVAS-04, CORE-22, PKG-DPIP-04, PKG-FACTORY-01, PKG-CANVAS-SUBTITLE-01 | 真实 video/canvas 绘制、字幕和 document PiP 恢复 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
|
||||
| PKG-CANVAS-06 | artplayer-proxy-canvas<br>验证分发并同步文档 | PKG-CANVAS-05, ENG-07 | canvas.js、三种产物、调用兼容与资源证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
| PKG-CANVAS-SUBTITLE-01 | artplayer-proxy-canvas<br>修复 Canvas 代理原生字幕轨道挂载 | PKG-CANVAS-04 | 初始字幕能力、原生轨道挂载与资源生命周期适配及新旧核心回归 | 复现旧字幕 cues 为空;新旧核心加载、寻址、切换、销毁通过;普通 Canvas 节点行为和分发入口保持兼容 | M | done |
|
||||
|
||||
@@ -611,6 +611,7 @@
|
||||
- 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)
|
||||
- PKG-CANVAS-04: [记录](changes/2026-09-12-PKG-CANVAS-04-types.md) [记录](baselines/canvas-types-validation.json) [记录](canvas-validation.md)
|
||||
- PKG-CANVAS-05: [记录](changes/2026-09-14-PKG-CANVAS-05-native-pip.md) [记录](baselines/canvas-dpip-validation.json) [记录](baselines/canvas-subtitles-validation.json) [记录](baselines/ambilight-installed-validation.json)
|
||||
- PKG-MB-01: [记录](baselines/mb-release.json) [记录](baselines/mb-surface.json) [记录](baselines/mb-contract.md) [记录](baselines/mb-contract-validation.json) [记录](mb-validation.md) [记录](changes/2026-09-12-PKG-MB-01-contract.md)
|
||||
- PKG-MB-02: [记录](changes/2026-09-12-PKG-MB-02-checkpoint.md) [记录](baselines/mb-media-checkpoint.json) [记录](mb-validation.md) [记录](changes/2026-09-12-PKG-MB-02-baseline.md) [记录](baselines/mb-behavior-validation.json)
|
||||
- PKG-MB-03: [记录](changes/2026-09-12-PKG-MB-03-input.md) [记录](baselines/mb-input-validation.json)
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-CANVAS-05 原生 PiP 与五包安装检查点(仍 doing)
|
||||
|
||||
源码首轮18项:Chromium/Firefox实际原生窗口12通过,WebKit仅6项API不可用记录。
|
||||
Canvas、底层video与track一起移入窗口,播放/绘制继续,暂停seek字幕正确;插件
|
||||
关闭、原生关闭和销毁均恢复/释放节点、窗口、占位与RAF。不是iframe模拟。
|
||||
进一步把Document PiP加入已核验安装包注册与CI:五包离线安装/冻结重装和指纹检查
|
||||
通过,main/legacy各51项成功退出(各12原生PiP+6不可用+33其他组合)。
|
||||
修复dpip加载器忽略安装map回退源码的问题:旧Node测试3通过1失败,修正后工程/
|
||||
工作流46通过;DPIP-INSTALLED-PROVENANCE-01按该证据关闭。缺map/缺包/陈旧输入及
|
||||
冲突覆盖均不允许源码替代。严格工具TS、lint(1既有warning)、actionlint/工具链通过。
|
||||
见[检查点](changes/2026-09-14-PKG-CANVAS-05-native-pip.md)和
|
||||
[证据](baselines/canvas-dpip-validation.json)。259项:190 done、20 doing、49 todo。
|
||||
Canvas真机/后台节流、DPIP完整消费者/设备、完整远端CI和发布仍开放;没有生产
|
||||
运行时修改、依赖新增、push、部署或发布。该检查点独立提交后审计再继续下一步。
|
||||
|
||||
## PKG-CANVAS-SUBTITLE-01 原生字幕轨道修复完成
|
||||
|
||||
新旧核心三引擎复现 Canvas 字幕为空。先前说明需修正:初始失败中的 $track
|
||||
|
||||
@@ -273,3 +273,4 @@
|
||||
| VCONSOLE-LIFECYCLE-01 | resolved / 已复现 | vConsole delayed log, panel and scroller work outlives its owner | SITE-07, SITE-05, SITE-VCONSOLE-01 |
|
||||
| CHAPTER-FULLSCREEN-GEOMETRY-01 | resolved / 已复现 | Chromium 原生全屏后章节 hover 标题曾出现后又隐藏 | PKG-CHAPTER-05, PKG-CHAPTER-HOVER-01 |
|
||||
| CANVAS-SUBTITLE-01 | resolved / 已复现 | Canvas底层video缺初始字幕轨道且普通Canvas挂载不能加载原生字幕 | PKG-CANVAS-SUBTITLE-01, PKG-CANVAS-05 |
|
||||
| DPIP-INSTALLED-PROVENANCE-01 | resolved / 已复现 | Document PiP测试加载器忽略显式安装map并回退到源码 | PKG-CANVAS-05, CI-01 |
|
||||
@@ -6241,6 +6241,30 @@
|
||||
"test/browser/canvas-subtitles.spec.js"
|
||||
],
|
||||
"resolutionRationale": "Initial candidate and routing-only controls each fail all six subtitle cases. Bootstrap + insertion adaptation passes; npm 1.1.0 independently fails all six. Expanded native source tests pass 24; installed main and legacy pass 33 each; final six pass after adding baseline selection. Captions also visibly render in the local demo. This fixes subtitle capability/insertion only; broader Canvas integration remains open."
|
||||
},
|
||||
{
|
||||
"id": "DPIP-INSTALLED-PROVENANCE-01",
|
||||
"title": "Document PiP测试加载器忽略显式安装map并回退到源码",
|
||||
"confirmation": "reproduced",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-CANVAS-05",
|
||||
"CI-01"
|
||||
],
|
||||
"evidence": [
|
||||
"test/package-check.test.js",
|
||||
"refactor/baselines/canvas-dpip-validation.json"
|
||||
],
|
||||
"compatibleResolution": "使用已安装文件及source/build指纹,缺输入拒绝执行;保留独立source模式。",
|
||||
"closureCriteria": "缺安装map旧红新绿,实际五包main/legacy原生组合与工作流配置验证。",
|
||||
"workspaceState": "Explicit map takes precedence; DPIP frozen/artifact overrides conflict instead of overriding installed provenance. Current CI prepares five packages before browser scopes.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-14-PKG-CANVAS-05-native-pip.md",
|
||||
"refactor/baselines/canvas-dpip-validation.json",
|
||||
"test/helpers/dpip.js",
|
||||
"refactor/scripts/ci-workflow.test.mjs"
|
||||
],
|
||||
"resolutionRationale": "Old loader test reports missing expected rejection; updated loader rejects missing/stale maps and conflicting modes. Five-package isolated build/pack/install supplies main and legacy native PiP tests with package hashes. Local workflow mutation protects the required DPIP preparation; remote runs remain unverified."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -90,10 +90,10 @@ export function validateCIWorkflow(source) {
|
||||
const vueIndex = browser.steps.findIndex(step => step.run === 'yarn test:vue-consumer 2>&1 | tee refactor/.cache/ci/vue-consumer.log')
|
||||
assert(vueIndex > consumerIndex && !Object.hasOwn(browser.steps[vueIndex], 'if'), 'Run Vue installed consumers after restoring canonical Node')
|
||||
assert(browser.steps.some(step => step.uses?.startsWith('actions/upload-artifact@') && step.if === 'always()' && step.with.path.split('\n').includes('refactor/.cache/vue-consumer-*/')), 'Retain Vue consumer failure evidence')
|
||||
const extraIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas '))
|
||||
const extraIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip '))
|
||||
const sourceEngineIndex = browser.steps.findIndex(step => step.run === 'yarn test:browser:source 2>&1 | tee refactor/.cache/ci/browser-source.log')
|
||||
const engineIndex = browser.steps.findIndex(step => step.run === 'yarn test:browser:installed 2>&1 | tee refactor/.cache/ci/browser-installed.log')
|
||||
assert(extraIndex > Math.max(consumerIndex, reactIndex, vueIndex) && extraIndex < engineIndex, 'Prepare installed Ambilight/Canvas after consumer probes and before browser checks')
|
||||
assert(extraIndex > Math.max(consumerIndex, reactIndex, vueIndex) && extraIndex < engineIndex, 'Prepare installed Ambilight/Canvas/Document PiP after consumer probes and before browser checks')
|
||||
assert(!Object.hasOwn(browser.steps[extraIndex], 'if') && browser.steps[extraIndex].run.includes('GITHUB_ENV') && browser.steps[extraIndex].run.includes('ARTPLAYER_BROWSER_ARTIFACTS='), 'Always select the additional installed browser artifact map')
|
||||
assert(sourceEngineIndex > extraIndex && sourceEngineIndex < engineIndex, 'Retain complete source checks before the additional installed suite')
|
||||
for (const index of [sourceEngineIndex, engineIndex]) {
|
||||
|
||||
@@ -34,6 +34,10 @@ for (const [name, mutate] of [
|
||||
['wrong consumer Node version', w => w.jobs['browser-smoke'].steps.find(s => s.id === 'consumer-node-20').with['node-version'] = '24'],
|
||||
['skipped installed consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.includes('--expected-node 22.12.0')).if = 'false'],
|
||||
['missing browser plugin installation', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include=')).run = 'echo omitted'],
|
||||
['missing Document PiP browser installation', (w) => {
|
||||
const step = w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include='))
|
||||
step.run = step.run.replace(',artplayer-plugin-document-pip', '')
|
||||
}],
|
||||
['skipped browser plugin installation', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include=')).if = 'false'],
|
||||
['missing source browser checks', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:browser:source ')).run = 'echo omitted'],
|
||||
['filtered source browser checks', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:browser:source ')).run += ' --grep subset'],
|
||||
|
||||
+7
-2
@@ -3638,11 +3638,16 @@
|
||||
"PKG-FACTORY-01",
|
||||
"PKG-CANVAS-SUBTITLE-01"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "真实 video/canvas 绘制、字幕和 document PiP 恢复",
|
||||
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-CANVAS-05-native-pip.md",
|
||||
"baselines/canvas-dpip-validation.json",
|
||||
"baselines/canvas-subtitles-validation.json",
|
||||
"baselines/ambilight-installed-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-CANVAS-06",
|
||||
|
||||
@@ -7,8 +7,8 @@ mislabel source/explicit-vendor SDK tests. The caller's environment is untouched
|
||||
Other deliberate fixture switches retain their existing meaning.
|
||||
|
||||
`yarn test:browser:installed` requires ARTPLAYER_BROWSER_ARTIFACTS from
|
||||
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`.
|
||||
Both launcher and config validate the selected four packages' installed hashes
|
||||
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip`.
|
||||
Both launcher and config validate the selected five packages' installed hashes
|
||||
and source/build freshness. `scope.ts` lists the currently supported installed
|
||||
test files. This is a growing installed subset, not full ecosystem acceptance.
|
||||
Source checks remain complete when the installed list grows.
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
export type BrowserScope = 'source' | 'installed'
|
||||
export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas']
|
||||
export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas', 'artplayer-plugin-document-pip']
|
||||
export const installedTests = [
|
||||
'playback.spec.js',
|
||||
'lifecycle.spec.js',
|
||||
@@ -14,6 +14,7 @@ export const installedTests = [
|
||||
'ambilight-proxy.spec.js',
|
||||
'canvas-lifecycle.spec.js',
|
||||
'canvas-subtitles.spec.js',
|
||||
'canvas-dpip.spec.js',
|
||||
]
|
||||
|
||||
export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) {
|
||||
|
||||
@@ -75,6 +75,7 @@ export async function checkPackages({ release = false, include = [] } = {}) {
|
||||
const supported = {
|
||||
'artplayer-plugin-ambilight': async () => (await import('../refactor/scripts/ambilight-contract.mjs')).verifyAmbilightContract(),
|
||||
'artplayer-proxy-canvas': async () => (await import('../refactor/scripts/canvas-contract.mjs')).verifyCanvasContract(),
|
||||
'artplayer-plugin-document-pip': async () => (await import('../refactor/scripts/dpip-contract.mjs')).verifyDpipContract(),
|
||||
}
|
||||
assert(new Set(include).size === include.length, 'Duplicate additional package')
|
||||
const historicalFiles = new Map()
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Browser regression entry
|
||||
|
||||
`canvas-dpip.spec.js` tests actual native windows with the Canvas video and subtitle
|
||||
track, continued playback, paused cue seeking and close/native-close/destroy.
|
||||
Chromium/Firefox native outcomes and WebKit API-unavailable outcomes must be
|
||||
counted separately. The installed scope requires five verified packages, including
|
||||
Document PiP; `dpipCandidate` cannot substitute a source build for a missing map.
|
||||
|
||||
`canvas-subtitles.spec.js` covers native track bootstrap/load/seek/replacement and
|
||||
cleanup on old/new cores, including ordinary Canvas child operations. It is part
|
||||
of both browser scopes. `ARTPLAYER_CANVAS_SUBTITLE_BASELINE=1.1.0` runs the exact
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import process from 'node:process'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { canvasCandidate } from '../helpers/canvas.js'
|
||||
import { dpipCandidate } from '../helpers/dpip.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const canvas = await canvasCandidate()
|
||||
const dpip = await dpipCandidate()
|
||||
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS)
|
||||
assert(canvas.name === 'candidate-installed' && dpip.name === 'candidate-installed', 'Installed Canvas PiP requires both verified packages')
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const ending of ['close', 'native-close', 'destroy']) {
|
||||
test(`${core}: canvas and subtitles survive native Document PiP ${ending}`, async ({ page, context, browserName }, testInfo) => {
|
||||
await page.route('**/canvas-pip.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:00:00.000 --> 00:00:03.000\nOpening caption\n\n00:00:03.000 --> 00:00:08.000\nPiP caption\n' }))
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
const supported = await page.evaluate(() => typeof window.documentPictureInPicture?.requestWindow === 'function')
|
||||
const evidence = { core, ending, browserName, supported, canvas: { name: canvas.name, sha256: hash(canvas.source) }, dpip: { name: dpip.name, sha256: hash(dpip.code) }, outcome: 'incomplete' }
|
||||
if (!supported) {
|
||||
evidence.outcome = 'native-document-pip-unavailable'
|
||||
await testInfo.attach('canvas-dpip', { contentType: 'application/json', body: JSON.stringify(evidence) })
|
||||
return
|
||||
}
|
||||
await page.addScriptTag({ content: `(() => {
|
||||
const pending = new Set();
|
||||
const requestAnimationFrame = callback => { const id = window.requestAnimationFrame(time => { pending.delete(id); callback(time); }); pending.add(id); return id; };
|
||||
const cancelAnimationFrame = id => { pending.delete(id); window.cancelAnimationFrame(id); };
|
||||
const module = { exports: {} }; const exports = module.exports;
|
||||
${canvas.source}; window.canvasFactory = module.exports.default || module.exports;
|
||||
window.canvasPending = pending;
|
||||
})();` })
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${dpip.code}; window.dpipFactory = module.exports.default || module.exports; })();` })
|
||||
await page.evaluate(() => {
|
||||
window.canvasDraws = 0
|
||||
window.canvasErrors = []
|
||||
window.canvasPipEvents = []
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/test/pattern.mp4',
|
||||
muted: true,
|
||||
loop: true,
|
||||
subtitle: { url: '/canvas-pip.vtt' },
|
||||
proxy: window.canvasFactory((context, video) => {
|
||||
window.canvasDraws++
|
||||
window.canvasContext = context
|
||||
window.canvasVideo = video
|
||||
}),
|
||||
plugins: [window.dpipFactory({ fallbackToVideoPiP: false })],
|
||||
})
|
||||
window.originalCanvas = window.art.video
|
||||
window.originalPlayer = window.art.template.$player
|
||||
window.originalParent = window.originalPlayer.parentNode
|
||||
window.pip = window.art.plugins.artplayerPluginDocumentPip
|
||||
window.art.on('document-pip', active => window.canvasPipEvents.push(active))
|
||||
window.art.on('artplayerProxyCanvas:error', error => window.canvasErrors.push(String(error)))
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
const open = document.createElement('button')
|
||||
open.id = 'canvas-open-pip'
|
||||
open.textContent = 'Open Canvas PiP'
|
||||
open.onclick = () => {
|
||||
window.openingCanvasPip = window.pip.open()
|
||||
}
|
||||
document.body.append(open)
|
||||
})
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady && window.art.subtitle.cues.length === 2)).toBe(true)
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.canvasDraws)).toBeGreaterThan(1)
|
||||
const popupPromise = context.waitForEvent('page', { timeout: 8000 })
|
||||
await page.click('#canvas-open-pip')
|
||||
const popup = await popupPromise
|
||||
await page.evaluate(() => window.openingCanvasPip)
|
||||
await expect(popup.locator('canvas.art-video')).toBeVisible()
|
||||
evidence.entered = await page.evaluate(() => ({
|
||||
active: window.pip.isActive,
|
||||
sameCanvas: window.art.video === window.originalCanvas,
|
||||
playerAdopted: window.originalPlayer.ownerDocument === window.documentPictureInPicture.window.document,
|
||||
videoAdopted: window.canvasVideo.ownerDocument === window.originalPlayer.ownerDocument,
|
||||
trackAdopted: window.art.template.$track.ownerDocument === window.originalPlayer.ownerDocument,
|
||||
time: window.art.currentTime,
|
||||
draws: window.canvasDraws,
|
||||
}))
|
||||
expect(evidence.entered.active && evidence.entered.sameCanvas && evidence.entered.playerAdopted && evidence.entered.videoAdopted && evidence.entered.trackAdopted).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.canvasDraws)).toBeGreaterThan(evidence.entered.draws + 2)
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(evidence.entered.time + 0.1)
|
||||
await page.evaluate(() => {
|
||||
window.art.pause()
|
||||
window.art.currentTime = 4
|
||||
})
|
||||
await expect(popup.locator('.art-subtitle')).toHaveText('PiP caption')
|
||||
await expect.poll(() => page.evaluate(() => window.canvasPending.size)).toBe(0)
|
||||
expect(await page.evaluate(() => window.canvasContext.getImageData(0, 0, 1, 1).data[3])).toBe(255)
|
||||
if (ending === 'destroy') {
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
}
|
||||
else {
|
||||
if (ending === 'native-close')
|
||||
await page.evaluate(() => window.documentPictureInPicture.window.close())
|
||||
else await page.evaluate(() => window.pip.close())
|
||||
await expect.poll(() => page.evaluate(() => window.pip.isActive)).toBe(false)
|
||||
expect(await page.evaluate(() => window.originalPlayer.parentNode === window.originalParent && window.canvasVideo.ownerDocument === document && window.originalCanvas === window.art.video)).toBe(true)
|
||||
await expect(page.locator('.art-subtitle')).toHaveText('PiP caption')
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(4.1)
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
}
|
||||
await expect.poll(() => page.evaluate(() => !window.documentPictureInPicture.window || window.documentPictureInPicture.window.closed)).toBe(true)
|
||||
const draws = await page.evaluate(() => window.canvasDraws)
|
||||
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))))
|
||||
evidence.final = await page.evaluate(() => ({ active: window.pip.isActive, connected: window.canvasVideo.isConnected, paused: window.canvasVideo.paused, src: window.canvasVideo.getAttribute('src'), pending: window.canvasPending.size, draws: window.canvasDraws, placeholders: document.querySelectorAll('.artplayer-document-pip-placeholder').length, events: window.canvasPipEvents, errors: window.canvasErrors }))
|
||||
expect(evidence.final).toEqual({ active: false, connected: false, paused: true, src: null, pending: 0, draws, placeholders: 0, events: ending === 'destroy' ? [true] : [true, false], errors: [] })
|
||||
evidence.outcome = 'native-window-playback-subtitles-and-cleanup'
|
||||
}
|
||||
finally {
|
||||
evidence.observed = await page.evaluate(() => ({ active: window.pip?.isActive, paused: window.canvasVideo?.paused, time: window.canvasVideo?.currentTime, draws: window.canvasDraws, subtitle: window.art?.template.$subtitle?.textContent, errors: window.canvasErrors, events: window.canvasPipEvents }))
|
||||
await page.evaluate(() => {
|
||||
if (!window.art.isDestroy)
|
||||
window.art.destroy(false)
|
||||
window.documentPictureInPicture.window?.close()
|
||||
})
|
||||
await testInfo.attach('canvas-dpip', { contentType: 'application/json', body: JSON.stringify(evidence) })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,22 @@ import { build, transform } from 'esbuild'
|
||||
import less from 'less'
|
||||
import { verifyDpipContract } from '../../refactor/scripts/dpip-contract.mjs'
|
||||
import { readMember } from '../../refactor/scripts/releases.mjs'
|
||||
import { verifyInstalledArtifacts } from '../../scripts/installed-artifacts.mjs'
|
||||
import { getEntryFile } from '../../scripts/projects.js'
|
||||
|
||||
export async function dpipCandidate() {
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS) {
|
||||
assert.notEqual(process.env.ARTPLAYER_DPIP_BASELINE, '1', 'Installed candidate cannot use the frozen workspace')
|
||||
assert(!process.env.ARTPLAYER_DPIP_ARTIFACT, 'Installed candidate cannot override Document PiP artifact')
|
||||
const { inputs } = verifyInstalledArtifacts(root, process.env.ARTPLAYER_BROWSER_ARTIFACTS, ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-document-pip'])
|
||||
const input = inputs.find(item => item.name === 'artplayer-plugin-document-pip')
|
||||
return { name: 'candidate-installed', code: await fs.readFile(input.file, 'utf8'), provenance: input }
|
||||
}
|
||||
if (process.env.ARTPLAYER_DPIP_BASELINE === '1')
|
||||
return (await dpipHistorical()).find(item => item.name === 'frozen-workspace')
|
||||
if (process.env.ARTPLAYER_DPIP_ARTIFACT)
|
||||
return { name: 'candidate-artifact', code: await fs.readFile(process.env.ARTPLAYER_DPIP_ARTIFACT, 'utf8') }
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
const output = await build({ entryPoints: [getEntryFile(path.join(root, 'packages/artplayer-plugin-document-pip'))], bundle: true, write: false, format: 'cjs', target: 'es2020', plugins: [{
|
||||
name: 'dpip-less-inline',
|
||||
setup(build) {
|
||||
|
||||
@@ -9,25 +9,34 @@ import { removeConsumer, runtimeConsumer } from '../scripts/package-consumer.mjs
|
||||
import { emitterContracts } from './contracts/emitter.js'
|
||||
import { ambilightCandidate } from './helpers/ambilight.js'
|
||||
import { canvasCandidate } from './helpers/canvas.js'
|
||||
import { dpipCandidate } from './helpers/dpip.js'
|
||||
|
||||
test('Additional browser packages cannot be mistaken for full release consumer acceptance', async () => {
|
||||
await assert.rejects(checkPackages({ release: true, include: ['artplayer-plugin-ambilight'] }), /not full release acceptance/)
|
||||
})
|
||||
|
||||
test('Explicit installed plugin maps never fall back to source or frozen workspace', async () => {
|
||||
const keys = ['ARTPLAYER_BROWSER_ARTIFACTS', 'ARTPLAYER_AMBILIGHT_BASELINE', 'ARTPLAYER_CANVAS_BASELINE']
|
||||
const keys = ['ARTPLAYER_BROWSER_ARTIFACTS', 'ARTPLAYER_AMBILIGHT_BASELINE', 'ARTPLAYER_CANVAS_BASELINE', 'ARTPLAYER_DPIP_BASELINE', 'ARTPLAYER_DPIP_ARTIFACT']
|
||||
const previous = Object.fromEntries(keys.map(key => [key, process.env[key]]))
|
||||
try {
|
||||
process.env.ARTPLAYER_BROWSER_ARTIFACTS = path.resolve('refactor/.cache/absent-installed-plugin-map.json')
|
||||
assert(!fs.existsSync(process.env.ARTPLAYER_BROWSER_ARTIFACTS))
|
||||
delete process.env.ARTPLAYER_AMBILIGHT_BASELINE
|
||||
delete process.env.ARTPLAYER_CANVAS_BASELINE
|
||||
delete process.env.ARTPLAYER_DPIP_BASELINE
|
||||
delete process.env.ARTPLAYER_DPIP_ARTIFACT
|
||||
await assert.rejects(ambilightCandidate(), { code: 'ENOENT' })
|
||||
await assert.rejects(canvasCandidate(), { code: 'ENOENT' })
|
||||
await assert.rejects(dpipCandidate(), { code: 'ENOENT' })
|
||||
process.env.ARTPLAYER_AMBILIGHT_BASELINE = '1'
|
||||
process.env.ARTPLAYER_CANVAS_BASELINE = '1'
|
||||
process.env.ARTPLAYER_DPIP_BASELINE = '1'
|
||||
await assert.rejects(ambilightCandidate(), /cannot use the frozen workspace/)
|
||||
await assert.rejects(canvasCandidate(), /cannot use the frozen workspace/)
|
||||
await assert.rejects(dpipCandidate(), /cannot use the frozen workspace/)
|
||||
delete process.env.ARTPLAYER_DPIP_BASELINE
|
||||
process.env.ARTPLAYER_DPIP_ARTIFACT = 'override.js'
|
||||
await assert.rejects(dpipCandidate(), /cannot override Document PiP artifact/)
|
||||
}
|
||||
finally {
|
||||
for (const key of keys) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Installed package checks
|
||||
|
||||
Run `yarn test:package` with the pinned Node and Yarn after a frozen install. The
|
||||
default consumer scope is core and chapter. For Ambilight/Canvas browser work,
|
||||
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`.
|
||||
These two additional packages have reviewed published contracts and use the same
|
||||
default consumer scope is core and chapter. For Ambilight/Canvas/Document PiP browser work,
|
||||
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip`.
|
||||
These three additional packages have reviewed published contracts and use the same
|
||||
source snapshot/build/pack/offline-install/frozen-reinstall pipeline. Installed
|
||||
files are copied only after archive hash checks. The report explicitly separates
|
||||
the core/chapter runtime/type scope from the additional browser package scope;
|
||||
|
||||
Reference in new issue
Block a user