refactor(audio): [PKG-AUDIO-03] separate track ownership and native lifecycle

This commit is contained in:
Harvey Zhao committed 2026-09-12 02:22:13 +08:00
1 parent 803e87252e
commit 9c07ea830c
20 files changed
+12654 -250

No files matched your search

+31 -20
View File
@@ -1,10 +1,27 @@
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import vm from 'node:vm'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { loadPackage } from './load.js'
async function readFactory(bytes, esm) {
if (esm)
return (await import(`data:text/javascript;base64,${Buffer.from(bytes).toString('base64')}`)).default
const context = {
module: { exports: {} },
Audio: function Audio() {
return new globalThis.Audio()
},
console,
}
context.exports = context.module.exports
vm.runInNewContext(bytes.toString(), context, { timeout: 5000 })
return context.module.exports
}
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)
@@ -13,29 +30,16 @@ export async function audioImplementations() {
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
}
const factory = await readFactory(bytes, format === 'module')
implementations.push({ name: `published-${format}`, factory })
}
for (const file of (process.env.ARTPLAYER_TEST_AUDIO || '').split(path.delimiter).filter(Boolean))
implementations.push({ name: `artifact-${path.basename(file)}`, factory: await readFactory(fs.readFileSync(file), file.endsWith('.mjs')) })
return implementations
}
// Records plugin intent only. It does not decode media or emulate browser timing.
export function audioHost(t) {
export function audioHost(t, initialize = () => {}) {
const instances = []
const warnings = []
let play = () => Promise.resolve()
@@ -43,17 +47,20 @@ export function audioHost(t) {
constructor() {
instances.push(this)
this.calls = []
this.values = new Map()
this.paused = true
for (const [name, initial] of Object.entries({ src: '', preload: '', currentTime: 0, playbackRate: 1, volume: 1, muted: false })) {
let value = initial
this.values.set(name, initial)
Object.defineProperty(this, name, {
get: () => value,
configurable: true,
get: () => this.values.get(name),
set: (next) => {
this.calls.push([name, next])
value = next
this.values.set(name, next)
},
})
}
initialize(this)
}
play() {
@@ -63,6 +70,10 @@ export function audioHost(t) {
pause() { this.calls.push(['pause']) }
load() { this.calls.push(['load']) }
removeAttribute(name) {
this.calls.push(['removeAttribute', name])
this.values.set(name, '')
}
}
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'Audio')
Object.defineProperty(globalThis, 'Audio', { configurable: true, value: Audio })