Files
ArtPlayer/test/browser/canvas-dpip.spec.js

127 lines
8.1 KiB
JavaScript

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) })
}
})
}
}