mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-22 05:11:20 -08:00
test(chapter): [PKG-CHAPTER-05] checkpoint native media timing controls
This commit is contained in:
1 parent
a03e5acc50
commit
34e4136b43
12 files changed
+8158
-5
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import process from 'node:process'
|
||||
import { devices } from '@playwright/test'
|
||||
import { hoverProgress } from './chapter-hover.ts'
|
||||
import { installChapterTiming, readChapterTiming } from './chapter-timing.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
async function openChapter(page, core, chapter) {
|
||||
@@ -35,6 +37,8 @@ async function openChapter(page, core, chapter) {
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await expect(page.locator('.art-chapter')).toHaveCount(3)
|
||||
if (process.env.ARTPLAYER_CHAPTER_TIMING_DIAGNOSTICS === '1')
|
||||
await installChapterTiming(page)
|
||||
}
|
||||
|
||||
async function hoverChapter(page, percentage, text) {
|
||||
@@ -80,6 +84,8 @@ async function waitForChapterRestart(page, testInfo) {
|
||||
}
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
if (process.env.ARTPLAYER_CHAPTER_TIMING_DIAGNOSTICS === '1')
|
||||
await readChapterTiming(page, testInfo)
|
||||
const state = await page.evaluate(() => ({
|
||||
at: performance.now(),
|
||||
timeOrigin: performance.timeOrigin,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import process from 'node:process'
|
||||
import { installChapterTiming, readChapterTiming } from './chapter-timing.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
test('native paused video restores position after quality URL replacement without ArtPlayer', async ({ page }, testInfo) => {
|
||||
await page.route('**/chapter-native.html', route => route.fulfill({ contentType: 'text/html', body: '<!doctype html><video id="media" muted playsinline style="width:640px;height:300px"></video><button id="play">Play</button><button id="pause">Pause</button><button id="quality">Quality A</button>' }))
|
||||
await page.goto('/chapter-native.html')
|
||||
await installChapterTiming(page, '#media')
|
||||
await page.evaluate((correctionMode) => {
|
||||
const video = document.querySelector('#media')
|
||||
video.muted = true
|
||||
window.nativeRestarts = []
|
||||
document.querySelector('#play').onclick = () => video.play()
|
||||
document.querySelector('#pause').onclick = () => video.pause()
|
||||
document.querySelector('#quality').onclick = () => {
|
||||
const time = video.currentTime
|
||||
window.nativeRestoreTarget = time
|
||||
window.nativeCorrections = 0
|
||||
window.nativeCorrectionMode = correctionMode
|
||||
if (correctionMode !== 'none') {
|
||||
video.addEventListener('seeked', () => {
|
||||
if (!window.nativeCorrections && Math.abs(video.currentTime - time) > 0.05) {
|
||||
window.nativeCorrections++
|
||||
if (correctionMode === 'deferred') {
|
||||
setTimeout(() => {
|
||||
video.currentTime = time
|
||||
}, 0)
|
||||
}
|
||||
else {
|
||||
video.currentTime = time
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
video.addEventListener('loadedmetadata', () => {
|
||||
video.currentTime = time
|
||||
}, { once: true })
|
||||
video.addEventListener('loadeddata', () => {
|
||||
window.nativeRestarts.push(video.currentSrc)
|
||||
}, { once: true })
|
||||
video.src = '/test/pattern.mp4?quality=A'
|
||||
}
|
||||
video.src = '/test/pattern.mp4?quality=B'
|
||||
}, process.env.ARTPLAYER_NATIVE_QUALITY_CORRECTION === 'deferred' ? 'deferred' : process.env.ARTPLAYER_NATIVE_QUALITY_CORRECTION === '1' ? 'direct' : 'none')
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => document.querySelector('#media').readyState >= 2)).toBe(true)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => document.querySelector('#media').currentTime)).toBeGreaterThan(0.2)
|
||||
await page.locator('#pause').click()
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#media').currentTime = 3.008
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => !document.querySelector('#media').seeking)).toBe(true)
|
||||
await page.locator('#quality').click()
|
||||
await expect.poll(() => page.evaluate(() => window.nativeRestarts.length)).toBe(1)
|
||||
await expect.poll(() => page.evaluate(() => {
|
||||
const video = document.querySelector('#media')
|
||||
return !video.seeking && video.readyState >= 2
|
||||
})).toBe(true)
|
||||
const state = await page.evaluate(() => {
|
||||
const video = document.querySelector('#media')
|
||||
return { time: video.currentTime, target: window.nativeRestoreTarget, corrections: window.nativeCorrections, correctionMode: window.nativeCorrectionMode, paused: video.paused, error: video.error?.code || null, artplayerLoaded: typeof window.Artplayer !== 'undefined' }
|
||||
})
|
||||
await testInfo.attach('native-quality-position', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
expect(state.artplayerLoaded).toBe(false)
|
||||
expect(state.time).toBeCloseTo(state.target, 1)
|
||||
expect(state.paused).toBe(true)
|
||||
expect(state.error).toBeNull()
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => document.querySelector('#media').currentTime)).toBeGreaterThan(state.time + 0.2)
|
||||
}
|
||||
finally {
|
||||
await readChapterTiming(page, testInfo)
|
||||
await page.evaluate(() => {
|
||||
const video = document.querySelector('#media')
|
||||
video.pause()
|
||||
video.removeAttribute('src')
|
||||
video.load()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
export async function installChapterTiming(page, selector) {
|
||||
await page.evaluate((selector) => {
|
||||
const video = selector ? document.querySelector(selector) : window.art.video
|
||||
const records = []
|
||||
let sequence = 0
|
||||
const record = (value) => {
|
||||
if (records.length < 10000)
|
||||
records.push({ at: performance.now(), ...value })
|
||||
}
|
||||
for (const name of ['currentTime', 'duration', 'readyState', 'seeking', 'playbackRate', 'paused']) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, name)
|
||||
if (!descriptor?.get)
|
||||
throw new Error(`Missing native media descriptor: ${name}`)
|
||||
const access = (kind, callback, value) => {
|
||||
const id = sequence++
|
||||
const start = performance.now()
|
||||
record({ id, kind, name, phase: 'before', value })
|
||||
try {
|
||||
const result = callback()
|
||||
record({ id, kind, name, phase: 'after', value: result, elapsed: performance.now() - start })
|
||||
return result
|
||||
}
|
||||
catch (error) {
|
||||
record({ id, kind, name, phase: 'error', error: String(error), elapsed: performance.now() - start })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Object.defineProperty(video, name, {
|
||||
configurable: true,
|
||||
get() { return access('get', () => descriptor.get.call(this)) },
|
||||
...(descriptor.set ? { set(value) { access('set', () => descriptor.set.call(this, value), value) } } : {}),
|
||||
})
|
||||
}
|
||||
const events = []
|
||||
for (const name of ['loadstart', 'loadedmetadata', 'loadeddata', 'canplay', 'seeking', 'seeked', 'playing', 'pause', 'error']) {
|
||||
video.addEventListener(name, () => events.push({ name, at: performance.now() }))
|
||||
}
|
||||
const heartbeat = []
|
||||
let previous = performance.now()
|
||||
const timer = setInterval(() => {
|
||||
const at = performance.now()
|
||||
heartbeat.push({ at, gap: at - previous })
|
||||
previous = at
|
||||
}, 100)
|
||||
window.chapterTiming = { records, events, heartbeat, started: previous, timeOrigin: performance.timeOrigin }
|
||||
window.stopChapterTiming = () => clearInterval(timer)
|
||||
}, selector)
|
||||
}
|
||||
|
||||
export async function readChapterTiming(page, testInfo) {
|
||||
const observation = await page.evaluate(() => {
|
||||
window.stopChapterTiming?.()
|
||||
return window.chapterTiming || null
|
||||
}).catch(error => ({ error: error.message }))
|
||||
await testInfo.attach('chapter-native-access-timing', { contentType: 'application/json', body: JSON.stringify(observation) })
|
||||
}
|
||||
Reference in new issue
Block a user