open-standards/joule-code/joule-code-rs/crates/joule-moq-web/web/room/worklet.js
Transaction Science f396180b4d joule-moq-web: the browser client, its test pages and its end-to-end harness
web/moq: the WebSocket binding's capsules, WebTransport and the binding behind one
transport interface (WebTransport first, offering moqt-22 and the relay's certificate
hash; the binding as the fallback, or forced), a MOQT session (SETUP with the token,
requests, every data stream read concurrently, GOAWAY), and the payload framings a WAI
track carries. web/room: the room client (WebCodecs publishing with receipts, verified
subscriptions, a late joiner's FETCH, the composed canvas and the mixed AudioWorklet,
the events timeline and captions with their origin, the move after GOAWAY). web/compose:
the composition corpus in the page. e2e: the relay on loopback from a test
configuration, real Chrome through Playwright with a fake camera and microphone, each
browser under memguard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 13:44:37 -04:00

47 lines
1.6 KiB
JavaScript

// The room's audio output: the mix (computed on the page by wai-live-web's integer mixer,
// live-compose §6) arrives as blocks of interleaved stereo 16-bit samples, and this
// processor plays them from a bounded queue. It mixes nothing itself: an AudioWorklet's
// scope has no text decoder for the wasm glue, so the mixer runs where the glue does.
class RoomMix extends AudioWorkletProcessor {
constructor() {
super();
this.queue = [];
this.at = 0;
this.played = 0;
this.underruns = 0;
this.peak = 0;
this.port.onmessage = (e) => {
if (e.data && e.data.pcm) {
this.queue.push(e.data.pcm);
// At most half a second queued: the oldest goes first.
while (this.queue.length > 25) this.queue.shift();
}
};
this.tick = 0;
}
process(_inputs, outputs) {
const out = outputs[0];
const l = out[0], r = out[1] || out[0];
for (let i = 0; i < l.length; i++) {
let block = this.queue[0];
if (!block) {
l[i] = 0; r[i] = 0;
if (i === 0) this.underruns++;
continue;
}
const sl = block[this.at] / 32768, sr = block[this.at + 1] / 32768;
l[i] = sl; r[i] = sr;
const a = Math.abs(sl) > Math.abs(sr) ? Math.abs(sl) : Math.abs(sr);
if (a > this.peak) this.peak = a;
this.at += 2;
this.played++;
if (this.at >= block.length) { this.queue.shift(); this.at = 0; }
}
if (++this.tick % 100 === 0) this.port.postMessage({ played: this.played, underruns: this.underruns, peak: this.peak });
return true;
}
}
registerProcessor("room-mix", RoomMix);