cat / goq
clone

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

fix/portal-dist-prebuild
goq / portal / audio-worklet.js
audio-worklet.js 2.6K
1import { BoundedAudioRing } from './audio-ring.mjs';23class SigilAudioProcessor extends AudioWorkletProcessor {4 constructor() {5 super();6 this.ring = new BoundedAudioRing();7 this.muted = false;8 this.processCount = 0;9 this.avSyncEpoch = 0;10 this.port.onmessage = (event) => {11 try {12 if (event.data?.type === 'samples') {13 const id = event.data.id;14 if (!Number.isSafeInteger(id) || id < 1) throw new Error('invalid audio message ID');15 this.ring.write(event.data.channels, event.data.timestampMicros);16 this.port.postMessage({ type: 'accepted', id });17 } else if (event.data?.type === 'av-sync-epoch' || event.data?.type === 'recover') {18 const avSyncEpoch = event.data.avSyncEpoch;19 if (!Number.isSafeInteger(avSyncEpoch) || avSyncEpoch < 0) {20 throw new Error('invalid A/V sync epoch');21 }22 this.avSyncEpoch = avSyncEpoch;23 if (event.data.type === 'recover') this.ring.clear({ recovery: true });24 } else if (event.data?.type === 'mute') this.muted = event.data.muted === true;25 else if (event.data?.type === 'clear') this.ring.clear();26 } catch (error) {27 this.ring.clear();28 this.port.postMessage({ type: 'error', error: String(error) });29 }30 };31 }3233 process(_inputs, outputs) {34 const output = outputs[0];35 const underflowsBefore = this.ring.underflows;36 if (output?.length === 2) this.ring.read(output, this.muted);37 const underflowStarted = this.ring.underflows !== underflowsBefore;38 const outputQuantumFrames = output?.[0]?.length ?? 0;39 const outputContextSampleRate = typeof sampleRate === 'number' ? sampleRate : null;40 const outputContextFrameEnd = typeof currentFrame === 'number'41 ? currentFrame + outputQuantumFrames42 : null;43 const outputContextTimeEndSeconds = outputContextFrameEnd !== null44 && outputContextSampleRate !== null45 ? outputContextFrameEnd / outputContextSampleRate46 : null;47 this.processCount++;48 // Invalidate the projected A/V timeline on the exact quantum that starts49 // an underrun. Routine telemetry remains rate-limited to every 16 quanta.50 if (underflowStarted || this.processCount % 16 === 0) {51 this.port.postMessage({52 type: 'stats',53 ...this.ring.snapshot(),54 muted: this.muted,55 outputQuantumFrames,56 outputContextFrameEnd,57 outputContextTimeEndSeconds,58 outputContextSampleRate,59 avSyncEpoch: this.avSyncEpoch,60 });61 }62 return true;63 }64}6566registerProcessor('sigil-audio-processor', SigilAudioProcessor);