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

105 lines
5.5 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)
})
test.afterEach(async ({ page }, testInfo) => {
const state = await page.evaluate(() => window.mediaErrorState?.()).catch(error => ({ error: error.message }))
await testInfo.attach('asr-media-error-final', { contentType: 'application/json', body: JSON.stringify(state ?? null) })
await page.evaluate(() => {
if (window.art && !window.art.isDestroy)
window.art.destroy()
})
})
for (const core of ['published-5.3.0', 'published', 'candidate']) {
for (const mode of ['default', 'capture']) {
test(`ASR ${mode} + ${core}: native media error invalidates pending recognition and permits recovery`, async ({ page }, 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-media-error-inputs', { contentType: 'application/json', body: JSON.stringify({ core, mode, capabilities, provenance: candidate.provenance, recognizer: 'Controlled pending caller Promise; PCM and media errors are native', errorSource: '/test/manifest.json', mediaAssignment: 'public art.video.src' }) })
if (capabilities.context === 'undefined') {
expect(process.platform).toBe('win32')
expect(testInfo.project.name).toBe('webkit')
expect(capabilities.worklet).toBe('undefined')
test.skip(true, 'Windows WebKit lacks native WebAudio; not Safari/device ASR acceptance')
}
await page.addScriptTag({ content: candidate.code })
await page.evaluate((mode) => {
const Context = window.AudioContext || window.webkitAudioContext
const contexts = []
const events = []
const chunks = []
window.AudioContext = class extends Context {
constructor(options) {
super(options)
contexts.push(this)
}
}
window.art = new window.Artplayer({
container: '.player',
url: '/test/audio-tone.m4a',
volume: 0.6,
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 })
if (!window.completeRecognition) {
if (peak > 0)
return new Promise(resolve => window.completeRecognition = resolve)
return
}
return 'Recovered recognition'
},
})],
})
for (const name of ['play', 'pause', 'restart', 'video:emptied', 'video:error'])
window.art.on(name, () => events.push({ name, at: performance.now(), error: window.art.video.error?.code || null }))
window.mediaErrorState = () => ({
contexts: contexts.map(context => context.state),
events,
chunks,
subtitle: document.querySelector('.art-layer-asr')?.textContent || '',
error: window.art.video.error?.code || null,
source: window.art.video.currentSrc,
paused: window.art.video.paused,
})
document.querySelector('#play').onclick = () => window.art.play()
}, mode)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.mediaErrorState().chunks.some(chunk => chunk.peak > 0))).toBe(true)
await page.evaluate(() => {
window.art.video.src = '/test/manifest.json?asr-media-error=unsupported'
})
await expect.poll(() => page.evaluate(() => window.mediaErrorState().error)).toBe(4)
await page.evaluate(async () => {
window.completeRecognition('Obsolete recognition')
await Promise.resolve()
await Promise.resolve()
})
const failed = await page.evaluate(() => window.mediaErrorState())
await testInfo.attach('asr-native-media-failure', { contentType: 'application/json', body: JSON.stringify(failed) })
expect(failed.subtitle).not.toContain('Obsolete recognition')
if (mode === 'capture')
await expect.poll(() => page.evaluate(() => window.mediaErrorState().contexts.every(state => state === 'closed'))).toBe(true)
await page.evaluate(() => {
window.art.url = '/test/audio-tone.m4a?asr-media-error=recovery'
})
await expect.poll(() => page.evaluate(() => window.art.video.currentSrc.includes('asr-media-error=recovery') && window.art.video.readyState >= 2)).toBe(true)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.mediaErrorState().chunks.filter(chunk => chunk.source.includes('asr-media-error=recovery') && chunk.peak > 0).length)).toBeGreaterThanOrEqual(3)
await expect(page.locator('.art-layer-asr')).toHaveText('Recovered recognition')
await page.evaluate(() => window.art.destroy())
await expect.poll(() => page.evaluate(() => window.mediaErrorState().contexts.every(state => state === 'closed'))).toBe(true)
})
}
}