import assert from 'node:assert/strict' import { Buffer } from 'node:buffer' import fs from 'node:fs' import vm from 'node:vm' import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs' import { loadPackage } from './load.js' export async function audioImplementations() { const { release } = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/audio-track-release.json', import.meta.url))) const archive = await ensureArchive(release) const implementations = [{ name: 'source', factory: (await loadPackage(release.name)).default }] for (const format of ['main', 'legacy', 'module']) { const member = `package/${release.manifest[format].replace(/^\.\//, '')}` const bytes = readMember(archive, member) assert.equal(hash(bytes), release.files[member]) let factory if (format === 'module') { factory = (await import(`data:text/javascript;base64,${Buffer.from(bytes).toString('base64')}`)).default } else { const context = { module: { exports: {} }, Audio: function Audio() { return new globalThis.Audio() }, console, } context.exports = context.module.exports vm.runInNewContext(bytes.toString(), context, { timeout: 5000 }) factory = context.module.exports } implementations.push({ name: `published-${format}`, factory }) } return implementations } // Records plugin intent only. It does not decode media or emulate browser timing. export function audioHost(t) { const instances = [] const warnings = [] let play = () => Promise.resolve() class Audio { constructor() { instances.push(this) this.calls = [] this.paused = true for (const [name, initial] of Object.entries({ src: '', preload: '', currentTime: 0, playbackRate: 1, volume: 1, muted: false })) { let value = initial Object.defineProperty(this, name, { get: () => value, set: (next) => { this.calls.push([name, next]) value = next }, }) } } play() { this.calls.push(['play']) return play(this) } pause() { this.calls.push(['pause']) } load() { this.calls.push(['load']) } } const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'Audio') Object.defineProperty(globalThis, 'Audio', { configurable: true, value: Audio }) t.after(() => descriptor ? Object.defineProperty(globalThis, 'Audio', descriptor) : Reflect.deleteProperty(globalThis, 'Audio')) t.mock.method(console, 'warn', error => warnings.push(error)) return { ...createAudioArt(), instances, warnings, setPlay: callback => play = callback } } export function createAudioArt() { const listeners = new Map() const art = { video: { playbackRate: 1.25 }, currentTime: 0, playing: false, volume: 0.6, muted: false, on(event, callback) { if (!listeners.has(event)) listeners.set(event, new Set()) listeners.get(event).add(callback) return art }, off(event, callback) { listeners.get(event)?.delete(callback) return art }, emit(event, ...args) { for (const callback of [...(listeners.get(event) || [])]) callback(...args) return art }, } return { art, listeners } }