mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-19 11:21:20 -08:00
refactor(mediabunny): [PKG-MB-03] type inputs and cancel owned loads
This commit is contained in:
1 parent
03451c029b
commit
adba8a3d12
35 files changed
+11260
-3327
No files matched your search
@@ -1,11 +1,11 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { mbHistorical } from '../helpers/mediabunny.js'
|
||||
import { mbBrowserImplementations } from '../helpers/mediabunny.js'
|
||||
import { tracklessMp4 } from '../helpers/trackless-mp4.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementations = (await mbHistorical()).filter(item => item.name.startsWith('published'))
|
||||
const implementations = await mbBrowserImplementations()
|
||||
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))
|
||||
@@ -98,7 +98,13 @@ for (const implementation of implementations) {
|
||||
else if (capabilities.AudioContext === 'undefined' && capabilities.webkitAudioContext === 'undefined') {
|
||||
expect(browserName).toBe('webkit')
|
||||
expect(capabilities).toEqual({ VideoDecoder: 'undefined', AudioDecoder: 'undefined', AudioContext: 'undefined', webkitAudioContext: 'undefined' })
|
||||
expect(loaded.error).toEqual({ code: 4, message: 'undefined is not a constructor (evaluating \'new t\')' })
|
||||
if (implementation.name.startsWith('candidate')) {
|
||||
expect(loaded.error.code).toBe(4)
|
||||
expect(loaded.error.message).toMatch(/^undefined is not a constructor \(evaluating 'new [\w$]+(?:\(\))?'\)$/)
|
||||
}
|
||||
else {
|
||||
expect(loaded.error).toEqual({ code: 4, message: 'undefined is not a constructor (evaluating \'new t\')' })
|
||||
}
|
||||
expect(loaded.readyState).toBeLessThan(4)
|
||||
outcome = 'unsupported-capability-control'
|
||||
}
|
||||
@@ -107,9 +113,9 @@ for (const implementation of implementations) {
|
||||
expect(loaded.readyState).toBe(4)
|
||||
if (input === 'trackless') {
|
||||
expect(loaded.dimensions).toEqual([0, 0])
|
||||
expect(await page.evaluate(() => window.mbEvents.filter(item => item.name === 'video:loadedmetadata').length)).toBe(implementation.name === 'published-1.2.0' ? 2 : 1)
|
||||
expect(await page.evaluate(() => window.mbEvents.filter(item => item.name === 'video:loadedmetadata').length)).toBe(implementation.name === 'published-1.0.0' ? 1 : 2)
|
||||
expect(await page.evaluate(() => window.mbEvents.filter(item => item.name === 'video:canplay').length)).toBe(1)
|
||||
outcome = 'historical-trackless-readiness-control'
|
||||
outcome = implementation.name.startsWith('candidate') ? 'candidate-trackless-readiness-gap' : 'historical-trackless-readiness-control'
|
||||
return
|
||||
}
|
||||
const readiness = await page.evaluate(() => window.mbEvents.map(item => item.name).filter(name => ['video:loadedmetadata', 'video:loadeddata', 'video:canplay', 'video:canplaythrough'].includes(name)))
|
||||
@@ -208,7 +214,7 @@ for (const implementation of implementations) {
|
||||
await expect.poll(() => page.evaluate(() => window.mbContext?.state || 'not-created')).toMatch(/^(?:closed|not-created)$/)
|
||||
expect(await page.evaluate(() => window.mbPendingFrames.size)).toBe(0)
|
||||
const cleanup = await page.evaluate(() => ({ audioContext: window.mbContext?.state || 'not-created', pendingFrames: window.mbPendingFrames.size, streamCancelled: window.mbStreamCancelled || false }))
|
||||
await testInfo.attach('mediabunny-native-input', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), input, capabilities, outcome, state, cleanup, hlsManifest: input === 'hls' ? manifest : null, tracklessFixture: input === 'trackless' ? { parentSha256: hash(pattern), sha256: hash(trackless) } : null, scope: 'Actual published proxy with native browser media and controlled ArtPlayer host using actual core utilities/config; frame timestamps sampled against the SDK audio clock, not acoustic/long-run AV-sync acceptance. Cleanup checks settled playback, not pending-operation races.' }) })
|
||||
await testInfo.attach('mediabunny-native-input', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), input, capabilities, outcome, state, cleanup, hlsManifest: input === 'hls' ? manifest : null, tracklessFixture: input === 'trackless' ? { parentSha256: hash(pattern), sha256: hash(trackless) } : null, scope: 'Identified proxy with native browser media and controlled ArtPlayer host using actual core utilities/config; frame timestamps sampled against the SDK audio clock, not acoustic/long-run AV-sync acceptance. Cleanup checks settled playback, not pending-operation races. Candidate trackless readiness remains MB-READY-01, not release acceptance.' }) })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { mbCandidate } from '../helpers/mediabunny.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementation = await mbCandidate()
|
||||
for (const ending of ['destroy', 'source', 'timeout']) {
|
||||
test(`MediaBunny ${implementation.name}: native pending stream cancelled by ${ending}`, async ({ page }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=published')
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports;
|
||||
${implementation.code}; window.mbFactory = module.exports.default || module.exports; })();` })
|
||||
await page.evaluate(() => {
|
||||
const listeners = new Map()
|
||||
window.mbCancelled = []
|
||||
window.mbEvents = []
|
||||
window.mbHost = {
|
||||
constructor: window.Artplayer,
|
||||
option: { url: '', autoSize: false },
|
||||
template: { $player: document.querySelector('.player') },
|
||||
on(name, callback) {
|
||||
const list = listeners.get(name) || []
|
||||
list.push(callback)
|
||||
listeners.set(name, list)
|
||||
},
|
||||
emit(name, ...args) {
|
||||
window.mbEvents.push(name)
|
||||
for (const callback of listeners.get(name) || []) callback(...args)
|
||||
},
|
||||
}
|
||||
window.mbCanvas = window.mbFactory({ loadTimeout: 1000 })(window.mbHost)
|
||||
window.mbStream = new ReadableStream({ cancel() {
|
||||
window.mbCancelled.push('old')
|
||||
} })
|
||||
window.mbSettled = false
|
||||
window.mbLoad = window.mbCanvas.engine.load(window.mbStream).then(() => window.mbSettled = true)
|
||||
})
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.mbStream.locked)).toBe(true)
|
||||
expect(await page.evaluate(() => window.mbSettled)).toBe(false)
|
||||
await page.evaluate((ending) => {
|
||||
if (ending === 'destroy')
|
||||
window.mbHost.emit('destroy')
|
||||
if (ending === 'source') {
|
||||
window.mbReplacement = new ReadableStream({ cancel() {
|
||||
window.mbCancelled.push('new')
|
||||
} })
|
||||
window.mbNextLoad = window.mbCanvas.engine.load(window.mbReplacement)
|
||||
}
|
||||
}, ending)
|
||||
await expect.poll(() => page.evaluate(() => window.mbSettled)).toBe(true)
|
||||
expect(await page.evaluate(() => window.mbCancelled)).toEqual(['old'])
|
||||
const state = await page.evaluate(() => ({ error: window.mbCanvas.error, readyState: window.mbCanvas.readyState, events: window.mbEvents, input: window.mbCanvas.engine.input === null }))
|
||||
expect(state.input).toBe(true)
|
||||
expect(state.readyState).toBe(0)
|
||||
expect(state.events.filter(name => ['video:loadedmetadata', 'video:loadeddata', 'video:canplay'].includes(name))).toEqual([])
|
||||
expect(state.error).toEqual(ending === 'timeout' ? { code: 4, message: 'Load timeout' } : null)
|
||||
if (ending === 'source')
|
||||
await expect.poll(() => page.evaluate(() => window.mbReplacement.locked)).toBe(true)
|
||||
await page.evaluate(() => window.mbHost.emit('destroy'))
|
||||
await expect.poll(() => page.evaluate(() => window.mbCancelled)).toEqual(ending === 'source' ? ['old', 'new'] : ['old'])
|
||||
await testInfo.attach('mediabunny-load-cancellation', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), ending, state, cancellations: await page.evaluate(() => window.mbCancelled), scope: 'Real browser ReadableStream and actual SDK Input disposal before first media bytes; real timeout; controlled ArtPlayer host; does not assert decoder, late frame or audio-node cleanup.' }) })
|
||||
}
|
||||
finally {
|
||||
await page.evaluate(() => window.mbHost.emit('destroy'))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { mbHistorical } from '../helpers/mediabunny.js'
|
||||
import { mbBrowserImplementations } from '../helpers/mediabunny.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementations = (await mbHistorical()).filter(item => item.name.startsWith('published'))
|
||||
const implementations = await mbBrowserImplementations()
|
||||
|
||||
for (const implementation of implementations) {
|
||||
test(`MediaBunny ${implementation.name}: native MP4 playback or exact Windows WebKit capability failure`, async ({ page, browserName }, testInfo) => {
|
||||
|
||||
Reference in new issue
Block a user