cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/website-download-wiring
goq / portal / audio-worklet.test.mjs
audio-worklet.test.mjs 5.0K
1import assert from 'node:assert/strict';2import { readFile } from 'node:fs/promises';3import test from 'node:test';45import { AUDIO_FRAME_DURATION_MICROS } from './audio-ring.mjs';67test('worklet reports its timeline and applies each recovery epoch plus clear atomically', async () => {8 const posted = [];9 let Processor = null;1011 class TestAudioWorkletProcessor {12 constructor() {13 this.port = {14 onmessage: null,15 postMessage(message) {16 posted.push(message);17 },18 };19 }20 }2122 globalThis.AudioWorkletProcessor = TestAudioWorkletProcessor;23 globalThis.registerProcessor = (name, processor) => {24 assert.equal(name, 'sigil-audio-processor');25 Processor = processor;26 };27 globalThis.sampleRate = 48_000;28 globalThis.currentFrame = 0;2930 const workletUrl = new URL('./audio-worklet.js', import.meta.url);31 const ringUrl = new URL('./audio-ring.mjs', import.meta.url).href;32 const source = (await readFile(workletUrl, 'utf8'))33 .replace("'./audio-ring.mjs'", JSON.stringify(ringUrl));34 await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`);35 assert.equal(typeof Processor, 'function');3637 const processor = new Processor();38 const frames = 2_048;39 processor.port.onmessage({40 data: {41 type: 'samples',42 id: 7,43 timestampMicros: 1_000_000,44 channels: [new Float32Array(frames), new Float32Array(frames)],45 },46 });47 assert.deepEqual(posted.shift(), { type: 'accepted', id: 7 });4849 for (let quantum = 0; quantum < 16; quantum++) {50 globalThis.currentFrame = quantum * 128;51 assert.equal(52 processor.process([], [[new Float32Array(128), new Float32Array(128)]]),53 true,54 );55 }5657 assert.equal(posted.length, 1);58 const stats = posted.shift();59 assert.equal(stats.type, 'stats');60 assert.equal(stats.avSyncEpoch, 0);61 assert.equal(stats.renderedMediaEndPtsMicros, 1_000_000 + frames * AUDIO_FRAME_DURATION_MICROS);62 assert.equal(stats.outputQuantumFrames, 128);63 assert.equal(stats.outputContextFrameEnd, 2_048);64 assert.equal(stats.outputContextTimeEndSeconds, 2_048 / 48_000);65 assert.equal(stats.outputContextSampleRate, 48_000);66 assert.equal(stats.silentFrames, 0);67 assert.equal(stats.underflowFrames, 0);6869 processor.port.onmessage({ data: { type: 'av-sync-epoch', avSyncEpoch: 1 } });70 globalThis.currentFrame = frames;71 processor.process([], [[new Float32Array(128), new Float32Array(128)]]);72 assert.equal(posted.length, 1, 'underrun must invalidate the timeline immediately');73 const underrunStats = posted.shift();74 assert.equal(underrunStats.avSyncEpoch, 1);75 assert.equal(underrunStats.renderedMediaEndPtsMicros, null);76 assert.equal(underrunStats.underflows, 1);7778 for (let quantum = 1; quantum < 16; quantum++) {79 globalThis.currentFrame = frames + quantum * 128;80 processor.process([], [[new Float32Array(128), new Float32Array(128)]]);81 }82 const postResetStats = posted.shift();83 assert.equal(postResetStats.avSyncEpoch, 1);84 assert.equal(postResetStats.renderedMediaEndPtsMicros, null);8586 processor.port.onmessage({87 data: {88 type: 'samples',89 id: 8,90 timestampMicros: 2_000_000,91 channels: [new Float32Array(256), new Float32Array(256)],92 },93 });94 assert.deepEqual(posted.shift(), { type: 'accepted', id: 8 });95 processor.port.onmessage({ data: { type: 'clear' } });96 for (let quantum = 0; quantum < 16; quantum++) {97 processor.process([], [[new Float32Array(128), new Float32Array(128)]]);98 }99 assert.equal(posted.shift().recoveryDiscardedFrames, 0);100101 processor.port.onmessage({102 data: {103 type: 'samples',104 id: 9,105 timestampMicros: 3_000_000,106 channels: [new Float32Array(320), new Float32Array(320)],107 },108 });109 assert.deepEqual(posted.shift(), { type: 'accepted', id: 9 });110 processor.port.onmessage({ data: { type: 'recover', avSyncEpoch: 2 } });111 assert.equal(processor.avSyncEpoch, 2);112 assert.equal(processor.ring.length, 0);113 assert.equal(processor.ring.recoveryDiscardedFrames, 320);114 for (let quantum = 0; quantum < 16; quantum++) {115 processor.process([], [[new Float32Array(128), new Float32Array(128)]]);116 }117 const firstRecoveryStats = posted.shift();118 assert.equal(firstRecoveryStats.avSyncEpoch, 2);119 assert.equal(firstRecoveryStats.recoveryDiscardedFrames, 320);120121 processor.port.onmessage({122 data: {123 type: 'samples',124 id: 10,125 timestampMicros: 4_000_000,126 channels: [new Float32Array(64), new Float32Array(64)],127 },128 });129 assert.deepEqual(posted.shift(), { type: 'accepted', id: 10 });130 processor.port.onmessage({ data: { type: 'recover', avSyncEpoch: 3 } });131 assert.equal(processor.avSyncEpoch, 3);132 assert.equal(processor.ring.length, 0);133 assert.equal(processor.ring.recoveryDiscardedFrames, 384);134 for (let quantum = 0; quantum < 16; quantum++) {135 processor.process([], [[new Float32Array(128), new Float32Array(128)]]);136 }137 const secondRecoveryStats = posted.shift();138 assert.equal(secondRecoveryStats.avSyncEpoch, 3);139 assert.equal(secondRecoveryStats.recoveryDiscardedFrames, 384);140});