@moq/watch 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"source-CWtgjSC_.js","names":["#control","#samples","#anchored","#enabled","#headroom","#waiters","#timestamp","#stalled","#worklet","#backpressure","#ring","#signals","#out","#signals","#consumerLatency","#runWorklet","#runEnabled","#runLatency","#runDecoder","#decodedSampleRate","#ring","#trimDecodeBuffered","#runCmafDecoder","#runLegacyDecoder","#decodeBuffered","#emit","#onDiscontinuity","#addDecodeBuffered","#discontinuity","#out","#signals","#gain","#out","#signals","#runCatalog","#runSupported","#runSelected","#select","#out","#announced","#wantAnnounced","#signals","#runAnnounced","#runBroadcast","#runCatalog","#isPathAnnounced","#out","#signals","#runPending","#runActive","#runDisplay","#runBuffering","#active","#clearCurrentFrame","#run","#discontinuity","#trimBuffered","#runCmaf","#runLegacy","#buffered","#onDiscontinuity","#addBuffered","#out","#signals","#ctx","#runVisible","#runRender","#runResize","#render","#out","#signals","#runCatalog","#runSupported","#runSelected","#select"],"sources":["../src/base64.ts","../src/audio/shared-ring-buffer.ts","../src/audio/buffer.ts","../src/audio/render-worklet.ts?worklet","../src/audio/unlock.ts","../src/audio/decoder.ts","../src/audio/emitter.ts","../src/audio/source.ts","../src/msf.ts","../src/broadcast.ts","../src/video/decoder.ts","../src/video/renderer.ts","../src/video/source.ts"],"sourcesContent":["/** Decode a base64 string into bytes. Throws on invalid input. */\nexport function base64ToBytes(b64: string): Uint8Array {\n\tconst raw = atob(b64);\n\tconst bytes = new Uint8Array(raw.length);\n\tfor (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);\n\treturn bytes;\n}\n","import { Time } from \"@moq/net\";\n\n// Control array slot indices\nconst WRITE = 0;\nconst READ = 1;\nconst LATENCY = 2;\nconst STALLED = 3;\nconst CONTROL_SLOTS = 4;\n\nexport interface SharedRingBufferInit {\n\tchannels: number;\n\tcapacity: number; // samples per channel\n\trate: number;\n\tsamples: SharedArrayBuffer; // channels * capacity * Float32Array.BYTES_PER_ELEMENT bytes\n\tcontrol: SharedArrayBuffer; // CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT bytes\n\t// Buffered mode: anchor to the first sample and never skip ahead on read.\n\tbuffered: boolean;\n}\n\nexport function allocSharedRingBuffer(\n\tchannels: number,\n\tcapacity: number,\n\trate: number,\n\tbuffered = false,\n): SharedRingBufferInit {\n\tif (channels <= 0) throw new Error(\"invalid channels\");\n\tif (capacity <= 0) throw new Error(\"invalid capacity\");\n\tif (rate <= 0) throw new Error(\"invalid sample rate\");\n\n\tconst samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\n\tconst control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\n\n\t// Initialize STALLED to 1\n\tconst ctrl = new Int32Array(control);\n\tAtomics.store(ctrl, STALLED, 1);\n\n\treturn { channels, capacity, rate, samples, control, buffered };\n}\n\n/** Modular i32 max: returns a if a is ahead of b, else b. */\nfunction i32Max(a: number, b: number): number {\n\treturn ((a - b) | 0) > 0 ? a : b;\n}\n\n/** Maps an absolute sample index to a [0, capacity) array slot. */\nfunction slot(idx: number, capacity: number): number {\n\treturn ((idx % capacity) + capacity) % capacity;\n}\n\n/**\n * Atomically advance `arr[idx]` to `candidate` iff `candidate` is strictly ahead\n * (in modular i32 ordering). Retries under contention so the slot only ever\n * moves forward and concurrent writers/readers can't clobber each other.\n */\nfunction casAdvance(arr: Int32Array, idx: number, candidate: number): number {\n\tfor (;;) {\n\t\tconst current = Atomics.load(arr, idx);\n\t\tif (((candidate - current) | 0) <= 0) return current;\n\t\tconst witnessed = Atomics.compareExchange(arr, idx, current, candidate);\n\t\tif (witnessed === current) return candidate;\n\t}\n}\n\nexport class SharedRingBuffer {\n\treadonly channels: number;\n\treadonly capacity: number;\n\treadonly rate: number;\n\treadonly buffered: boolean;\n\treadonly init: SharedRingBufferInit;\n\n\t#control: Int32Array;\n\t#samples: Float32Array[];\n\n\t// Whether READ/WRITE have been anchored to the first inserted sample (buffered mode).\n\t#anchored = false;\n\n\tconstructor(init: SharedRingBufferInit) {\n\t\tthis.channels = init.channels;\n\t\tthis.capacity = init.capacity;\n\t\tthis.rate = init.rate;\n\t\tthis.buffered = init.buffered;\n\t\tthis.init = init;\n\n\t\tthis.#control = new Int32Array(init.control);\n\t\tthis.#samples = [];\n\t\tfor (let i = 0; i < this.channels; i++) {\n\t\t\tthis.#samples.push(\n\t\t\t\tnew Float32Array(init.samples, i * this.capacity * Float32Array.BYTES_PER_ELEMENT, this.capacity),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Insert audio samples at the given timestamp.\n\t * Main thread only. Handles out-of-order writes, gap filling, and overflow.\n\t */\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tif (data.length !== this.channels) throw new Error(\"wrong number of channels\");\n\n\t\tlet start = Math.round(Time.Second.fromMicro(timestamp) * this.rate);\n\t\tconst originalLength = data[0].length;\n\t\tlet offset = 0;\n\n\t\t// Buffered mode: anchor READ/WRITE to the first sample so playback starts at its\n\t\t// timestamp, instead of skipping ahead or gap-filling silence from index 0.\n\t\tif (this.buffered && !this.#anchored) {\n\t\t\tAtomics.store(this.#control, READ, start | 0);\n\t\t\tAtomics.store(this.#control, WRITE, start | 0);\n\t\t\tthis.#anchored = true;\n\t\t}\n\n\t\tconst end = (start + originalLength) | 0;\n\n\t\t// Trim old: discard samples before the read index\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\tconst behind = (read - start) | 0;\n\t\tif (behind > 0) {\n\t\t\tif (behind >= originalLength) {\n\t\t\t\t// All samples are too old\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toffset = behind;\n\t\t\tstart = (start + behind) | 0;\n\t\t}\n\n\t\tconst samples = originalLength - offset;\n\n\t\t// Overflow: if the write would exceed capacity from current READ, advance READ.\n\t\t// Use CAS so a concurrent reader advance isn't clobbered backward.\n\t\tif (((end - read) | 0) > this.capacity) {\n\t\t\tcasAdvance(this.#control, READ, (end - this.capacity) | 0);\n\t\t}\n\n\t\t// Gap fill: zero-fill from current WRITE to start if there's a discontinuity\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst gap = (start - write) | 0;\n\t\tif (gap > 0) {\n\t\t\tconst gapSize = Math.min(gap, this.capacity);\n\t\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\t\tconst dst = this.#samples[channel];\n\t\t\t\tfor (let i = 0; i < gapSize; i++) {\n\t\t\t\t\tdst[slot((write + i) | 0, this.capacity)] = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Write sample data\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = data[channel];\n\t\t\tconst dst = this.#samples[channel];\n\t\t\tfor (let i = 0; i < samples; i++) {\n\t\t\t\tdst[slot((start + i) | 0, this.capacity)] = src[offset + i];\n\t\t\t}\n\t\t}\n\n\t\t// Advance WRITE (only forward)\n\t\tAtomics.store(this.#control, WRITE, i32Max(Atomics.load(this.#control, WRITE), end));\n\n\t\t// Un-stall: if buffered data >= LATENCY\n\t\tconst currentRead = Atomics.load(this.#control, READ);\n\t\tconst currentWrite = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tif (((currentWrite - currentRead) | 0) >= latency && latency > 0) {\n\t\t\tAtomics.store(this.#control, STALLED, 0);\n\t\t}\n\t}\n\n\t/**\n\t * Read audio samples into the output buffers.\n\t * AudioWorklet only. Returns the number of samples read.\n\t */\n\tread(output: Float32Array[]): number {\n\t\tif (Atomics.load(this.#control, STALLED) === 1) return 0;\n\n\t\tlet read = Atomics.load(this.#control, READ);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\n\t\t// Latency skip: if buffered data exceeds LATENCY, skip ahead.\n\t\t// CAS ensures we never step backward relative to a concurrent writer advance.\n\t\t// Disabled in buffered mode, where we deliberately play through the whole buffer.\n\t\tconst buffered = (write - read) | 0;\n\t\tif (!this.buffered && latency > 0 && buffered > latency) {\n\t\t\tconst skipTo = (write - latency) | 0;\n\t\t\tread = casAdvance(this.#control, READ, skipTo);\n\t\t}\n\n\t\tconst available = (write - read) | 0;\n\t\tconst count = Math.min(available, output[0].length);\n\t\tif (count <= 0) return 0;\n\n\t\t// Copy samples\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = this.#samples[channel];\n\t\t\tconst dst = output[channel];\n\t\t\tfor (let i = 0; i < count; i++) {\n\t\t\t\tdst[i] = src[slot((read + i) | 0, this.capacity)];\n\t\t\t}\n\t\t}\n\n\t\t// Advance READ via CAS so a concurrent writer overflow can't be undone.\n\t\tcasAdvance(this.#control, READ, (read + count) | 0);\n\n\t\treturn count;\n\t}\n\n\t/** Update the target latency in samples. */\n\tsetLatency(samples: number): void {\n\t\tAtomics.store(this.#control, LATENCY, samples);\n\t}\n\n\t/**\n\t * Flush buffered samples and re-stall, ready to anchor the next utterance (buffered mode).\n\t * Main thread only. The worklet reader sees STALLED and stops until the next insert.\n\t */\n\treset(): void {\n\t\tthis.#anchored = false;\n\t\tAtomics.store(this.#control, STALLED, 1);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tAtomics.store(this.#control, READ, write);\n\t}\n\n\t/**\n\t * Allocate a new ring with `newCapacity` samples and copy the unread window\n\t * [READ, WRITE) plus control state into it. Used when growing capacity so\n\t * we don't drop buffered audio. If `newCapacity` is smaller than the unread\n\t * span, the oldest samples are truncated.\n\t *\n\t * Main thread only. `resize()` reads from the source `SharedRingBuffer` and\n\t * writes into a freshly allocated buffer from `allocSharedRingBuffer`, so it\n\t * relies on the same invariant as `insert()`: no concurrent main-thread\n\t * writers. The AudioWorklet reader is tolerated via the CAS discipline used\n\t * by READ/WRITE elsewhere.\n\t */\n\tresize(newCapacity: number): SharedRingBuffer {\n\t\tconst init = allocSharedRingBuffer(this.channels, newCapacity, this.rate, this.buffered);\n\t\tconst dst = new SharedRingBuffer(init);\n\t\tdst.#anchored = this.#anchored;\n\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tconst stalled = Atomics.load(this.#control, STALLED);\n\n\t\tconst available = (write - read) | 0;\n\t\tconst copyCount = Math.max(0, Math.min(available, dst.capacity));\n\t\tconst copyStart = (write - copyCount) | 0;\n\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = this.#samples[channel];\n\t\t\tconst out = dst.#samples[channel];\n\t\t\tfor (let i = 0; i < copyCount; i++) {\n\t\t\t\tconst idx = (copyStart + i) | 0;\n\t\t\t\tout[slot(idx, dst.capacity)] = src[slot(idx, this.capacity)];\n\t\t\t}\n\t\t}\n\n\t\tAtomics.store(dst.#control, READ, copyStart);\n\t\tAtomics.store(dst.#control, WRITE, write);\n\t\tAtomics.store(dst.#control, LATENCY, latency);\n\t\tAtomics.store(dst.#control, STALLED, stalled);\n\n\t\treturn dst;\n\t}\n\n\t/** Current playback timestamp derived from READ position. */\n\tget timestamp(): Time.Micro {\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\treturn Time.Micro.fromSecond((read / this.rate) as Time.Second);\n\t}\n\n\t/** Whether the buffer is stalled (waiting to fill). */\n\tget stalled(): boolean {\n\t\treturn Atomics.load(this.#control, STALLED) === 1;\n\t}\n\n\t/**\n\t * Number of buffered samples (WRITE - READ).\n\t *\n\t * Non-atomic: WRITE and READ are loaded separately, so a concurrent\n\t * writer/reader can make the two loads inconsistent. Intended for\n\t * tests and diagnostics, not control-flow decisions.\n\t */\n\tget length(): number {\n\t\treturn (Atomics.load(this.#control, WRITE) - Atomics.load(this.#control, READ)) | 0;\n\t}\n}\n","import { Time } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { Data, InitPost, InitShared, Latency, Reset, State } from \"./render\";\nimport { allocSharedRingBuffer, SharedRingBuffer } from \"./shared-ring-buffer\";\n\n/**\n * Timestamp-based backpressure for buffered playback. The decoded PCM ring only holds the latency\n * floor; everything above it (the buffered lookahead, up to the ceiling) stays upstream as encoded\n * Opus. `wait(timestamp)` stays pending until the playhead is within `headroom` (the floor) of\n * `timestamp`, so the decode loop holds a frame as Opus instead of decoding it too far ahead of the\n * floor-sized ring. Both transports share this; they differ only in how they observe the playhead\n * (Atomics poll vs worklet state messages). A no-op when not buffered (the ring bounds itself).\n */\nclass Backpressure {\n\treadonly #enabled: boolean;\n\t#headroom: Time.Micro;\n\t#waiters: Array<{ timestamp: Time.Micro; resolve: () => void }> = [];\n\n\tconstructor(enabled: boolean, headroom: Time.Micro) {\n\t\tthis.#enabled = enabled;\n\t\tthis.#headroom = headroom;\n\t}\n\n\t// Move the gate as the floor changes (e.g. \"real-time\" jitter tracking RTT).\n\tsetHeadroom(headroom: Time.Micro): void {\n\t\tthis.#headroom = headroom;\n\t}\n\n\twait(timestamp: Time.Micro, playhead: Time.Micro): Promise<void> {\n\t\tif (!this.#enabled) return Promise.resolve();\n\t\tif (playhead >= ((timestamp - this.#headroom) | 0)) return Promise.resolve();\n\t\treturn new Promise((resolve) => this.#waiters.push({ timestamp, resolve }));\n\t}\n\n\t// Resolve every waiter the playhead has reached. Thresholds are recomputed live so a changed\n\t// headroom takes effect on queued waiters too.\n\tadvance(playhead: Time.Micro): void {\n\t\tif (this.#waiters.length === 0) return;\n\t\tthis.#waiters = this.#waiters.filter(({ timestamp, resolve }) => {\n\t\t\tif (playhead < ((timestamp - this.#headroom) | 0)) return true;\n\t\t\tresolve();\n\t\t\treturn false;\n\t\t});\n\t}\n\n\t// Resolve everything unconditionally (reset/close): never strand a decode loop.\n\tflush(): void {\n\t\tfor (const { resolve } of this.#waiters) resolve();\n\t\tthis.#waiters = [];\n\t}\n}\n\n/** Convert a sample count to a Time.Micro duration at the given sample rate. */\nfunction samplesToMicro(samples: number, rate: number): Time.Micro {\n\treturn Time.Micro.fromSecond((samples / rate) as Time.Second);\n}\n\n/**\n * Unified interface for the audio buffer between the main thread and the AudioWorklet.\n *\n * Two implementations exist:\n * - `SharedAudioBuffer`: backed by SharedArrayBuffer, lock-free writes via Atomics.\n * - `PostAudioBuffer`: backed by postMessage transfer (the fallback when SAB is unavailable).\n *\n * Use `createAudioBuffer()` to pick the right implementation automatically.\n */\nexport interface AudioBuffer {\n\treadonly rate: number;\n\treadonly channels: number;\n\n\t/** Insert audio samples at the given timestamp. Handles out-of-order writes. */\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void;\n\n\t/** Update the target latency in samples. */\n\tsetLatency(samples: number): void;\n\n\t/** Flush buffered samples and re-stall, ready to anchor the next utterance (buffered mode). */\n\treset(): void;\n\n\t/**\n\t * Resolve once the playhead is near enough to decode a frame at `timestamp`. In buffered mode this\n\t * applies backpressure: it stays pending while decoding `timestamp` would run more than the latency\n\t * floor ahead of the playhead, so the caller holds the (encoded) frame instead of decoding it too\n\t * far ahead of the floor-sized ring. Resolves immediately when not buffered (the ring bounds itself).\n\t */\n\twait(timestamp: Time.Micro): Promise<void>;\n\n\t/** Current playback timestamp (derived from reader position). */\n\treadonly timestamp: Getter<Time.Micro>;\n\n\t/** Whether the buffer is stalled (waiting to fill). */\n\treadonly stalled: Getter<boolean>;\n\n\t/** Release any resources (event listeners, intervals, etc.). */\n\tclose(): void;\n}\n\n/** Returns true when SharedArrayBuffer is available and usable in the current context. */\nexport function supportsSharedArrayBuffer(): boolean {\n\tif (typeof SharedArrayBuffer === \"undefined\") return false;\n\t// In browsers, SharedArrayBuffer requires cross-origin isolation (COOP/COEP).\n\t// crossOriginIsolated is a browser global; in Node/Bun it's undefined.\n\tif (typeof crossOriginIsolated !== \"undefined\" && !crossOriginIsolated) return false;\n\treturn true;\n}\n\n/**\n * Create the best audio buffer implementation for the current environment.\n * Picks `SharedAudioBuffer` when possible, falling back to `PostAudioBuffer`.\n */\nexport function createAudioBuffer(\n\tworklet: AudioWorkletNode,\n\tchannels: number,\n\trate: number,\n\tlatencySamples: number,\n\tbuffered = false,\n): AudioBuffer {\n\tif (supportsSharedArrayBuffer()) {\n\t\tconsole.log(\"[audio] using SharedArrayBuffer audio buffer\");\n\t\treturn new SharedAudioBuffer(worklet, channels, rate, latencySamples, buffered);\n\t}\n\tconsole.log(\"[audio] using postMessage audio buffer (SharedArrayBuffer unavailable)\");\n\treturn new PostAudioBuffer(worklet, channels, rate, latencySamples, buffered);\n}\n\n/** SharedArrayBuffer-backed implementation. Writes go directly into shared memory. */\nclass SharedAudioBuffer implements AudioBuffer {\n\treadonly rate: number;\n\treadonly channels: number;\n\t#worklet: AudioWorkletNode;\n\t#ring: SharedRingBuffer;\n\n\treadonly #timestamp = new Signal<Time.Micro>(0 as Time.Micro);\n\treadonly timestamp: Getter<Time.Micro> = this.#timestamp;\n\n\treadonly #stalled = new Signal<boolean>(true);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t#backpressure: Backpressure;\n\n\t#signals = new Effect();\n\n\tconstructor(worklet: AudioWorkletNode, channels: number, rate: number, latencySamples: number, buffered: boolean) {\n\t\tthis.#worklet = worklet;\n\t\tthis.channels = channels;\n\t\tthis.rate = rate;\n\n\t\t// The ring holds the latency floor as decoded PCM (headroom above it for overflow). In\n\t\t// buffered mode the lookahead above the floor stays encoded upstream, held back by `wait()`.\n\t\tconst capacity = Math.max(rate, latencySamples * 2);\n\t\tthis.#backpressure = new Backpressure(buffered, samplesToMicro(latencySamples, rate));\n\n\t\tconst init = allocSharedRingBuffer(channels, capacity, rate, buffered);\n\t\tthis.#ring = new SharedRingBuffer(init);\n\t\tthis.#ring.setLatency(latencySamples);\n\n\t\tconst msg: InitShared = { type: \"init-shared\", ...init };\n\t\tworklet.port.postMessage(msg);\n\n\t\t// Poll the shared control array and reflect it into signals.\n\t\tthis.#signals.interval(() => {\n\t\t\tconst stalled = this.#ring.stalled;\n\t\t\tthis.#timestamp.set(this.#ring.timestamp);\n\t\t\tthis.#stalled.set(stalled);\n\t\t\t// While stalled the playhead is parked, so release the decode loop to refill the floor;\n\t\t\t// once playing, hold it to ~the floor ahead.\n\t\t\tif (stalled) this.#backpressure.flush();\n\t\t\telse this.#backpressure.advance(this.#ring.timestamp);\n\t\t}, 50);\n\t}\n\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tthis.#ring.insert(timestamp, data);\n\t}\n\n\tsetLatency(samples: number): void {\n\t\tthis.#backpressure.setHeadroom(samplesToMicro(samples, this.rate));\n\n\t\t// Grow the ring (preserving the unread window) if it's too small for the new latency.\n\t\tif (this.#ring.capacity < samples * 1.5) {\n\t\t\tconst newCapacity = Math.max(this.rate, samples * 2);\n\t\t\tthis.#ring = this.#ring.resize(newCapacity);\n\t\t\tthis.#ring.setLatency(samples);\n\n\t\t\tconst msg: InitShared = { type: \"init-shared\", ...this.#ring.init };\n\t\t\tthis.#worklet.port.postMessage(msg);\n\t\t} else {\n\t\t\tthis.#ring.setLatency(samples);\n\t\t}\n\t}\n\n\treset(): void {\n\t\tthis.#ring.reset();\n\t\tthis.#backpressure.flush(); // the old timeline is gone; let the decode loop re-anchor\n\t}\n\n\twait(timestamp: Time.Micro): Promise<void> {\n\t\t// Stalled = still filling the floor (bootstrap or underflow): let frames through to refill.\n\t\tif (this.#ring.stalled) return Promise.resolve();\n\t\treturn this.#backpressure.wait(timestamp, this.#ring.timestamp);\n\t}\n\n\tclose(): void {\n\t\tthis.#backpressure.flush(); // never leave a decode loop awaiting a closed buffer\n\t\tthis.#signals.close();\n\t}\n}\n\n/** postMessage-backed fallback implementation. Samples are transferred, not shared. */\nclass PostAudioBuffer implements AudioBuffer {\n\treadonly rate: number;\n\treadonly channels: number;\n\t#worklet: AudioWorkletNode;\n\n\treadonly #timestamp = new Signal<Time.Micro>(0 as Time.Micro);\n\treadonly timestamp: Getter<Time.Micro> = this.#timestamp;\n\n\treadonly #stalled = new Signal<boolean>(true);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t// Backpressure runs off the playhead the worklet reports in its state messages.\n\t#backpressure: Backpressure;\n\n\t#signals = new Effect();\n\n\tconstructor(worklet: AudioWorkletNode, channels: number, rate: number, latencySamples: number, buffered: boolean) {\n\t\tthis.#worklet = worklet;\n\t\tthis.channels = channels;\n\t\tthis.rate = rate;\n\n\t\tthis.#backpressure = new Backpressure(buffered, samplesToMicro(latencySamples, rate));\n\n\t\tconst latency = Time.Milli.fromSecond((latencySamples / rate) as Time.Second);\n\t\tconst msg: InitPost = { type: \"init-post\", channels, rate, latency, buffered };\n\t\tworklet.port.postMessage(msg);\n\n\t\t// Listen for state updates from the worklet.\n\t\tthis.#signals.event(worklet.port, \"message\", (ev: Event) => {\n\t\t\tconst data = (ev as MessageEvent<State>).data;\n\t\t\tif (data?.type === \"state\") {\n\t\t\t\tthis.#timestamp.set(data.timestamp);\n\t\t\t\tthis.#stalled.set(data.stalled);\n\t\t\t\t// While stalled the playhead is parked, so release the decode loop to refill the floor;\n\t\t\t\t// once playing, hold it to ~the floor ahead.\n\t\t\t\tif (data.stalled) this.#backpressure.flush();\n\t\t\t\telse this.#backpressure.advance(data.timestamp);\n\t\t\t}\n\t\t});\n\t\t// addEventListener on a MessagePort requires start() to begin delivery.\n\t\tworklet.port.start();\n\t}\n\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tconst msg: Data = { type: \"data\", data, timestamp };\n\t\t// Transfer the ArrayBuffers to avoid a copy. This is why samples can be dropped\n\t\t// under load: the main thread loses access until the worklet drains the message queue.\n\t\tthis.#worklet.port.postMessage(\n\t\t\tmsg,\n\t\t\tdata.map((d) => d.buffer),\n\t\t);\n\t}\n\n\tsetLatency(samples: number): void {\n\t\tthis.#backpressure.setHeadroom(samplesToMicro(samples, this.rate));\n\n\t\tconst latency = Time.Milli.fromSecond((samples / this.rate) as Time.Second);\n\t\tconst msg: Latency = { type: \"latency\", latency };\n\t\tthis.#worklet.port.postMessage(msg);\n\t}\n\n\treset(): void {\n\t\tconst msg: Reset = { type: \"reset\" };\n\t\tthis.#worklet.port.postMessage(msg);\n\t\tthis.#backpressure.flush(); // the old timeline is gone; let the decode loop re-anchor\n\t}\n\n\twait(timestamp: Time.Micro): Promise<void> {\n\t\t// Stalled = still filling the floor (bootstrap or underflow): let frames through to refill.\n\t\tif (this.#stalled.peek()) return Promise.resolve();\n\t\t// Uses the worklet-reported playhead, which lags by a state-message interval; the floor's\n\t\t// headroom covers that. The worklet still drops the oldest if a frame slips through.\n\t\treturn this.#backpressure.wait(timestamp, this.#timestamp.peek());\n\t}\n\n\tclose(): void {\n\t\tthis.#backpressure.flush(); // never leave a decode loop awaiting a closed buffer\n\t\tthis.#signals.close();\n\t}\n}\n","const code = \"var __defProp = Object.defineProperty;\\nvar __export = (target, all) => {\\n for (var name in all)\\n __defProp(target, name, { get: all[name], enumerable: true });\\n};\\n\\n// ../net/src/time.ts\\nvar time_exports = {};\\n__export(time_exports, {\\n Micro: () => Micro,\\n Milli: () => Milli,\\n Nano: () => Nano,\\n Second: () => Second,\\n Timescale: () => Timescale,\\n Timestamp: () => Timestamp\\n});\\nvar Nano = Object.assign((value) => value, {\\n zero: 0,\\n fromMicro: (us) => us * 1e3,\\n fromMilli: (ms) => ms * 1e6,\\n fromSecond: (s) => s * 1e9,\\n toMicro: (ns) => ns / 1e3,\\n toMilli: (ns) => ns / 1e6,\\n toSecond: (ns) => ns / 1e9,\\n now: () => performance.now() * 1e6,\\n add: (a, b) => a + b,\\n sub: (a, b) => a - b,\\n mul: (a, b) => a * b,\\n div: (a, b) => a / b,\\n max: (a, b) => Math.max(a, b),\\n min: (a, b) => Math.min(a, b)\\n});\\nvar Micro = Object.assign((value) => value, {\\n zero: 0,\\n fromNano: (ns) => ns / 1e3,\\n fromMilli: (ms) => ms * 1e3,\\n fromSecond: (s) => s * 1e6,\\n toNano: (us) => us * 1e3,\\n toMilli: (us) => us / 1e3,\\n toSecond: (us) => us / 1e6,\\n now: () => performance.now() * 1e3,\\n add: (a, b) => a + b,\\n sub: (a, b) => a - b,\\n mul: (a, b) => a * b,\\n div: (a, b) => a / b,\\n max: (a, b) => Math.max(a, b),\\n min: (a, b) => Math.min(a, b)\\n});\\nvar Milli = Object.assign((value) => value, {\\n zero: 0,\\n fromNano: (ns) => ns / 1e6,\\n fromMicro: (us) => us / 1e3,\\n fromSecond: (s) => s * 1e3,\\n toNano: (ms) => ms * 1e6,\\n toMicro: (ms) => ms * 1e3,\\n toSecond: (ms) => ms / 1e3,\\n now: () => performance.now(),\\n add: (a, b) => a + b,\\n sub: (a, b) => a - b,\\n mul: (a, b) => a * b,\\n div: (a, b) => a / b,\\n max: (a, b) => Math.max(a, b),\\n min: (a, b) => Math.min(a, b)\\n});\\nvar Timescale = Object.assign(\\n (unitsPerSecond) => {\\n if (!Number.isInteger(unitsPerSecond) || unitsPerSecond <= 0) {\\n throw new Error(`invalid timescale: ${unitsPerSecond}`);\\n }\\n return unitsPerSecond;\\n },\\n {\\n /** One unit per second. */\\n SECOND: 1,\\n /** 1,000 units per second. */\\n MILLI: 1e3,\\n /** 1,000,000 units per second. */\\n MICRO: 1e6,\\n /** 1,000,000,000 units per second. */\\n NANO: 1e9\\n }\\n);\\nvar Timestamp = class _Timestamp {\\n /** The raw value, in `scale` units. */\\n value;\\n /** Units per second the {@link value} is measured in. */\\n scale;\\n /** Build a timestamp of `value` units at `scale`. */\\n constructor(value, scale) {\\n if (!Number.isFinite(value) || value < 0) {\\n throw new Error(`invalid timestamp: ${value}`);\\n }\\n this.value = value;\\n this.scale = scale;\\n }\\n /** Monotonic now (`performance.now()`, milliseconds since page load), not wall-clock time. */\\n static now() {\\n return new _Timestamp(performance.now(), Timescale.MILLI);\\n }\\n /** A timestamp of `ms` milliseconds. */\\n static fromMillis(ms) {\\n return new _Timestamp(ms, Timescale.MILLI);\\n }\\n /** A timestamp of `us` microseconds. */\\n static fromMicros(us) {\\n return new _Timestamp(us, Timescale.MICRO);\\n }\\n /** This timestamp's value re-expressed at `scale` (a raw number, not a new Timestamp). */\\n as(scale) {\\n return scale === this.scale ? this.value : this.value * scale / this.scale;\\n }\\n /** The value in milliseconds. */\\n asMillis() {\\n return this.as(Timescale.MILLI);\\n }\\n /** The value in microseconds. */\\n asMicros() {\\n return this.as(Timescale.MICRO);\\n }\\n};\\nvar Second = Object.assign((value) => value, {\\n zero: 0,\\n fromNano: (ns) => ns / 1e9,\\n fromMicro: (us) => us / 1e6,\\n fromMilli: (ms) => ms / 1e3,\\n toNano: (s) => s * 1e9,\\n toMicro: (s) => s * 1e6,\\n toMilli: (s) => s * 1e3,\\n now: () => performance.now() / 1e3,\\n add: (a, b) => a + b,\\n sub: (a, b) => a - b,\\n mul: (a, b) => a * b,\\n div: (a, b) => a / b,\\n max: (a, b) => Math.max(a, b),\\n min: (a, b) => Math.min(a, b)\\n});\\n\\n// src/audio/ring-buffer.ts\\nvar AudioRingBuffer = class {\\n #buffer;\\n #writeIndex = 0;\\n #readIndex = 0;\\n rate;\\n channels;\\n #stalled = true;\\n // Buffered mode: anchor to the first sample, play through without skipping ahead.\\n #buffered;\\n // Un-stall threshold in samples (how much to buffer before playback starts).\\n #latencySamples;\\n // Whether the read/write indices have been anchored to the first inserted sample.\\n #anchored = false;\\n constructor(props) {\\n if (props.channels <= 0) throw new Error(\\\"invalid channels\\\");\\n if (props.rate <= 0) throw new Error(\\\"invalid sample rate\\\");\\n if (props.latency <= 0) throw new Error(\\\"invalid latency\\\");\\n this.#latencySamples = Math.ceil(props.rate * time_exports.Second.fromMilli(props.latency));\\n if (this.#latencySamples === 0) throw new Error(\\\"empty buffer\\\");\\n this.rate = props.rate;\\n this.channels = props.channels;\\n this.#buffered = props.buffered ?? false;\\n const capacity = this.#capacityFor(this.#latencySamples);\\n this.#buffer = [];\\n for (let i = 0; i < this.channels; i++) {\\n this.#buffer[i] = new Float32Array(capacity);\\n }\\n }\\n #capacityFor(latencySamples) {\\n return this.#buffered ? latencySamples * 2 : latencySamples;\\n }\\n get stalled() {\\n return this.#stalled;\\n }\\n get timestamp() {\\n return time_exports.Micro.fromSecond(this.#readIndex / this.rate);\\n }\\n get length() {\\n return this.#writeIndex - this.#readIndex;\\n }\\n get capacity() {\\n return this.#buffer[0]?.length;\\n }\\n resize(latency) {\\n this.#latencySamples = Math.ceil(this.rate * time_exports.Second.fromMilli(latency));\\n const newCapacity = this.#capacityFor(this.#latencySamples);\\n if (newCapacity === this.capacity) return;\\n if (newCapacity === 0) throw new Error(\\\"empty buffer\\\");\\n const newBuffer = [];\\n for (let i = 0; i < this.channels; i++) {\\n newBuffer[i] = new Float32Array(newCapacity);\\n }\\n const samplesToKeep = Math.min(this.length, newCapacity);\\n if (samplesToKeep > 0) {\\n const copyStart = this.#writeIndex - samplesToKeep;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#buffer[channel];\\n const dst = newBuffer[channel];\\n for (let i = 0; i < samplesToKeep; i++) {\\n const srcPos = (copyStart + i) % src.length;\\n const dstPos = (copyStart + i) % dst.length;\\n dst[dstPos] = src[srcPos];\\n }\\n }\\n }\\n this.#buffer = newBuffer;\\n this.#readIndex = this.#writeIndex - samplesToKeep;\\n if (samplesToKeep === 0) this.#stalled = true;\\n }\\n write(timestamp, data) {\\n if (data.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n let start = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n let samples = data[0].length;\\n if (this.#buffered && !this.#anchored) {\\n this.#readIndex = start;\\n this.#writeIndex = start;\\n this.#anchored = true;\\n }\\n let offset = this.#readIndex - start;\\n if (offset > samples) {\\n return;\\n } else if (offset > 0) {\\n samples -= offset;\\n start += offset;\\n } else {\\n offset = 0;\\n }\\n const end = start + samples;\\n const overflow = end - this.#readIndex - this.#buffer[0].length;\\n if (overflow >= 0) {\\n this.#stalled = false;\\n this.#readIndex += overflow;\\n }\\n if (start > this.#writeIndex) {\\n const gapSize = Math.min(start - this.#writeIndex, this.#buffer[0].length);\\n if (gapSize === 1) {\\n console.warn(\\\"floating point inaccuracy detected\\\");\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = this.#buffer[channel];\\n for (let i = 0; i < gapSize; i++) {\\n const writePos = (this.#writeIndex + i) % dst.length;\\n dst[writePos] = 0;\\n }\\n }\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n let src = data[channel];\\n src = src.subarray(src.length - samples);\\n const dst = this.#buffer[channel];\\n if (src.length !== samples) throw new Error(\\\"mismatching number of samples\\\");\\n for (let i = 0; i < samples; i++) {\\n const writePos = (start + i) % dst.length;\\n dst[writePos] = src[i];\\n }\\n }\\n if (end > this.#writeIndex) {\\n this.#writeIndex = end;\\n }\\n if (this.#buffered && this.length >= this.#latencySamples) {\\n this.#stalled = false;\\n }\\n }\\n // Flush all buffered samples and re-stall, ready to anchor the next utterance.\\n reset() {\\n this.#readIndex = 0;\\n this.#writeIndex = 0;\\n this.#stalled = true;\\n this.#anchored = false;\\n }\\n read(output) {\\n if (output.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n if (this.#stalled) return 0;\\n const samples = Math.min(this.#writeIndex - this.#readIndex, output[0].length);\\n if (samples === 0) return 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = output[channel];\\n const src = this.#buffer[channel];\\n if (dst.length !== output[0].length) throw new Error(\\\"mismatching number of samples\\\");\\n for (let i = 0; i < samples; i++) {\\n const readPos = (this.#readIndex + i) % src.length;\\n dst[i] = src[readPos];\\n }\\n }\\n this.#readIndex += samples;\\n return samples;\\n }\\n};\\n\\n// src/audio/shared-ring-buffer.ts\\nvar WRITE = 0;\\nvar READ = 1;\\nvar LATENCY = 2;\\nvar STALLED = 3;\\nvar CONTROL_SLOTS = 4;\\nfunction allocSharedRingBuffer(channels, capacity, rate, buffered = false) {\\n if (channels <= 0) throw new Error(\\\"invalid channels\\\");\\n if (capacity <= 0) throw new Error(\\\"invalid capacity\\\");\\n if (rate <= 0) throw new Error(\\\"invalid sample rate\\\");\\n const samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\\n const control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\\n const ctrl = new Int32Array(control);\\n Atomics.store(ctrl, STALLED, 1);\\n return { channels, capacity, rate, samples, control, buffered };\\n}\\nfunction i32Max(a, b) {\\n return (a - b | 0) > 0 ? a : b;\\n}\\nfunction slot(idx, capacity) {\\n return (idx % capacity + capacity) % capacity;\\n}\\nfunction casAdvance(arr, idx, candidate) {\\n for (; ; ) {\\n const current = Atomics.load(arr, idx);\\n if ((candidate - current | 0) <= 0) return current;\\n const witnessed = Atomics.compareExchange(arr, idx, current, candidate);\\n if (witnessed === current) return candidate;\\n }\\n}\\nvar SharedRingBuffer = class _SharedRingBuffer {\\n channels;\\n capacity;\\n rate;\\n buffered;\\n init;\\n #control;\\n #samples;\\n // Whether READ/WRITE have been anchored to the first inserted sample (buffered mode).\\n #anchored = false;\\n constructor(init) {\\n this.channels = init.channels;\\n this.capacity = init.capacity;\\n this.rate = init.rate;\\n this.buffered = init.buffered;\\n this.init = init;\\n this.#control = new Int32Array(init.control);\\n this.#samples = [];\\n for (let i = 0; i < this.channels; i++) {\\n this.#samples.push(\\n new Float32Array(init.samples, i * this.capacity * Float32Array.BYTES_PER_ELEMENT, this.capacity)\\n );\\n }\\n }\\n /**\\n * Insert audio samples at the given timestamp.\\n * Main thread only. Handles out-of-order writes, gap filling, and overflow.\\n */\\n insert(timestamp, data) {\\n if (data.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n let start = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n const originalLength = data[0].length;\\n let offset = 0;\\n if (this.buffered && !this.#anchored) {\\n Atomics.store(this.#control, READ, start | 0);\\n Atomics.store(this.#control, WRITE, start | 0);\\n this.#anchored = true;\\n }\\n const end = start + originalLength | 0;\\n const read = Atomics.load(this.#control, READ);\\n const behind = read - start | 0;\\n if (behind > 0) {\\n if (behind >= originalLength) {\\n return;\\n }\\n offset = behind;\\n start = start + behind | 0;\\n }\\n const samples = originalLength - offset;\\n if ((end - read | 0) > this.capacity) {\\n casAdvance(this.#control, READ, end - this.capacity | 0);\\n }\\n const write = Atomics.load(this.#control, WRITE);\\n const gap = start - write | 0;\\n if (gap > 0) {\\n const gapSize = Math.min(gap, this.capacity);\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = this.#samples[channel];\\n for (let i = 0; i < gapSize; i++) {\\n dst[slot(write + i | 0, this.capacity)] = 0;\\n }\\n }\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = data[channel];\\n const dst = this.#samples[channel];\\n for (let i = 0; i < samples; i++) {\\n dst[slot(start + i | 0, this.capacity)] = src[offset + i];\\n }\\n }\\n Atomics.store(this.#control, WRITE, i32Max(Atomics.load(this.#control, WRITE), end));\\n const currentRead = Atomics.load(this.#control, READ);\\n const currentWrite = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n if ((currentWrite - currentRead | 0) >= latency && latency > 0) {\\n Atomics.store(this.#control, STALLED, 0);\\n }\\n }\\n /**\\n * Read audio samples into the output buffers.\\n * AudioWorklet only. Returns the number of samples read.\\n */\\n read(output) {\\n if (Atomics.load(this.#control, STALLED) === 1) return 0;\\n let read = Atomics.load(this.#control, READ);\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n const buffered = write - read | 0;\\n if (!this.buffered && latency > 0 && buffered > latency) {\\n const skipTo = write - latency | 0;\\n read = casAdvance(this.#control, READ, skipTo);\\n }\\n const available = write - read | 0;\\n const count = Math.min(available, output[0].length);\\n if (count <= 0) return 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#samples[channel];\\n const dst = output[channel];\\n for (let i = 0; i < count; i++) {\\n dst[i] = src[slot(read + i | 0, this.capacity)];\\n }\\n }\\n casAdvance(this.#control, READ, read + count | 0);\\n return count;\\n }\\n /** Update the target latency in samples. */\\n setLatency(samples) {\\n Atomics.store(this.#control, LATENCY, samples);\\n }\\n /**\\n * Flush buffered samples and re-stall, ready to anchor the next utterance (buffered mode).\\n * Main thread only. The worklet reader sees STALLED and stops until the next insert.\\n */\\n reset() {\\n this.#anchored = false;\\n Atomics.store(this.#control, STALLED, 1);\\n const write = Atomics.load(this.#control, WRITE);\\n Atomics.store(this.#control, READ, write);\\n }\\n /**\\n * Allocate a new ring with `newCapacity` samples and copy the unread window\\n * [READ, WRITE) plus control state into it. Used when growing capacity so\\n * we don't drop buffered audio. If `newCapacity` is smaller than the unread\\n * span, the oldest samples are truncated.\\n *\\n * Main thread only. `resize()` reads from the source `SharedRingBuffer` and\\n * writes into a freshly allocated buffer from `allocSharedRingBuffer`, so it\\n * relies on the same invariant as `insert()`: no concurrent main-thread\\n * writers. The AudioWorklet reader is tolerated via the CAS discipline used\\n * by READ/WRITE elsewhere.\\n */\\n resize(newCapacity) {\\n const init = allocSharedRingBuffer(this.channels, newCapacity, this.rate, this.buffered);\\n const dst = new _SharedRingBuffer(init);\\n dst.#anchored = this.#anchored;\\n const read = Atomics.load(this.#control, READ);\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n const stalled = Atomics.load(this.#control, STALLED);\\n const available = write - read | 0;\\n const copyCount = Math.max(0, Math.min(available, dst.capacity));\\n const copyStart = write - copyCount | 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#samples[channel];\\n const out = dst.#samples[channel];\\n for (let i = 0; i < copyCount; i++) {\\n const idx = copyStart + i | 0;\\n out[slot(idx, dst.capacity)] = src[slot(idx, this.capacity)];\\n }\\n }\\n Atomics.store(dst.#control, READ, copyStart);\\n Atomics.store(dst.#control, WRITE, write);\\n Atomics.store(dst.#control, LATENCY, latency);\\n Atomics.store(dst.#control, STALLED, stalled);\\n return dst;\\n }\\n /** Current playback timestamp derived from READ position. */\\n get timestamp() {\\n const read = Atomics.load(this.#control, READ);\\n return time_exports.Micro.fromSecond(read / this.rate);\\n }\\n /** Whether the buffer is stalled (waiting to fill). */\\n get stalled() {\\n return Atomics.load(this.#control, STALLED) === 1;\\n }\\n /**\\n * Number of buffered samples (WRITE - READ).\\n *\\n * Non-atomic: WRITE and READ are loaded separately, so a concurrent\\n * writer/reader can make the two loads inconsistent. Intended for\\n * tests and diagnostics, not control-flow decisions.\\n */\\n get length() {\\n return Atomics.load(this.#control, WRITE) - Atomics.load(this.#control, READ) | 0;\\n }\\n};\\n\\n// src/audio/render-worklet.ts\\nvar Render = class extends AudioWorkletProcessor {\\n // Set after init, depending on which path the main thread chose.\\n #backend;\\n #underflow = 0;\\n #stateCounter = 0;\\n constructor() {\\n super();\\n this.port.onmessage = (event) => {\\n const msg = event.data;\\n if (msg.type === \\\"init-shared\\\") {\\n console.log(\\\"[audio-worklet] init-shared: using SharedArrayBuffer path\\\");\\n this.#backend = new SharedRingBuffer(msg);\\n this.#underflow = 0;\\n } else if (msg.type === \\\"init-post\\\") {\\n console.log(\\\"[audio-worklet] init-post: using postMessage path\\\");\\n this.#backend = new AudioRingBuffer(msg);\\n this.#underflow = 0;\\n } else if (msg.type === \\\"data\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.write(msg.timestamp, msg.data);\\n } else if (msg.type === \\\"latency\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.resize(msg.latency);\\n } else if (msg.type === \\\"reset\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.reset();\\n }\\n };\\n }\\n process(_inputs, outputs, _parameters) {\\n const output = outputs[0];\\n const backend = this.#backend;\\n const samplesRead = backend?.read(output) ?? 0;\\n if (samplesRead < output[0].length) {\\n this.#underflow += output[0].length - samplesRead;\\n } else if (this.#underflow > 0 && backend) {\\n console.debug(`audio underflow: ${Math.round(1e3 * this.#underflow / backend.rate)}ms`);\\n this.#underflow = 0;\\n }\\n if (backend instanceof AudioRingBuffer) {\\n this.#stateCounter++;\\n if (this.#stateCounter >= 5) {\\n this.#stateCounter = 0;\\n const state = {\\n type: \\\"state\\\",\\n timestamp: backend.timestamp,\\n stalled: backend.stalled\\n };\\n this.port.postMessage(state);\\n }\\n }\\n return true;\\n }\\n};\\nregisterProcessor(\\\"render\\\", Render);\\n\";\nconst blob = new Blob([code], { type: \"application/javascript\" });\nexport default URL.createObjectURL(blob);","import { type Effect, Signal } from \"@moq/signals\";\n\n/**\n * Resume a suspended {@link AudioContext} from a real user gesture.\n *\n * The context is built at page load, before any user activation can exist, so browsers that\n * gate audio on a gesture reject a `resume()` made then. A single unconditional attempt would\n * fire once, be rejected, and never retry, leaving audio silent. This instead attempts\n * `resume()` immediately (for autoplay-permissive browsers like Chrome with prior engagement),\n * then retries on every `pointerdown`/`keydown` until the context is actually running, dropping\n * the gesture listeners once it is. `pointerdown` and `keydown` cover mouse, touch, pen, and\n * keyboard, and each carries a user activation.\n *\n * Safari also reports an \"interrupted\" state (a WebKit-only value outside the\n * suspended/running/closed set) and can leave it on its own; mirroring `statechange` into a\n * signal picks that up so the listeners are re-armed or dropped as the state moves.\n *\n * Scoped to `effect`: the listeners are removed when the effect reruns or closes.\n */\nexport function unlockOnGesture(effect: Effect, context: AudioContext): void {\n\tconst running = new Signal(context.state === \"running\");\n\teffect.event(context, \"statechange\", () => running.set(context.state === \"running\"));\n\n\teffect.run((inner) => {\n\t\tif (inner.get(running)) return;\n\n\t\tconst resume = () => {\n\t\t\tcontext.resume().catch(() => {});\n\t\t};\n\n\t\tresume();\n\t\tinner.event(document, \"pointerdown\", resume);\n\t\tinner.event(document, \"keydown\", resume);\n\t});\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport * as Util from \"@moq/hang/util\";\nimport type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport { base64ToBytes } from \"../base64\";\n\nimport type { Sync } from \"../sync\";\nimport { type AudioBuffer, createAudioBuffer } from \"./buffer\";\n// Compiled and inlined as a blob URL via vite-plugin-worklet.\nimport RenderWorklet from \"./render-worklet.ts?worklet\";\nimport type { Source } from \"./source\";\nimport { unlockOnGesture } from \"./unlock\";\n\nexport type DecoderInput = {\n\t// Enable to download the audio track.\n\tenabled: Getter<boolean>;\n};\n\ntype DecoderOutput = {\n\tcontext: Signal<AudioContext | undefined>;\n\n\t// The root of the audio graph, which can be used for custom visualizations.\n\t// Downcast to AudioNode so it matches Publish.Audio\n\troot: Signal<AudioNode | undefined>;\n\n\tsampleRate: Signal<number | undefined>;\n\tstats: Signal<Stats | undefined>;\n\n\t// Current playback timestamp from worklet\n\ttimestamp: Signal<Time.Milli | undefined>;\n\n\t// Whether the audio buffer is stalled (waiting to fill)\n\tstalled: Signal<boolean>;\n\n\t// Combined buffered ranges (network jitter + decode buffer)\n\tbuffered: Signal<Container.BufferedRanges>;\n};\n\n/** Cumulative audio statistics since the decoder started. */\nexport interface Stats {\n\t/** Number of encoded bytes received. */\n\tbytesReceived: number;\n}\n\n/**\n * Downloads audio from a track and emits it to an AudioContext.\n *\n * The user is responsible for hooking up audio to speakers, an analyzer, etc.\n */\nexport class Decoder {\n\treadonly in: Readonlys<DecoderInput>;\n\treadonly source: Source;\n\treadonly sync: Sync;\n\n\treadonly #out: DecoderOutput = {\n\t\tcontext: new Signal<AudioContext | undefined>(undefined),\n\t\troot: new Signal<AudioNode | undefined>(undefined),\n\t\tsampleRate: new Signal<number | undefined>(undefined),\n\t\tstats: new Signal<Stats | undefined>(undefined),\n\t\ttimestamp: new Signal<Time.Milli | undefined>(undefined),\n\t\tstalled: new Signal<boolean>(true),\n\t\tbuffered: new Signal<Container.BufferedRanges>([]),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t// Decode buffer: audio sent to worklet but not yet played\n\t#decodeBuffered = new Signal<Container.BufferedRanges>([]);\n\n\t// Audio ring bridging main thread and worklet (shared memory or postMessage transport).\n\t#ring: AudioBuffer | undefined;\n\n\t// The rate the decoder actually outputs, learned from the first decoded frame. This is the source\n\t// of truth for the graph: a decoder can output a different rate than it was configured with (e.g.\n\t// Opus decodes to 48kHz on Chrome/Firefox but to the configured rate on Safari). Until a frame\n\t// arrives we pre-build the graph from the catalog rate; if the real rate differs we rebuild it.\n\t#decodedSampleRate = new Signal<number | undefined>(undefined);\n\n\t// The last discontinuity count seen from the container consumer. A change means the\n\t// publisher rewound the timeline (e.g. a voice agent interrupted) and we must flush.\n\t#discontinuity = 0;\n\n\t// How much buffered audio the container consumer retains before skipping\n\t// ahead. This must be the latency CEILING (maxBuffer), not the floor\n\t// (buffer): in buffered playback the producer writes faster than real-time\n\t// with future PTS, so the group span legitimately exceeds the floor and\n\t// would otherwise be skipped. When collapsed, maxBuffer equals the floor.\n\t//\n\t// Held in a plain Signal driven by a running effect (below) rather than a\n\t// lazy `computed`: the container consumer only `.peek()`s this (it never\n\t// subscribes), and an unsubscribed computed peeks as `undefined`, which\n\t// would make the consumer's threshold NaN and skip every group.\n\t#consumerLatency = new Signal<Time.Milli>(Time.Milli.zero);\n\n\t#signals = new Effect();\n\n\tconstructor(source: Source, sync: Sync, props?: Inputs<DecoderInput>) {\n\t\tthis.in = {\n\t\t\tenabled: getter(props?.enabled ?? false),\n\t\t};\n\n\t\tthis.source = source;\n\t\tthis.sync = sync;\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tthis.#consumerLatency.set(effect.get(this.sync.out.maxBuffer));\n\t\t});\n\n\t\tthis.#signals.run(this.#runWorklet.bind(this));\n\t\tthis.#signals.run(this.#runEnabled.bind(this));\n\t\tthis.#signals.run(this.#runLatency.bind(this));\n\t\tthis.#signals.run(this.#runDecoder.bind(this));\n\t}\n\n\t#runWorklet(effect: Effect): void {\n\t\t// It takes a second or so to initialize the AudioContext/AudioWorklet, so do it even if disabled.\n\t\t// This is less efficient for video-only playback but makes muting/unmuting instant.\n\n\t\t//const enabled = effect.get(this.enabled);\n\t\t//if (!enabled) return;\n\n\t\tconst config = effect.get(this.source.out.config);\n\t\tif (!config) return;\n\n\t\t// Pre-build the graph at the catalog rate so warm-up starts before the first frame arrives. The\n\t\t// decoder's actual output rate is the source of truth (see #emit); if it differs, #emit sets\n\t\t// #decodedSampleRate, which re-runs this effect and rebuilds the graph at the real rate.\n\t\tconst sampleRate = effect.get(this.#decodedSampleRate) ?? config.sampleRate;\n\t\tconst channelCount = config.numberOfChannels;\n\n\t\t// Expose the rate the graph actually runs at.\n\t\teffect.set(this.#out.sampleRate, sampleRate);\n\n\t\tconst context = new AudioContext({\n\t\t\tlatencyHint: \"interactive\", // We don't use real-time because of the buffer.\n\t\t\tsampleRate,\n\t\t});\n\t\teffect.set(this.#out.context, context);\n\n\t\teffect.cleanup(() => context.close());\n\n\t\teffect.spawn(async () => {\n\t\t\t// Register the AudioWorklet processor, racing the load against teardown. If teardown wins,\n\t\t\t// `loaded` is undefined and we bail before constructing the node: the module registration was\n\t\t\t// abandoned, so building against its name would throw. Gate on the race result, not\n\t\t\t// `context.state`, because `AudioContext.close()` only flips `.state` to \"closed\" synchronously\n\t\t\t// on Chrome (Firefox/Safari report \"suspended\").\n\t\t\tconst loaded = await Promise.race([\n\t\t\t\tcontext.audioWorklet.addModule(RenderWorklet).then(() => true),\n\t\t\t\teffect.cancel,\n\t\t\t]);\n\t\t\tif (!loaded) return;\n\n\t\t\t// Create the worklet node. outputChannelCount must be set explicitly\n\t\t\t// so the process() callback receives a matching channel layout.\n\t\t\t// Firefox defaults differently than Chrome otherwise.\n\t\t\tconst worklet = new AudioWorkletNode(context, \"render\", {\n\t\t\t\tchannelCount,\n\t\t\t\tchannelCountMode: \"explicit\",\n\t\t\t\toutputChannelCount: [channelCount],\n\t\t\t});\n\t\t\teffect.cleanup(() => worklet.disconnect());\n\n\t\t\t// Initial target latency in samples.\n\t\t\tconst latency = this.sync.out.buffer.peek();\n\t\t\tconst latencySamples = Math.ceil(sampleRate * Time.Second.fromMilli(latency));\n\t\t\tconst buffered = this.sync.out.buffered.peek();\n\n\t\t\t// Let the factory pick the best transport (SharedArrayBuffer or postMessage).\n\t\t\tconst ring = createAudioBuffer(worklet, channelCount, sampleRate, latencySamples, buffered);\n\t\t\tthis.#ring = ring;\n\t\t\teffect.cleanup(() => {\n\t\t\t\tring.close();\n\t\t\t\tthis.#ring = undefined;\n\t\t\t});\n\n\t\t\t// Mirror ring state (timestamp/stalled) onto our public signals.\n\t\t\teffect.run((inner) => {\n\t\t\t\tconst ts = Time.Milli.fromMicro(inner.get(ring.timestamp));\n\t\t\t\tthis.#out.timestamp.set(ts);\n\t\t\t\tthis.#trimDecodeBuffered(ts);\n\t\t\t});\n\t\t\teffect.run((inner) => {\n\t\t\t\tthis.#out.stalled.set(inner.get(ring.stalled));\n\t\t\t});\n\n\t\t\teffect.set(this.#out.root, worklet);\n\t\t});\n\t}\n\n\t#runEnabled(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst context = effect.get(this.#out.context);\n\t\tif (!context) return;\n\n\t\t// The context is built at page load (see #runWorklet), before any user gesture, so it\n\t\t// must be started from a real interaction. See unlockOnGesture.\n\t\tunlockOnGesture(effect, context);\n\n\t\t// NOTE: You should disconnect/reconnect the worklet to save power when disabled.\n\t}\n\n\t#runLatency(effect: Effect): void {\n\t\t// Gate on the worklet signal so this effect re-runs once the ring is created.\n\t\tconst worklet = effect.get(this.#out.root);\n\t\tif (!worklet) return;\n\n\t\tconst ring = this.#ring;\n\t\tif (!ring) return;\n\n\t\tconst latency = effect.get(this.sync.out.buffer);\n\t\tconst latencySamples = Math.ceil(ring.rate * Time.Second.fromMilli(latency));\n\t\tring.setLatency(latencySamples);\n\t}\n\n\t#runDecoder(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst broadcast = effect.get(this.source.in.broadcast);\n\t\tif (!broadcast) return;\n\n\t\tconst track = effect.get(this.source.out.track);\n\t\tif (!track) return;\n\n\t\tconst config = effect.get(this.source.out.config);\n\t\tif (!config) return;\n\n\t\t// Honor a per-rendition `broadcast` override: subscribe on the resolved source\n\t\t// broadcast instead of the catalog's own broadcast.\n\t\tconst active = broadcast.relativeBroadcast(effect, config.broadcast);\n\t\tif (!active) return;\n\n\t\tconst sub = active.track(track).subscribe({ priority: Catalog.PRIORITY.audio });\n\t\teffect.cleanup(() => sub.close());\n\n\t\tif (config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmafDecoder(effect, sub, config);\n\t\t} else {\n\t\t\tthis.#runLegacyDecoder(effect, sub, config);\n\t\t}\n\t}\n\n\t#runLegacyDecoder(effect: Effect, sub: Moq.Track.Subscriber, config: Catalog.AudioConfig): void {\n\t\tconst format = config.container.kind === \"loc\" ? new Container.Loc.Format() : new Container.Legacy.Format();\n\t\t// Create consumer with slightly less latency than the render worklet to avoid underflowing.\n\t\t// TODO include JITTER_UNDERHEAD\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat,\n\t\t\tlatency: this.#consumerLatency,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#decodeBuffered);\n\t\t\tthis.#out.buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\teffect.spawn(async () => {\n\t\t\tconst loaded = await Util.Libav.polyfill();\n\t\t\tif (!loaded) return; // cancelled\n\n\t\t\tlet warmed = 0;\n\n\t\t\tconst decoder = new AudioDecoder({\n\t\t\t\toutput: (data) => {\n\t\t\t\t\twarmed++;\n\t\t\t\t\tif (warmed <= 3) {\n\t\t\t\t\t\t// Drop the first 3 frames to prime the decoder.\n\t\t\t\t\t\tdata.close();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.#emit(data);\n\t\t\t\t},\n\t\t\t\terror: (error) => console.error(\"audio decoder error\", error),\n\t\t\t});\n\t\t\teffect.cleanup(() => {\n\t\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t\t});\n\n\t\t\t// Opus in CMAF uses raw packets; dOps is not a valid OGG Identification Header.\n\t\t\tconst description =\n\t\t\t\tconfig.codec === \"opus\"\n\t\t\t\t\t? undefined\n\t\t\t\t\t: config.description\n\t\t\t\t\t\t? Util.Hex.toBytes(config.description)\n\t\t\t\t\t\t: undefined;\n\t\t\tdecoder.configure({\n\t\t\t\t...config,\n\t\t\t\tdescription,\n\t\t\t});\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\t// Publisher rewound the timeline: flush + re-anchor before decoding the new frame.\n\t\t\t\tthis.#onDiscontinuity(next.discontinuity);\n\n\t\t\t\tconst { frame } = next;\n\t\t\t\tif (!frame) continue;\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\tthis.sync.received(timestamp, \"audio\");\n\n\t\t\t\tthis.#out.stats.update((stats) => ({\n\t\t\t\t\tbytesReceived: (stats?.bytesReceived ?? 0) + frame.payload.byteLength,\n\t\t\t\t}));\n\n\t\t\t\t// Backpressure: in buffered mode this holds the encoded frame until the playhead nears\n\t\t\t\t// it, keeping the lookahead above the floor as Opus instead of decoded PCM. No-op live.\n\t\t\t\tawait this.#ring?.wait(frame.timestamp as Time.Micro);\n\n\t\t\t\tconst chunk = new EncodedAudioChunk({\n\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\tdata: frame.payload,\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t});\n\n\t\t\t\t// A fatal decode error closes the decoder, so decoding again throws InvalidStateError out\n\t\t\t\t// of this loop. Stop instead: the error callback already reported the real failure.\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tdecoder.decode(chunk);\n\t\t\t}\n\t\t});\n\t}\n\n\t#runCmafDecoder(effect: Effect, sub: Moq.Track.Subscriber, config: Catalog.AudioConfig): void {\n\t\tif (config.container.kind !== \"cmaf\") return; // just to help typescript\n\n\t\tconst initSegment = base64ToBytes(config.container.init);\n\t\tconst init = Container.Cmaf.decodeInitSegment(initSegment);\n\t\t// Opus in CMAF uses raw packets (not OGG-wrapped), so description must be omitted.\n\t\t// The dOps box from the init segment is not a valid OGG Identification Header.\n\t\tconst description =\n\t\t\tconfig.codec === \"opus\"\n\t\t\t\t? undefined\n\t\t\t\t: config.description\n\t\t\t\t\t? Util.Hex.toBytes(config.description)\n\t\t\t\t\t: init.description;\n\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat: new Container.Cmaf.Format(init),\n\t\t\tlatency: this.#consumerLatency,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#decodeBuffered);\n\t\t\tthis.#out.buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\teffect.spawn(async () => {\n\t\t\tconst loaded = await Util.Libav.polyfill();\n\t\t\tif (!loaded) return; // cancelled\n\n\t\t\tconst decoder = new AudioDecoder({\n\t\t\t\toutput: (data) => this.#emit(data),\n\t\t\t\terror: (error) => console.error(\"audio decoder error\", error),\n\t\t\t});\n\t\t\teffect.cleanup(() => {\n\t\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t\t});\n\n\t\t\t// Configure decoder with description from catalog\n\t\t\tdecoder.configure({\n\t\t\t\tcodec: config.codec,\n\t\t\t\tsampleRate: config.sampleRate,\n\t\t\t\tnumberOfChannels: config.numberOfChannels,\n\t\t\t\tdescription,\n\t\t\t});\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\t// Publisher rewound the timeline: flush + re-anchor before decoding the new frame.\n\t\t\t\tthis.#onDiscontinuity(next.discontinuity);\n\n\t\t\t\tconst { frame } = next;\n\t\t\t\tif (!frame) continue;\n\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\tthis.sync.received(timestamp, \"audio\");\n\n\t\t\t\tthis.#out.stats.update((stats) => ({\n\t\t\t\t\tbytesReceived: (stats?.bytesReceived ?? 0) + frame.payload.byteLength,\n\t\t\t\t}));\n\n\t\t\t\t// Backpressure: in buffered mode this holds the encoded frame until the playhead nears\n\t\t\t\t// it, keeping the lookahead above the floor as Opus instead of decoded PCM. No-op live.\n\t\t\t\tawait this.#ring?.wait(frame.timestamp);\n\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tdecoder.decode(\n\t\t\t\t\tnew EncodedAudioChunk({\n\t\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\t\tdata: frame.payload,\n\t\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\t#emit(sample: AudioData) {\n\t\tconst timestamp = sample.timestamp as Time.Micro;\n\t\tconst timestampMilli = Time.Milli.fromMicro(timestamp);\n\n\t\tconst ring = this.#ring;\n\t\tif (!ring) {\n\t\t\t// We're probably in the process of closing.\n\t\t\tsample.close();\n\t\t\treturn;\n\t\t}\n\n\t\t// sample.sampleRate is the source of truth, and it can differ from the rate we pre-built the\n\t\t// graph against (Opus decodes to 48kHz on Chrome/Firefox but to the configured rate on Safari).\n\t\t// If they disagree, rebuild the graph at the real rate and drop this frame; the ring being torn\n\t\t// down can't accept it, and the next frame lands in the correctly-rated ring.\n\t\tif (sample.sampleRate !== ring.rate) {\n\t\t\tthis.#decodedSampleRate.set(sample.sampleRate);\n\t\t\tsample.close();\n\t\t\treturn;\n\t\t}\n\n\t\t// Calculate end time from sample duration\n\t\tconst durationMicro = ((sample.numberOfFrames / sample.sampleRate) * 1_000_000) as Time.Micro;\n\t\tconst durationMilli = Time.Milli.fromMicro(durationMicro);\n\t\tconst end = Time.Milli.add(timestampMilli, durationMilli);\n\n\t\t// Add to decode buffer\n\t\tthis.#addDecodeBuffered(timestampMilli, end);\n\n\t\t// Firefox's Opus decoder sometimes outputs more channels than requested\n\t\t// (e.g. 6 for stereo). Clamp to the ring's channel count.\n\t\tconst channels = Math.min(sample.numberOfChannels, ring.channels);\n\t\tconst channelData: Float32Array[] = [];\n\t\tfor (let channel = 0; channel < channels; channel++) {\n\t\t\tconst data = new Float32Array(sample.numberOfFrames);\n\t\t\tsample.copyTo(data, { format: \"f32-planar\", planeIndex: channel });\n\t\t\tchannelData.push(data);\n\t\t}\n\n\t\t// Hand off to the ring. Shared transport writes directly; post transport\n\t\t// transfers the ArrayBuffers.\n\t\tring.insert(timestamp, channelData);\n\n\t\tsample.close();\n\t}\n\n\t#addDecodeBuffered(start: Time.Milli, end: Time.Milli): void {\n\t\tif (start > end) return;\n\n\t\tthis.#decodeBuffered.mutate((current) => {\n\t\t\tfor (const range of current) {\n\t\t\t\t// Extend range if new sample overlaps or is adjacent (1ms tolerance for float precision)\n\t\t\t\tif (start <= range.end + 1 && end >= range.start) {\n\t\t\t\t\trange.start = Time.Milli.min(range.start, start);\n\t\t\t\t\trange.end = Time.Milli.max(range.end, end);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrent.push({ start, end });\n\t\t\tcurrent.sort((a, b) => a.start - b.start);\n\t\t});\n\t}\n\n\t#trimDecodeBuffered(timestamp: Time.Milli): void {\n\t\tthis.#decodeBuffered.mutate((current) => {\n\t\t\twhile (current.length > 0) {\n\t\t\t\tif (current[0].end >= timestamp) {\n\t\t\t\t\tcurrent[0].start = Time.Milli.max(current[0].start, timestamp);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent.shift();\n\t\t\t}\n\t\t});\n\t}\n\n\t// Flush the audio buffer and re-stall, re-anchoring playback to the next frame.\n\t// Use in buffered mode at an utterance boundary (see Sync.reset).\n\treset(): void {\n\t\tthis.#ring?.reset();\n\t}\n\n\t// React to the container consumer's discontinuity counter. When it changes the publisher\n\t// has rewound the timeline, so flush the queued PCM and re-anchor the shared clock before\n\t// the first frame of the new utterance is decoded. This makes the wire signal trigger the\n\t// same flush as a manual `reset()`, with no app involvement.\n\t#onDiscontinuity(count: number): void {\n\t\tif (count === this.#discontinuity) return;\n\t\tthis.#discontinuity = count;\n\t\tthis.#ring?.reset();\n\t\tthis.sync.reset();\n\t}\n\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n\n\t// Whether the WebCodecs audio decoder can play this config.\n\tstatic supported = supported;\n}\n\nasync function supported(config: Catalog.AudioConfig): Promise<boolean> {\n\tif (!Catalog.containerSupported(config.container)) {\n\t\t// `kind` is the literal \"unknown\" tag; the container the publisher actually named is in `raw`.\n\t\tconst kind = config.container.kind === \"unknown\" ? config.container.raw.kind : config.container.kind;\n\t\tconsole.warn(`audio: ignoring rendition with unknown container: ${kind}`);\n\t\treturn false;\n\t}\n\n\t// Opus only runs at its native rates, so a catalog advertising anything else is wrong and Safari\n\t// refuses to decode it. Warn rather than reject: Chrome and Firefox ignore the configured rate and\n\t// play these streams fine, so rejecting would silence them for a publisher they handle today.\n\tif (config.codec === \"opus\" && !Util.Opus.supportsRate(config.sampleRate)) {\n\t\tconsole.warn(`audio: opus advertised at ${config.sampleRate}Hz, which some browsers cannot decode`);\n\t}\n\n\t// Opus in CMAF uses raw packets; dOps is not a valid OGG Identification Header.\n\tlet description: Uint8Array | undefined;\n\tif (config.codec !== \"opus\") {\n\t\tif (config.description) {\n\t\t\tdescription = Util.Hex.toBytes(config.description);\n\t\t} else if (config.container.kind === \"cmaf\") {\n\t\t\ttry {\n\t\t\t\tdescription = Container.Cmaf.decodeInitSegment(base64ToBytes(config.container.init)).description;\n\t\t\t} catch (err) {\n\t\t\t\t// A malformed init segment means we can't extract the codec\n\t\t\t\t// description, so we can't probe support reliably. Reject the\n\t\t\t\t// track rather than letting isConfigSupported pass on a\n\t\t\t\t// description-less config and then having decode() fail later.\n\t\t\t\tconsole.warn(`audio: malformed CMAF init segment for codec ${config.codec}`, err);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\tconst res = await AudioDecoder.isConfigSupported({\n\t\t...config,\n\t\tdescription,\n\t});\n\treturn res.supported ?? false;\n}\n","import { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Decoder } from \"./decoder\";\n\nconst MIN_GAIN = 0.001;\nconst FADE_TIME = 0.2;\n\nexport type EmitterInput = {\n\tvolume: Getter<number>;\n\n\t// Silences the audio and stops the download. Muted samples aren't worth the bandwidth,\n\t// and the decoder keeps the AudioContext warm so unmuting is still instant.\n\tmuted: Getter<boolean>;\n\n\t// Pauses playback, which also stops the download.\n\tpaused: Getter<boolean>;\n};\n\ntype EmitterOutput = {\n\t// Whether audio should be downloaded. Wired into the decoder's `enabled` input by the owner.\n\tenabled: Signal<boolean>;\n};\n\n// A helper that emits audio directly to the speakers.\nexport class Emitter {\n\treadonly source: Decoder;\n\n\treadonly in: Readonlys<EmitterInput>;\n\n\treadonly #out: EmitterOutput = {\n\t\tenabled: new Signal<boolean>(false),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t#signals = new Effect();\n\n\t// The gain node used to adjust the volume.\n\t#gain = new Signal<GainNode | undefined>(undefined);\n\n\tconstructor(source: Decoder, props?: Inputs<EmitterInput>) {\n\t\tthis.source = source;\n\t\tthis.in = {\n\t\t\tvolume: getter(props?.volume ?? 0.5),\n\t\t\tmuted: getter(props?.muted ?? false),\n\t\t\tpaused: getter(props?.paused ?? false),\n\t\t};\n\n\t\t// Only download while playing audible audio. Pausing or muting stops it.\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst enabled = !effect.get(this.in.paused) && !effect.get(this.in.muted);\n\t\t\tthis.#out.enabled.set(enabled);\n\t\t});\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst root = effect.get(this.source.out.root);\n\t\t\tif (!root) return;\n\n\t\t\tconst gain = new GainNode(root.context, { gain: effect.get(this.in.volume) });\n\t\t\troot.connect(gain);\n\n\t\t\teffect.set(this.#gain, gain);\n\n\t\t\teffect.run((inner) => {\n\t\t\t\t// We only connect/disconnect when enabled to save power.\n\t\t\t\t// Otherwise the worklet keeps running in the background returning 0s.\n\t\t\t\tconst enabled = inner.get(this.#out.enabled);\n\t\t\t\tif (!enabled) return;\n\n\t\t\t\tgain.connect(root.context.destination); // speakers\n\t\t\t\tinner.cleanup(() => gain.disconnect());\n\t\t\t});\n\t\t});\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst gain = effect.get(this.#gain);\n\t\t\tif (!gain) return;\n\n\t\t\t// Cancel any scheduled transitions on change.\n\t\t\teffect.cleanup(() => gain.gain.cancelScheduledValues(gain.context.currentTime));\n\n\t\t\tconst volume = effect.get(this.in.volume);\n\t\t\tif (volume < MIN_GAIN) {\n\t\t\t\tgain.gain.exponentialRampToValueAtTime(MIN_GAIN, gain.context.currentTime + FADE_TIME);\n\t\t\t\tgain.gain.setValueAtTime(0, gain.context.currentTime + FADE_TIME + 0.01);\n\t\t\t} else {\n\t\t\t\tgain.gain.exponentialRampToValueAtTime(volume, gain.context.currentTime + FADE_TIME);\n\t\t\t}\n\t\t});\n\t}\n\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\n\n// AudioWorklet always renders in 128-sample quanta.\nconst WORKLET_QUANTUM = 128;\n\nexport type Target = {\n\t// Optional manual override for the selected rendition name.\n\tname?: string;\n};\n\n/**\n * A function that checks if an audio configuration can be played.\n *\n * `Decoder.supported` is the WebCodecs probe used by `<moq-watch>`.\n */\nexport type Supported = (config: Catalog.AudioConfig) => Promise<boolean>;\n\nexport type SourceInput = {\n\tbroadcast: Getter<Broadcast | undefined>;\n\n\t// The desired rendition/bitrate of the audio.\n\ttarget: Getter<Target | undefined>;\n\n\t// A function that checks if an audio configuration can be played. Renditions that fail the\n\t// probe are filtered out. Nothing is selected until one is provided.\n\tsupported: Getter<Supported | undefined>;\n};\n\ntype SourceOutput = {\n\tcatalog: Signal<Catalog.Audio | undefined>;\n\tavailable: Signal<Record<string, Catalog.AudioConfig>>;\n\ttrack: Signal<string | undefined>;\n\tconfig: Signal<Catalog.AudioConfig | undefined>;\n\n\t// The per-rendition jitter (ms) to add to the sync buffer. Wired into Sync by the parent.\n\tjitter: Signal<Moq.Time.Milli | undefined>;\n};\n\n/**\n * Source handles catalog extraction, support checking, and rendition selection\n * for audio playback. The Decoder consumes whichever rendition it picks.\n */\nexport class Source {\n\treadonly in: Readonlys<SourceInput>;\n\n\treadonly #out: SourceOutput = {\n\t\tcatalog: new Signal<Catalog.Audio | undefined>(undefined),\n\t\tavailable: new Signal<Record<string, Catalog.AudioConfig>>({}),\n\t\ttrack: new Signal<string | undefined>(undefined),\n\t\tconfig: new Signal<Catalog.AudioConfig | undefined>(undefined),\n\t\tjitter: new Signal<Moq.Time.Milli | undefined>(undefined),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t#signals = new Effect();\n\n\tconstructor(props?: Inputs<SourceInput>) {\n\t\tthis.in = {\n\t\t\tbroadcast: getter(props?.broadcast),\n\t\t\ttarget: getter(props?.target),\n\t\t\tsupported: getter(props?.supported),\n\t\t};\n\n\t\tthis.#signals.run(this.#runCatalog.bind(this));\n\t\tthis.#signals.run(this.#runSupported.bind(this));\n\t\tthis.#signals.run(this.#runSelected.bind(this));\n\t}\n\n\t#runCatalog(effect: Effect): void {\n\t\tconst broadcast = effect.get(this.in.broadcast);\n\t\tif (!broadcast) return;\n\n\t\tconst catalog = effect.get(broadcast.out.catalog)?.audio;\n\t\tif (!catalog) return;\n\n\t\teffect.set(this.#out.catalog, catalog);\n\t}\n\n\t#runSupported(effect: Effect): void {\n\t\tconst renditions = effect.get(this.#out.catalog)?.renditions ?? {};\n\t\tconst supported = effect.get(this.in.supported);\n\t\tif (!supported) return;\n\n\t\teffect.spawn(async () => {\n\t\t\tconst available: Record<string, Catalog.AudioConfig> = {};\n\n\t\t\tfor (const [name, config] of Object.entries(renditions)) {\n\t\t\t\tconst isSupported = await supported(config);\n\t\t\t\tif (isSupported) available[name] = config;\n\t\t\t}\n\n\t\t\tif (Object.keys(available).length === 0 && Object.keys(renditions).length > 0) {\n\t\t\t\tconsole.warn(\"no supported audio renditions found:\", renditions);\n\t\t\t}\n\n\t\t\tthis.#out.available.set(available);\n\t\t});\n\t}\n\n\t#runSelected(effect: Effect): void {\n\t\tconst available = effect.get(this.#out.available);\n\t\tif (Object.keys(available).length === 0) return;\n\n\t\tconst target = effect.get(this.in.target);\n\n\t\tlet selected: { track: string; config: Catalog.AudioConfig } | undefined;\n\n\t\t// Manual selection by name\n\t\tif (target?.name && target.name in available) {\n\t\t\tselected = { track: target.name, config: available[target.name] };\n\t\t} else {\n\t\t\t// Automatic selection\n\t\t\tselected = this.#select(available);\n\t\t\tif (!selected) return;\n\t\t}\n\n\t\teffect.set(this.#out.track, selected.track);\n\t\teffect.set(this.#out.config, selected.config);\n\n\t\t// Use catalog jitter if available, otherwise estimate from codec frame duration.\n\t\t// Add the worklet render quantum so the ring buffer has margin between frame arrivals.\n\t\tconst codecJitter = selected.config.jitter ?? defaultAudioJitter(selected.config) ?? 0;\n\t\tconst overhead = Math.ceil((WORKLET_QUANTUM / selected.config.sampleRate) * 1000);\n\t\tconst jitter = codecJitter + overhead;\n\t\teffect.set(this.#out.jitter, Time.Milli(jitter));\n\t}\n\n\t/**\n\t * Select rendition based on the configured strategy.\n\t */\n\t#select(\n\t\trenditions: Record<string, Catalog.AudioConfig>,\n\t): { track: string; config: Catalog.AudioConfig } | undefined {\n\t\tconst entries = Object.entries(renditions);\n\t\tif (entries.length === 0) return undefined;\n\n\t\tfor (const [track, config] of entries) {\n\t\t\tif (config.container.kind === \"legacy\") {\n\t\t\t\treturn { track, config };\n\t\t\t}\n\t\t}\n\n\t\tfor (const [track, config] of entries) {\n\t\t\tif (config.container.kind === \"loc\") {\n\t\t\t\treturn { track, config };\n\t\t\t}\n\t\t}\n\n\t\tfor (const [track, config] of entries) {\n\t\t\tif (config.container.kind === \"cmaf\") {\n\t\t\t\treturn { track, config };\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n\n// Estimate the minimum jitter (frame duration) based on the audio codec.\n// TODO these are defaults; the actual frame duration depends on encoder config.\nfunction defaultAudioJitter(config: Catalog.AudioConfig): number | undefined {\n\tif (config.codec.startsWith(\"opus\")) {\n\t\t// Opus supports 2.5–60ms but 20ms is the real-time default.\n\t\treturn 20;\n\t}\n\n\tif (config.codec.startsWith(\"mp4a\")) {\n\t\t// 1024 samples for LC-AAC; HE-AAC/AAC-LD use different sizes.\n\t\treturn Math.ceil((1024 / config.sampleRate) * 1000);\n\t}\n\n\tif (config.codec === \"mp3\") {\n\t\t// 1152 samples per frame for MPEG-1 Layer III; MPEG-2/2.5 use 576.\n\t\tconst samples = config.sampleRate >= 32000 ? 1152 : 576;\n\t\treturn Math.ceil((samples / config.sampleRate) * 1000);\n\t}\n\n\treturn undefined;\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport { u53 } from \"@moq/hang/catalog\";\nimport type * as Msf from \"@moq/msf\";\nimport { base64ToBytes } from \"./base64\";\n\nconst DEFAULT_SAMPLE_RATE = 48000;\nconst DEFAULT_NUMBER_OF_CHANNELS = 2;\n\nfunction bytesToHex(bytes: Uint8Array): string {\n\tlet hex = \"\";\n\tfor (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, \"0\");\n\treturn hex;\n}\n\ninterface ContainerInfo {\n\tcontainer: Catalog.Container;\n\tdescription?: string;\n}\n\nfunction toContainer(track: Msf.Track): ContainerInfo {\n\tlet initBytes: Uint8Array | undefined;\n\ttry {\n\t\tinitBytes = track.initData ? base64ToBytes(track.initData) : undefined;\n\t} catch {\n\t\tinitBytes = undefined;\n\t}\n\n\tif (track.packaging === \"cmaf\" && track.initData && initBytes) {\n\t\treturn {\n\t\t\tcontainer: { kind: \"cmaf\", init: track.initData },\n\t\t\t// hang's CMAF path reads codec metadata from the init segment, so we\n\t\t\t// don't need to surface a separate description here.\n\t\t\tdescription: undefined,\n\t\t};\n\t}\n\n\treturn {\n\t\tcontainer: { kind: \"legacy\" },\n\t\tdescription: initBytes ? bytesToHex(initBytes) : undefined,\n\t};\n}\n\nfunction toVideoConfig(track: Msf.Track): Catalog.VideoConfig | undefined {\n\tif (!track.codec) return undefined;\n\n\tconst { container, description } = toContainer(track);\n\treturn {\n\t\tcodec: track.codec,\n\t\tcontainer,\n\t\tdescription,\n\t\tcodedWidth: track.width != null ? u53(track.width) : undefined,\n\t\tcodedHeight: track.height != null ? u53(track.height) : undefined,\n\t\tframerate: track.framerate,\n\t\tbitrate: track.bitrate != null ? u53(track.bitrate) : undefined,\n\t\tjitter: track.jitter != null ? u53(track.jitter) : undefined,\n\t};\n}\n\nfunction toAudioConfig(track: Msf.Track): Catalog.AudioConfig | undefined {\n\tif (!track.codec) return undefined;\n\n\tconst channels = (() => {\n\t\tif (!track.channelConfig) return DEFAULT_NUMBER_OF_CHANNELS;\n\t\tconst parsed = Number.parseInt(track.channelConfig, 10);\n\t\treturn Number.isFinite(parsed) ? parsed : DEFAULT_NUMBER_OF_CHANNELS;\n\t})();\n\n\tconst { container, description } = toContainer(track);\n\treturn {\n\t\tcodec: track.codec,\n\t\tcontainer,\n\t\tdescription,\n\t\tsampleRate: u53(track.samplerate ?? DEFAULT_SAMPLE_RATE),\n\t\tnumberOfChannels: u53(channels),\n\t\tbitrate: track.bitrate != null ? u53(track.bitrate) : undefined,\n\t\tjitter: track.jitter != null ? u53(track.jitter) : undefined,\n\t};\n}\n\n/** Convert an MSF catalog to a hang catalog Root. */\nexport function toHang(msf: Msf.Catalog): Catalog.Root {\n\tconst videoRenditions: Record<string, Catalog.VideoConfig> = {};\n\tconst audioRenditions: Record<string, Catalog.AudioConfig> = {};\n\n\tfor (const track of msf.tracks) {\n\t\tif (track.role === \"video\") {\n\t\t\tconst config = toVideoConfig(track);\n\t\t\tif (config) videoRenditions[track.name] = config;\n\t\t} else if (track.role === \"audio\") {\n\t\t\tconst config = toAudioConfig(track);\n\t\t\tif (config) audioRenditions[track.name] = config;\n\t\t}\n\t}\n\n\tconst root: Catalog.Root = {};\n\n\tif (Object.keys(videoRenditions).length > 0) {\n\t\troot.video = { renditions: videoRenditions };\n\t}\n\n\tif (Object.keys(audioRenditions).length > 0) {\n\t\troot.audio = { renditions: audioRenditions };\n\t}\n\n\treturn root;\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Json from \"@moq/json\";\nimport * as Msf from \"@moq/msf\";\nimport type * as Moq from \"@moq/net\";\nimport { Path } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\n\nimport { toHang } from \"./msf\";\n\n// Connections already warned about missing broadcast-discovery support, so the\n// announcement check logs at most once per connection.\nconst warnedNoDiscovery = new WeakSet<Moq.Connection.Established>();\n\n// Whether to skip the announcement gate for this connection: without discovery, waiting on an\n// announcement would hang forever, so subscribe immediately and warn once per connection.\nfunction skipDiscovery(conn: Moq.Connection.Established): boolean {\n\tif (conn.discovery) return false;\n\tif (!warnedNoDiscovery.has(conn)) {\n\t\twarnedNoDiscovery.add(conn);\n\t\tconsole.warn(\"relay does not support broadcast discovery; ignoring reload signal.\");\n\t}\n\treturn true;\n}\n\n// Watch supports the on-the-wire catalog formats from @moq/hang, plus \"hangz\" (the\n// DEFLATE-compressed `catalog.json.z` track) and a \"manual\" mode where the user supplies the\n// catalog directly without fetching. \"hangz\" is opt-in only: it shares the `.hang` broadcast suffix\n// and is never auto-detected, so set it explicitly via `catalogFormat`.\nexport const CATALOG_FORMATS = [...Catalog.FORMATS, \"hangz\", \"manual\"] as const;\nexport type CatalogFormat = (typeof CATALOG_FORMATS)[number];\n\nexport function parseCatalogFormat(value: string | null): CatalogFormat | undefined {\n\tif (value === null) return undefined;\n\treturn CATALOG_FORMATS.find((f) => f === value);\n}\n\ntype Status = \"offline\" | \"loading\" | \"live\";\n\n// Signals the component reads. Whoever owns the backing Signal (the caller, or\n// another component whose output is wired in) does the writing.\nexport type BroadcastInput = {\n\tconnection: Getter<Moq.Connection.Established | undefined>;\n\n\t// Whether to start downloading the broadcast.\n\t// Defaults to false so you can make sure everything is ready before starting.\n\tenabled: Getter<boolean>;\n\n\t// The broadcast name.\n\tname: Getter<Moq.Path.Valid>;\n\n\t// Whether to reload the broadcast when it goes offline.\n\t// Defaults to true; pass false to subscribe immediately without waiting for an announcement.\n\treload: Getter<boolean>;\n\n\t// Which catalog format to use. When `undefined` (the default), the format is\n\t// auto-detected from the broadcast name extension (`.hang`, `.msf`), falling\n\t// back to `\"hang\"` if the name has no recognized extension. Set to a\n\t// specific value to override auto-detection. `\"hangz\"` (the compressed\n\t// `catalog.json.z` track) is opt-in only and never auto-detected.\n\tcatalogFormat: Getter<CatalogFormat | undefined>;\n\n\t// The manual-mode catalog source. Used directly when catalogFormat is \"manual\";\n\t// ignored otherwise. Read `output.catalog` for the effective catalog in any mode.\n\tcatalog: Getter<Catalog.Root | undefined>;\n};\n\ntype BroadcastOutput = {\n\tstatus: Signal<Status>;\n\tactive: Signal<Moq.Broadcast.Consumer | undefined>;\n\n\t// The effective catalog: the fetched one, or a copy of input.catalog in manual mode.\n\tcatalog: Signal<Catalog.Root | undefined>;\n};\n\n// A catalog source that (optionally) reloads automatically when live/offline.\nexport class Broadcast {\n\treadonly in: Readonlys<BroadcastInput>;\n\n\treadonly #out: BroadcastOutput = {\n\t\tstatus: new Signal<Status>(\"offline\"),\n\t\tactive: new Signal<Moq.Broadcast.Consumer | undefined>(undefined),\n\t\tcatalog: new Signal<Catalog.Root | undefined>(undefined),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t// The set of announced paths on the connection, for cross-broadcast (`broadcast: ../`) references\n\t// so `relativeBroadcast` can gate on whether a sibling is announced. `undefined` until the stream\n\t// is open. Opened lazily; the main broadcast doesn't use it (`#runBroadcast` drives off its own\n\t// name-scoped stream).\n\treadonly #announced = new Signal<Set<Moq.Path.Valid> | undefined>(undefined);\n\n\t// Set true the first time a relative reference needs the announcement gate, so a broadcast with\n\t// no cross-broadcast renditions never opens the (broad) connection-scoped announcement stream.\n\treadonly #wantAnnounced = new Signal(false);\n\n\t#signals = new Effect();\n\n\tconstructor(props?: Inputs<BroadcastInput>) {\n\t\tthis.in = {\n\t\t\tconnection: getter(props?.connection),\n\t\t\tname: getter(props?.name ?? Path.empty()),\n\t\t\tenabled: getter(props?.enabled ?? false),\n\t\t\treload: getter(props?.reload ?? true),\n\t\t\tcatalogFormat: getter<CatalogFormat | undefined>(props?.catalogFormat),\n\t\t\tcatalog: getter(props?.catalog),\n\t\t};\n\n\t\tthis.#signals.run(this.#runAnnounced.bind(this));\n\t\tthis.#signals.run(this.#runBroadcast.bind(this));\n\t\tthis.#signals.run(this.#runCatalog.bind(this));\n\t}\n\n\t// Maintain the set of announced paths used by `relativeBroadcast`, by draining a connection-scoped\n\t// announcement stream. Only opened once a relative reference asks for it (see `#wantAnnounced`),\n\t// and reopened per connection.\n\t#runAnnounced(effect: Effect): void {\n\t\tthis.#announced.set(undefined);\n\n\t\tif (!effect.get(this.#wantAnnounced)) return;\n\t\tif (!effect.get(this.in.reload)) return;\n\n\t\tconst conn = effect.get(this.in.connection);\n\t\tif (!conn || skipDiscovery(conn)) return;\n\n\t\tconst announced = conn.announced(Path.empty());\n\t\teffect.cleanup(() => announced.close());\n\t\tthis.#announced.set(new Set());\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst entry = await Promise.race([effect.cancel, announced.next()]);\n\t\t\t\tif (!entry) break;\n\t\t\t\tthis.#announced.mutate((active) => {\n\t\t\t\t\tif (!active) return;\n\t\t\t\t\tif (entry.active) active.add(entry.path);\n\t\t\t\t\telse active.delete(entry.path);\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\t// Whether `path` is currently announced, for `relativeBroadcast`'s cross-broadcast refs. Returns\n\t// true (subscribe immediately) when the gate can't apply: reload is off, or the relay doesn't\n\t// support discovery. Opens the announcement stream on first use.\n\t#isPathAnnounced(effect: Effect, path: Moq.Path.Valid): boolean {\n\t\tif (!effect.get(this.in.reload)) return true;\n\n\t\tconst conn = effect.get(this.in.connection);\n\t\tif (conn && skipDiscovery(conn)) return true;\n\n\t\tthis.#wantAnnounced.set(true);\n\n\t\tconst active = effect.get(this.#announced);\n\t\tif (!active) return false; // stream not open yet: wait rather than subscribe to a maybe-absent path\n\t\treturn active.has(path);\n\t}\n\n\t// Subscribe to the broadcast, re-consuming on every (re-)announce so a same-name republish (a new\n\t// publisher, or a relay-failover RESTART) re-attaches to the new instance instead of clinging to\n\t// the dead one. Driven off the announcement stream's updates rather than a membership flag, since\n\t// a coalesced republish leaves the active set unchanged yet still emits a fresh update.\n\t#runBroadcast(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst conn = effect.get(this.in.connection);\n\t\tif (!conn) return;\n\n\t\tconst name = effect.get(this.in.name);\n\n\t\t// No announcement gate: subscribe immediately (reload off, or the relay lacks discovery).\n\t\tif (!effect.get(this.in.reload) || skipDiscovery(conn)) {\n\t\t\tconst broadcast = conn.consume(name);\n\t\t\teffect.cleanup(() => broadcast.close());\n\t\t\teffect.set(this.#out.active, broadcast, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tconst announced = conn.announced(name);\n\t\teffect.cleanup(() => announced.close());\n\n\t\tlet current: Moq.Broadcast.Consumer | undefined;\n\t\teffect.cleanup(() => {\n\t\t\tcurrent?.close();\n\t\t\tcurrent = undefined;\n\t\t\tthis.#out.active.set(undefined);\n\t\t});\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst event = await Promise.race([effect.cancel, announced.next()]);\n\t\t\t\tif (!event) break;\n\n\t\t\t\t// Scoped to `name`, so the exact broadcast arrives with an empty suffix; ignore children.\n\t\t\t\tif (event.path !== Path.empty()) continue;\n\n\t\t\t\tif (event.active) {\n\t\t\t\t\t// A live subscription survives a redundant (re-)announce; only replace a dead one.\n\t\t\t\t\tif (current && current.closed.peek() === undefined) continue;\n\t\t\t\t\tcurrent?.close();\n\t\t\t\t\tcurrent = conn.consume(name);\n\t\t\t\t\tthis.#out.active.set(current);\n\t\t\t\t} else {\n\t\t\t\t\tcurrent?.close();\n\t\t\t\t\tcurrent = undefined;\n\t\t\t\t\tthis.#out.active.set(undefined);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t#runCatalog(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst catalogFormat = effect.get(this.in.catalogFormat);\n\t\tconst name = effect.get(this.in.name);\n\t\t// Explicit override beats name-derived auto-detection. When neither is\n\t\t// set we fall back to the default, keeping legacy names that have no\n\t\t// extension working.\n\t\tconst format: CatalogFormat = catalogFormat ?? Catalog.detectFormat(name) ?? Catalog.DEFAULT_FORMAT;\n\n\t\tif (format === \"manual\") {\n\t\t\t// Mirror the caller-supplied catalog into the effective output.\n\t\t\tconst catalog = effect.get(this.in.catalog);\n\t\t\teffect.set(this.#out.catalog, catalog, undefined);\n\t\t\tthis.#out.status.set(catalog ? \"live\" : \"loading\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst broadcast = effect.get(this.out.active);\n\t\tif (!broadcast) return;\n\n\t\tthis.#out.status.set(\"loading\");\n\n\t\tconst trackName = format === \"hang\" ? Catalog.TRACK : format === \"hangz\" ? Catalog.TRACK_COMPRESSED : \"catalog\";\n\t\tconst track = broadcast.track(trackName).subscribe({ priority: Catalog.PRIORITY.catalog });\n\t\teffect.cleanup(() => track.close());\n\n\t\t// The hang catalog is reconstructed from snapshots (and future deltas) via @moq/json, with\n\t\t// \"hangz\" decompressing the `.z` track; MSF stays on its own one-blob-per-group fetch.\n\t\tlet fetchNext: () => Promise<Catalog.Root | undefined>;\n\t\tif (format === \"hang\" || format === \"hangz\") {\n\t\t\tconst consumer = new Json.Snapshot.Consumer<Catalog.Root>(track, {\n\t\t\t\tschema: Catalog.RootSchema,\n\t\t\t\tcompression: format === \"hangz\",\n\t\t\t});\n\t\t\tfetchNext = () => consumer.next();\n\t\t} else {\n\t\t\tfetchNext = async () => {\n\t\t\t\tconst update = await Msf.fetch(track);\n\t\t\t\treturn update ? toHang(update) : undefined;\n\t\t\t};\n\t\t}\n\n\t\teffect.spawn(async () => {\n\t\t\ttry {\n\t\t\t\tfor (;;) {\n\t\t\t\t\tconst update = await Promise.race([effect.cancel, fetchNext()]);\n\t\t\t\t\tif (!update) break;\n\n\t\t\t\t\tconsole.debug(\"received catalog\", format, this.in.name.peek(), update);\n\n\t\t\t\t\tthis.#out.catalog.set(update);\n\t\t\t\t\tthis.#out.status.set(\"live\");\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.warn(\"error fetching catalog\", this.in.name.peek(), err);\n\t\t\t} finally {\n\t\t\t\tthis.#out.catalog.set(undefined);\n\t\t\t\tthis.#out.status.set(\"offline\");\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Resolve the `Moq.Broadcast.Consumer` that publishes a given track.\n\t *\n\t * If `rel` is set (a rendition's catalog `broadcast` field), treat it as a path\n\t * relative to this broadcast's name and consume the resolved broadcast on the same\n\t * connection. Otherwise return the catalog's own active broadcast.\n\t *\n\t * The consumer is scoped to the caller's `effect` (closed on its next run), so a\n\t * reference resolves lazily and reacts to `enabled` / connection / announcement\n\t * changes exactly like the catalog broadcast.\n\t */\n\trelativeBroadcast(effect: Effect, rel: string | undefined): Moq.Broadcast.Consumer | undefined {\n\t\tif (!rel) return effect.get(this.out.active);\n\n\t\tconst base = effect.get(this.in.name);\n\t\tconst resolved = Path.resolve(base, rel);\n\n\t\t// A reference that walks back to the catalog's own broadcast (or resolves to\n\t\t// the empty root, via excess `..`) is served by the catalog broadcast itself,\n\t\t// avoiding a duplicate subscription on the same path.\n\t\tif (resolved === base || resolved === Path.empty()) return effect.get(this.out.active);\n\n\t\tif (!effect.get(this.in.enabled)) return undefined;\n\n\t\tconst conn = effect.get(this.in.connection);\n\t\tif (!conn) return undefined;\n\n\t\tif (!this.#isPathAnnounced(effect, resolved)) return undefined;\n\n\t\tconst broadcast = conn.consume(resolved);\n\t\teffect.cleanup(() => broadcast.close());\n\t\treturn broadcast;\n\t}\n\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport * as Util from \"@moq/hang/util\";\nimport type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport { base64ToBytes } from \"../base64\";\n\nimport type { Sync } from \"../sync\";\nimport type { Source } from \"./source\";\n\n// The amount of time to wait before considering the video to be buffering.\nconst BUFFERING = Time.Milli(500);\nconst SWITCH = Time.Milli(100);\n\nexport type DecoderInput = {\n\t// Whether to download the video track. Wired from the renderer's output by the parent.\n\tenabled: Getter<boolean>;\n};\n\n/** Cumulative video statistics since the decoder started. */\nexport interface Stats {\n\t/** Number of decoded frames. */\n\tframeCount: number;\n\n\t/** Number of encoded bytes received. */\n\tbytesReceived: number;\n}\n\ntype DecoderOutput = {\n\t// The current frame to render.\n\tframe: Signal<VideoFrame | undefined>;\n\n\t// The timestamp of the current frame.\n\ttimestamp: Signal<Time.Milli | undefined>;\n\n\t// The display size of the video in pixels, ideally sourced from the catalog.\n\tdisplay: Signal<{ width: number; height: number } | undefined>;\n\n\tstalled: Signal<boolean>;\n\tstats: Signal<Stats | undefined>;\n\n\t// Combined buffered ranges (network jitter + decode buffer)\n\tbuffered: Signal<Container.BufferedRanges>;\n};\n\n// The types in VideoDecoderConfig that cause a hard reload.\n// ex. codedWidth/Height are optional and can be changed in-band, so we don't want to trigger a reload.\n// This way we can keep the current subscription active.\ntype RequiredDecoderConfig = Omit<Catalog.VideoConfig, \"codedWidth\" | \"codedHeight\">;\n\n/** Downloads video from a track and decodes it into {@link VideoFrame}s with WebCodecs. */\nexport class Decoder {\n\treadonly in: Readonlys<DecoderInput>;\n\treadonly source: Source;\n\treadonly sync: Sync;\n\n\treadonly #out: DecoderOutput = {\n\t\tframe: new Signal<VideoFrame | undefined>(undefined),\n\t\ttimestamp: new Signal<Time.Milli | undefined>(undefined),\n\t\tdisplay: new Signal<{ width: number; height: number } | undefined>(undefined),\n\t\tstalled: new Signal<boolean>(false),\n\t\tstats: new Signal<Stats | undefined>(undefined),\n\t\tbuffered: new Signal<Container.BufferedRanges>([]),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t// The current track running, held so we can cancel it when the new track is ready.\n\t#active = new Signal<DecoderTrack | undefined>(undefined);\n\n\t#signals = new Effect();\n\n\t#clearCurrentFrame(): void {\n\t\tthis.#out.frame.update((prev) => {\n\t\t\tprev?.close();\n\t\t\treturn undefined;\n\t\t});\n\t\tthis.#out.timestamp.set(undefined);\n\t}\n\n\tconstructor(source: Source, sync: Sync, props?: Inputs<DecoderInput>) {\n\t\tthis.in = {\n\t\t\tenabled: getter(props?.enabled ?? false),\n\t\t};\n\n\t\tthis.source = source;\n\t\tthis.sync = sync;\n\n\t\tthis.#signals.run(this.#runPending.bind(this));\n\t\tthis.#signals.run(this.#runActive.bind(this));\n\t\tthis.#signals.run(this.#runDisplay.bind(this));\n\t\tthis.#signals.run(this.#runBuffering.bind(this));\n\t}\n\n\t#runPending(effect: Effect): void {\n\t\tconst values = effect.getAll([\n\t\t\tthis.in.enabled,\n\t\t\tthis.source.in.broadcast,\n\t\t\tthis.source.out.track,\n\t\t\tthis.source.out.config,\n\t\t]);\n\t\tif (!values) {\n\t\t\t// Close the active track when disabled (e.g. paused or not visible).\n\t\t\t// The pending cleanup won't do this because it was already promoted to #active.\n\t\t\tthis.#active.set(undefined);\n\t\t\treturn;\n\t\t}\n\t\tconst [_, broadcast, track, config] = values;\n\n\t\t// Honor a per-rendition `broadcast` override: subscribe on the resolved source\n\t\t// broadcast instead of the catalog's own broadcast.\n\t\tconst active: Moq.Broadcast.Consumer | undefined = broadcast.relativeBroadcast(effect, config.broadcast);\n\t\tif (!active) {\n\t\t\t// Going offline should clear the last rendered frame.\n\t\t\tthis.#active.set(undefined);\n\t\t\tthis.#clearCurrentFrame();\n\t\t\tthis.#out.buffered.set([]);\n\t\t\treturn;\n\t\t}\n\n\t\t// Start a new pending effect.\n\t\tlet pending: DecoderTrack | undefined = new DecoderTrack({\n\t\t\tsync: this.sync,\n\t\t\tbroadcast: active,\n\t\t\ttrack,\n\t\t\tconfig,\n\t\t\tstats: this.#out.stats,\n\t\t});\n\n\t\teffect.cleanup(() => pending?.close());\n\n\t\teffect.run((effect) => {\n\t\t\tif (!pending) return;\n\n\t\t\tconst current = effect.get(this.#active);\n\t\t\tif (current) {\n\t\t\t\tconst pendingTimestamp = effect.get(pending.timestamp);\n\t\t\t\tconst activeTimestamp = effect.get(current.timestamp);\n\n\t\t\t\t// Switch to the new track if it's ready and we've caught up enough.\n\t\t\t\tif (!pendingTimestamp) return;\n\t\t\t\tif (activeTimestamp && activeTimestamp > pendingTimestamp + SWITCH) return;\n\t\t\t}\n\n\t\t\t// Upgrade the pending track to active.\n\t\t\t// #runActive will be in charge of it now.\n\t\t\tthis.#active.set(pending);\n\t\t\tpending = undefined;\n\n\t\t\t// This effect is done; close it to avoid a useless re-run.\n\t\t\teffect.close();\n\t\t});\n\t}\n\n\t#runActive(effect: Effect): void {\n\t\tconst active = effect.get(this.#active);\n\t\tif (!active) {\n\t\t\t// Clear stale data when disabled (e.g. paused or not visible).\n\t\t\tthis.#out.buffered.set([]);\n\t\t\treturn;\n\t\t}\n\n\t\teffect.cleanup(() => active.close());\n\n\t\t// Clone the frame so we own it independently of the DecoderTrack.\n\t\t// proxy() would share the same reference, allowing the source to close our frame.\n\t\teffect.run((inner) => {\n\t\t\tconst frame = inner.get(active.frame);\n\t\t\tthis.#out.frame.update((prev) => {\n\t\t\t\tprev?.close();\n\t\t\t\treturn frame?.clone();\n\t\t\t});\n\t\t});\n\t\teffect.proxy(this.#out.timestamp, active.timestamp);\n\t\teffect.proxy(this.#out.buffered, active.buffered);\n\t}\n\n\t#runDisplay(effect: Effect): void {\n\t\tconst catalog = effect.get(this.source.out.catalog);\n\t\tif (!catalog) return;\n\n\t\tconst display = catalog.display;\n\t\tif (display) {\n\t\t\teffect.set(this.#out.display, {\n\t\t\t\twidth: display.width,\n\t\t\t\theight: display.height,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst frame = effect.get(this.#out.frame);\n\t\tif (!frame) return;\n\n\t\teffect.set(this.#out.display, {\n\t\t\twidth: frame.displayWidth,\n\t\t\theight: frame.displayHeight,\n\t\t});\n\t}\n\n\t#runBuffering(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst frame = effect.get(this.#out.frame);\n\t\tif (!frame) {\n\t\t\tthis.#out.stalled.set(true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#out.stalled.set(false);\n\n\t\teffect.timer(() => {\n\t\t\tthis.#out.stalled.set(true);\n\t\t}, BUFFERING);\n\t}\n\n\tclose() {\n\t\tthis.#clearCurrentFrame();\n\n\t\tthis.#signals.close();\n\t}\n\n\t// Whether the WebCodecs video decoder can play this config.\n\tstatic supported = supported;\n}\n\ninterface DecoderTrackProps {\n\tsync: Sync;\n\tbroadcast: Moq.Broadcast.Consumer;\n\ttrack: string;\n\tconfig: Catalog.VideoConfig;\n\n\tstats: Signal<Stats | undefined>;\n}\n\nclass DecoderTrack {\n\tsync: Sync;\n\tbroadcast: Moq.Broadcast.Consumer;\n\ttrack: string;\n\tconfig: RequiredDecoderConfig;\n\tstats: Signal<Stats | undefined>;\n\n\ttimestamp = new Signal<Time.Milli | undefined>(undefined);\n\tframe = new Signal<VideoFrame | undefined>(undefined);\n\n\t// Network jitter + decode buffer.\n\tbuffered = new Signal<Container.BufferedRanges>([]);\n\n\t// Decoded frames waiting to be rendered.\n\t#buffered = new Signal<Container.BufferedRanges>([]);\n\n\t// The last discontinuity count seen from the container consumer; doubles as a generation\n\t// so in-flight decodes from before a rewind can be dropped on output.\n\t#discontinuity = 0;\n\n\t#signals = new Effect();\n\n\tconstructor(props: DecoderTrackProps) {\n\t\t// Remove the codedWidth/Height from the config to avoid a hard reload if nothing else has changed.\n\t\tconst { codedWidth: _, codedHeight: __, ...requiredConfig } = props.config;\n\n\t\tthis.sync = props.sync;\n\t\tthis.broadcast = props.broadcast;\n\t\tthis.track = props.track;\n\t\tthis.config = requiredConfig;\n\t\tthis.stats = props.stats;\n\n\t\tthis.#signals.run(this.#run.bind(this));\n\t}\n\n\t#run(effect: Effect): void {\n\t\tconst sub = this.broadcast.track(this.track).subscribe({ priority: Catalog.PRIORITY.video });\n\t\teffect.cleanup(() => sub.close());\n\n\t\tconst decoder = new VideoDecoder({\n\t\t\toutput: async (frame: VideoFrame) => {\n\t\t\t\ttry {\n\t\t\t\t\t// The generation this frame was decoded in. If a rewind bumps it while we wait\n\t\t\t\t\t// below, this frame belongs to the reneged timeline and must be dropped.\n\t\t\t\t\tconst generation = this.#discontinuity;\n\n\t\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\t\tif (timestamp < (this.timestamp.peek() ?? 0)) {\n\t\t\t\t\t\t// Late frame, don't render it.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this.frame.peek() === undefined) {\n\t\t\t\t\t\t// Render something while we wait for the sync to catch up.\n\t\t\t\t\t\tthis.frame.set(frame.clone());\n\t\t\t\t\t}\n\n\t\t\t\t\tconst wait = this.sync.wait(timestamp).then(() => true);\n\t\t\t\t\tconst ok = await Promise.race([wait, effect.cancel]);\n\t\t\t\t\tif (!ok) return;\n\t\t\t\t\tif (generation !== this.#discontinuity) return; // a rewind happened while waiting\n\n\t\t\t\t\tif (timestamp < (this.timestamp.peek() ?? 0)) {\n\t\t\t\t\t\t// Late frame, don't render it.\n\t\t\t\t\t\t// NOTE: This can happen when the ref is updated, such as on playback start.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.timestamp.set(timestamp);\n\n\t\t\t\t\t// Trim the decode buffer as frames are rendered\n\t\t\t\t\tthis.#trimBuffered(timestamp);\n\n\t\t\t\t\tthis.frame.update((prev) => {\n\t\t\t\t\t\tprev?.close();\n\t\t\t\t\t\treturn frame.clone(); // avoid closing the frame here\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tframe.close();\n\t\t\t\t}\n\t\t\t},\n\t\t\t// TODO bubble up error\n\t\t\terror: (error) => {\n\t\t\t\tconsole.error(error);\n\t\t\t\teffect.close();\n\t\t\t},\n\t\t});\n\t\teffect.cleanup(() => {\n\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t});\n\n\t\t// Input processing - depends on container type\n\t\tif (this.config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmaf(effect, sub, decoder);\n\t\t} else {\n\t\t\tthis.#runLegacy(effect, sub, decoder);\n\t\t}\n\t}\n\n\t#runLegacy(effect: Effect, sub: Moq.Track.Subscriber, decoder: VideoDecoder): void {\n\t\tconst format =\n\t\t\tthis.config.container.kind === \"loc\" ? new Container.Loc.Format() : new Container.Legacy.Format();\n\t\t// Create consumer that reorders groups/frames up to the provided latency.\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat,\n\t\t\tlatency: this.sync.out.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#buffered);\n\t\t\tthis.buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\tdecoder.configure({\n\t\t\t...this.config,\n\t\t\tdescription: this.config.description ? Util.Hex.toBytes(this.config.description) : undefined,\n\t\t\toptimizeForLatency: this.config.optimizeForLatency ?? true,\n\t\t\t// @ts-expect-error Only supported by Chrome, so the renderer has to flip manually.\n\t\t\tflip: false,\n\t\t});\n\n\t\tlet previous: { timestamp: Time.Micro; group: number; final: boolean } | undefined;\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\t// Publisher rewound: flush queued/in-flight video and re-anchor before decoding.\n\t\t\t\tif (this.#onDiscontinuity(next.discontinuity)) previous = undefined;\n\n\t\t\t\tconst { frame, group } = next;\n\n\t\t\t\tif (!frame) {\n\t\t\t\t\tif (previous) {\n\t\t\t\t\t\tprevious.final = true;\n\t\t\t\t\t}\n\t\t\t\t\t// The group is done\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\tthis.sync.received(timestamp, \"video\");\n\n\t\t\t\tconst chunk = new EncodedVideoChunk({\n\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\tdata: frame.payload,\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t});\n\n\t\t\t\t// Track both frame count and bytes received for stats in the UI\n\t\t\t\tthis.stats.update((current) => ({\n\t\t\t\t\tframeCount: (current?.frameCount ?? 0) + 1,\n\t\t\t\t\tbytesReceived: (current?.bytesReceived ?? 0) + frame.payload.byteLength,\n\t\t\t\t}));\n\n\t\t\t\t// Track decode buffer: frames sent to decoder but not yet rendered\n\t\t\t\tconst prior = previous;\n\t\t\t\tif (prior && (prior.group === group || (prior.final && prior.group + 1 === group))) {\n\t\t\t\t\tconst start = Time.Milli.fromMicro(prior.timestamp);\n\t\t\t\t\tconst end = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\t\tthis.#addBuffered(start, end);\n\t\t\t\t}\n\n\t\t\t\tprevious = {\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\tgroup,\n\t\t\t\t\tfinal: false,\n\t\t\t\t};\n\n\t\t\t\tdecoder.decode(chunk);\n\t\t\t}\n\t\t});\n\t}\n\n\t#runCmaf(effect: Effect, sub: Moq.Track.Subscriber, decoder: VideoDecoder): void {\n\t\tconst container = this.config.container;\n\t\tif (container.kind !== \"cmaf\") return;\n\n\t\tconst initSegment = base64ToBytes(container.init);\n\t\tconst init = Container.Cmaf.decodeInitSegment(initSegment);\n\t\tconst description = this.config.description ? Util.Hex.toBytes(this.config.description) : init.description;\n\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat: new Container.Cmaf.Format(init),\n\t\t\tlatency: this.sync.out.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#buffered);\n\t\t\tthis.buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\t// Configure decoder with description from catalog\n\t\tdecoder.configure({\n\t\t\tcodec: this.config.codec,\n\t\t\tdescription,\n\t\t\toptimizeForLatency: this.config.optimizeForLatency ?? true,\n\t\t\t// @ts-expect-error Only supported by Chrome, so the renderer has to flip manually.\n\t\t\tflip: false,\n\t\t});\n\n\t\tlet previous: { timestamp: Time.Micro; group: number; final: boolean } | undefined;\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\t// Publisher rewound: flush queued/in-flight video and re-anchor before decoding.\n\t\t\t\tif (this.#onDiscontinuity(next.discontinuity)) previous = undefined;\n\n\t\t\t\tconst { frame, group } = next;\n\n\t\t\t\tif (!frame) {\n\t\t\t\t\tif (previous) {\n\t\t\t\t\t\tprevious.final = true;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\tthis.sync.received(timestamp, \"video\");\n\n\t\t\t\t// Track stats\n\t\t\t\tthis.stats.update((current) => ({\n\t\t\t\t\tframeCount: (current?.frameCount ?? 0) + 1,\n\t\t\t\t\tbytesReceived: (current?.bytesReceived ?? 0) + frame.payload.byteLength,\n\t\t\t\t}));\n\n\t\t\t\t// Track decode buffer\n\t\t\t\tconst prior = previous;\n\t\t\t\tif (prior && (prior.group === group || (prior.final && prior.group + 1 === group))) {\n\t\t\t\t\tconst start = Time.Milli.fromMicro(prior.timestamp);\n\t\t\t\t\tconst end = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\t\tthis.#addBuffered(start, end);\n\t\t\t\t}\n\n\t\t\t\tprevious = {\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\tgroup,\n\t\t\t\t\tfinal: false,\n\t\t\t\t};\n\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tdecoder.decode(\n\t\t\t\t\tnew EncodedVideoChunk({\n\t\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\t\tdata: frame.payload,\n\t\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\t// React to the container consumer's discontinuity counter. On a change the publisher has\n\t// rewound the timeline, so drop what's queued downstream and re-anchor the shared clock\n\t// before the new utterance. Clearing `timestamp` is load-bearing: otherwise its stale high\n\t// value would late-reject the rewound (lower-timestamp) frames at the output guard. Bumping\n\t// the generation drops in-flight decodes on output. The held frame is left in place so the\n\t// last picture shows until the new keyframe renders, instead of flashing empty. Returns true\n\t// if a rewind was handled.\n\t#onDiscontinuity(count: number): boolean {\n\t\tif (count === this.#discontinuity) return false;\n\t\tthis.#discontinuity = count;\n\t\tthis.timestamp.set(undefined);\n\t\tthis.#buffered.set([]);\n\t\tthis.sync.reset();\n\t\treturn true;\n\t}\n\n\t// Add a range to the decode buffer (decoded, waiting to render)\n\t#addBuffered(start: Time.Milli, end: Time.Milli): void {\n\t\tif (start > end) return;\n\n\t\tthis.#buffered.mutate((current) => {\n\t\t\tfor (const range of current) {\n\t\t\t\t// Check if there's any overlap, then merge\n\t\t\t\tif (range.start <= end && range.end >= start) {\n\t\t\t\t\trange.start = Time.Milli.min(range.start, start);\n\t\t\t\t\trange.end = Time.Milli.max(range.end, end);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrent.push({ start, end });\n\t\t\tcurrent.sort((a, b) => a.start - b.start);\n\t\t});\n\t}\n\n\t// Trim the decode buffer up to the rendered timestamp\n\t#trimBuffered(timestamp: Time.Milli): void {\n\t\tthis.#buffered.mutate((current) => {\n\t\t\twhile (current.length > 0) {\n\t\t\t\tif (current[0].end >= timestamp) {\n\t\t\t\t\tcurrent[0].start = Time.Milli.max(current[0].start, timestamp);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent.shift();\n\t\t\t}\n\t\t});\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\n\t\tthis.frame.update((prev) => {\n\t\t\tprev?.close();\n\t\t\treturn undefined;\n\t\t});\n\t}\n}\n\nasync function supported(config: Catalog.VideoConfig): Promise<boolean> {\n\tif (!Catalog.containerSupported(config.container)) {\n\t\t// `kind` is the literal \"unknown\" tag; the container the publisher actually named is in `raw`.\n\t\tconst kind = config.container.kind === \"unknown\" ? config.container.raw.kind : config.container.kind;\n\t\tconsole.warn(`video: ignoring rendition with unknown container: ${kind}`);\n\t\treturn false;\n\t}\n\n\tlet description: Uint8Array | undefined;\n\tif (config.description) {\n\t\tdescription = Util.Hex.toBytes(config.description);\n\t} else if (config.container.kind === \"cmaf\") {\n\t\ttry {\n\t\t\tdescription = Container.Cmaf.decodeInitSegment(base64ToBytes(config.container.init)).description;\n\t\t} catch (err) {\n\t\t\t// A malformed init segment means we can't extract the codec\n\t\t\t// description, so we can't probe support reliably. Reject the\n\t\t\t// track rather than letting isConfigSupported pass on a\n\t\t\t// description-less config and then having runCmaf fail later.\n\t\t\tconsole.warn(`video: malformed CMAF init segment for codec ${config.codec}`, err);\n\t\t\treturn false;\n\t\t}\n\t}\n\tconst { supported } = await VideoDecoder.isConfigSupported({\n\t\tcodec: config.codec,\n\t\tdescription,\n\t\toptimizeForLatency: config.optimizeForLatency ?? true,\n\t});\n\n\tif (supported) return true;\n\n\t// Safari rejects `avc3.*` codec strings even though its H.264 decoder handles\n\t// inline SPS/PPS. Rewrite to `avc1.*` and retry; mutate config.codec so the\n\t// later `decoder.configure()` call uses the accepted string too.\n\tif (config.codec.startsWith(\"avc3.\")) {\n\t\tconst avc1 = `avc1.${config.codec.slice(\"avc3.\".length)}`;\n\t\tconst retry = await VideoDecoder.isConfigSupported({\n\t\t\tcodec: avc1,\n\t\t\tdescription,\n\t\t\toptimizeForLatency: config.optimizeForLatency ?? true,\n\t\t});\n\t\tif (retry.supported) {\n\t\t\tconfig.codec = avc1;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n","import { Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Decoder } from \"./decoder\";\n\n// Fraction of the canvas that must intersect the viewport before it counts as visible.\nconst INTERSECTION_THRESHOLD = 0.01;\n\n/**\n * Controls when video is downloaded relative to the canvas position.\n *\n * - `\"never\"`: never download video.\n * - `\"always\"`: always download video, regardless of the canvas position or tab visibility.\n * - a CSS length (`\"0px\"`, `\"200px\"`, `\"100%\"`, ...): download while the canvas is within\n * that distance of the viewport (used as the {@link IntersectionObserver} `rootMargin`) and\n * the tab is visible. `\"0px\"` means strictly on screen; larger values pre-warm the video\n * before it scrolls in.\n */\nexport type Visible = \"never\" | \"always\" | (string & {});\n\nexport type RendererInput = {\n\tcanvas: Getter<HTMLCanvasElement | undefined>;\n\n\t// When video is downloaded relative to the canvas position. See {@link Visible}. Defaults to \"20%\".\n\tvisible: Getter<Visible>;\n};\n\ntype RendererOutput = {\n\t// The most recently rendered frame, updated after each rAF paint.\n\tframe: Signal<VideoFrame | undefined>;\n\n\t// The media timestamp of the most recently rendered frame.\n\ttimestamp: Signal<Time.Milli | undefined>;\n\n\t// Whether the canvas should currently download per the configured distance and tab focus.\n\t// The owner combines this with `paused` to drive the decoder's `enabled` input.\n\tvisible: Signal<boolean>;\n};\n\n// An component to render a video to a canvas.\nexport class Renderer {\n\treadonly decoder: Decoder;\n\n\treadonly in: Readonlys<RendererInput>;\n\n\treadonly #out: RendererOutput = {\n\t\tframe: new Signal<VideoFrame | undefined>(undefined),\n\t\ttimestamp: new Signal<Time.Milli | undefined>(undefined),\n\t\tvisible: new Signal(false),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t#ctx = new Signal<CanvasRenderingContext2D | undefined>(undefined);\n\t#signals = new Effect();\n\n\tconstructor(decoder: Decoder, props?: Inputs<RendererInput>) {\n\t\tthis.decoder = decoder;\n\t\tthis.in = {\n\t\t\tcanvas: getter(props?.canvas),\n\t\t\tvisible: getter(props?.visible ?? \"20%\"),\n\t\t};\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst canvas = effect.get(this.in.canvas);\n\t\t\tthis.#ctx.set(canvas?.getContext(\"2d\") ?? undefined);\n\t\t});\n\n\t\tthis.#signals.run(this.#runVisible.bind(this));\n\t\tthis.#signals.run(this.#runRender.bind(this));\n\t\tthis.#signals.run(this.#runResize.bind(this));\n\t}\n\n\t#runResize(effect: Effect) {\n\t\tconst values = effect.getAll([this.in.canvas, this.decoder.out.display]);\n\t\tif (!values) return; // Keep current canvas size until we have new dimensions\n\t\tconst [canvas, display] = values;\n\n\t\t// Only update if dimensions actually changed (setting canvas.width/height clears the canvas)\n\t\t// TODO I thought the signals library would prevent this, but I'm too lazy to investigate.\n\t\tif (canvas.width !== display.width || canvas.height !== display.height) {\n\t\t\tcanvas.width = display.width;\n\t\t\tcanvas.height = display.height;\n\t\t}\n\t}\n\n\t// Track whether the canvas should currently download per the configured distance and tab focus.\n\t#runVisible(effect: Effect): void {\n\t\tconst visible = effect.get(this.in.visible);\n\n\t\t// \"never\" forces the check off; \"always\" forces it on regardless of viewport or tab state.\n\t\tif (visible === \"never\") {\n\t\t\tthis.#out.visible.set(false);\n\t\t\treturn;\n\t\t}\n\n\t\tif (visible === \"always\") {\n\t\t\tthis.#out.visible.set(true);\n\t\t\teffect.cleanup(() => this.#out.visible.set(false));\n\t\t\treturn;\n\t\t}\n\n\t\t// A distance gates on the viewport (used as the rootMargin) and the tab being visible.\n\t\tconst canvas = effect.get(this.in.canvas);\n\t\tif (!canvas) {\n\t\t\tthis.#out.visible.set(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet intersecting = false;\n\t\tconst update = () => {\n\t\t\tthis.#out.visible.set(intersecting && !document.hidden);\n\t\t};\n\n\t\tconst callback = (entries: IntersectionObserverEntry[]) => {\n\t\t\tfor (const entry of entries) {\n\t\t\t\tintersecting = entry.isIntersecting;\n\t\t\t\tupdate();\n\t\t\t}\n\t\t};\n\n\t\t// `visible` is a CSS length, but the programmatic API accepts arbitrary strings. An\n\t\t// invalid rootMargin throws a SyntaxError, so fall back to the default margin.\n\t\tlet observer: IntersectionObserver;\n\t\ttry {\n\t\t\tobserver = new IntersectionObserver(callback, { threshold: INTERSECTION_THRESHOLD, rootMargin: visible });\n\t\t} catch {\n\t\t\tconsole.warn(`moq-watch: invalid visible margin \"${visible}\", using \"0px\"`);\n\t\t\tobserver = new IntersectionObserver(callback, { threshold: INTERSECTION_THRESHOLD });\n\t\t}\n\n\t\tupdate();\n\t\teffect.event(document, \"visibilitychange\", update);\n\t\tobserver.observe(canvas);\n\t\teffect.cleanup(() => observer.disconnect());\n\t\teffect.cleanup(() => this.#out.visible.set(false));\n\t}\n\n\t#runRender(effect: Effect) {\n\t\tconst ctx = effect.get(this.#ctx);\n\t\tif (!ctx) return;\n\n\t\tconst frame = effect.get(this.decoder.out.frame);\n\n\t\t// Request a callback to render the frame based on the monitor's refresh rate.\n\t\t// Always render, even when paused (to show last frame).\n\t\tlet animate: number | undefined = requestAnimationFrame(() => {\n\t\t\tthis.#render(ctx, frame);\n\n\t\t\tif (frame) {\n\t\t\t\tthis.#out.frame.update((current) => {\n\t\t\t\t\tcurrent?.close();\n\t\t\t\t\treturn frame.clone();\n\t\t\t\t});\n\t\t\t\tthis.#out.timestamp.set(Time.Milli.fromMicro(frame.timestamp as Time.Micro));\n\t\t\t} else {\n\t\t\t\tthis.#out.frame.update((current) => {\n\t\t\t\t\tcurrent?.close();\n\t\t\t\t\treturn undefined;\n\t\t\t\t});\n\t\t\t\tthis.#out.timestamp.set(undefined);\n\t\t\t}\n\n\t\t\tanimate = undefined;\n\t\t});\n\n\t\t// Clean up any pending animation request.\n\t\teffect.cleanup(() => {\n\t\t\tif (animate) cancelAnimationFrame(animate);\n\t\t});\n\t}\n\n\t#render(ctx: CanvasRenderingContext2D, frame?: VideoFrame) {\n\t\tif (!frame) {\n\t\t\t// Clear canvas when no frame\n\t\t\tctx.fillStyle = \"#000\";\n\t\t\tctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\t\t\treturn;\n\t\t}\n\n\t\t// Prepare background and transformations for this draw\n\t\tctx.save();\n\t\tctx.fillStyle = \"#000\";\n\t\tctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\n\t\t// Apply horizontal flip if specified in the video config\n\t\tconst flip = this.decoder.source.out.catalog.peek()?.flip;\n\t\tif (flip) {\n\t\t\tctx.scale(-1, 1);\n\t\t\tctx.translate(-ctx.canvas.width, 0);\n\t\t}\n\n\t\tctx.drawImage(frame, 0, 0, ctx.canvas.width, ctx.canvas.height);\n\t\tctx.restore();\n\t}\n\n\t// Close the track and all associated resources.\n\tclose() {\n\t\tthis.#out.frame.update((current) => {\n\t\t\tcurrent?.close();\n\t\t\treturn undefined;\n\t\t});\n\t\tthis.#out.timestamp.set(undefined);\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\n\n/**\n * A function that checks if a video configuration can be played.\n *\n * `Decoder.supported` is the WebCodecs probe used by `<moq-watch>`.\n */\nexport type Supported = (config: Catalog.VideoConfig) => Promise<boolean>;\n\n/** A video source error that prevents choosing a usable rendition. */\nexport type SourceError = \"unsupported\";\n\nexport type Target = {\n\t// Optional manual override for the selected rendition name.\n\tname?: string;\n\n\t// Maximum desired pixel area (codedWidth * codedHeight).\n\tpixels?: number;\n\n\t// Maximum desired coded width in pixels.\n\twidth?: number;\n\n\t// Maximum desired coded height in pixels.\n\theight?: number;\n\n\t// Maximum desired bitrate in bits per second.\n\tbitrate?: number;\n};\n\nexport type SourceInput = {\n\tbroadcast: Getter<Broadcast | undefined>;\n\ttarget: Getter<Target | undefined>;\n\n\t// A function that checks if a video configuration can be played. Renditions that fail the\n\t// probe are filtered out. Nothing is selected until one is provided.\n\tsupported: Getter<Supported | undefined>;\n};\n\ntype SourceOutput = {\n\tcatalog: Signal<Catalog.Video | undefined>;\n\tavailable: Signal<Record<string, Catalog.VideoConfig>>;\n\n\t// The current source error, or undefined while healthy or still probing.\n\terror: Signal<SourceError | undefined>;\n\n\t// The name of the active rendition.\n\ttrack: Signal<string | undefined>;\n\tconfig: Signal<Catalog.VideoConfig | undefined>;\n\n\t// The per-rendition jitter (ms) to add to the sync buffer. Wired into Sync by the parent.\n\tjitter: Signal<Moq.Time.Milli | undefined>;\n};\n\n/**\n * A filter that returns matching renditions sorted by preference (most preferred first).\n * Must return at least one rendition.\n */\ntype RenditionFilter = (entries: [string, Catalog.VideoConfig][]) => string[];\n\n/**\n * Filter and rank renditions by a maximum pixel count.\n * Returns renditions within budget (largest first for best quality).\n * Over-budget and unknown-resolution renditions are excluded.\n * If nothing is within budget, falls back to the single smallest rendition.\n */\nfunction byPixels(target: number): RenditionFilter {\n\treturn (entries) => {\n\t\tconst within: { name: string; size: number }[] = [];\n\t\tconst rest: { name: string; size: number }[] = [];\n\n\t\tfor (const [name, config] of entries) {\n\t\t\tif (config.codedWidth && config.codedHeight) {\n\t\t\t\tconst size = config.codedWidth * config.codedHeight;\n\t\t\t\tif (size <= target) {\n\t\t\t\t\twithin.push({ name, size });\n\t\t\t\t} else {\n\t\t\t\t\trest.push({ name, size });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Best quality within budget\n\t\twithin.sort((a, b) => b.size - a.size);\n\n\t\tif (within.length > 0) {\n\t\t\treturn within.map((e) => e.name);\n\t\t}\n\n\t\t// Degrade to smallest over-budget resolution.\n\t\tif (rest.length > 0) {\n\t\t\trest.sort((a, b) => a.size - b.size);\n\t\t\treturn [rest[0].name];\n\t\t}\n\n\t\t// No entries had resolution metadata — return all names unranked.\n\t\treturn entries.map(([name]) => name);\n\t};\n}\n\n/**\n * Filter and rank renditions by maximum coded dimensions.\n * Returns renditions where codedWidth <= width AND codedHeight <= height\n * (each cap is optional). Within-budget renditions rank by area (largest first).\n * If nothing fits, falls back to the single smallest over-budget rendition.\n */\nfunction byDimensions(width?: number, height?: number): RenditionFilter {\n\treturn (entries) => {\n\t\tconst within: { name: string; size: number }[] = [];\n\t\tconst rest: { name: string; size: number }[] = [];\n\n\t\tfor (const [name, config] of entries) {\n\t\t\tif (!config.codedWidth || !config.codedHeight) continue;\n\t\t\tconst size = config.codedWidth * config.codedHeight;\n\t\t\tconst fitsWidth = width == null || config.codedWidth <= width;\n\t\t\tconst fitsHeight = height == null || config.codedHeight <= height;\n\t\t\tif (fitsWidth && fitsHeight) {\n\t\t\t\twithin.push({ name, size });\n\t\t\t} else {\n\t\t\t\trest.push({ name, size });\n\t\t\t}\n\t\t}\n\n\t\t// Best quality within budget\n\t\twithin.sort((a, b) => b.size - a.size);\n\n\t\tif (within.length > 0) {\n\t\t\treturn within.map((e) => e.name);\n\t\t}\n\n\t\t// Degrade to smallest over-budget rendition.\n\t\tif (rest.length > 0) {\n\t\t\trest.sort((a, b) => a.size - b.size);\n\t\t\treturn [rest[0].name];\n\t\t}\n\n\t\t// No entries had resolution metadata — return all names unranked.\n\t\treturn entries.map(([name]) => name);\n\t};\n}\n\n/**\n * Filter and rank renditions by a maximum bitrate budget.\n * Returns renditions within budget (highest bitrate first for best quality).\n * Over-budget and unknown-bitrate renditions are excluded.\n * If nothing is within budget, falls back to the single lowest-bitrate rendition.\n */\nfunction byBitrate(target: number): RenditionFilter {\n\treturn (entries) => {\n\t\tconst within: { name: string; bitrate: number }[] = [];\n\t\tconst rest: { name: string; bitrate: number }[] = [];\n\n\t\tfor (const [name, config] of entries) {\n\t\t\tif (config.bitrate != null && config.bitrate <= target) {\n\t\t\t\twithin.push({ name, bitrate: config.bitrate });\n\t\t\t} else if (config.bitrate != null) {\n\t\t\t\trest.push({ name, bitrate: config.bitrate });\n\t\t\t}\n\t\t}\n\n\t\t// Best quality within budget\n\t\twithin.sort((a, b) => b.bitrate - a.bitrate);\n\n\t\tif (within.length > 0) {\n\t\t\treturn within.map((e) => e.name);\n\t\t}\n\n\t\t// Degrade to lowest over-budget bitrate.\n\t\tif (rest.length > 0) {\n\t\t\trest.sort((a, b) => a.bitrate - b.bitrate);\n\t\t\treturn [rest[0].name];\n\t\t}\n\n\t\t// No entries had bitrate metadata — return all names unranked.\n\t\treturn entries.map(([name]) => name);\n\t};\n}\n\n/**\n * Pick the best rendition when no filters are active.\n * Prefers the largest resolution, falls back to highest bitrate,\n * then falls back to the first entry.\n */\nfunction bestRendition(entries: [string, Catalog.VideoConfig][]): string {\n\tlet best = entries[0];\n\n\tfor (const entry of entries) {\n\t\tconst [, config] = entry;\n\t\tconst [, bestConfig] = best;\n\n\t\tconst size = (config.codedWidth ?? 0) * (config.codedHeight ?? 0);\n\t\tconst bestSize = (bestConfig.codedWidth ?? 0) * (bestConfig.codedHeight ?? 0);\n\n\t\tif (size !== bestSize) {\n\t\t\tif (size > bestSize) best = entry;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((config.bitrate ?? 0) > (bestConfig.bitrate ?? 0)) {\n\t\t\tbest = entry;\n\t\t}\n\t}\n\n\treturn best[0];\n}\n\n/**\n * Source handles catalog extraction, support checking, and rendition selection\n * for video playback. The Decoder consumes whichever rendition it picks.\n */\nexport class Source {\n\treadonly in: Readonlys<SourceInput>;\n\n\treadonly #out: SourceOutput = {\n\t\tcatalog: new Signal<Catalog.Video | undefined>(undefined),\n\t\tavailable: new Signal<Record<string, Catalog.VideoConfig>>({}),\n\t\terror: new Signal<SourceError | undefined>(undefined),\n\t\ttrack: new Signal<string | undefined>(undefined),\n\t\tconfig: new Signal<Catalog.VideoConfig | undefined>(undefined),\n\t\tjitter: new Signal<Moq.Time.Milli | undefined>(undefined),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t#signals = new Effect();\n\n\tconstructor(props?: Inputs<SourceInput>) {\n\t\tthis.in = {\n\t\t\tbroadcast: getter(props?.broadcast),\n\t\t\ttarget: getter(props?.target),\n\t\t\tsupported: getter(props?.supported),\n\t\t};\n\n\t\tthis.#signals.run(this.#runCatalog.bind(this));\n\t\tthis.#signals.run(this.#runSupported.bind(this));\n\t\tthis.#signals.run(this.#runSelected.bind(this));\n\t}\n\n\t#runCatalog(effect: Effect): void {\n\t\tconst broadcast = effect.get(this.in.broadcast);\n\t\tif (!broadcast) return;\n\n\t\tconst catalog = effect.get(broadcast.out.catalog)?.video;\n\t\tif (!catalog) return;\n\n\t\teffect.set(this.#out.catalog, catalog);\n\t}\n\n\t#runSupported(effect: Effect): void {\n\t\tconst supported = effect.get(this.in.supported);\n\t\tif (!supported) {\n\t\t\tthis.#out.error.set(undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tconst renditions = effect.get(this.#out.catalog)?.renditions ?? {};\n\t\tthis.#out.error.set(undefined);\n\n\t\teffect.spawn(async () => {\n\t\t\tconst available: Record<string, Catalog.VideoConfig> = {};\n\n\t\t\tfor (const [name, config] of Object.entries(renditions)) {\n\t\t\t\tlet isSupported = false;\n\t\t\t\ttry {\n\t\t\t\t\tisSupported = await supported(config);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Source] video rendition ${name} (${config.codec}) support probe failed; treating as unsupported`,\n\t\t\t\t\t\terr,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (isSupported) available[name] = config;\n\t\t\t}\n\n\t\t\tconst error =\n\t\t\t\tObject.keys(available).length === 0 && Object.keys(renditions).length > 0 ? \"unsupported\" : undefined;\n\t\t\tif (error === \"unsupported\") {\n\t\t\t\tconsole.warn(\"[Source] No supported video renditions found:\", renditions);\n\t\t\t}\n\n\t\t\tthis.#out.error.set(error);\n\t\t\tthis.#out.available.set(available);\n\t\t});\n\t}\n\n\t#runSelected(effect: Effect): void {\n\t\tconst available = effect.get(this.#out.available);\n\t\tif (Object.keys(available).length === 0) return;\n\n\t\tconst target = effect.get(this.in.target);\n\n\t\t// Manual selection by name — skip all ABR logic.\n\t\tif (target?.name && target.name in available) {\n\t\t\tconst config = available[target.name];\n\t\t\teffect.set(this.#out.track, target.name);\n\t\t\teffect.set(this.#out.config, config);\n\t\t\teffect.set(this.#out.jitter, config.jitter !== undefined ? Time.Milli(config.jitter) : undefined);\n\t\t\treturn;\n\t\t}\n\n\t\t// Auto-select: use recv bandwidth if no explicit bitrate target.\n\t\tlet effectiveTarget = target;\n\t\tif (!target?.bitrate) {\n\t\t\tconst broadcast = effect.get(this.in.broadcast);\n\t\t\tconst connection = broadcast ? effect.get(broadcast.in.connection) : undefined;\n\t\t\tconst estimate = connection && effect.get(connection.probe).estimatedRecvRate;\n\t\t\tif (estimate != null) {\n\t\t\t\t// Apply a safety margin (80%) to avoid oscillation.\n\t\t\t\tconst safeBitrate = Math.round(estimate * 0.8);\n\t\t\t\teffectiveTarget = { ...target, bitrate: safeBitrate };\n\t\t\t}\n\t\t}\n\n\t\tconst selected = this.#select(available, effectiveTarget);\n\t\tif (!selected) return;\n\n\t\tconst config = available[selected];\n\n\t\teffect.set(this.#out.track, selected);\n\t\teffect.set(this.#out.config, config);\n\n\t\t// Use catalog jitter if available, otherwise estimate from framerate.\n\t\tconst jitter = config.jitter ?? (config.framerate ? Math.ceil(1000 / config.framerate) : undefined);\n\t\teffect.set(this.#out.jitter, jitter !== undefined ? Time.Milli(jitter) : undefined);\n\t}\n\n\t/**\n\t * Select the best rendition using a generic filter system.\n\t *\n\t * Each enabled filter returns matching renditions sorted by preference.\n\t * The first rendition present in every filter's output is selected.\n\t * If no rendition satisfies all filters, a warning is logged.\n\t */\n\t#select(renditions: Record<string, Catalog.VideoConfig>, target?: Target): string | undefined {\n\t\tconst entries = Object.entries(renditions);\n\t\tif (entries.length === 0) return undefined;\n\t\tif (entries.length === 1) return entries[0][0];\n\n\t\t// Build enabled filters based on the target.\n\t\tconst filters: RenditionFilter[] = [];\n\n\t\tif (target?.pixels != null) {\n\t\t\tfilters.push(byPixels(target.pixels));\n\t\t}\n\t\tif (target?.width != null || target?.height != null) {\n\t\t\tfilters.push(byDimensions(target.width, target.height));\n\t\t}\n\t\tif (target?.bitrate != null) {\n\t\t\tfilters.push(byBitrate(target.bitrate));\n\t\t}\n\n\t\t// No filters — pick the best rendition by quality.\n\t\tif (filters.length === 0) {\n\t\t\treturn bestRendition(entries);\n\t\t}\n\n\t\t// Run each filter to get ranked preference lists.\n\t\tconst rankings = filters.map((f) => f(entries));\n\n\t\t// Select the first rendition (in the first ranking's order) present in all rankings.\n\t\tconst sets = rankings.map((r) => new Set(r));\n\n\t\tfor (const name of rankings[0]) {\n\t\t\tif (sets.every((s) => s.has(name))) {\n\t\t\t\treturn name;\n\t\t\t}\n\t\t}\n\n\t\tconsole.warn(\"conflicting rendition filters, no rendition satisfies all criteria\");\n\t\treturn undefined;\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n"],"mappings":"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"}