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]>
47 lines
1.6 KiB
JavaScript
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);
|