mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
test(mediabunny): [PKG-MB-09] verify native picture-in-picture checkpoint
This commit is contained in:
1 parent
fb9fb23f53
commit
190fdb2776
9 files changed
+1466
-6
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,44 @@
|
||||
# PKG-MB-09 检查点:真实 Document PiP 组合
|
||||
|
||||
本任务保持doing;本检查点只完成原生画中画的首组组合验证,不代表长播放、设备与全部组合验收。
|
||||
执行环境为本机Windows,Playwright Chromium 153.0.8010.12、Firefox 155.0、WebKit 26.6。
|
||||
原生行为按实际API探测与运行结果记录,不由浏览器名称猜测,也不将WebKit称为真实Safari设备。
|
||||
|
||||
新增mediabunny-dpip.spec.js使用固定main产物、实际发布核心/候选核心和Document PiP产物。
|
||||
从真实button click调用requestWindow,未覆盖API,未使用iframe代替窗口。确认独立Window与
|
||||
Canvas/播放器节点迁移,迁移后实际drawImage及媒体时钟继续前进。
|
||||
|
||||
矩阵为2个核心×video-only/HLS音视频×插件close/原生window.close/destroy×3个引擎,共36项。
|
||||
Chromium/Firefox共24项完成真实窗口/Canvas播放;HLS另验证实际AudioBufferSourceNode调度,
|
||||
插件close场景在PiP内切换到90P与French音轨后还原。关闭还原验证原parent/document、同一Canvas、
|
||||
占位符清零和原生窗口关闭;销毁验证引擎关闭且不再向销毁后的ArtPlayer发document-pip=false。
|
||||
WebKit共12项记录为Document PiP API不可用,未创建播放器或计作媒体播放成功。
|
||||
|
||||
此前dpip-helpers.js仍是受控requestWindow与真实iframe DOM测试;本文件有明确独立作用,
|
||||
不能用本轮少数组合替换那些异常时序/部分setup失败回归,也不能把旧iframe证据重新标为原生。
|
||||
首轮12项(8原生/4不可用)及扩展36项均保留report.json和整个results目录。
|
||||
|
||||
验证命令:设置ARTPLAYER_MB_ARTIFACT和ARTPLAYER_DPIP_ARTIFACT指向各包已构建main后,
|
||||
运行`yarn test:browser test/browser/mediabunny-dpip.spec.js`。源模式也能运行,但报告必须记录
|
||||
实际code hash和实施来源;产物验证不能默默降级为源构建。新用例已由默认浏览器文件发现机制纳入。
|
||||
|
||||
结果、产物/测试哈希、核心来源和浏览器版本见[原生窗口证据](../baselines/mb-native-pip-checkpoint.json)。
|
||||
当前没有生产源码或依赖变更。最终完整CI1418项和44重复契约通过,324生产TS;台账失败与修复均保留。
|
||||
|
||||
## 接续范围
|
||||
|
||||
- 生成可重复长AV媒体,独立运行真实持续播放与1×/2×、seek/quality/audio切换,记录逐帧与音频时钟偏差。
|
||||
- 跟踪在用音频节点、迭代器/RAF与destroy后清理,避免测试自身持有所有历史节点造成伪内存增长。
|
||||
- 验证PiP opener不可见、重复开关、恢复播放与更多键盘/焦点组合;本轮opener始终visible,不作后台节流保证。
|
||||
- 原生视频、Canvas proxy与完整Document PiP插件验收仍归PKG-DPIP-05,不能由本轮只测MediaBunny宣告完成。
|
||||
- 真实Safari/设备支持矩阵、长播放和完整分发/许可仍未关闭;MB-CAP-01、MB-LIFE-01、MB-LICENSE-01保持open。
|
||||
|
||||
FFmpeg已在D:\ffmpeg\bin\ffmpeg.exe可用,长媒体应由明确命令生成并保存版本/参数/哈希,不能把短视频
|
||||
若干次seek冒充长时间持续播放。本检查点不发布、不推送,不计作任务完成。
|
||||
|
||||
## CI台账修复
|
||||
|
||||
首轮完整CI在风险检查失败:MB-08验收后更新状态时,遗漏了MB-TYPE-01和MB-READY-01
|
||||
的resolutionEvidence/resolutionRationale。原关闭事实及证据不变,补齐台账要求字段,
|
||||
保留失败日志mb09-native-ci.log,不放宽验证器。今后任务/风险状态与最终文档更新后,
|
||||
还须执行plan --check、risk-register --check及风险测试,避免只检查状态更新前的快照。
|
||||
@@ -101,3 +101,12 @@ video-only输入及替换无sink仍readyState=4;任务保持doing,下一步
|
||||
[变更](changes/2026-09-12-PKG-MB-08-capability.md)和[证据](baselines/mb-capability-validation.json)
|
||||
核对全部源码/产物/报告/安装文件哈希。MB-TYPE-01/MB-READY-01关闭;任务done并立即专用提交。
|
||||
39个WebKit能力对照不是播放或物理Safari支持,长播放/设备/组合及分发许可仍由09/10接续。
|
||||
|
||||
## PKG-MB-09 原生画中画检查点(doing)
|
||||
|
||||
36个新旧核心/媒体/关闭方式/浏览器场景通过,24项实际原生窗口中Canvas解码播放,
|
||||
12项WebKit缺失API对照未计作播放。HLS在画中画中切换质量/音轨,关闭还原与销毁清理通过。
|
||||
本轮opener始终visible,未覆盖后台节流。完整CI1418与44重复契约通过;补齐MB-08风险
|
||||
关闭字段遗漏,失败日志保留。见[变更](changes/2026-09-12-PKG-MB-09-native-pip-checkpoint.md)
|
||||
和[证据](baselines/mb-native-pip-checkpoint.json)。222项:105 done、8 doing、109 todo。
|
||||
MB-09仍doing;接续持续播放/AV同步/更多组合与设备,DPIP-05仍独立验收。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 110 / doing 7 / blocked 0 / done 105 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 109 / doing 8 / blocked 0 / done 105 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
| PKG-MB-06 | artplayer-proxy-mediabunny<br>整理音频解码、时钟与同步 | PKG-MB-04 | AudioEngine 时钟、缓冲和 AudioContext 归属 | AV sync、倍速、无音轨、静音/音量和暂停恢复通过 | H | done |
|
||||
| PKG-MB-07 | artplayer-proxy-mediabunny<br>整理 HLS 配对轨道与 selector | PKG-MB-05, PKG-MB-06, CORE-14 | m3u8 配对、质量/音频选择及拓扑清理 | 实际选择高亮、切到无轨道来源清理、无重复 readiness | H | done |
|
||||
| PKG-MB-08 | artplayer-proxy-mediabunny<br>完成 TS 与媒体能力声明 | PKG-MB-07, ENG-04, ENG-06 | 8 个自有 JS 模块迁移与 Result/shim 类型 | 解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持 | H | done |
|
||||
| PKG-MB-09 | artplayer-proxy-mediabunny<br>完成新旧核心和真实媒体组合 | PKG-MB-08, CORE-22 | 跨浏览器能力、长播放、DPiP、HLS 音轨/质量报告 | 资源释放、事件顺序、AV sync 与支持范围满足基线 | H | todo |
|
||||
| PKG-MB-09 | artplayer-proxy-mediabunny<br>完成新旧核心和真实媒体组合 | PKG-MB-08, CORE-22 | 跨浏览器能力、长播放、DPiP、HLS 音轨/质量报告 | 资源释放、事件顺序、AV sync 与支持范围满足基线 | H | doing |
|
||||
| PKG-MB-10 | artplayer-proxy-mediabunny<br>完成分发和文档 | PKG-MB-09, ENG-07 | mediabunny.js、README、依赖版本、三产物和许可 | tarball 可消费、无意外依赖升级、旧调用与回退可用 | H | todo |
|
||||
|
||||
## 5 包迁移:artplayer-tool-iframe
|
||||
@@ -528,4 +528,5 @@
|
||||
- PKG-MB-06: [记录](baselines/mb-audio-source.json) [记录](changes/2026-09-12-PKG-MB-06-audio.md) [记录](baselines/mb-audio-validation.json)
|
||||
- PKG-MB-07: [记录](baselines/mb-hls-source.json) [记录](changes/2026-09-12-PKG-MB-07-hls.md) [记录](baselines/mb-hls-validation.json)
|
||||
- PKG-MB-08: [记录](baselines/mb-entry-source.json) [记录](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md) [记录](baselines/mb-entry-checkpoint.json) [记录](baselines/mb-capability-source.json) [记录](changes/2026-09-12-PKG-MB-08-capability.md) [记录](baselines/mb-capability-validation.json)
|
||||
- PKG-MB-09: [记录](changes/2026-09-12-PKG-MB-09-native-pip-checkpoint.md) [记录](baselines/mb-native-pip-checkpoint.json)
|
||||
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
|
||||
@@ -1,5 +1,14 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MB-09 原生画中画检查点(doing)
|
||||
|
||||
36个新旧核心/媒体/关闭方式/浏览器场景通过,24项实际原生窗口中Canvas解码播放,
|
||||
12项WebKit缺失API对照未计作播放。HLS在画中画中切换质量/音轨,关闭还原与销毁清理通过。
|
||||
本轮opener始终visible,未覆盖后台节流。完整CI1418与44重复契约通过;补齐MB-08风险
|
||||
关闭字段遗漏,失败日志保留。见[变更](changes/2026-09-12-PKG-MB-09-native-pip-checkpoint.md)
|
||||
和[证据](baselines/mb-native-pip-checkpoint.json)。222项:105 done、8 doing、109 todo。
|
||||
MB-09仍doing;接续持续播放/AV同步/更多组合与设备,DPIP-05仍独立验收。
|
||||
|
||||
## PKG-MB-08完成:严格TS、兼容声明与解码能力边界
|
||||
|
||||
全包34生产TS已迁移,默认可选工厂/精确Canvas兼容;显式媒体类型不泄漏SDK现代声明。
|
||||
|
||||
@@ -54,3 +54,7 @@ yarn ci:check
|
||||
```
|
||||
|
||||
校验已包含在 test:baseline 中:保护既有基线发现、任务引用、证据路径、生成索引、包覆盖、依赖解析和内嵌文件指纹。负例覆盖漏项、错误任务、无证据关闭、越界路径、错误哈希和锁文件版本偏移。它不能自动发现所有新的第三方文件或判定许可证合规,新增资源仍须进入人工/AI 审查与包任务验收。
|
||||
|
||||
任务或风险验收后若再次更新状态/关闭字段,提交前须针对最终快照运行plan --check、
|
||||
risk-register --check与风险测试。resolved/accepted-with-scope必须填写resolutionEvidence
|
||||
和resolutionRationale;不能仅在compatibleResolution或普通evidence中写关闭事实。
|
||||
+12
-2
@@ -3690,7 +3690,12 @@
|
||||
"refactor/baselines/mb-entry-checkpoint.json",
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"refactor/baselines/mb-capability-validation.json"
|
||||
]
|
||||
],
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"refactor/baselines/mb-capability-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Preserved exact optional whole-factory and HTMLCanvasElement result; real VideoShim implements opt-in media/Player/HLS/synthetic-frame types without modern SDK declarations leaking to older compilers. Conditional ESM declarations and classic legacy resolution work. Runtime supports both historical default namespace and callable use without a required default property narrowing factory replacement. Final isolated installs of two frozen releases and candidate pass 17 compiler/runtime cases; seven candidate modes each reject fifteen invalid uses and check bidirectional assignments against actual frozen declaration bytes. Complete distribution/license/device gates remain separate."
|
||||
},
|
||||
{
|
||||
"id": "MB-CAP-01",
|
||||
@@ -3729,7 +3734,12 @@
|
||||
"refactor/baselines/mb-entry-checkpoint.json",
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"refactor/baselines/mb-capability-validation.json"
|
||||
]
|
||||
],
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"refactor/baselines/mb-capability-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Coordinated metadata publishes once, trackless input rejects before decoder setup, and stale callbacks cannot publish. MB-08 additionally rejects existing but wholly undecodable tracks before metadata/data/canplay and before replacement success, preserving one usable audio/video track, SDK query errors and late-operation cancellation. Sixteen controlled Node cases and thirty added browser cases distinguish twelve controlled rejection cases, eight native partial-decoder playback cases and ten missing-API controls; the final 150-case media suite passes. Physical Safari/support-range work remains MB-CAP-01 rather than being claimed from these controls."
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
-2
@@ -3570,11 +3570,14 @@
|
||||
"PKG-MB-08",
|
||||
"CORE-22"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "跨浏览器能力、长播放、DPiP、HLS 音轨/质量报告",
|
||||
"acceptance": "资源释放、事件顺序、AV sync 与支持范围满足基线",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-MB-09-native-pip-checkpoint.md",
|
||||
"baselines/mb-native-pip-checkpoint.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MB-10",
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { dpipCandidate } from '../helpers/dpip.js'
|
||||
import { mbCandidate } from '../helpers/mediabunny.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementation = await mbCandidate()
|
||||
const dpip = await dpipCandidate()
|
||||
const manifest = JSON.parse(fs.readFileSync(new URL('./media/hls/manifest.json', import.meta.url)))
|
||||
const hls = new Map(Object.entries(manifest.files).map(([name, expected]) => {
|
||||
const bytes = fs.readFileSync(new URL(`./media/hls/${name}`, import.meta.url))
|
||||
assert.equal(hash(bytes), expected.sha256)
|
||||
return [name, bytes]
|
||||
}))
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const media of ['video', 'hls']) {
|
||||
for (const ending of ['close', 'native-close', 'destroy']) {
|
||||
test(`MediaBunny ${implementation.name}: ${core} core native Document PiP ${media} ${ending}`, async ({ page, browserName }, testInfo) => {
|
||||
await page.route('**/mb-dpip-hls/**', async (route) => {
|
||||
const name = new URL(route.request().url()).pathname.split('/').at(-1)
|
||||
const body = hls.get(name)
|
||||
await route.fulfill({ status: body ? 200 : 404, body: body || 'Missing fixture', contentType: name.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : 'video/mp2t' })
|
||||
})
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
const supported = await page.evaluate(() => typeof window.documentPictureInPicture?.requestWindow === 'function')
|
||||
if (!supported) {
|
||||
await testInfo.attach('mediabunny-dpip', { contentType: 'application/json', body: JSON.stringify({ core, media, ending, implementation: implementation.name, sha256: hash(implementation.code), dpipSha256: hash(dpip.code), outcome: 'native-document-pip-unavailable', browserName, supported }) })
|
||||
return
|
||||
}
|
||||
for (const [code, global] of [[implementation.code, 'mbFactory'], [dpip.code, 'dpipFactory']]) {
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${code}; window.${global} = module.exports.default || module.exports; })();` })
|
||||
}
|
||||
await page.evaluate((media) => {
|
||||
window.mbDraws = 0
|
||||
window.mbPipEvents = []
|
||||
window.art = new window.Artplayer({ container: '.player', url: media === 'video' ? '/test/pattern.mp4' : '/mb-dpip-hls/master.m3u8', muted: true, proxy: window.mbFactory(), plugins: [window.dpipFactory({ fallbackToVideoPiP: false })] })
|
||||
window.mbCanvas = window.art.video
|
||||
window.mbPlayer = window.art.template.$player
|
||||
window.mbParent = window.mbPlayer.parentNode
|
||||
window.mbPip = window.art.plugins.artplayerPluginDocumentPip
|
||||
window.art.on('document-pip', value => window.mbPipEvents.push(value))
|
||||
const ctx = window.mbCanvas.getContext('2d')
|
||||
const draw = ctx.drawImage.bind(ctx)
|
||||
ctx.drawImage = (...args) => {
|
||||
window.mbDraws++
|
||||
return draw(...args)
|
||||
}
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
const button = document.createElement('button')
|
||||
button.id = 'open-native-pip'
|
||||
button.textContent = 'Open native Document PiP'
|
||||
button.onclick = () => {
|
||||
window.mbOpening = window.mbPip.open()
|
||||
}
|
||||
document.body.append(button)
|
||||
}, media)
|
||||
let result
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.mbCanvas.currentTime)).toBeGreaterThan(0.1)
|
||||
await page.click('#open-native-pip')
|
||||
await page.evaluate(() => window.mbOpening)
|
||||
expect(await page.evaluate(() => window.mbPip.isActive)).toBe(true)
|
||||
const before = await page.evaluate(() => ({ time: window.mbCanvas.currentTime, draws: window.mbDraws, adopted: window.mbPlayer.ownerDocument === window.documentPictureInPicture.window.document, sameCanvas: window.mbCanvas === window.art.video, distinctWindow: window.documentPictureInPicture.window !== window, openerVisibility: document.visibilityState }))
|
||||
expect(before.adopted).toBe(true)
|
||||
expect(before.sameCanvas).toBe(true)
|
||||
expect(before.distinctWindow).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.mbDraws)).toBeGreaterThan(before.draws + 2)
|
||||
await expect.poll(() => page.evaluate(() => window.mbCanvas.currentTime)).toBeGreaterThan(before.time + 0.15)
|
||||
if (media === 'hls') {
|
||||
await expect.poll(() => page.evaluate(() => window.mbCanvas.engine.audio.queuedNodes.size)).toBeGreaterThan(0)
|
||||
if (ending === 'close') {
|
||||
await page.evaluate(async () => {
|
||||
const state = await window.mbCanvas.getM3u8State()
|
||||
await window.mbCanvas.switchM3u8Quality(state.levels.find(level => level.height === 90).id)
|
||||
const next = await window.mbCanvas.getM3u8State()
|
||||
await window.mbCanvas.switchM3u8Audio(next.audios.find(audio => audio.lang === 'fr').id)
|
||||
})
|
||||
expect(await page.evaluate(async () => {
|
||||
const state = await window.mbCanvas.getM3u8State()
|
||||
return { height: state.currentLevel.height, language: state.currentAudio.lang, adopted: window.mbCanvas.ownerDocument === window.documentPictureInPicture.window.document }
|
||||
})).toEqual({ height: 90, language: 'fr', adopted: true })
|
||||
}
|
||||
}
|
||||
if (ending !== 'destroy') {
|
||||
if (ending === 'native-close')
|
||||
await page.evaluate(() => window.documentPictureInPicture.window.close())
|
||||
else await page.evaluate(() => window.mbPip.close())
|
||||
await expect.poll(() => page.evaluate(() => window.mbPip.isActive)).toBe(false)
|
||||
expect(await page.evaluate(() => window.mbPlayer.parentNode === window.mbParent && window.mbPlayer.ownerDocument === document && window.mbCanvas === window.art.video)).toBe(true)
|
||||
}
|
||||
else {
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
await expect.poll(() => page.evaluate(() => window.mbPip.isActive)).toBe(false)
|
||||
expect(await page.evaluate(() => window.mbCanvas.engine.destroyed)).toBe(true)
|
||||
}
|
||||
result = await page.evaluate(() => ({ events: window.mbPipEvents, placeholders: document.querySelectorAll('.artplayer-document-pip-placeholder').length, nativeWindowClosed: !window.documentPictureInPicture.window || window.documentPictureInPicture.window.closed }))
|
||||
expect(result).toEqual({ events: ending === 'destroy' ? [true] : [true, false], placeholders: 0, nativeWindowClosed: true })
|
||||
await testInfo.attach('mediabunny-dpip', { contentType: 'application/json', body: JSON.stringify({ core, media, ending, implementation: implementation.name, sha256: hash(implementation.code), dpipSha256: hash(dpip.code), outcome: 'native-document-pip-canvas-playback', before, result, scope: 'Actual requestWindow from a click, no iframe or API replacement; native Canvas decoding and window lifecycle. HLS includes native audio, and close cases change quality/audio inside PiP.' }) })
|
||||
}
|
||||
finally {
|
||||
await page.evaluate(() => {
|
||||
if (!window.art.isDestroy)
|
||||
window.art.destroy(false)
|
||||
window.documentPictureInPicture.window?.close()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user