mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-21 21:01:20 -08:00
fix(danmuku): [PKG-DANMUKU-START-01] retain the initial playback sampling interval
This commit is contained in:
1 parent
b5f8b5e28e
commit
267b21226a
19 files changed
+9004
-26
No files matched your search
@@ -19,7 +19,7 @@ test.beforeAll(async () => {
|
||||
})
|
||||
|
||||
for (const implementation of ['published', 'candidate']) {
|
||||
for (const scenario of ['main-thread-gap', 'async-visibility-gap']) {
|
||||
for (const scenario of ['main-thread-gap', 'initial-frame-gap', 'initial-zero-frame-gap', 'async-visibility-gap']) {
|
||||
test(`${implementation}: native ${scenario} records actual frame and eligibility gaps`, async ({ page }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=candidate')
|
||||
const code = implementations.get(implementation)
|
||||
@@ -29,6 +29,14 @@ for (const implementation of ['published', 'candidate']) {
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
await page.click('#pause')
|
||||
if (scenario === 'initial-zero-frame-gap') {
|
||||
await page.evaluate(async () => {
|
||||
const art = window.art
|
||||
const seeked = new Promise(resolve => art.video.addEventListener('seeked', resolve, { once: true }))
|
||||
art.currentTime = 0
|
||||
await seeked
|
||||
})
|
||||
}
|
||||
await page.evaluate(async (scenario) => {
|
||||
const art = window.art
|
||||
const evidence = window.timingEvidence = { frames: [], eligibility: [], visible: [], callbacks: [], block: null }
|
||||
@@ -68,6 +76,15 @@ for (const implementation of ['published', 'candidate']) {
|
||||
return ready
|
||||
} })
|
||||
await plugin.load(rows)
|
||||
if (scenario.startsWith('initial-')) {
|
||||
art.once('artplayerPluginDanmuku:start', () => {
|
||||
const start = performance.now()
|
||||
const before = art.currentTime
|
||||
// Hold the real start listener after scheduling but before the first native RAF.
|
||||
while (performance.now() - start < 1000) { /* Controlled CPU load. */ }
|
||||
evidence.block = { start, end: performance.now(), before, after: art.currentTime }
|
||||
})
|
||||
}
|
||||
let blocked = false
|
||||
function observe(wall) {
|
||||
if (art.playing) {
|
||||
@@ -101,8 +118,8 @@ for (const implementation of ['published', 'candidate']) {
|
||||
expect(observation.frames.length).toBeGreaterThan(2)
|
||||
expect(observation.eligibility.length).toBeGreaterThan(2)
|
||||
expect(observation.visible.some(row => row.id === 'sentinel')).toBe(true)
|
||||
if (scenario === 'main-thread-gap')
|
||||
expect(observation.block.end - observation.block.start).toBeGreaterThanOrEqual(800)
|
||||
if (scenario !== 'async-visibility-gap')
|
||||
expect(observation.block.end - observation.block.start).toBeGreaterThanOrEqual(scenario.startsWith('initial-') ? 1000 : 800)
|
||||
else
|
||||
expect(observation.callbacks.find(row => row.id === 'first').end - observation.callbacks.find(row => row.id === 'first').start).toBeGreaterThanOrEqual(550)
|
||||
if (scenario === 'async-visibility-gap') {
|
||||
@@ -117,6 +134,8 @@ for (const implementation of ['published', 'candidate']) {
|
||||
expect(observation.missing).toEqual([])
|
||||
expect(observation.visible.map(row => row.id)).toEqual(['first', 'middle', 'sentinel'])
|
||||
}
|
||||
if (scenario.startsWith('initial-'))
|
||||
expect(observation.eligibility[0].wall).toBeGreaterThanOrEqual(observation.block.end)
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,6 +103,40 @@ test(`${prefix}: non-overlap-disabled placement does not queue a Worker round tr
|
||||
assert.deepEqual([...owner.queue].map(row => row.$ref.offsetTop), [owner.marginTop, owner.marginTop + 20])
|
||||
})
|
||||
|
||||
test(`${prefix}: playback anchors eligibility before a delayed first animation frame`, async (t) => {
|
||||
const { env, plugin, owner } = await fixture({ antiOverlap: false })
|
||||
t.after(() => env.destroy())
|
||||
await plugin.load([{ text: 'first', time: 10.2, mode: 1 }, { text: 'middle', time: 10.5, mode: 1 }])
|
||||
env.art.playing = true
|
||||
env.art.emit('video:play')
|
||||
assert.equal(output(env, 'visible').length, 0, 'Starting must not dispatch outside RAF')
|
||||
env.tick(800)
|
||||
env.art.currentTime = 10.8
|
||||
// A repeated playing event must not move an existing start anchor forward.
|
||||
env.art.emit('video:playing')
|
||||
assert.equal(owner.readys.length, 0, 'The public getter stays a point-in-time query')
|
||||
await env.frame()
|
||||
await env.flush()
|
||||
assert.deepEqual(output(env, 'visible').map(event => event.args[0].text), ['first', 'middle'])
|
||||
})
|
||||
|
||||
test(`${prefix}: native playback at zero anchors before the legacy playing getter becomes true`, async (t) => {
|
||||
const { env, plugin, owner } = await fixture({ antiOverlap: false })
|
||||
t.after(() => env.destroy())
|
||||
env.art.currentTime = 0
|
||||
env.art.video = { paused: false, ended: false, readyState: 4, seeking: false }
|
||||
await plugin.load([{ text: 'first', time: 0.2, mode: 1 }, { text: 'middle', time: 0.5, mode: 1 }])
|
||||
env.art.emit('video:play')
|
||||
assert.equal(output(env, 'visible').length, 0)
|
||||
env.tick(800)
|
||||
env.art.currentTime = 0.8
|
||||
env.art.playing = true
|
||||
assert.equal(owner.readys.length, 0)
|
||||
await env.frame()
|
||||
await env.flush()
|
||||
assert.deepEqual(output(env, 'visible').map(event => event.args[0].text), ['first', 'middle'])
|
||||
})
|
||||
|
||||
for (const boundary of ['continuous', 'seek', 'stop', 'hide', 'visibility']) {
|
||||
test(`${prefix}: CPU gap recovery respects ${boundary} boundaries and keeps public readys unchanged`, async (t) => {
|
||||
const { env, plugin, owner } = await fixture({}, { automatic: true })
|
||||
|
||||
Reference in new issue
Block a user