mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-21 21:01:20 -08:00
perf(core): [MOD-03] complete hot-path measurement and compatibility acceptance
This commit is contained in:
1 parent
48a7f1ecb0
commit
b1e0b976b0
11 files changed
+17732
-11
No files matched your search
@@ -231,6 +231,14 @@ Five timings of 1000 synthetic paused updates follow a 300-update warmup; operat
|
||||
counts and actual playback are measured separately. Keep raw samples and do not
|
||||
interpret these diagnostics as full initialization/resource or playback-FPS gates.
|
||||
|
||||
`storage-performance.spec.js` compares published and candidate playback records
|
||||
in three alternating groups with actual video and localStorage. It records five
|
||||
100-event timing samples separately from Storage/JSON instrumentation, verifies
|
||||
fresh nested read results and interleaved unrelated settings, and checks paused
|
||||
updates. Saved media time must fall between native reads immediately around the
|
||||
refresh; exact equality with a later read is invalid when the clock advances.
|
||||
This is a synchronous public-get interleaving test, not a multi-process race test.
|
||||
|
||||
JASSUB Firefox diagnostics retain the canonical failing cases. Set
|
||||
`ARTPLAYER_JASSUB_OFFSCREEN=default` to exercise native capability selection.
|
||||
`ARTPLAYER_JASSUB_ASYNC_RENDER=false` explicitly selects synchronous rendering;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
for (let group = 0; group < 3; group++) {
|
||||
test(`playback persistence measurements and fresh-state contracts group ${group + 1}`, async ({ page }, testInfo) => {
|
||||
const runs = []
|
||||
for (const core of group % 2 ? ['candidate', 'published'] : ['published', 'candidate']) {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('artplayer_settings', JSON.stringify({ times: { another: 2 }, external: { retained: true }, volume: 0.7 }))
|
||||
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, autoPlayback: true, id: 'persistence-measurement' })
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
})
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.playing && window.art.currentTime > 0.1)).toBe(true)
|
||||
try {
|
||||
const result = await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const refresh = () => art.emit('video:timeupdate', new Event('timeupdate'))
|
||||
for (let index = 0; index < 100; index++)
|
||||
refresh()
|
||||
// Timings run without replacing native Storage/JSON methods.
|
||||
const samples = []
|
||||
for (let sample = 0; sample < 5; sample++) {
|
||||
const started = performance.now()
|
||||
for (let index = 0; index < 100; index++)
|
||||
refresh()
|
||||
samples.push(performance.now() - started)
|
||||
}
|
||||
const original = { getItem: Storage.prototype.getItem, setItem: Storage.prototype.setItem, parse: JSON.parse, stringify: JSON.stringify }
|
||||
const counts = { getItem: 0, setItem: 0, parse: 0, stringify: 0 }
|
||||
Storage.prototype.getItem = function (...args) {
|
||||
if (args[0] === art.storage.name)
|
||||
counts.getItem++
|
||||
return original.getItem.apply(this, args)
|
||||
}
|
||||
Storage.prototype.setItem = function (...args) {
|
||||
if (args[0] === art.storage.name)
|
||||
counts.setItem++
|
||||
return original.setItem.apply(this, args)
|
||||
}
|
||||
JSON.parse = (...args) => {
|
||||
counts.parse++
|
||||
return original.parse(...args)
|
||||
}
|
||||
JSON.stringify = (...args) => {
|
||||
counts.stringify++
|
||||
return original.stringify(...args)
|
||||
}
|
||||
try {
|
||||
for (let index = 0; index < 100; index++)
|
||||
refresh()
|
||||
}
|
||||
finally {
|
||||
Storage.prototype.getItem = original.getItem
|
||||
Storage.prototype.setItem = original.setItem
|
||||
JSON.parse = original.parse
|
||||
JSON.stringify = original.stringify
|
||||
}
|
||||
// WebKit's native clock can advance between consecutive property reads.
|
||||
const beforeRefresh = art.currentTime
|
||||
refresh()
|
||||
const afterRefresh = art.currentTime
|
||||
const stored = art.storage.get()
|
||||
const time = { beforeRefresh, afterRefresh }
|
||||
const isolated = art.storage.get()
|
||||
isolated.times.transient = 999
|
||||
isolated.external.retained = false
|
||||
const fresh = art.storage.get()
|
||||
const calls = []
|
||||
const get = art.storage.get
|
||||
// A legal custom get can update unrelated settings between record read and save.
|
||||
art.storage.get = function (key) {
|
||||
calls.push(key ?? null)
|
||||
const result = get.call(this, key)
|
||||
if (key === 'times') {
|
||||
const latest = get.call(this)
|
||||
latest.external = { retained: true, revision: 'interleaved' }
|
||||
localStorage.setItem(this.name, JSON.stringify(latest))
|
||||
}
|
||||
return result
|
||||
}
|
||||
try {
|
||||
refresh()
|
||||
}
|
||||
finally {
|
||||
art.storage.get = get
|
||||
}
|
||||
const merged = art.storage.get()
|
||||
const pausedBefore = localStorage.getItem(art.storage.name)
|
||||
art.pause()
|
||||
refresh()
|
||||
const pausedAfter = localStorage.getItem(art.storage.name)
|
||||
return { samples, counts, time, stored, fresh, merged, calls, pausedBefore, pausedAfter }
|
||||
})
|
||||
runs.push({ core, group, result })
|
||||
expect(result.stored.times['persistence-measurement']).toBeGreaterThanOrEqual(result.time.beforeRefresh)
|
||||
expect(result.stored.times['persistence-measurement']).toBeLessThanOrEqual(result.time.afterRefresh)
|
||||
expect(result.stored.times.another).toBe(2)
|
||||
expect(result.stored.external).toEqual({ retained: true })
|
||||
expect(result.fresh.times).not.toHaveProperty('transient')
|
||||
expect(result.fresh.external).toEqual({ retained: true })
|
||||
expect(result.merged.external).toEqual({ retained: true, revision: 'interleaved' })
|
||||
expect(result.calls).toEqual(['times', null])
|
||||
expect(result.pausedAfter).toBe(result.pausedBefore)
|
||||
}
|
||||
finally {
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
await testInfo.attach(`persistence-${core}`, { contentType: 'application/json', body: JSON.stringify(runs.at(-1) || { core, group, failedBeforeMeasurements: true }) })
|
||||
}
|
||||
}
|
||||
await testInfo.attach('persistence-paired-summary', { contentType: 'application/json', body: JSON.stringify({ runs, limitation: 'Actual video and native same-origin localStorage, followed by synthetic timeupdate dispatch while playing. 100 warmups and five 100-event timing samples without native method wrappers; a separate instrumented window reports Storage/JSON calls. A custom public get tests synchronous interleaving, not a concurrent multi-process storage race. No storage implementation or write cadence change is made.' }) })
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user