Files
ArtPlayer/test/browser/asr-initial-error.spec.js

108 lines
5.6 KiB
JavaScript

import process from 'node:process'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
test.beforeAll(async () => {
candidate = await browserCandidate('artplayer-plugin-asr', process.env.ARTPLAYER_ASR_ARTIFACT)
})
async function createFailedPlayer(page, core, mode, testInfo) {
await page.goto(`/test/player.html?core=${core}`)
const capabilities = await page.evaluate(() => ({ context: typeof (window.AudioContext || window.webkitAudioContext), worklet: typeof window.AudioWorkletNode }))
await testInfo.attach('asr-initial-error-inputs', { contentType: 'application/json', body: JSON.stringify({ core, mode, capabilities, provenance: candidate.provenance, errorSource: '/test/manifest.json', initialReady: false, forcedFailure: false }) })
await page.addScriptTag({ content: candidate.code })
await page.evaluate((mode) => {
const Context = window.AudioContext || window.webkitAudioContext
const contexts = []
const chunks = []
if (Context) {
window.AudioContext = class extends Context {
constructor(options) {
super(options)
contexts.push(this)
}
}
}
window.art = new window.Artplayer({
container: '.player',
url: '/test/manifest.json?asr-initial-error=unsupported',
plugins: [window.artplayerPluginAsr({
...(mode === 'capture' ? { audioInput: { type: 'capture' } } : {}),
onAudioChunk({ pcm }) {
let peak = 0
for (const sample of new Int16Array(pcm))
peak = Math.max(peak, Math.abs(sample))
chunks.push({ peak, source: window.art.video.currentSrc })
return 'Recovered audio'
},
})],
})
window.initialErrorState = () => ({ ready: window.art.isReady, destroyed: window.art.isDestroy, error: window.art.video.error?.code || null, paused: window.art.video.paused, contexts: contexts.map(context => context.state), chunks, playResult: window.playResult, subtitle: document.querySelector('.art-layer-asr')?.textContent || '' })
document.querySelector('#play').onclick = async () => {
window.playResult = { outcome: 'pending' }
try {
await window.art.play()
window.playResult = { outcome: 'resolved' }
}
catch (error) {
window.playResult = { outcome: 'rejected', name: error.name, message: error.message }
}
}
}, mode)
await expect.poll(() => page.evaluate(() => window.initialErrorState().error)).toBe(4)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.initialErrorState().playResult?.outcome)).toBe('rejected')
const failed = await page.evaluate(() => window.initialErrorState())
expect(failed.playResult.name).toBe('NotSupportedError')
expect(failed.ready).toBe(false)
expect(failed.contexts).toEqual([])
expect(failed.chunks).toEqual([])
expect(failed.subtitle).toBe('')
await testInfo.attach('asr-initial-media-failure', { contentType: 'application/json', body: JSON.stringify(failed) })
return capabilities
}
test.afterEach(async ({ page }, testInfo) => {
await page.evaluate(() => {
if (window.art && !window.art.isDestroy)
window.art.destroy()
})
const state = await page.evaluate(() => window.initialErrorState?.()).catch(error => ({ error: error.message }))
await testInfo.attach('asr-initial-error-final', { contentType: 'application/json', body: JSON.stringify(state ?? null) })
})
for (const core of ['published-5.3.0', 'published', 'candidate']) {
for (const mode of ['default', 'capture']) {
test(`ASR ${mode} + ${core}: initial unsupported media remains lazy through stop and destroy`, async ({ page }, testInfo) => {
await createFailedPlayer(page, core, mode, testInfo)
await page.evaluate(() => window.art.plugins.artplayerPluginAsr.stop())
expect(await page.evaluate(() => window.initialErrorState().contexts)).toEqual([])
await page.evaluate(() => window.art.destroy())
expect(await page.evaluate(() => window.initialErrorState())).toMatchObject({ destroyed: true, contexts: [], chunks: [] })
await expect(page.locator('.art-video-player')).toHaveCount(0)
})
test(`ASR ${mode} + ${core}: initial unsupported media recovers on the first valid source`, async ({ page }, testInfo) => {
const capabilities = await createFailedPlayer(page, core, mode, testInfo)
if (capabilities.context === 'undefined') {
expect(process.platform).toBe('win32')
expect(testInfo.project.name).toBe('webkit')
expect(capabilities.worklet).toBe('undefined')
test.skip(true, 'Initial failure checked; WebKit lacks WebAudio for recovery, not Safari acceptance')
}
await page.evaluate(() => {
window.art.url = '/test/audio-tone.m4a?asr-initial-error=recovery'
})
await expect.poll(() => page.evaluate(() => window.art.isReady && window.art.video.readyState >= 2)).toBe(true)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.initialErrorState().chunks.filter(chunk => chunk.peak > 0 && chunk.source.includes('asr-initial-error=recovery')).length)).toBeGreaterThanOrEqual(3)
expect(await page.evaluate(() => window.initialErrorState().contexts)).toEqual(['running'])
await expect(page.locator('.art-layer-asr')).toHaveText('Recovered audio')
await testInfo.attach('asr-first-valid-source', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => window.initialErrorState())) })
await page.evaluate(() => window.art.destroy())
await expect.poll(() => page.evaluate(() => window.initialErrorState().contexts)).toEqual(['closed'])
})
}
}