mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
124 lines
7.4 KiB
JavaScript
124 lines
7.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import fs from 'node:fs'
|
|
import { hash } from '../../refactor/scripts/releases.mjs'
|
|
import { mbCandidate } from '../helpers/mediabunny.js'
|
|
import { expect, test } from './fixtures.js'
|
|
|
|
const implementation = await mbCandidate()
|
|
|
|
test.beforeEach(async ({ browserName }, testInfo) => {
|
|
await testInfo.attach('mediabunny-available-inputs', { contentType: 'application/json', body: JSON.stringify([implementation].map(item => ({ browserName, name: item.name, sha256: hash(item.code), provenance: item.provenance || { kind: item.name } }))) })
|
|
})
|
|
|
|
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))
|
|
assert.equal(hash(bytes), expected.sha256)
|
|
return [name, bytes]
|
|
}))
|
|
|
|
for (const core of ['published', 'candidate']) {
|
|
for (const scenario of ['video-unsupported', 'audio-unsupported', 'mixed-video-unsupported', 'mixed-audio-unsupported', 'replacement']) {
|
|
test(`MediaBunny ${implementation.name}: ${core} core decoder capability ${scenario}`, async ({ page, browserName }, testInfo) => {
|
|
await page.route('**/mb-cap-hls/**', async (route) => {
|
|
const name = new URL(route.request().url()).pathname.split('/').at(-1)
|
|
const body = hls.get(name)
|
|
await route.fulfill({ status: body ? 200 : 404, body: body || 'Missing fixture', contentType: name.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : 'video/mp2t' })
|
|
})
|
|
await page.goto(`/test/player.html?core=${core}`)
|
|
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports;
|
|
${implementation.code}; window.mbFactory = module.exports.default || module.exports; })();` })
|
|
const capabilities = await page.evaluate(() => ({ AudioContext: typeof AudioContext, webkitAudioContext: typeof window.webkitAudioContext, AudioDecoder: typeof AudioDecoder, VideoDecoder: typeof VideoDecoder }))
|
|
let outcome = 'unverified'
|
|
let result
|
|
try {
|
|
await page.evaluate((scenario) => {
|
|
window.mbEvents = []
|
|
window.mbQueries = { video: 0, audio: 0 }
|
|
window.mbBlock = scenario === 'replacement' ? [] : [scenario.includes('video-') ? 'video' : 'audio']
|
|
window.art = new window.Artplayer({
|
|
container: '.player',
|
|
url: '',
|
|
muted: true,
|
|
setting: true,
|
|
proxy: window.mbFactory({ m3u8: { quality: { control: true, setting: true }, audio: { control: true, setting: true } } }),
|
|
})
|
|
window.mbCanvas = window.art.video
|
|
const engine = window.mbCanvas.engine
|
|
for (const kind of ['video', 'audio']) {
|
|
const load = engine[kind].load.bind(engine[kind])
|
|
engine[kind].load = (media, ...args) => {
|
|
const track = media[`${kind}Track`]
|
|
if (track && window.mbBlock.includes(kind)) {
|
|
track.canDecode = async () => {
|
|
window.mbQueries[kind]++
|
|
return false
|
|
}
|
|
}
|
|
return load(media, ...args)
|
|
}
|
|
}
|
|
for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'seeked', 'error'])
|
|
window.mbCanvas.events.addEventListener(name, () => window.mbEvents.push(name))
|
|
const source = scenario === 'video-unsupported' ? '/test/pattern.mp4' : scenario === 'audio-unsupported' ? '/test/audio-tone.m4a' : '/mb-cap-hls/master.m3u8'
|
|
window.mbLoading = engine.load(source)
|
|
document.querySelector('#play').onclick = () => window.art.play()
|
|
}, scenario)
|
|
await page.evaluate(() => window.mbLoading)
|
|
if (capabilities.AudioContext === 'undefined' && capabilities.webkitAudioContext === 'undefined') {
|
|
expect(browserName).toBe('webkit')
|
|
expect(await page.evaluate(() => window.mbCanvas.error?.code)).toBe(4)
|
|
expect(await page.evaluate(() => window.art.isReady)).toBe(false)
|
|
outcome = 'unsupported-capability-control'
|
|
return
|
|
}
|
|
if (scenario === 'replacement') {
|
|
expect(await page.evaluate(() => window.art.isReady)).toBe(true)
|
|
await expect(page.locator('.art-control-mediabunny-quality')).toHaveCount(1)
|
|
const error = await page.evaluate(async () => {
|
|
window.mbEvents.length = 0
|
|
window.mbBlock = ['video', 'audio']
|
|
try {
|
|
await window.mbCanvas.switchM3u8Quality('auto')
|
|
return null
|
|
}
|
|
catch (error) { return error.message }
|
|
})
|
|
expect(error).toBe('Input has no decodable audio or video tracks.')
|
|
await expect(page.locator('.art-control-mediabunny-quality')).toHaveCount(0)
|
|
await expect(page.locator('.art-control-mediabunny-audio')).toHaveCount(0)
|
|
}
|
|
if (scenario.startsWith('mixed-')) {
|
|
expect(await page.evaluate(() => window.art.isReady)).toBe(true)
|
|
await page.click('#play')
|
|
await expect.poll(() => page.evaluate(() => window.mbCanvas.currentTime)).toBeGreaterThan(0.2)
|
|
result = await page.evaluate(() => ({ video: Boolean(window.mbCanvas.engine.video.videoSink), audio: Boolean(window.mbCanvas.engine.audio.audioSink), nodes: window.mbCanvas.engine.audio.queuedNodes.size, width: window.mbCanvas.videoWidth, events: window.mbEvents, error: window.mbCanvas.error, queries: window.mbQueries }))
|
|
expect(result.error).toBe(null)
|
|
expect(result.events).toEqual(['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough'])
|
|
expect(result.video).toBe(scenario === 'mixed-audio-unsupported')
|
|
expect(result.audio).toBe(scenario === 'mixed-video-unsupported')
|
|
if (result.audio)
|
|
expect(result.nodes).toBeGreaterThan(0)
|
|
if (result.video)
|
|
expect(result.width).toBeGreaterThan(0)
|
|
outcome = 'native-partial-decoder-playback-with-controlled-unsupported-track'
|
|
}
|
|
else {
|
|
result = await page.evaluate(() => ({ readyState: window.mbCanvas.readyState, networkState: window.mbCanvas.networkState, error: window.mbCanvas.error, events: window.mbEvents, queries: window.mbQueries }))
|
|
expect(result.readyState).toBe(0)
|
|
expect(result.networkState).toBe(3)
|
|
expect(result.error).toEqual({ code: 4, message: 'Input has no decodable audio or video tracks.' })
|
|
expect(result.events).toEqual(['error'])
|
|
outcome = 'controlled-unsupported-track-rejected-before-readiness'
|
|
}
|
|
const kind = scenario.includes('video-') ? 'video' : 'audio'
|
|
expect(result.queries).toEqual(scenario === 'replacement' ? { video: 1, audio: 1 } : { video: kind === 'video' ? 1 : 0, audio: kind === 'audio' ? 1 : 0 })
|
|
}
|
|
finally {
|
|
await page.evaluate(() => window.art?.destroy(false))
|
|
await testInfo.attach('mediabunny-capability', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, provenance: implementation.provenance, sha256: hash(implementation.code), core, scenario, capabilities, outcome, result, scope: 'Actual SDK parsing, Canvas, Web Audio and two cores. canDecode=false is deliberately controlled for selected tracks; partial cases play the remaining native decoder. Windows WebKit missing APIs is a separate control, not playback.' }) })
|
|
}
|
|
})
|
|
}
|
|
}
|