@norskvideo/moq-watch 0.2.0 → 0.2.2
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.
- package/element.d.ts +10 -2
- package/element.d.ts.map +1 -1
- package/element.js +95 -80
- package/element.js.map +1 -1
- package/index.js +1 -1
- package/package.json +6 -6
- package/{player-C19uf7Dc.js → player-C7Ih0ExN.js} +64 -36
- package/player-C7Ih0ExN.js.map +1 -0
- package/video/renderer.d.ts +10 -1
- package/video/renderer.d.ts.map +1 -1
- package/video/tab-visible.d.ts +28 -0
- package/video/tab-visible.d.ts.map +1 -0
- package/player-C19uf7Dc.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-C7Ih0ExN.js","names":[],"sources":["../src/base64.ts","../src/media.ts","../src/audio/shared-ring-buffer.ts","../src/audio/buffer.ts","../src/audio/config.ts","../src/audio/handover.ts","../src/audio/latency.ts","../src/audio/render-worklet.ts?worklet","../src/audio/terminal.ts","../src/audio/warmup.ts","../src/audio/decoder.ts","../src/audio/emitter.ts","../src/audio/source.ts","../src/msf.ts","../src/broadcast.ts","../src/sync.ts","../../../node_modules/media-captions/styles/captions.css?inline","../../../node_modules/media-captions/styles/regions.css?inline","../src/text/renderer.ts","../src/text/source.ts","../src/video/config.ts","../src/video/playhead.ts","../src/video/presentation.ts","../src/video/decoder.ts","../src/video/tab-visible.ts","../src/video/renderer.ts","../src/video/source.ts","../src/player.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 type { Container } from \"@moq/hang\";\nimport type * as Moq from \"@moq/net\";\nimport { Error as NetError, type Time } from \"@moq/net\";\nimport type { Effect, Getter } from \"@moq/signals\";\n\n/**\n * Open a media subscription with its max age on the initial request and every update.\n *\n * @internal\n */\nexport function subscribeMedia(\n\teffect: Effect,\n\tprops: {\n\t\tbroadcast: Moq.Broadcast.Consumer;\n\t\ttrack: string;\n\t\tpriority: number;\n\t\tmaxAge: Getter<Time.Milli>;\n\t},\n): Moq.Track.Subscriber | undefined {\n\tif (effect.get(props.broadcast.closed) !== undefined) return;\n\tconst subscription = () => ({ priority: props.priority, maxAge: props.maxAge.peek() });\n\tconst subscriber = props.broadcast.track(props.track).subscribe(subscription());\n\teffect.cleanup(() => subscriber.close());\n\n\teffect.run((inner) => {\n\t\tsubscriber.update({ priority: props.priority, maxAge: inner.get(props.maxAge) });\n\t});\n\n\treturn subscriber;\n}\n\n/** Read the next media frame, ending playback when its subscription is reset. @internal */\nexport async function nextMedia(consumer: Container.Consumer) {\n\ttry {\n\t\treturn await consumer.next();\n\t} catch (err) {\n\t\tif (!(err instanceof NetError.Stream)) throw err;\n\t\t// The subscription is over, even when other tracks on the session are still live.\n\t\tconsole.debug(\"media subscription ended\", err);\n\t\treturn undefined;\n\t}\n}\n","import { Time } from \"@moq/net\";\n\n// Control array slot indices. The playhead is not here: see `state`.\nconst WRITE = 0;\nconst LATENCY = 1;\nconst STALLED = 2;\n// Timeline identity changes only on re-anchor, independently of the packed mutation epoch.\nconst TIMELINE = 3;\nconst CONTROL_SLOTS = 4;\n\n/**\n * The playhead and its mutation epoch, packed into one 64-bit word: epoch in the high\n * half, read cursor in the low half.\n *\n * They have to move together. The reader samples the ring, copies, and only then publishes, and\n * the writer can rebase the timeline anywhere in between. Checking an epoch and then writing a\n * cursor leaves a window between the two no ordering closes, which is how a stale cursor ends up\n * on a fresh timeline: the playhead lands past WRITE, `read` returns nothing, and `insert` drops\n * everything as too old until WRITE catches up. Packed, the check *is* the write: the reader\n * compare-exchanges the whole word, so a rebase makes its publish fail rather than land.\n */\nconst CURSOR_MASK = 0xffffffffn;\n\nfunction pack(epoch: number, read: number): bigint {\n\treturn (BigInt(epoch >>> 0) << 32n) | BigInt(read >>> 0);\n}\n\n/**\n * The mutation epoch. Even while settled, odd while `truncate` retreats WRITE.\n *\n * WRITE lives in a different word, so a retreat cannot move it and the epoch together. Marking\n * the epoch on either side of the retreat is what covers the gap: a reader that snapshotted\n * before the mark finds the word moved and its exchange fails, one that snapshots between the\n * marks sees the odd epoch and renders silence, and one that snapshots after already reads the\n * retreated WRITE. So the writer steps by two on a re-anchor, keeping odd for the retreat alone.\n */\nfunction epochOf(state: bigint): number {\n\treturn Number(state >> 32n) | 0;\n}\n\n/** Whether a `truncate` is mid-retreat, so WRITE may still describe samples it is dropping. */\nfunction retreating(state: bigint): boolean {\n\treturn (epochOf(state) & 1) !== 0;\n}\n\n/** The playhead half, back as an i32 so the modular comparisons elsewhere still hold. */\nfunction readOf(state: bigint): number {\n\treturn Number(state & CURSOR_MASK) | 0;\n}\n\nexport interface SharedRingBufferInit {\n\tchannels: number;\n\tcapacity: number; // samples per channel, always a power of two\n\trate: number;\n\tsamples: SharedArrayBuffer; // channels * capacity * Float32Array.BYTES_PER_ELEMENT bytes\n\tcontrol: SharedArrayBuffer; // CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT bytes\n\tstate: SharedArrayBuffer; // one BigInt64Array element: epoch and read cursor, packed\n\t// Buffered mode: never skip ahead on read, so we play through everything buffered.\n\tbuffered: boolean;\n}\n\n/** Rounds up to a power of two, which is what makes `slot` wrap-invariant. */\nfunction ceilPow2(n: number): number {\n\treturn n <= 1 ? 1 : 1 << (32 - Math.clz32(n - 1));\n}\n\n/**\n * Allocate the shared memory for a ring holding at least `capacity` samples per channel.\n *\n * The capacity is rounded up to a power of two so `slot` can mask instead of taking a\n * remainder; read `init.capacity` back rather than assuming the requested value.\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 || capacity > 2 ** 30) throw new Error(\"invalid capacity\");\n\tif (rate <= 0) throw new Error(\"invalid sample rate\");\n\n\tcapacity = ceilPow2(capacity);\n\n\tconst samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\n\tconst control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\n\tconst state = new SharedArrayBuffer(BigInt64Array.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, state, 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/**\n * Maps a sample index to a [0, capacity) array slot.\n *\n * `capacity` is a power of two, so masking is exact for negative indexes and, unlike a\n * remainder, survives the i32 wrap: consecutive indexes stay consecutive slots across\n * 0x7fffffff -> 0x80000000, which is what keeps unread samples from aliasing there.\n */\nfunction slot(idx: number, capacity: number): number {\n\treturn idx & (capacity - 1);\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#state: BigInt64Array;\n\t#samples: Float32Array[];\n\n\t// Whether READ/WRITE have been anchored to the first inserted sample.\n\t#anchored = false;\n\n\t// Absolute sample index of that first sample. READ/WRITE are stored relative to it, so\n\t// `timestamp` adds it back to recover media time. Main-thread only: the worklet reads by\n\t// difference and never needs an absolute position.\n\t#anchor = 0;\n\n\t// Unwrapped READ, in anchor-relative samples, plus the raw i32 it was last derived from.\n\t// READ is Int32 and wraps every ~13.5h at 44.1kHz. That is harmless for the modular\n\t// comparisons the ring runs on, but reading the negative half as a magnitude would report\n\t// the playhead jumping back 2^32 samples, so accumulate deltas here instead. Main-thread\n\t// only, and only accurate while `timestamp` is observed more often than READ can advance\n\t// 2^31 samples, which the 50ms poll in `buffer.ts` satisfies by six orders of magnitude.\n\t#position = 0;\n\t#lastRead = 0;\n\n\t/**\n\t * Wrap the shared memory described by `init`.\n\t *\n\t * Pass `previous` in the worklet when this ring replaces one already being read. `resize`\n\t * snapshots the playhead on the main thread and hands the replacement over by message, so the\n\t * reader keeps draining the old ring in the meantime. Carrying its cursor across means those\n\t * samples are not played twice.\n\t */\n\tconstructor(init: SharedRingBufferInit, previous?: SharedRingBuffer) {\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.#state = new BigInt64Array(init.state);\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\n\t\tif (previous !== undefined) this.#handoff(previous);\n\t}\n\n\t/**\n\t * Carry `source`'s playhead across, if the replacement is still the timeline it belongs to.\n\t *\n\t * Truncation changes the mutation epoch but preserves timeline identity. Sample state before\n\t * identity: a re-anchor publishes identity first, then replaces state. Either the identity\n\t * check rejects the old cursor, the exchange fails, or the re-anchor overwrites the carried\n\t * cursor. A truncate only forces a retry, preserving audio consumed since the resize.\n\t */\n\t#handoff(source: SharedRingBuffer): void {\n\t\tif (source.channels !== this.channels || source.rate !== this.rate) return;\n\n\t\tconst timeline = Atomics.load(source.#control, TIMELINE);\n\t\tconst from = Atomics.load(source.#state, 0);\n\n\t\tfor (;;) {\n\t\t\tconst state = Atomics.load(this.#state, 0);\n\t\t\tif (Atomics.load(this.#control, TIMELINE) !== timeline) return;\n\t\t\tif (((readOf(from) - readOf(state)) | 0) <= 0) return;\n\n\t\t\tconst next = pack(epochOf(state), readOf(from));\n\t\t\tif (Atomics.compareExchange(this.#state, 0, state, next) === state) return;\n\t\t}\n\t}\n\n\t/**\n\t * Move the playhead forward to `candidate`, staying on whatever timeline is current.\n\t *\n\t * Retries while the word changes under it, and never steps backwards. Used by the writer's\n\t * overflow path; the reader publishes with its own exchange so it can tell a rebase apart\n\t * from losing a race.\n\t */\n\t#advance(candidate: number): void {\n\t\tfor (;;) {\n\t\t\tconst state = Atomics.load(this.#state, 0);\n\t\t\tif (((candidate - readOf(state)) | 0) <= 0) return;\n\n\t\t\tconst next = pack(epochOf(state), candidate);\n\t\t\tif (Atomics.compareExchange(this.#state, 0, state, next) === state) return;\n\t\t}\n\t}\n\n\t/**\n\t * Step the epoch by `by`, leaving the playhead wherever the reader has taken it.\n\t *\n\t * Retries while the word changes under it: only the epoch half is the writer's to move.\n\t */\n\t#step(by: number): void {\n\t\tfor (;;) {\n\t\t\tconst state = Atomics.load(this.#state, 0);\n\t\t\tconst next = pack((epochOf(state) + by) | 0, readOf(state));\n\t\t\tif (Atomics.compareExchange(this.#state, 0, state, next) === state) return;\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// Anchor to the first sample so playback starts at its timestamp rather than gap-filling\n\t\t// from index 0.\n\t\tif (!this.#anchored) {\n\t\t\tthis.#anchor = start;\n\t\t\t// One store rebases both halves, so a reader's compare-exchange against the old word\n\t\t\t// cannot land afterwards. Two, because odd epochs belong to `truncate`.\n\t\t\tconst epoch = epochOf(Atomics.load(this.#state, 0));\n\t\t\t// Publish identity first so a handoff cannot apply an old cursor to the new state.\n\t\t\tAtomics.add(this.#control, TIMELINE, 1);\n\t\t\tAtomics.store(this.#state, 0, pack((epoch + 2) | 0, 0));\n\t\t\tAtomics.store(this.#control, WRITE, 0);\n\t\t\tthis.#anchored = true;\n\t\t\tthis.#position = 0;\n\t\t\tthis.#lastRead = 0;\n\t\t}\n\n\t\t// Positions are relative to the anchor. READ/WRITE are Int32, so an absolute sample index\n\t\t// wraps once a stream has been broadcasting a while, and the wrapped value reads as far\n\t\t// ahead of READ, so every insert would be discarded as too old and the ring would go\n\t\t// silent for good. Relative positions start at 0 instead. They still wrap, after ~13.5h at\n\t\t// 44.1kHz, but nothing here reads them as magnitudes: the comparisons are modular, `slot`\n\t\t// masks a power-of-two capacity, and `timestamp` keeps its own unwrapped position.\n\t\tstart = (start - this.#anchor) | 0;\n\n\t\tconst end = (start + originalLength) | 0;\n\n\t\t// Trim old: discard samples before the read index\n\t\tconst read = readOf(Atomics.load(this.#state, 0));\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\tthis.#advance((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\tthis.#resume();\n\t}\n\n\t// Un-stall once the buffered data covers LATENCY.\n\t#resume(): void {\n\t\tconst read = readOf(Atomics.load(this.#state, 0));\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tif (((write - read) | 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\t// Sample the word before admission, not after. A re-anchor publishes the new state and\n\t\t// only then clears WRITE, so a reader admitted in between would pair a fresh epoch with\n\t\t// the previous timeline's WRITE and its exchange would succeed. Taken first, a reader\n\t\t// admitted before the rebase still holds the old word and its exchange fails, while one\n\t\t// arriving after sees the STALLED that `reset` raised and never starts.\n\t\tconst state = Atomics.load(this.#state, 0);\n\t\tif (Atomics.load(this.#control, STALLED) === 1) return 0;\n\t\t// A retreat is in flight, so WRITE still describes samples `truncate` is dropping and a\n\t\t// later exchange could not tell them apart. Render the quantum as silence instead.\n\t\tif (retreating(state)) return 0;\n\n\t\tlet read = readOf(state);\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\tif (((skipTo - read) | 0) > 0) 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) {\n\t\t\t// A latency skip still has to be published, and still only if nothing moved.\n\t\t\tif (((read - readOf(state)) | 0) > 0) {\n\t\t\t\tAtomics.compareExchange(this.#state, 0, state, pack(epochOf(state), read));\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\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// Publish. The exchange fails if anything moved the word since the snapshot above, which\n\t\t// covers both a rebase and a concurrent overflow, so these samples are only ever counted\n\t\t// as played on the timeline they came from.\n\t\tconst next = pack(epochOf(state), (read + count) | 0);\n\t\tif (Atomics.compareExchange(this.#state, 0, state, next) !== state) {\n\t\t\t// The worklet takes the return value as an underflow count rather than clearing the\n\t\t\t// buffer it handed over, so silence the prefix or the discarded audio renders anyway.\n\t\t\tfor (let channel = 0; channel < this.channels; channel++) output[channel].fill(0, 0, count);\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn count;\n\t}\n\n\t/**\n\t * Update the target latency in samples. Main thread only.\n\t *\n\t * A deeper floor parks playback until it refills. Video holds the extra delay on its own, so\n\t * audio that kept draining at the old depth would run ahead by the difference. Parking keeps\n\t * what is buffered, so a rise costs only its own size in silence, and none if the buffer\n\t * already covers the new floor.\n\t */\n\tsetLatency(samples: number): void {\n\t\tconst previous = Atomics.exchange(this.#control, LATENCY, samples);\n\t\tif (previous > 0 && samples > previous) {\n\t\t\tAtomics.store(this.#control, STALLED, 1);\n\t\t\t// A reader already past the STALLED check would still publish its quantum. Moving the\n\t\t\t// word fails its exchange, and one that snapshots after this sees STALLED. Two keeps the\n\t\t\t// epoch even, since odd belongs to `truncate`.\n\t\t\tthis.#step(2);\n\t\t}\n\n\t\t// Parked, the playhead holds still, so this measures exactly what is buffered. It also\n\t\t// resumes a refill that a shallower floor has already covered, which `insert` would only\n\t\t// notice on the next frame, never for a source that has stopped.\n\t\tthis.#resume();\n\t}\n\n\t/**\n\t * Drop buffered samples at or after `timestamp`, keeping whatever is already due.\n\t * Main thread only.\n\t *\n\t * A successor track overwrites the slots its own samples land on, but anything the previous\n\t * track wrote beyond them would otherwise still play once the successor runs out.\n\t */\n\ttruncate(timestamp: Time.Micro): void {\n\t\tconst target = (Math.round(Time.Second.fromMicro(timestamp) * this.rate) - this.#anchor) | 0;\n\t\tif (((Atomics.load(this.#control, WRITE) - target) | 0) <= 0) return; // nothing past the new timeline\n\n\t\t// Bracket the retreat with epoch steps, so a reader is never left holding a WRITE this\n\t\t// dropped the tail of. See `epochOf`.\n\t\tthis.#step(1);\n\t\tfor (;;) {\n\t\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\t\t// Never retreat past the playhead: those samples are already due. The worklet can still\n\t\t\t// advance READ past the value read here, leaving READ ahead of WRITE. That reads as an\n\t\t\t// empty ring (read() returns nothing, insert() trims what the worklet already played) and\n\t\t\t// heals on the first successor sample past the playhead, so it costs a quantum of silence\n\t\t\t// rather than replaying anything.\n\t\t\tconst clamped = i32Max(target, readOf(Atomics.load(this.#state, 0)));\n\t\t\tif (((write - clamped) | 0) <= 0) break;\n\t\t\tif (Atomics.compareExchange(this.#control, WRITE, write, clamped) === write) break;\n\t\t}\n\t\tthis.#step(1);\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\tconst state = Atomics.load(this.#state, 0);\n\t\tAtomics.store(this.#state, 0, pack(epochOf(state), 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\t\tdst.#anchor = this.#anchor;\n\n\t\tconst state = Atomics.load(this.#state, 0);\n\t\tconst read = readOf(state);\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, TIMELINE, Atomics.load(this.#control, TIMELINE));\n\t\tAtomics.store(dst.#state, 0, pack(epochOf(state), 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\t// Carry the unwrapped playhead over, rebased onto dst's READ. Fold the same `read`\n\t\t// snapshot the copy used so both sides agree on one observation; `copyStart` is at or\n\t\t// ahead of it whenever the copy dropped the oldest samples.\n\t\tdst.#position = this.#foldRead(read) + ((copyStart - read) | 0);\n\t\tdst.#lastRead = copyStart;\n\n\t\treturn dst;\n\t}\n\n\t/**\n\t * Fold an observed READ into `#position`, so the returned offset keeps counting up across\n\t * the i32 wrap. Idempotent: folding the same value twice adds a zero delta.\n\t */\n\t#foldRead(read: number): number {\n\t\tthis.#position += (read - this.#lastRead) | 0;\n\t\tthis.#lastRead = read;\n\t\treturn this.#position;\n\t}\n\n\t/** `#foldRead` against the current READ. */\n\t#unwrapRead(): number {\n\t\treturn this.#foldRead(readOf(Atomics.load(this.#state, 0)));\n\t}\n\n\t/**\n\t * Current playback timestamp derived from READ position.\n\t *\n\t * Main thread only, and stateful: it advances the unwrapped read position, so it has to be\n\t * polled rather than sampled once. See `#position`.\n\t */\n\tget timestamp(): Time.Micro {\n\t\treturn Time.Micro.fromSecond(((this.#anchor + this.#unwrapRead()) / 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) - readOf(Atomics.load(this.#state, 0))) | 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, Truncate } 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 * Drop buffered samples at or after `timestamp`, keeping what is already due.\n\t *\n\t * Used when a new track takes over the timeline: its samples overwrite the slots they land on,\n\t * but the previous track's write-ahead tail would otherwise play once the new audio runs out.\n\t * Unlike `reset()` this keeps playing, so there's no gap at the handover.\n\t */\n\ttruncate(timestamp: Time.Micro): 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.warn(\n\t\t\"[audio] SharedArrayBuffer unavailable, falling back to the higher latency postMessage audio buffer. \" +\n\t\t\t\"Serve the page cross-origin isolated (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp) to avoid this.\",\n\t);\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// Park the current ring before any resize: the worklet keeps reading it until `init-shared`\n\t\t// lands, and `resize` carries the floor and the park over to the copy.\n\t\tthis.#ring.setLatency(samples);\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\n\t\t\tconst msg: InitShared = { type: \"init-shared\", ...this.#ring.init };\n\t\t\tthis.#worklet.port.postMessage(msg);\n\t\t}\n\t}\n\n\ttruncate(timestamp: Time.Micro): void {\n\t\tthis.#ring.truncate(timestamp);\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\ttruncate(timestamp: Time.Micro): void {\n\t\tconst msg: Truncate = { type: \"truncate\", timestamp };\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","import type * as Catalog from \"@moq/hang/catalog\";\nimport { Time } from \"@moq/net\";\n\n// AudioWorklet always renders in 128-sample quanta.\nconst WORKLET_QUANTUM = 128;\nconst OPUS_FRAME_DURATION_MS = 20;\nconst AAC_LC_FRAME_SAMPLES = 1024;\nconst MP3_MPEG1_FRAME_SAMPLES = 1152;\nconst MP3_MPEG2_FRAME_SAMPLES = 576;\nconst MP3_MPEG1_MIN_SAMPLE_RATE = 32000;\n\n/** The catalog fields that determine the demuxer and WebCodecs decoder instance. */\nexport type DecoderConfig = Pick<\n\tCatalog.AudioConfig,\n\t\"codec\" | \"container\" | \"description\" | \"sampleRate\" | \"numberOfChannels\"\n>;\n\n/** The routing and decoder state whose changes require a replacement subscription. */\nexport type PlaybackIdentity = {\n\tbroadcast: Catalog.AudioConfig[\"broadcast\"];\n\tdecoder: DecoderConfig;\n};\n\n/** Reduce a rendition config to the fields that require a new decoder. */\nexport function decoderConfig(config: Catalog.AudioConfig): DecoderConfig {\n\treturn {\n\t\tcodec: config.codec,\n\t\tcontainer: config.container,\n\t\tdescription: config.description,\n\t\tsampleRate: config.sampleRate,\n\t\tnumberOfChannels: config.numberOfChannels,\n\t};\n}\n\n/** Reduce a rendition config to the fields that require a replacement subscription. */\nexport function playbackIdentity(config: Catalog.AudioConfig): PlaybackIdentity {\n\treturn {\n\t\tbroadcast: config.broadcast,\n\t\tdecoder: decoderConfig(config),\n\t};\n}\n\n/**\n * The sync buffer a rendition needs, in milliseconds: its catalog `delay` behind the broadcast's\n * earliest rendition plus its own jitter.\n */\nexport function playbackJitter(config: Catalog.AudioConfig): Time.Milli {\n\t// A publisher advertising 0 is claiming frames are never delayed, which no encoder can do, so\n\t// fall back to the codec's frame duration the same way an absent field does.\n\tconst codecJitter = (config.jitter || defaultJitter(config)) ?? 0;\n\n\t// Add the worklet render quantum so the ring buffer has margin between frame arrivals.\n\tconst overhead = Math.ceil((WORKLET_QUANTUM / config.sampleRate) * 1000);\n\treturn Time.Milli((config.delay ?? 0) + codecJitter + overhead);\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 defaultJitter(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 OPUS_FRAME_DURATION_MS;\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((AAC_LC_FRAME_SAMPLES / 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 =\n\t\t\tconfig.sampleRate >= MP3_MPEG1_MIN_SAMPLE_RATE ? MP3_MPEG1_FRAME_SAMPLES : MP3_MPEG2_FRAME_SAMPLES;\n\t\treturn Math.ceil((samples / config.sampleRate) * 1000);\n\t}\n\n\treturn undefined;\n}\n","/**\n * Tracks whether the audio ring still holds samples from a superseded subscription.\n *\n * The ring outlives the subscription that fills it, so reopening one (a rendition swap, a\n * republished broadcast, a reconnect) leaves the previous subscription's decoded audio buffered.\n * The replacement is the authority from its first frame onwards, so everything the ring holds past\n * that timestamp is stale and has to go.\n */\nexport class Handover {\n\t#open = false;\n\t#pending = false;\n\n\t/** Record a subscription opening on the current ring. */\n\topened(): void {\n\t\tif (this.#open) this.#pending = true;\n\t\tthis.#open = true;\n\t}\n\n\t/** Consume one decoded frame and report whether it is the first of a replacement subscription. */\n\ttakeover(): boolean {\n\t\tconst pending = this.#pending;\n\t\tthis.#pending = false;\n\t\treturn pending;\n\t}\n}\n","import { Time } from \"@moq/net\";\n\n// An AudioWorkletProcessor renders in fixed 128-sample quanta, so a ring shallower than one can\n// never be read from.\nconst RENDER_QUANTUM = 128;\n\n/**\n * The ring depth for a target delay, floored at one AudioWorklet render quantum.\n *\n * `delay=\"instant\"` reports a zero buffer, which the ring rejects outright: construction throws,\n * the worklet is left with no backend, and every later resize is gated on that backend existing, so\n * audio never recovers. Audio keeps its own floor rather than deriving its depth verbatim from a\n * buffer whose meaning is \"video holds nothing\".\n */\nexport function ringSamples(rate: number, delay: Time.Milli): number {\n\treturn Math.max(RENDER_QUANTUM, Math.ceil(rate * Time.Second.fromMilli(delay)));\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.isSafeInteger(unitsPerSecond) || unitsPerSecond <= 0) {\\n throw new RangeError(`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);\\nfunction assertTimestampValue(value) {\\n if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER) {\\n throw new RangeError(`invalid timestamp: ${value}`);\\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 assertTimestampValue(value);\\n this.value = value;\\n this.scale = Timescale(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`; throws if the result is not in the safe range. */\\n as(scale) {\\n const dest = Timescale(scale);\\n const converted = dest === this.scale ? this.value : this.value * dest / this.scale;\\n assertTimestampValue(converted);\\n return converted;\\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: play through everything buffered 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 const latencySamples = Math.ceil(this.rate * time_exports.Second.fromMilli(latency));\\n if (latencySamples > this.#latencySamples) this.#stalled = true;\\n this.#latencySamples = latencySamples;\\n const newCapacity = this.#capacityFor(this.#latencySamples);\\n if (newCapacity !== this.capacity) this.#reallocate(newCapacity);\\n if (latencySamples > 0 && this.length >= latencySamples) this.#stalled = false;\\n }\\n #reallocate(newCapacity) {\\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.#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 /**\\n * Drop buffered samples at or after `timestamp`, keeping whatever is already due.\\n *\\n * A successor track overwrites the slots its own samples land on, but anything the previous\\n * track wrote beyond them would otherwise still play once the successor runs out.\\n */\\n truncate(timestamp) {\\n const target = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n if (target >= this.#writeIndex) return;\\n this.#writeIndex = Math.max(target, this.#readIndex);\\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 LATENCY = 1;\\nvar STALLED = 2;\\nvar TIMELINE = 3;\\nvar CONTROL_SLOTS = 4;\\nvar CURSOR_MASK = 0xffffffffn;\\nfunction pack(epoch, read) {\\n return BigInt(epoch >>> 0) << 32n | BigInt(read >>> 0);\\n}\\nfunction epochOf(state) {\\n return Number(state >> 32n) | 0;\\n}\\nfunction retreating(state) {\\n return (epochOf(state) & 1) !== 0;\\n}\\nfunction readOf(state) {\\n return Number(state & CURSOR_MASK) | 0;\\n}\\nfunction ceilPow2(n) {\\n return n <= 1 ? 1 : 1 << 32 - Math.clz32(n - 1);\\n}\\nfunction allocSharedRingBuffer(channels, capacity, rate, buffered = false) {\\n if (channels <= 0) throw new Error(\\\"invalid channels\\\");\\n if (capacity <= 0 || capacity > 2 ** 30) throw new Error(\\\"invalid capacity\\\");\\n if (rate <= 0) throw new Error(\\\"invalid sample rate\\\");\\n capacity = ceilPow2(capacity);\\n const samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\\n const control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\\n const state = new SharedArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT);\\n const ctrl = new Int32Array(control);\\n Atomics.store(ctrl, STALLED, 1);\\n return { channels, capacity, rate, samples, control, state, buffered };\\n}\\nfunction i32Max(a, b) {\\n return (a - b | 0) > 0 ? a : b;\\n}\\nfunction slot(idx, capacity) {\\n return idx & capacity - 1;\\n}\\nvar SharedRingBuffer = class _SharedRingBuffer {\\n channels;\\n capacity;\\n rate;\\n buffered;\\n init;\\n #control;\\n #state;\\n #samples;\\n // Whether READ/WRITE have been anchored to the first inserted sample.\\n #anchored = false;\\n // Absolute sample index of that first sample. READ/WRITE are stored relative to it, so\\n // `timestamp` adds it back to recover media time. Main-thread only: the worklet reads by\\n // difference and never needs an absolute position.\\n #anchor = 0;\\n // Unwrapped READ, in anchor-relative samples, plus the raw i32 it was last derived from.\\n // READ is Int32 and wraps every ~13.5h at 44.1kHz. That is harmless for the modular\\n // comparisons the ring runs on, but reading the negative half as a magnitude would report\\n // the playhead jumping back 2^32 samples, so accumulate deltas here instead. Main-thread\\n // only, and only accurate while `timestamp` is observed more often than READ can advance\\n // 2^31 samples, which the 50ms poll in `buffer.ts` satisfies by six orders of magnitude.\\n #position = 0;\\n #lastRead = 0;\\n /**\\n * Wrap the shared memory described by `init`.\\n *\\n * Pass `previous` in the worklet when this ring replaces one already being read. `resize`\\n * snapshots the playhead on the main thread and hands the replacement over by message, so the\\n * reader keeps draining the old ring in the meantime. Carrying its cursor across means those\\n * samples are not played twice.\\n */\\n constructor(init, previous) {\\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.#state = new BigInt64Array(init.state);\\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 if (previous !== void 0) this.#handoff(previous);\\n }\\n /**\\n * Carry `source`'s playhead across, if the replacement is still the timeline it belongs to.\\n *\\n * Truncation changes the mutation epoch but preserves timeline identity. Sample state before\\n * identity: a re-anchor publishes identity first, then replaces state. Either the identity\\n * check rejects the old cursor, the exchange fails, or the re-anchor overwrites the carried\\n * cursor. A truncate only forces a retry, preserving audio consumed since the resize.\\n */\\n #handoff(source) {\\n if (source.channels !== this.channels || source.rate !== this.rate) return;\\n const timeline = Atomics.load(source.#control, TIMELINE);\\n const from = Atomics.load(source.#state, 0);\\n for (; ; ) {\\n const state = Atomics.load(this.#state, 0);\\n if (Atomics.load(this.#control, TIMELINE) !== timeline) return;\\n if ((readOf(from) - readOf(state) | 0) <= 0) return;\\n const next = pack(epochOf(state), readOf(from));\\n if (Atomics.compareExchange(this.#state, 0, state, next) === state) return;\\n }\\n }\\n /**\\n * Move the playhead forward to `candidate`, staying on whatever timeline is current.\\n *\\n * Retries while the word changes under it, and never steps backwards. Used by the writer's\\n * overflow path; the reader publishes with its own exchange so it can tell a rebase apart\\n * from losing a race.\\n */\\n #advance(candidate) {\\n for (; ; ) {\\n const state = Atomics.load(this.#state, 0);\\n if ((candidate - readOf(state) | 0) <= 0) return;\\n const next = pack(epochOf(state), candidate);\\n if (Atomics.compareExchange(this.#state, 0, state, next) === state) return;\\n }\\n }\\n /**\\n * Step the epoch by `by`, leaving the playhead wherever the reader has taken it.\\n *\\n * Retries while the word changes under it: only the epoch half is the writer's to move.\\n */\\n #step(by) {\\n for (; ; ) {\\n const state = Atomics.load(this.#state, 0);\\n const next = pack(epochOf(state) + by | 0, readOf(state));\\n if (Atomics.compareExchange(this.#state, 0, state, next) === state) return;\\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.#anchored) {\\n this.#anchor = start;\\n const epoch = epochOf(Atomics.load(this.#state, 0));\\n Atomics.add(this.#control, TIMELINE, 1);\\n Atomics.store(this.#state, 0, pack(epoch + 2 | 0, 0));\\n Atomics.store(this.#control, WRITE, 0);\\n this.#anchored = true;\\n this.#position = 0;\\n this.#lastRead = 0;\\n }\\n start = start - this.#anchor | 0;\\n const end = start + originalLength | 0;\\n const read = readOf(Atomics.load(this.#state, 0));\\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 this.#advance(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 this.#resume();\\n }\\n // Un-stall once the buffered data covers LATENCY.\\n #resume() {\\n const read = readOf(Atomics.load(this.#state, 0));\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n if ((write - read | 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 const state = Atomics.load(this.#state, 0);\\n if (Atomics.load(this.#control, STALLED) === 1) return 0;\\n if (retreating(state)) return 0;\\n let read = readOf(state);\\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 if ((skipTo - read | 0) > 0) read = skipTo;\\n }\\n const available = write - read | 0;\\n const count = Math.min(available, output[0].length);\\n if (count <= 0) {\\n if ((read - readOf(state) | 0) > 0) {\\n Atomics.compareExchange(this.#state, 0, state, pack(epochOf(state), read));\\n }\\n return 0;\\n }\\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 const next = pack(epochOf(state), read + count | 0);\\n if (Atomics.compareExchange(this.#state, 0, state, next) !== state) {\\n for (let channel = 0; channel < this.channels; channel++) output[channel].fill(0, 0, count);\\n return 0;\\n }\\n return count;\\n }\\n /**\\n * Update the target latency in samples. Main thread only.\\n *\\n * A deeper floor parks playback until it refills. Video holds the extra delay on its own, so\\n * audio that kept draining at the old depth would run ahead by the difference. Parking keeps\\n * what is buffered, so a rise costs only its own size in silence, and none if the buffer\\n * already covers the new floor.\\n */\\n setLatency(samples) {\\n const previous = Atomics.exchange(this.#control, LATENCY, samples);\\n if (previous > 0 && samples > previous) {\\n Atomics.store(this.#control, STALLED, 1);\\n this.#step(2);\\n }\\n this.#resume();\\n }\\n /**\\n * Drop buffered samples at or after `timestamp`, keeping whatever is already due.\\n * Main thread only.\\n *\\n * A successor track overwrites the slots its own samples land on, but anything the previous\\n * track wrote beyond them would otherwise still play once the successor runs out.\\n */\\n truncate(timestamp) {\\n const target = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate) - this.#anchor | 0;\\n if ((Atomics.load(this.#control, WRITE) - target | 0) <= 0) return;\\n this.#step(1);\\n for (; ; ) {\\n const write = Atomics.load(this.#control, WRITE);\\n const clamped = i32Max(target, readOf(Atomics.load(this.#state, 0)));\\n if ((write - clamped | 0) <= 0) break;\\n if (Atomics.compareExchange(this.#control, WRITE, write, clamped) === write) break;\\n }\\n this.#step(1);\\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 const state = Atomics.load(this.#state, 0);\\n Atomics.store(this.#state, 0, pack(epochOf(state), 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 dst.#anchor = this.#anchor;\\n const state = Atomics.load(this.#state, 0);\\n const read = readOf(state);\\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, TIMELINE, Atomics.load(this.#control, TIMELINE));\\n Atomics.store(dst.#state, 0, pack(epochOf(state), copyStart));\\n Atomics.store(dst.#control, WRITE, write);\\n Atomics.store(dst.#control, LATENCY, latency);\\n Atomics.store(dst.#control, STALLED, stalled);\\n dst.#position = this.#foldRead(read) + (copyStart - read | 0);\\n dst.#lastRead = copyStart;\\n return dst;\\n }\\n /**\\n * Fold an observed READ into `#position`, so the returned offset keeps counting up across\\n * the i32 wrap. Idempotent: folding the same value twice adds a zero delta.\\n */\\n #foldRead(read) {\\n this.#position += read - this.#lastRead | 0;\\n this.#lastRead = read;\\n return this.#position;\\n }\\n /** `#foldRead` against the current READ. */\\n #unwrapRead() {\\n return this.#foldRead(readOf(Atomics.load(this.#state, 0)));\\n }\\n /**\\n * Current playback timestamp derived from READ position.\\n *\\n * Main thread only, and stateful: it advances the unwrapped read position, so it has to be\\n * polled rather than sampled once. See `#position`.\\n */\\n get timestamp() {\\n return time_exports.Micro.fromSecond((this.#anchor + this.#unwrapRead()) / 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) - readOf(Atomics.load(this.#state, 0)) | 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 const previous = this.#backend instanceof SharedRingBuffer ? this.#backend : void 0;\\n this.#backend = new SharedRingBuffer(msg, previous);\\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 === \\\"truncate\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.truncate(msg.timestamp);\\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 { Time } from \"@moq/net\";\n\ninterface SampleSpan {\n\treadonly timestamp: number;\n\treadonly sampleRate: number;\n\treadonly numberOfFrames: number;\n}\n\ninterface Update {\n\treadonly discontinuity: number;\n\treadonly group: number;\n\treadonly end?: Time.Micro;\n\treadonly frame?: { readonly timestamp: Time.Micro };\n}\n\n/** The portion of decoded audio that belongs to the source timeline. */\nexport interface DecodedSpan {\n\t/** Source timestamp of the first retained frame. */\n\treadonly timestamp: Time.Micro;\n\t/** Decoded frames to skip before copying. */\n\treadonly frameOffset: number;\n\t/** Decoded frames to retain after the offset. */\n\treadonly frames: number;\n}\n\n/** Track codec epochs and map decoded output onto the source timeline. */\nexport class Terminal {\n\t#discontinuity = 0;\n\t#end?: Time.Micro;\n\t// The group that carried `#end`. It bounds only the terminal packets that follow it there.\n\t#endGroup?: number;\n\t#epoch?: Time.Micro;\n\t#preSkip = 0;\n\t#preSkipRemaining?: number;\n\n\t/** The exclusive source endpoint most recently received. */\n\tget end(): Time.Micro | undefined {\n\t\treturn this.#end;\n\t}\n\n\t/** Reset state for a new container consumer with Opus pre-skip in 48 kHz frames. */\n\tclear(preSkip = 0): void {\n\t\tthis.#discontinuity = 0;\n\t\tthis.#end = undefined;\n\t\tthis.#preSkip = preSkip;\n\t\tthis.#resetEpoch();\n\t}\n\n\t/** Apply one ordered consumer result and report whether its codec epoch changed. */\n\tupdate(next: Update): boolean {\n\t\tconst reset = next.discontinuity !== this.#discontinuity;\n\t\tif (reset) {\n\t\t\tthis.#discontinuity = next.discontinuity;\n\t\t\tthis.#end = undefined;\n\t\t\tthis.#resetEpoch();\n\t\t}\n\t\t// A frame from a later group is new media, e.g. resuming after a discontinuity marker.\n\t\tif (next.frame && next.group !== this.#endGroup) this.#end = undefined;\n\t\tif (next.frame && this.#epoch === undefined) this.#epoch = next.frame.timestamp;\n\t\tif (next.end !== undefined) {\n\t\t\tthis.#end = next.end;\n\t\t\tthis.#endGroup = next.group;\n\t\t}\n\t\treturn reset;\n\t}\n\n\t/** Remove codec pre-skip and terminal padding from one decoded sample. */\n\tspan(sample: SampleSpan): DecodedSpan {\n\t\tconst epoch = this.#epoch ?? (sample.timestamp as Time.Micro);\n\t\tthis.#epoch = epoch;\n\n\t\tconst delayFrames = Math.floor((this.#preSkip * sample.sampleRate) / 48_000);\n\t\tconst remaining = this.#preSkipRemaining ?? delayFrames;\n\t\tconst frameOffset = Math.min(remaining, sample.numberOfFrames);\n\t\tthis.#preSkipRemaining = remaining - frameOffset;\n\n\t\tconst delay = Math.round((delayFrames * 1_000_000) / sample.sampleRate);\n\t\tconst timestamp = Math.max(epoch, sample.timestamp - delay) as Time.Micro;\n\t\tlet frames = sample.numberOfFrames - frameOffset;\n\t\tif (this.#end !== undefined) {\n\t\t\tif (this.#end <= timestamp) {\n\t\t\t\tframes = 0;\n\t\t\t} else {\n\t\t\t\tconst duration = this.#end - timestamp;\n\t\t\t\tconst terminalFrames = Math.round((duration * sample.sampleRate) / 1_000_000);\n\t\t\t\tframes = Math.min(frames, terminalFrames);\n\t\t\t}\n\t\t}\n\n\t\treturn { timestamp, frameOffset, frames };\n\t}\n\n\t#resetEpoch(): void {\n\t\tthis.#epoch = undefined;\n\t\tthis.#preSkipRemaining = undefined;\n\t}\n}\n","/** Tracks decoder callbacks discarded during initial playback setup. */\nexport class Warmup {\n\t#remaining: number;\n\n\t/** Create a warm-up window containing `callbacks` decoder callbacks. */\n\tconstructor(callbacks: number) {\n\t\tthis.#remaining = callbacks;\n\t}\n\n\t/** Consume one callback and report whether it belongs to the warm-up window. */\n\tdrop(): boolean {\n\t\tif (this.#remaining === 0) return false;\n\t\tthis.#remaining--;\n\t\treturn true;\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 {\n\ttype Computed,\n\tEffect,\n\ttype Getter,\n\tgetter,\n\ttype Inputs,\n\ttype Readonlys,\n\treadonlys,\n\tSignal,\n} from \"@moq/signals\";\nimport { base64ToBytes } from \"../base64\";\nimport { nextMedia, subscribeMedia } from \"../media\";\n\nimport type { Sync } from \"../sync\";\nimport { type AudioBuffer, createAudioBuffer } from \"./buffer\";\nimport { type DecoderConfig, decoderConfig, type PlaybackIdentity, playbackIdentity } from \"./config\";\nimport { Handover } from \"./handover\";\nimport { ringSamples } from \"./latency\";\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 { type DecodedSpan, Terminal } from \"./terminal\";\nimport { Warmup } from \"./warmup\";\n\nconst LEGACY_WARMUP_CALLBACKS = 3;\n\nexport type DecoderInput = {\n\t// Whether to download the audio track. Defaults to true.\n\tenabled: Getter<boolean>;\n};\n\n/** Constructor properties for {@link Decoder}. */\nexport type DecoderProps = Inputs<DecoderInput> & {\n\t/** Rendition selector supplying encoded audio. */\n\tsource: Source;\n\t/** Shared playback clock. */\n\tsync: Sync;\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// Ordered discontinuity and endpoint state from the container consumer.\n\t#terminal = new Terminal();\n\n\t// Which subscription the ring's buffered samples came from. See #runDecoder.\n\t#handover = new Handover();\n\n\t#signals = new Effect();\n\n\t// The catalog fields that require a replacement subscription or decoder.\n\treadonly #identity: Computed<PlaybackIdentity | undefined>;\n\n\t// The decoder fields that require a new audio graph. Routing and metadata changes leave the\n\t// context, worklet, and ring alone.\n\treadonly #config: Computed<DecoderConfig | undefined>;\n\n\tconstructor(props: DecoderProps) {\n\t\tthis.in = {\n\t\t\tenabled: getter(props?.enabled ?? true),\n\t\t};\n\n\t\tthis.source = props.source;\n\t\tthis.sync = props.sync;\n\t\tthis.#signals.cleanup(this.sync.register(this.source.out.jitter));\n\t\tthis.#identity = this.#signals.computed((effect) => {\n\t\t\tconst config = effect.get(this.source.out.config);\n\t\t\treturn config ? playbackIdentity(config) : undefined;\n\t\t});\n\t\tthis.#config = this.#signals.computed((effect) => {\n\t\t\tconst config = effect.get(this.source.out.config);\n\t\t\treturn config ? decoderConfig(config) : undefined;\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.#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 effect.race(context.audioWorklet.addModule(RenderWorklet).then(() => true));\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 ring depth in samples.\n\t\t\tconst delay = this.sync.out.delay.peek();\n\t\t\tconst latencySamples = ringSamples(sampleRate, delay);\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\t\tif (effect.get(this.sync.in.delay) === \"instant\") {\n\t\t\tthis.reset();\n\t\t\treturn;\n\t\t}\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.\n\t\tUtil.Gesture.unlock(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\t// A rise parks playback until the ring refills to the new floor, which is what keeps audio in\n\t\t// step with video. A catalog's jitter estimate can rise a millisecond at a time.\n\t\tconst delay = effect.get(this.sync.out.delay);\n\t\tring.setLatency(ringSamples(ring.rate, delay));\n\t}\n\n\t#runDecoder(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\t\tif (effect.get(this.sync.in.delay) === \"instant\") 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 identity = effect.get(this.#identity);\n\t\tif (!identity) return;\n\n\t\tconst config = identity.decoder;\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, identity.broadcast);\n\t\tif (!active) return;\n\n\t\t// The ring outlives this effect (it's keyed on the sample rate and channel count), so a\n\t\t// replacement subscription (a rendition swap, a republished broadcast, a reconnect) inherits\n\t\t// whatever its predecessor decoded. Samples are timestamp indexed, so the replacement\n\t\t// overwrites the slots it lands on, but a publisher writing ahead of real-time leaves seconds\n\t\t// of tail beyond them. Drop that once the replacement's first frame says where it starts.\n\t\tthis.#handover.opened();\n\n\t\tconst sub = subscribeMedia(effect, {\n\t\t\tbroadcast: active,\n\t\t\ttrack,\n\t\t\tpriority: Catalog.PRIORITY.audio,\n\t\t\tmaxAge: this.sync.out.maxAge,\n\t\t});\n\t\tif (!sub) return;\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: DecoderConfig): void {\n\t\tconst preSkip =\n\t\t\tconfig.codec === \"opus\" && config.description ? Util.Opus.preSkip(Util.Hex.toBytes(config.description)) : 0;\n\t\tthis.#terminal.clear(preSkip);\n\t\tconst format =\n\t\t\tconfig.container.kind === \"loc\" ? new Container.Loc.Format(\"audio\") : new Container.Legacy.Format(config);\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\tmaxAge: this.sync.out.maxAge,\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 warmup = new Warmup(LEGACY_WARMUP_CALLBACKS);\n\n\t\t\tconst decoder = new AudioDecoder({\n\t\t\t\toutput: (data) => {\n\t\t\t\t\tconst decoded = this.#terminal.span(data);\n\t\t\t\t\tif (warmup.drop()) {\n\t\t\t\t\t\t// Drop initial callbacks 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, decoded);\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\tconst decoderConfig: AudioDecoderConfig = {\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\t\t\tdecoder.configure(decoderConfig);\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await nextMedia(consumer);\n\t\t\t\tif (!next) break;\n\t\t\t\tif (this.#onNext(next)) {\n\t\t\t\t\tdecoder.reset();\n\t\t\t\t\tdecoder.configure(decoderConfig);\n\t\t\t\t}\n\t\t\t\tif (next.end !== undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\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: DecoderConfig): 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\tconst opusDescription = config.description ? Util.Hex.toBytes(config.description) : init.description;\n\t\tconst preSkip = config.codec === \"opus\" && opusDescription ? Util.Opus.preSkip(opusDescription) : 0;\n\t\tthis.#terminal.clear(preSkip);\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\tmaxAge: this.sync.out.maxAge,\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\tconst decoderConfig: AudioDecoderConfig = {\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\t\t\tdecoder.configure(decoderConfig);\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await nextMedia(consumer);\n\t\t\t\tif (!next) break;\n\n\t\t\t\t// Reset and re-anchor before decoding the first frame of a new codec epoch.\n\t\t\t\tif (this.#onNext(next)) {\n\t\t\t\t\tdecoder.reset();\n\t\t\t\t\tdecoder.configure(decoderConfig);\n\t\t\t\t}\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, decoded: DecodedSpan = this.#terminal.span(sample)) {\n\t\tconst { timestamp, frameOffset, frames } = decoded;\n\t\tconst timestampMilli = Time.Milli.fromMicro(timestamp);\n\t\tif (frames === 0) {\n\t\t\tsample.close();\n\t\t\treturn;\n\t\t}\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 = ((frames / 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// A new subscription has taken over the timeline: drop the previous one's write-ahead tail\n\t\t// rather than letting it play out after this frame. See #runDecoder.\n\t\tif (this.#handover.takeover()) {\n\t\t\tring.truncate(timestamp);\n\t\t\tthis.#truncateDecodeBuffered(timestampMilli);\n\t\t}\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(frames);\n\t\t\tsample.copyTo(data, { format: \"f32-planar\", planeIndex: channel, frameOffset, frameCount: frames });\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// Drop reported decode ranges at or after `timestamp`, mirroring a ring truncation.\n\t#truncateDecodeBuffered(timestamp: Time.Milli): void {\n\t\tthis.#decodeBuffered.mutate((current) => {\n\t\t\twhile (current.length > 0 && current[current.length - 1].start >= timestamp) current.pop();\n\t\t\tconst last = current[current.length - 1];\n\t\t\tif (last && last.end > timestamp) last.end = timestamp;\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// Apply ordered container metadata before handling the result. An endpoint that also\n\t// starts a new epoch must survive the reset so its following drain is trimmed.\n\t#onNext(next: {\n\t\tdiscontinuity: number;\n\t\tgroup: number;\n\t\tend?: Time.Micro;\n\t\tframe?: { timestamp: Time.Micro };\n\t}): boolean {\n\t\tif (!this.#terminal.update(next)) return false;\n\t\tthis.#ring?.reset();\n\t\tthis.sync.reset();\n\t\treturn true;\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\n/** Constructor properties for {@link Emitter}. */\nexport type EmitterProps = Inputs<EmitterInput> & {\n\t/** Decoder supplying PCM. */\n\tsource: Decoder;\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(props: EmitterProps) {\n\t\tthis.source = props.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 { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\nimport { playbackJitter } from \"./config\";\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\t// `supported` comes from the consumer, so we cannot assume it ever settles. A rerun\n\t\t\t\t// waits for the tasks it spawned, so an unraced probe would hold the next run shut.\n\t\t\t\tconst isSupported = await effect.race(supported(config));\n\n\t\t\t\t// Torn down: stop probing and publish nothing, since the rerun redoes this.\n\t\t\t\tif (effect.abort.aborted) return;\n\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\teffect.set(this.#out.jitter, playbackJitter(selected.config));\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","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\n// `packaging` declares how the bytes are framed, not an optional capability, so every value maps to\n// its own container and an unrecognized one drops the rendition. Reading LOC's property block as\n// legacy's VarInt prefix is exactly what a permissive fallback caused.\nfunction toContainer(track: Msf.Track): ContainerInfo | undefined {\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\tconst description = initBytes ? bytesToHex(initBytes) : undefined;\n\n\tswitch (track.packaging) {\n\t\tcase \"cmaf\":\n\t\t\t// Unusable without its init segment, so a missing or malformed one drops the rendition rather\n\t\t\t// than falling back to a different framing. hang's CMAF path reads the codec metadata from\n\t\t\t// that segment, so there is no separate description to surface.\n\t\t\tif (!track.initData || !initBytes) return undefined;\n\t\t\treturn { container: { kind: \"cmaf\", init: track.initData }, description: undefined };\n\t\tcase \"loc\":\n\t\t\treturn { container: { kind: \"loc\" }, description };\n\t\tcase \"legacy\":\n\t\t\treturn { container: { kind: \"legacy\" }, description };\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n// Hang stores delay as whole milliseconds rounded up, and zero means the rendition is not behind.\n// MSF writes a fractional millisecond, which u53 rejects.\nfunction delayMillis(value: number): ReturnType<typeof u53> | undefined {\n\tif (value <= 0) return undefined;\n\treturn u53(Math.ceil(value));\n}\n\nfunction toVideoConfig(track: Msf.Track): Catalog.VideoConfig | undefined {\n\tif (!track.codec) return undefined;\n\n\tconst info = toContainer(track);\n\tif (!info) return undefined;\n\tconst { container, description } = info;\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\tstalled: track.stalled,\n\t\tjitter: track.jitter != null ? u53(track.jitter) : undefined,\n\t\tdelay: track.delay != null ? delayMillis(track.delay) : 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 info = toContainer(track);\n\tif (!info) return undefined;\n\tconst { container, description } = info;\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\tdelay: track.delay != null ? delayMillis(track.delay) : undefined,\n\t\tlanguage: track.lang,\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 { Announce, Error as NetError, Path } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\n\nimport { toHang } from \"./msf\";\n\ntype ReferencedRendition = {\n\tbroadcast?: Path.Relative;\n};\n\n// Either the catalog's own broadcast, or a sibling to consume by path.\ntype RelativeTarget = { local: true } | { local: false; path: Moq.Path.Valid };\n\nfunction filterRenditions<T extends ReferencedRendition>(\n\trenditions: Record<string, T>,\n\tusable: (rel: Path.Relative | undefined) => boolean,\n): Record<string, T> {\n\treturn Object.fromEntries(Object.entries(renditions).filter(([, config]) => usable(config.broadcast)));\n}\n\n// Every section carrying a `broadcast` reference must be listed here, same as `Catalog.checkResolvable`;\n// one left out silently exempts its tracks from the reachability filter.\nfunction filterCatalog(catalog: Catalog.Root, usable: (rel: Path.Relative | undefined) => boolean): Catalog.Root {\n\treturn {\n\t\t...catalog,\n\t\tvideo: catalog.video\n\t\t\t? { ...catalog.video, renditions: filterRenditions(catalog.video.renditions, usable) }\n\t\t\t: undefined,\n\t\taudio: catalog.audio\n\t\t\t? { ...catalog.audio, renditions: filterRenditions(catalog.audio.renditions, usable) }\n\t\t\t: undefined,\n\t\ttext: catalog.text\n\t\t\t? { ...catalog.text, renditions: filterRenditions(catalog.text.renditions, usable) }\n\t\t\t: undefined,\n\t\tjson: catalog.json ? { ...catalog.json, tracks: filterRenditions(catalog.json.tracks, usable) } : undefined,\n\t\tbinary: catalog.binary\n\t\t\t? { ...catalog.binary, tracks: filterRenditions(catalog.binary.tracks, usable) }\n\t\t\t: undefined,\n\t};\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\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\t// The origin to consume from. Independent of any connection: whichever sessions feed\n\t// the origin resolve the broadcast, and the handle spans their reconnects.\n\torigin: Getter<Moq.Origin.Table | undefined>;\n\n\t// Whether to start downloading the broadcast. Defaults to true.\n\tenabled: Getter<boolean>;\n\n\t// The broadcast name.\n\tname: Getter<Moq.Path.Valid>;\n\n\t// Whether to wait for the broadcast to be announced before subscribing.\n\t// Defaults to true; pass false to subscribe immediately without waiting for an announcement.\n\tannounced: 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, minus\n\t// any rendition this consumer can't use (see `#runFiltered`). A rendition referencing another\n\t// broadcast appears once that broadcast is announced, so this can change without a new catalog.\n\tcatalog: Signal<Catalog.Root | undefined>;\n};\n\n// A catalog source that can wait for announcement before subscribing.\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// The catalog as published, before renditions this consumer cannot use are dropped. Kept\n\t// separate so the effective catalog can be re-derived when a reference becomes reachable.\n\t//\n\t// Writes notify unconditionally. A manual catalog can be mutated in place, and the rerun that\n\t// delivers it lands inside the same flush, where a value-compare coalesces the write away and\n\t// strands the filtered copy on the previous contents.\n\treadonly #raw = new Signal<Catalog.Root | undefined>(undefined);\n\n\t#signals: Effect;\n\n\tconstructor(props?: Inputs<BroadcastInput>) {\n\t\tif (props && \"reload\" in props) {\n\t\t\tthrow new Error(\"Watch.Broadcast: `reload` was renamed to `announced`\");\n\t\t}\n\t\tthis.#signals = new Effect();\n\t\tthis.in = {\n\t\t\torigin: getter(props?.origin),\n\t\t\tname: getter(props?.name ?? Path.empty()),\n\t\t\tenabled: getter(props?.enabled ?? true),\n\t\t\tannounced: getter(props?.announced ?? 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\tthis.#signals.run(this.#runFiltered.bind(this));\n\t}\n\n\t// Maintain the set of announced paths used by `relativeBroadcast`, by draining an origin-scoped\n\t// announcement stream. Only opened once a relative reference asks for it (see `#wantAnnounced`).\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.announced)) return;\n\n\t\tconst origin = effect.get(this.in.origin);\n\t\tif (!origin) return;\n\n\t\tconst announced = origin.announced();\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 effect.race(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 (Announce.isActive(entry.kind)) active.add(entry.prefix);\n\t\t\t\t\telse active.delete(entry.prefix);\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\t// Publish the catalog minus every rendition this consumer cannot use: one naming a\n\t// broadcast that isn't announced to us. Selecting one of those would render nothing,\n\t// since `relativeBroadcast` resolves it to no broadcast at all. Reruns as announcements\n\t// arrive, so a rendition appears once its broadcast does.\n\t#runFiltered(effect: Effect): void {\n\t\tconst raw = effect.get(this.#raw);\n\t\tconst usable = (rel: Path.Relative | undefined) => this.#relativeTarget(effect, rel) !== undefined;\n\t\teffect.set(this.#out.catalog, raw ? filterCatalog(raw, usable) : undefined);\n\t}\n\n\t// Whether `path` is covered by an announced route, for `relativeBroadcast`'s\n\t// cross-broadcast refs. Announcements are prefix routes, so a route at \"room/\" covers\n\t// \"room/alice/cam.hang\" without naming it. Opens the announcement stream on first use.\n\t// The blind cases (announcement gate off, no discovery) never reach here; see `#relativeTarget`.\n\t#isPathAnnounced(effect: Effect, path: Moq.Path.Valid): boolean {\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\tif (active.has(path)) return true;\n\t\tfor (const prefix of active) {\n\t\t\tif (Path.hasPrefix(prefix, path)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t// Resolve `path` without waiting for an announcement. The request is table-first, so a\n\t// routed broadcast (a local publish, or anything announced) resolves synchronously and\n\t// a blind session answer covers the rest, arriving on a later run.\n\t#requestBroadcast(\n\t\teffect: Effect,\n\t\torigin: Moq.Origin.Table,\n\t\tpath: Moq.Path.Valid,\n\t): Moq.Broadcast.Consumer | undefined {\n\t\tconst request = origin.request(path);\n\t\teffect.cleanup(() => request.close());\n\t\treturn effect.get(request.active);\n\t}\n\n\t// Subscribe to the broadcast, waiting for its announcement so we never race a publisher that\n\t// comes online after us. @moq/net drives the re-consume on a same-name republish and the blind\n\t// fallback on a relay without discovery; mirror its handle into `active`.\n\t#runBroadcast(effect: Effect): void {\n\t\tconst enabled = effect.get(this.in.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst origin = effect.get(this.in.origin);\n\t\tif (!origin) return;\n\n\t\tconst name = effect.get(this.in.name);\n\n\t\t// No announcement gate: subscribe immediately.\n\t\tif (!effect.get(this.in.announced)) {\n\t\t\teffect.set(this.#out.active, this.#requestBroadcast(effect, origin, name), undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tconst announced = origin.request(name, { announced: true });\n\t\teffect.cleanup(() => announced.close());\n\n\t\teffect.run((nested) => {\n\t\t\tnested.set(this.#out.active, nested.get(announced.active), undefined);\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. A caller-supplied\n\t\t\t// catalog is rejected the same way a fetched one is, minus the throw: this runs in\n\t\t\t// the effect body, where an exception would surface as an unhandled error.\n\t\t\tconst catalog = effect.get(this.in.catalog);\n\t\t\tlet accepted: Catalog.Root | undefined;\n\t\t\ttry {\n\t\t\t\taccepted = catalog && Catalog.checkResolvable(Catalog.checkRenditions(catalog), name);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"rejecting catalog\", name, err);\n\t\t\t}\n\t\t\tthis.#raw.set(accepted, true);\n\t\t\teffect.cleanup(() => this.#raw.set(undefined, true));\n\t\t\tthis.#out.status.set(accepted ? \"live\" : \"loading\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst broadcast = effect.get(this.out.active);\n\t\t// A withdrawn handle can close before its removal propagates through the active signal.\n\t\tif (!broadcast || effect.get(broadcast.closed) !== undefined) 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>({\n\t\t\t\ttrack,\n\t\t\t\tschema: Catalog.RootSchema,\n\t\t\t\tcompression: format === \"hangz\" ? \"deflate\" : \"none\",\n\t\t\t});\n\t\t\tfetchNext = () => consumer.next();\n\t\t} else {\n\t\t\tconst ordered = track.ordered();\n\t\t\tfetchNext = async () => {\n\t\t\t\tconst update = await Msf.fetch(ordered);\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 effect.race(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.#raw.set(Catalog.checkResolvable(Catalog.checkRenditions(update), name), true);\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\tif (err instanceof NetError.Stream)\n\t\t\t\t\tconsole.debug(\"catalog subscription ended\", this.in.name.peek(), err);\n\t\t\t\telse console.error(\"error fetching catalog\", this.in.name.peek(), err);\n\t\t\t} finally {\n\t\t\t\tthis.#raw.set(undefined);\n\t\t\t\tthis.#out.status.set(\"offline\");\n\t\t\t}\n\t\t});\n\t}\n\n\t// Where a rendition's `broadcast` reference points once resolved and gated on the announcement\n\t// stream, or `undefined` when it names nothing consumable right now. Playback and rendition\n\t// selection both go through this so they cannot disagree about what is reachable.\n\t#relativeTarget(effect: Effect, rel: Path.Relative | undefined): RelativeTarget | undefined {\n\t\tif (!rel) return { local: true };\n\n\t\tconst base = effect.get(this.in.name);\n\t\tconst resolved = Path.tryResolve(base, rel);\n\t\tif (resolved === undefined) {\n\t\t\tconsole.warn(\"ignoring rendition: broadcast reference escapes the root\", base, rel);\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// A reference that walks back to the catalog's own broadcast is served by the\n\t\t// catalog broadcast itself, avoiding a duplicate subscription on the same path.\n\t\tif (resolved === base) return { local: true };\n\n\t\tconst origin = effect.get(this.in.origin);\n\t\t// Nothing to ask yet. `relativeBroadcast` still bails on the missing origin, and this\n\t\t// keeps a reconnect from briefly hiding every cross-broadcast rendition from selection.\n\t\tif (!origin) return { local: false, path: resolved };\n\n\t\t// Without an announcement gate (disabled, or no session supports discovery),\n\t\t// resolve blind rather than waiting for an announcement that never comes. With the\n\t\t// gate, only report the path usable once it is announced: the request then resolves\n\t\t// from the table, never blind.\n\t\tif (effect.get(this.in.announced) && effect.get(origin.discovery) !== false) {\n\t\t\tif (!this.#isPathAnnounced(effect, resolved)) return undefined;\n\t\t}\n\n\t\treturn { local: false, path: resolved };\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 * Returns `undefined` for a reference that walks above the root: hang requires such a\n\t * rendition to be ignored, since clamping at the root would point it at an unrelated\n\t * 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: Path.Relative | undefined): Moq.Broadcast.Consumer | undefined {\n\t\tconst target = this.#relativeTarget(effect, rel);\n\t\tif (!target) return undefined;\n\t\tif (target.local) return effect.get(this.out.active);\n\n\t\tif (!effect.get(this.in.enabled)) return undefined;\n\n\t\tconst origin = effect.get(this.in.origin);\n\t\tif (!origin) return undefined;\n\n\t\treturn this.#requestBroadcast(effect, origin, target.path);\n\t}\n\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport {\n\ttype Dispose,\n\tEffect,\n\ttype Getter,\n\tgetter,\n\ttype Inputs,\n\ttype Readonlys,\n\treadonlys,\n\tSignal,\n} from \"@moq/signals\";\n\n/**\n * How far playback trails the live edge.\n *\n * `\"auto\"` (the default) sizes the jitter buffer from the connection RTT; a `Time.Milli` fixes it.\n *\n * `\"instant\"` drops the buffer and the pacing together: nothing is held, and {@link Sync.wait}\n * returns without sleeping, so a frame presents as soon as it exists. It also overrides\n * {@link SyncInput.buffer}, since holding a lookahead would contradict holding nothing. Audio is\n * the owner's business: a ring with no depth underruns, so whoever wants this mode is expected to\n * turn audio off.\n */\nexport type Delay = \"instant\" | \"auto\" | Time.Milli;\n\nconst MIN_JITTER = Time.Milli(20);\nconst FALLBACK_JITTER = Time.Milli(100);\n\nexport type SyncInput = {\n\t/** How far playback trails the live edge. See {@link Delay}. */\n\tdelay: Getter<Delay>;\n\n\t/**\n\t * Future-dated media held beyond the live edge before playback skips ahead (default: zero).\n\t *\n\t * Zero minimizes latency, the live default: playback re-anchors as soon as anything arrives\n\t * early. A larger value lets faster-than-real-time frames (a TTS response with future\n\t * timestamps) build up instead of being skipped. Always finite, so worst case the audio ring\n\t * drops its oldest samples rather than exhausting memory.\n\t */\n\tbuffer: Getter<Time.Milli>;\n\n\t/**\n\t * The connection's PROBE estimates, whose RTT drives \"auto\" jitter. Usually wired\n\t * from a `Connection`'s `probe`.\n\t */\n\tprobe: Getter<Moq.Connection.Probe | undefined>;\n};\n\ntype SyncOutput = {\n\t// The earliest time we've received a frame, relative to its timestamp.\n\t// This will keep being updated as we catch up to the live playhead then will be relatively static.\n\treference: Signal<Time.Milli | undefined>;\n\n\t// The resolved delay from the live edge to the playhead: jitter + max(audio, video).\n\tdelay: Signal<Time.Milli>;\n\n\t// The jitter component of `delay` (always numeric).\n\t// In \"auto\" mode this is updated automatically from RTT.\n\t// When the delay is a number, jitter equals that number.\n\tjitter: Signal<Time.Milli>;\n\n\t// The media timestamp of the most recently received frame.\n\ttimestamp: Signal<Time.Milli | undefined>;\n\n\t// Derived: true when a lookahead is configured. Buffered playback lets the reference stay\n\t// anchored so future-dated frames build up, re-anchoring (skipping ahead) only once they\n\t// would sit further than `maxAge` ahead of the playhead. See `reset()`.\n\tbuffered: Signal<boolean>;\n\n\t// Derived: `delay + buffer`, the furthest a held frame may sit ahead of the playhead. Feeds the\n\t// transport subscription and the container consumer, which bound the same span. Always finite.\n\tmaxAge: Signal<Time.Milli>;\n};\n\nexport class Sync {\n\treadonly in: Readonlys<SyncInput>;\n\n\treadonly #out: SyncOutput = {\n\t\treference: new Signal<Time.Milli | undefined>(undefined),\n\t\tdelay: new Signal<Time.Milli>(Time.Milli.zero),\n\t\tjitter: new Signal<Time.Milli>(FALLBACK_JITTER),\n\t\ttimestamp: new Signal<Time.Milli | undefined>(undefined),\n\t\tbuffered: new Signal<boolean>(false),\n\t\tmaxAge: new Signal<Time.Milli>(Time.Milli.zero),\n\t};\n\treadonly out = readonlys(this.#out);\n\n\t// Per-label late-frame tracking: accumulate count and max lateness, flush on recovery.\n\t#late = new Map<string, { count: number; maxMs: number }>();\n\n\t// Minimum RTT seen, used as the baseline for jitter calculation.\n\t// Avoids inflating jitter due to bufferbloat.\n\t#minRtt: number | undefined;\n\t#media = new Signal<{ jitter: Getter<Time.Milli | undefined> }[]>([]);\n\n\t#signals = new Effect();\n\n\tconstructor(props?: Inputs<SyncInput>) {\n\t\tthis.in = {\n\t\t\tdelay: getter(props?.delay ?? (\"auto\" as Delay)),\n\t\t\tbuffer: getter(props?.buffer ?? Time.Milli.zero),\n\t\t\tprobe: getter(props?.probe),\n\t\t};\n\n\t\tthis.#signals.run(this.#runJitter.bind(this));\n\t\tthis.#signals.run(this.#runDelay.bind(this));\n\t\tthis.#signals.run(this.#runMaxAge.bind(this));\n\t}\n\n\t/** Include a decoder's rendition delay in the shared playback clock until disposed. */\n\tregister(jitter: Getter<Time.Milli | undefined>): Dispose {\n\t\tconst registered = { jitter };\n\t\tthis.#media.update((media) => [...media, registered]);\n\t\treturn () => this.#media.update((media) => media.filter((candidate) => candidate !== registered));\n\t}\n\n\t// Derive `buffered` / `maxAge` from the resolved delay and the configured lookahead.\n\t#runMaxAge(effect: Effect): void {\n\t\tconst delay = effect.get(this.#out.delay);\n\t\t// \"instant\" holds nothing, so a configured lookahead doesn't apply.\n\t\tconst buffer = effect.get(this.in.delay) === \"instant\" ? Time.Milli.zero : effect.get(this.in.buffer);\n\n\t\tthis.#out.buffered.set(buffer > 0);\n\t\tthis.#out.maxAge.set(Time.Milli.add(delay, buffer));\n\t}\n\n\t#runJitter(effect: Effect): void {\n\t\tconst delay = effect.get(this.in.delay);\n\n\t\tif (delay === \"instant\") {\n\t\t\t// Holds nothing at all.\n\t\t\tthis.#minRtt = undefined;\n\t\t\tthis.#out.jitter.set(Time.Milli.zero);\n\t\t\treturn;\n\t\t}\n\n\t\tif (typeof delay === \"number\") {\n\t\t\t// Fixed mode: the configured delay is the jitter.\n\t\t\tthis.#minRtt = undefined;\n\t\t\tthis.#out.jitter.set(delay);\n\t\t\treturn;\n\t\t}\n\n\t\t// \"auto\" mode: compute jitter from the connection's RTT estimate.\n\t\tconst rtt = effect.get(this.in.probe)?.rtt;\n\t\tif (rtt !== undefined) {\n\t\t\t// Track minimum RTT as baseline, ignoring bufferbloat.\n\t\t\tthis.#minRtt = this.#minRtt !== undefined ? Math.min(this.#minRtt, rtt) : rtt;\n\n\t\t\t// Buffer enough for a retransmit (1 RTT for ACK + retransmit).\n\t\t\tconst jitter = Time.Milli(Math.max(MIN_JITTER, this.#minRtt * 1.25));\n\t\t\tthis.#out.jitter.set(jitter);\n\t\t\treturn;\n\t\t}\n\n\t\t// No RTT available: fall back to static default.\n\t\tthis.#minRtt = undefined;\n\t\tthis.#out.jitter.set(FALLBACK_JITTER);\n\t}\n\n\t#runDelay(effect: Effect): void {\n\t\tconst jitter = effect.get(this.#out.jitter);\n\t\tlet media = Time.Milli.zero;\n\t\tfor (const registered of effect.get(this.#media)) {\n\t\t\tmedia = Time.Milli.max(media, effect.get(registered.jitter) ?? Time.Milli.zero);\n\t\t}\n\n\t\t// A zero delay still holds the rendition's own delay, which is a frame interval at 60fps.\n\t\t// \"instant\" holds nothing at all.\n\t\tconst instant = effect.get(this.in.delay) === \"instant\";\n\t\tconst delay = instant ? Time.Milli.zero : Time.Milli.add(media, jitter);\n\t\tthis.#out.delay.set(delay);\n\t}\n\n\t// Fold a newly received frame into the reference. The reference anchors playback to the\n\t// wall clock; we lower it (skip ahead) only when keeping it would push the lookahead past `maxAge`.\n\treceived(timestamp: Time.Milli, label = \"\"): void {\n\t\tthis.#out.timestamp.update((current) => (current === undefined || timestamp > current ? timestamp : current));\n\t\tconst now = Time.Milli.now();\n\t\tconst ref = Time.Milli.sub(now, timestamp);\n\t\tconst currentRef = this.#out.reference.peek();\n\n\t\t// First frame anchors the reference.\n\t\tif (currentRef === undefined) {\n\t\t\tthis.#out.reference.set(ref);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if `wait()` would not sleep at all.\n\t\t// NOTE: We check here instead of in `wait()` so we can identify when frames are received late.\n\t\t// Otherwise, chained `wait()` calls would cause a false-positive during CPU starvation.\n\t\tconst delay = this.#out.delay.peek();\n\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), delay);\n\t\tif (sleep < 0) {\n\t\t\tconst entry = this.#late.get(label);\n\t\t\tif (entry) {\n\t\t\t\tentry.count++;\n\t\t\t\tentry.maxMs = Math.max(entry.maxMs, -sleep);\n\t\t\t} else {\n\t\t\t\tthis.#late.set(label, { count: 1, maxMs: -sleep });\n\t\t\t}\n\t\t} else {\n\t\t\tconst entry = this.#late.get(label);\n\t\t\tif (entry) {\n\t\t\t\tconst prefix = label ? `sync[${label}]` : \"sync\";\n\t\t\t\tconst behind = Sync.#formatDuration(entry.maxMs);\n\t\t\t\tconsole.debug(`${prefix}: ${entry.count} late frame(s), max ${behind} behind`);\n\t\t\t\tthis.#late.delete(label);\n\t\t\t}\n\t\t}\n\n\t\t// Frame isn't earlier than the anchor: it can't add lookahead, so keep the reference.\n\t\tif (ref >= currentRef) return;\n\n\t\t// Frame is earlier (more lookahead). `sleep` is how far ahead of the playhead keeping the\n\t\t// anchor would put it.\n\t\tconst cap = this.#out.maxAge.peek();\n\t\tif (sleep <= cap) return; // within budget: let the buffer grow instead of skipping ahead\n\n\t\t// Over the cap: re-anchor down so the resulting lookahead is exactly the cap.\n\t\tthis.#out.reference.set(Time.Milli.add(ref, Time.Milli.sub(cap, delay)));\n\t}\n\n\t// Re-anchor playback to the next frame received. Call this at an utterance boundary\n\t// in buffered mode (typically alongside flushing the audio buffer) so the new content\n\t// plays from its own first frame instead of inheriting the previous reference.\n\treset(): void {\n\t\tthis.#out.reference.set(undefined);\n\t\tthis.#late.clear();\n\t}\n\n\t// The PTS that should be rendering right now, derived from the reference + buffer.\n\t// Returns undefined if no frames have been received yet.\n\tnow(): Time.Milli | undefined {\n\t\tconst reference = this.#out.reference.peek();\n\t\tif (reference === undefined) return undefined;\n\t\treturn Time.Milli.sub(Time.Milli.sub(Time.Milli.now(), reference), this.#out.delay.peek());\n\t}\n\n\t// Sleep until it's time to render this frame.\n\tasync wait(timestamp: Time.Milli): Promise<void> {\n\t\t// A zero delay still sleeps: the sleep comes from the reference, which holds an early frame\n\t\t// until its timestamp comes up. \"instant\" is the only thing that skips the wait itself.\n\t\tif (this.in.delay.peek() === \"instant\") return;\n\n\t\tconst reference = this.#out.reference.peek();\n\t\tif (reference === undefined) {\n\t\t\tthrow new Error(\"reference not set; call received() first\");\n\t\t}\n\n\t\tfor (;;) {\n\t\t\t// Switching to \"instant\" wakes the sleep below, so frames parked here leave.\n\t\t\tif (this.in.delay.peek() === \"instant\") return;\n\n\t\t\t// Sleep until it's time to decode the next frame.\n\t\t\t// NOTE: This function runs in parallel for each frame.\n\t\t\tconst now = Time.Milli.now();\n\t\t\tconst ref = Time.Milli.sub(now, timestamp);\n\n\t\t\tconst currentRef = this.#out.reference.peek();\n\t\t\tif (currentRef === undefined) return;\n\n\t\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), this.#out.delay.peek());\n\t\t\tif (sleep <= 0) return;\n\n\t\t\t// Skip setTimeout for small sleeps; the timer resolution (~4ms) would overshoot.\n\t\t\tif (sleep < 5) return;\n\n\t\t\tif (await this.#sleep(sleep)) return;\n\t\t}\n\t}\n\n\t// Sleeps for `ms`, or returns false early once anything the sleep was computed from changes.\n\t// Releases every listener either way: frames sleep once each, so a listener left on a signal\n\t// that never changes would pile up for the life of the player.\n\t#sleep(ms: number): Promise<boolean> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst wake = (ok: boolean) => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tfor (const dispose of disposes) dispose();\n\t\t\t\tresolve(ok);\n\t\t\t};\n\t\t\tconst timer = setTimeout(() => wake(true), ms);\n\t\t\tconst disposes = [\n\t\t\t\tthis.in.delay.changed(() => wake(false)),\n\t\t\t\tthis.#out.delay.changed(() => wake(false)),\n\t\t\t\tthis.#out.reference.changed(() => wake(false)),\n\t\t\t];\n\t\t});\n\t}\n\n\tstatic #formatDuration(ms: number): string {\n\t\tms = Math.round(ms);\n\t\tif (ms < 1000) return `${ms}ms`;\n\t\tconst s = ms / 1000;\n\t\tif (s < 60) return `${Math.round(s * 10) / 10}s`;\n\t\tconst m = s / 60;\n\t\treturn `${Math.round(m * 10) / 10}m`;\n\t}\n\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n}\n",":where([part='captions']) {\n --overlay-padding: 1%;\n --cue-color: white;\n --cue-bg-color: rgba(0, 0, 0, 0.8);\n --cue-font-size: calc(var(--overlay-height) / 100 * 5);\n --cue-line-height: calc(var(--cue-font-size) * 1.2);\n --cue-padding-x: calc(var(--cue-font-size) * 0.6);\n --cue-padding-y: calc(var(--cue-font-size) * 0.4);\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 1;\n contain: content;\n margin: var(--overlay-padding);\n font-size: var(--cue-font-size);\n font-family: sans-serif;\n box-sizing: border-box;\n pointer-events: none;\n user-select: none;\n word-spacing: normal;\n word-break: break-word;\n}\n\n:where([part='captions'] > [part='cue-display']) {\n position: absolute;\n direction: ltr;\n overflow: visible;\n contain: content;\n top: var(--cue-top);\n left: var(--cue-left);\n right: var(--cue-right);\n bottom: var(--cue-bottom);\n width: var(--cue-width, auto);\n height: var(--cue-height, auto);\n box-sizing: border-box;\n transform: var(--cue-transform);\n text-align: var(--cue-text-align);\n writing-mode: var(--cue-writing-mode, unset);\n white-space: pre-line;\n unicode-bidi: plaintext;\n min-width: min-content;\n min-height: min-content;\n}\n\n:where([data-dir='rtl'] [part='cue-display']) {\n direction: rtl;\n}\n\n:where([part='captions'] [part='cue']) {\n display: inline-block;\n padding: var(--cue-padding-y) var(--cue-padding-x);\n line-height: var(--cue-line-height);\n background-color: var(--cue-bg-color);\n box-sizing: border-box;\n color: var(--cue-color);\n box-shadow: var(--cue-box-shadow);\n white-space: var(--cue-white-space, pre-wrap);\n outline: var(--cue-outline);\n text-shadow: var(--cue-text-shadow);\n}\n\n:where([part='captions'] [part='cue-display'][data-vertical] [part='cue']) {\n padding: var(--cue-padding-x) var(--cue-padding-y);\n}\n",":where([part='captions'] [part='region']) {\n position: absolute;\n display: inline-flex;\n flex-flow: column;\n justify-content: flex-start;\n width: var(--region-width);\n height: var(--region-height);\n min-height: 0px;\n max-height: var(--region-height);\n writing-mode: horizontal-tb;\n top: calc(\n var(\n --region-top,\n var(--overlay-height) * var(--region-viewport-anchor-y) / 100 - var(--region-height) *\n var(--region-anchor-y) / 100\n )\n );\n left: var(\n --region-left,\n calc(\n calc(var(--region-viewport-anchor-x) * 1%) -\n calc(var(--region-width) * var(--region-anchor-x) / 100)\n )\n );\n right: var(--region-right);\n bottom: var(--region-bottom);\n overflow: hidden;\n overflow-wrap: break-word;\n box-sizing: border-box;\n}\n\n:where([part='captions'] [part='region'][data-active]) {\n}\n\n:where([part='captions'] [part='region'][data-scroll='up']) {\n justify-content: end;\n}\n\n:where([part='captions'] [part='region'][data-active][data-scroll='up']) {\n transition: top 0.433s;\n}\n\n:where([part='captions'] [part='region'] > [part='cue-display']) {\n position: relative;\n width: auto;\n left: var(--cue-offset);\n height: var(--cue-height, auto);\n text-align: var(--cue-text-align);\n unicode-bidi: plaintext;\n margin-top: 1px;\n}\n\n:where([part='captions'] [part='region'] [part='cue']) {\n position: relative;\n padding: calc(var(--cue-padding-y) / 2) var(--cue-padding-x);\n border-radius: 0px;\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport { Error as NetError, Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys } from \"@moq/signals\";\nimport { CaptionsRenderer, parseText, VTTCue, type VTTRegion } from \"media-captions\";\n// media-captions positions and styles cues purely through these stylesheets (via `[part]`\n// attributes and CSS variables it sets); without them the overlay renders nothing visible.\nimport captionsCss from \"media-captions/styles/captions.css?inline\";\nimport regionsCss from \"media-captions/styles/regions.css?inline\";\nimport { subscribeMedia } from \"../media\";\nimport type { Sync } from \"../sync\";\nimport type { Source } from \"./source\";\n\n// The renderer positions cues relative to a `[part=\"captions\"]` box using the stylesheets above.\nconst OVERLAY_CSS = `${captionsCss}\\n${regionsCss}`;\n\n// Drop cues whose end is more than this far behind the playhead, to bound memory on a long stream.\nconst PRUNE_BEHIND = Time.Milli(30_000);\n\n// How long a `utf8` roll-up cue lingers when no later cue supersedes it.\nconst UTF8_LINGER = Time.Milli(30_000);\n\n// How far a VTT payload's own timing may sit from its frame timestamp before we call it a bug.\n// Generous: this is catching a wrong clock, not measuring drift.\nconst SKEW_TOLERANCE = Time.Milli(1_000);\n\n/**\n * The cue state for one selected track, accumulated as groups arrive.\n *\n * `clears` holds the timestamps of `utf8` clear frames. They outlive the cue they terminate because\n * groups are read concurrently and can complete out of order: a clear may land before the cue it is\n * meant to end, and has to still apply when that cue shows up.\n *\n * @internal\n */\nexport type CueStore = {\n\tcues: VTTCue[];\n\tregions: Map<string, VTTRegion>;\n\tclears: number[];\n};\n\n/**\n * Neutralize a cue payload before the renderer splices it into `innerHTML`.\n *\n * [media-captions](https://github.com/vidstack/captions) decodes a fixed set of HTML entities in\n * cue text *after* tokenizing it, then concatenates the result into an HTML string and assigns that\n * to `innerHTML`. So `&lt;img src=x onerror=…&gt;` never presents a literal `<` for the\n * tokenizer to intercept, gets decoded once it is too late, and becomes a live element whose\n * `onerror` runs script in the page's origin. Its tag allowlist does not help, and\n * `pointer-events: none` does not stop a load handler. Annotation values (`<v Name>`) are also\n * interpolated into attributes without quote escaping.\n *\n * Escaping one level deeper than usual means that single decode pass lands on the ordinary escape,\n * so the text renders exactly as the publisher wrote it and no markup survives. `&` becomes\n * `&amp;amp;`, decodes to `&amp;`, and displays as `&`. Since no raw `<` is left, no tags\n * are built and the attribute hole closes with it.\n *\n * VTT styling tags are lost, which is the same trade the publish side already makes when it escapes\n * outgoing cues. Rendering an allowlist of WebVTT elements as real DOM nodes is the upgrade path.\n *\n * @internal\n */\nexport function sanitizeCue(text: string): string {\n\treturn text\n\t\t.replaceAll(\"&\", \"&amp;\")\n\t\t.replaceAll(\"<\", \"&lt;\")\n\t\t.replaceAll(\">\", \"&gt;\")\n\t\t.replaceAll('\"', \"&quot;\");\n}\n\n/**\n * Insert a cue keeping `cues` sorted by start time.\n *\n * Groups arrive in delivery order rather than sequence order, so a cue can legitimately land\n * before one that is already stored. Exported for tests.\n *\n * @internal\n */\nexport function insertCue(cues: VTTCue[], cue: VTTCue): void {\n\tlet i = cues.length;\n\twhile (i > 0 && cues[i - 1].startTime > cue.startTime) i--;\n\tcues.splice(i, 0, cue);\n}\n\n/**\n * Drop every cue that ended before `cutoff`, returning whether anything was removed.\n *\n * Scans the whole array rather than shifting off the front: cues are ordered by start time, not\n * end time, so one long-running early cue would otherwise stop the sweep and let every expired cue\n * behind it accumulate for the life of the stream. Exported for tests.\n *\n * @internal\n */\nexport function pruneCues(store: CueStore, cutoff: number): boolean {\n\tconst { cues, clears } = store;\n\n\t// Clear marks age out on the same horizon; nothing arriving now can precede them.\n\tlet keptClears = 0;\n\tfor (const t of clears) {\n\t\tif (t >= cutoff) clears[keptClears++] = t;\n\t}\n\tclears.length = keptClears;\n\n\tlet kept = 0;\n\tfor (const cue of cues) {\n\t\tif (cue.endTime >= cutoff) cues[kept++] = cue;\n\t}\n\tif (kept === cues.length) return false;\n\tcues.length = kept;\n\treturn true;\n}\n\n/**\n * Clamp a `utf8` roll-up cue against its neighbours, so exactly one caption shows at a time.\n *\n * A roll-up cue has no real end: it runs until the next one starts. Both neighbours are checked\n * because the following cue may already have arrived out of order. Exported for tests.\n *\n * @internal\n */\nexport function rollUp(store: CueStore, cue: VTTCue): void {\n\tconst { cues, clears } = store;\n\tconst i = cues.indexOf(cue);\n\tconst prev = i > 0 ? cues[i - 1] : undefined;\n\tif (prev && prev.endTime > cue.startTime) prev.endTime = cue.startTime;\n\n\tconst next = cues[i + 1];\n\tif (next && cue.endTime > next.startTime) cue.endTime = next.startTime;\n\n\t// A clear that already arrived ends this cue, even though the cue is the later delivery.\n\tconst clear = clears.find((t) => t > cue.startTime);\n\tif (clear !== undefined && cue.endTime > clear) cue.endTime = clear;\n}\n\n/**\n * End whichever roll-up cue is showing at `time`, adding nothing.\n *\n * An empty `utf8` payload clears the caption rather than displaying an empty one, so the clear is\n * a scheduled event in its own right. Exported for tests.\n *\n * @internal\n */\nexport function clearCue(store: CueStore, time: number): void {\n\tconst { cues, clears } = store;\n\n\t// Remember it first: the cue this clear terminates may not have arrived yet.\n\tlet at = clears.length;\n\twhile (at > 0 && clears[at - 1] > time) at--;\n\tif (clears[at] !== time) clears.splice(at, 0, time);\n\n\tfor (let i = cues.length - 1; i >= 0; i--) {\n\t\tconst cue = cues[i];\n\t\tif (cue.startTime > time) continue;\n\t\tif (cue.endTime > time) cue.endTime = time;\n\t\treturn;\n\t}\n}\n\nexport type RendererInput = {\n\t// The overlay element captions are drawn into. Owned by the caller (e.g. the <moq-watch> element),\n\t// which positions it over the video canvas.\n\tcontainer: Getter<HTMLElement | undefined>;\n\n\t// Whether to subscribe and render. Defaults to true; the parent may gate this (e.g. on connection).\n\tenabled: Getter<boolean>;\n};\n\n/** Constructor properties for {@link Renderer}. */\nexport type RendererProps = Inputs<RendererInput> & {\n\t/** Caption rendition selector. */\n\tsource: Source;\n\t/** Shared playback clock. */\n\tsync: Sync;\n};\n\n/**\n * Subscribes to the selected caption track, parses each cue, and renders it into an overlay element\n * via [media-captions](https://github.com/vidstack/captions).\n *\n * There is no WebCodecs decoder for text, so this parses cue payloads directly. Cues are scheduled\n * against the shared {@link Sync} clock (not wall time) so they line up with the audio/video frames.\n * A caption-only broadcast (no audio/video to anchor the clock) shows nothing until media arrives.\n */\nexport class Renderer {\n\treadonly source: Source;\n\treadonly sync: Sync;\n\treadonly in: Readonlys<RendererInput>;\n\n\t#signals = new Effect();\n\n\t// Whether the clock-skew warning has already fired for the current track, so a misconfigured\n\t// publisher costs one line instead of one per cue.\n\t#skewWarned = false;\n\n\tconstructor(props: RendererProps) {\n\t\tthis.source = props.source;\n\t\tthis.sync = props.sync;\n\t\tthis.in = {\n\t\t\tcontainer: getter(props?.container),\n\t\t\tenabled: getter(props?.enabled ?? true),\n\t\t};\n\n\t\tthis.#signals.run(this.#run.bind(this));\n\t}\n\n\t#run(effect: Effect): void {\n\t\tconst values = effect.getAll([\n\t\t\tthis.in.enabled,\n\t\t\tthis.in.container,\n\t\t\tthis.source.out.track,\n\t\t\tthis.source.out.config,\n\t\t\tthis.source.in.broadcast,\n\t\t]);\n\t\tif (!values) return;\n\t\tconst [_enabled, container, track, config, broadcast] = values;\n\n\t\t// Honor a per-rendition `broadcast` override: subscribe on the resolved source broadcast.\n\t\tconst active = broadcast.relativeBroadcast(effect, config.broadcast);\n\t\tif (!active) return;\n\n\t\t// A new track gets its own chance to complain about its clock.\n\t\tthis.#skewWarned = false;\n\n\t\t// Pick the frame container. Text uses the timestamped `legacy` container or LOC; anything else\n\t\t// (e.g. `cmaf`) would misread the cue payload, so skip it rather than render garbage.\n\t\tlet format: Container.Format;\n\t\tif (config.container.kind === \"legacy\") {\n\t\t\tformat = new Container.Legacy.Format(\"data\");\n\t\t} else if (config.container.kind === \"loc\") {\n\t\t\tformat = new Container.Loc.Format();\n\t\t} else {\n\t\t\tconsole.warn(`captions: unsupported container \"${config.container.kind}\" for track ${track}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// The media-captions stylesheet, mounted as a sibling of the overlay (its `[part]` selectors are\n\t\t// global). It can't live inside the overlay: the renderer clears `overlay.textContent` on every\n\t\t// track change, which would wipe it.\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.textContent = OVERLAY_CSS;\n\t\tcontainer.appendChild(style);\n\t\teffect.cleanup(() => style.remove());\n\n\t\t// A fresh overlay per selection, so cleanup is just removing it.\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.style.position = \"absolute\";\n\t\toverlay.style.inset = \"0\";\n\t\toverlay.style.pointerEvents = \"none\";\n\t\tcontainer.appendChild(overlay);\n\t\teffect.cleanup(() => overlay.remove());\n\n\t\tconst renderer = new CaptionsRenderer(overlay);\n\t\t// The renderer installs a ResizeObserver; destroy() disconnects it (removing the overlay alone\n\t\t// does not), so a track switch or reconnect doesn't leak an observer.\n\t\teffect.cleanup(() => renderer.destroy());\n\n\t\tconst sub = subscribeMedia(effect, {\n\t\t\tbroadcast: active,\n\t\t\ttrack,\n\t\t\tpriority: Catalog.PRIORITY.text,\n\t\t\tmaxAge: this.sync.out.maxAge,\n\t\t});\n\t\tif (!sub) return;\n\t\tconst store: CueStore = { cues: [], regions: new Map(), clears: [] };\n\t\tconst commit = () => renderer.changeTrack({ cues: [...store.cues], regions: [...store.regions.values()] });\n\n\t\t// Drive the caption clock off the shared media clock so cues line up with audio/video, and\n\t\t// prune cues that have scrolled well past the playhead. Reschedules itself each frame.\n\t\tconst tick = () => {\n\t\t\tconst now = this.sync.now();\n\t\t\tif (now !== undefined) {\n\t\t\t\trenderer.currentTime = now / 1000;\n\n\t\t\t\tif (pruneCues(store, (now - PRUNE_BEHIND) / 1000)) commit();\n\t\t\t}\n\t\t\teffect.animate(tick);\n\t\t};\n\t\teffect.animate(tick);\n\n\t\t// Accept groups and read each in its own task, like `Container.Consumer` does for media. A\n\t\t// group whose stream stalls then delays only its own cue instead of every later one.\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst group = await sub.recvGroup().catch((err) => {\n\t\t\t\t\tif (!(err instanceof NetError.Stream)) throw err;\n\t\t\t\t\tconsole.debug(\"captions subscription ended\", err);\n\t\t\t\t\treturn undefined;\n\t\t\t\t});\n\t\t\t\tif (!group) break;\n\n\t\t\t\teffect.spawn(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (;;) {\n\t\t\t\t\t\t\tconst frame = await group.readFrame();\n\t\t\t\t\t\t\tif (!frame) break;\n\t\t\t\t\t\t\tfor (const sample of format.decode(frame)) {\n\t\t\t\t\t\t\t\tawait this.#ingest(config.format, sample, store);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tif (!(err instanceof NetError.Stream)) throw err;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tgroup.close();\n\t\t\t\t\t}\n\n\t\t\t\t\tcommit();\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\t// Parse one cue payload into the cue/region store. The frame timestamp is the authoritative cue\n\t// start on the media clock; a VTT payload also carries its own absolute timing.\n\tasync #ingest(format: string, sample: Container.Frame, store: CueStore): Promise<void> {\n\t\tconst text = new TextDecoder().decode(sample.payload);\n\t\tconst start = (sample.timestamp as number) / 1e6;\n\n\t\tif (format === \"utf8\") {\n\t\t\t// An empty payload clears the caption (per the hang draft) rather than showing nothing,\n\t\t\t// so it still has to be scheduled: it just ends the current cue instead of adding one.\n\t\t\tif (text.length === 0) {\n\t\t\t\tclearCue(store, start);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst cue = new VTTCue(start, start + UTF8_LINGER / 1000, sanitizeCue(text));\n\t\t\tinsertCue(store.cues, cue);\n\t\t\trollUp(store, cue);\n\t\t\treturn;\n\t\t}\n\n\t\tif (text.length === 0) return;\n\n\t\t// vtt: a self-contained WEBVTT segment. `strict: false` (the default) tolerates a missing header.\n\t\tlet parsed: Awaited<ReturnType<typeof parseText>>;\n\t\ttry {\n\t\t\tparsed = await parseText(text, { type: \"vtt\" });\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"captions: failed to parse VTT cue\", err);\n\t\t\treturn;\n\t\t}\n\n\t\t// The spec requires the payload's timing to be absolute on the same clock as the frame\n\t\t// timestamp. A publisher emitting segment-relative times (the usual WebVTT-in-fMP4 habit)\n\t\t// would otherwise place every cue silently wrong, so say so once rather than never.\n\t\tconst first = parsed.cues.at(0);\n\t\tif (!this.#skewWarned && first && Math.abs(first.startTime - start) > SKEW_TOLERANCE / 1000) {\n\t\t\tthis.#skewWarned = true;\n\t\t\tconsole.warn(\n\t\t\t\t`captions: cue timing is ${(first.startTime - start).toFixed(3)}s off its frame timestamp; ` +\n\t\t\t\t\t\"the payload must carry absolute times on the media clock\",\n\t\t\t);\n\t\t}\n\n\t\tfor (const region of parsed.regions) store.regions.set(region.id, region);\n\t\tfor (const cue of parsed.cues) {\n\t\t\t// The payload is untrusted, and the renderer builds HTML out of this text.\n\t\t\tcue.text = sanitizeCue(cue.text);\n\t\t\tinsertCue(store.cues, cue);\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\n\n// The cue formats the renderer knows how to display. Renditions in any other format (e.g. \"ttml\")\n// are still listed in `available` for a picker, but selecting one warns and renders nothing.\nconst RENDERABLE = new Set([\"vtt\", \"utf8\"]);\n\n/** Whether the renderer can display a text rendition's cue format. */\nexport function renderable(config: Catalog.TextConfig): boolean {\n\treturn RENDERABLE.has(config.format);\n}\n\nexport type SourceInput = {\n\tbroadcast: Getter<Broadcast | undefined>;\n\n\t// The selected caption track name, or `undefined` for off (the default; captions are opt-in).\n\ttarget: Getter<string | undefined>;\n};\n\ntype SourceOutput = {\n\t// The catalog text section, or undefined when the broadcast publishes no captions.\n\tcatalog: Signal<Catalog.Text | undefined>;\n\n\t// Every text rendition, keyed by track name, for a picker to list.\n\tavailable: Signal<Record<string, Catalog.TextConfig>>;\n\n\t// The selected track name and its config, or undefined when off / not found.\n\ttrack: Signal<string | undefined>;\n\tconfig: Signal<Catalog.TextConfig | undefined>;\n};\n\n/**\n * Selects a caption rendition from the catalog `text` section.\n *\n * Unlike audio/video there is no support probe and no automatic selection: captions are opt-in, so\n * nothing is chosen until `target` names a track. The {@link Renderer} consumes whatever it picks.\n */\nexport class Source {\n\treadonly in: Readonlys<SourceInput>;\n\n\treadonly #out: SourceOutput = {\n\t\tcatalog: new Signal<Catalog.Text | undefined>(undefined),\n\t\tavailable: new Signal<Record<string, Catalog.TextConfig>>({}),\n\t\ttrack: new Signal<string | undefined>(undefined),\n\t\tconfig: new Signal<Catalog.TextConfig | 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};\n\n\t\tthis.#signals.run(this.#runCatalog.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\tconst catalog = broadcast ? effect.get(broadcast.out.catalog)?.text : undefined;\n\n\t\teffect.set(this.#out.catalog, catalog);\n\t\tthis.#out.available.set(catalog?.renditions ?? {});\n\t}\n\n\t#runSelected(effect: Effect): void {\n\t\tconst available = effect.get(this.#out.available);\n\t\tconst target = effect.get(this.in.target);\n\n\t\tconst config = target ? available[target] : undefined;\n\t\tif (!target || !config) {\n\t\t\teffect.set(this.#out.track, undefined);\n\t\t\teffect.set(this.#out.config, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!renderable(config)) {\n\t\t\tconsole.warn(`captions: unsupported format ${JSON.stringify(config.format)} for track ${target}`);\n\t\t}\n\n\t\teffect.set(this.#out.track, target);\n\t\teffect.set(this.#out.config, config);\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\n\n/** Internal key used to describe which fields can change a support probe's result. */\nexport const supportCacheKey = Symbol(\"supportCacheKey\");\n\n/** The catalog fields that determine the demuxer and WebCodecs decoder instance. */\nexport type DecoderConfig = Pick<\n\tCatalog.VideoConfig,\n\t\"codec\" | \"container\" | \"description\" | \"displayAspectWidth\" | \"displayAspectHeight\"\n> & {\n\toptimizeForLatency: boolean;\n};\n\n/** The routing and decoder state whose changes require a new playback pipeline. */\nexport type PlaybackIdentity = {\n\tbroadcast: Catalog.VideoConfig[\"broadcast\"];\n\tdecoder: DecoderConfig;\n};\n\n/** Reduce a rendition config to the fields that require a new playback pipeline. */\nexport function playbackIdentity(config: Catalog.VideoConfig): PlaybackIdentity {\n\treturn {\n\t\tbroadcast: config.broadcast,\n\t\tdecoder: {\n\t\t\tcodec: config.codec,\n\t\t\tcontainer: config.container,\n\t\t\tdescription: config.description,\n\t\t\tdisplayAspectWidth: config.displayAspectWidth,\n\t\t\tdisplayAspectHeight: config.displayAspectHeight,\n\t\t\toptimizeForLatency: config.optimizeForLatency ?? true,\n\t\t},\n\t};\n}\n\n/** Return a stable cache key for the fields used by the WebCodecs support probe. */\nexport function decoderConfigKey(config: Catalog.VideoConfig): string {\n\treturn JSON.stringify(playbackIdentity(config).decoder);\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport { Time } from \"@moq/net\";\n\n// How far the incoming rendition may still trail the picture it replaces when we promote it,\n// absorbing scheduling noise so the switch doesn't hinge on landing inside a single frame\n// interval. This is also the largest step backwards a switch can make visible.\nconst SLACK = Time.Milli(100);\n\n/**\n * How far behind live a rendition's playhead can sit while still being at its own live edge.\n *\n * Its catalog `delay` behind the broadcast's earliest rendition, plus its own spread: frames arrive\n * one group at a time, so the sync buffer has to cover the group cadence or playback starves\n * between groups. The catalog `jitter` wins. Otherwise assume the publisher flushes each frame as\n * it's encoded, so a frame interval is the longest we wait. Undefined when the catalog declares\n * none of them.\n */\nexport function renditionJitter(config: Catalog.VideoConfig): Time.Milli | undefined {\n\tlet spread: Time.Milli | undefined;\n\tif (config.jitter !== undefined) spread = Time.Milli(config.jitter);\n\telse if (config.framerate) spread = Time.Milli(Math.ceil(1000 / config.framerate));\n\n\tif (config.delay === undefined) return spread;\n\treturn Time.Milli.add(Time.Milli(config.delay), spread ?? Time.Milli.zero);\n}\n\n/** The playheads involved in promoting a new rendition. */\nexport interface CaughtUp {\n\t/** The incoming rendition's playhead: the most recent timestamp it has rendered. */\n\tplayhead: Time.Milli;\n\n\t/** The outgoing rendition's playhead, or undefined when it has rendered nothing. */\n\tactive?: Time.Milli;\n}\n\n/**\n * Whether the incoming rendition has caught up enough to take over the picture.\n *\n * The bar is the outgoing playhead rather than the live edge, which is a moving target: the sync\n * buffer grows the moment a coarser rendition is selected, dropping live below the outgoing\n * playhead, and it runs ahead of both playheads whenever delivery is late, because the sync\n * reference only ever moves down. Barring on live would promote in the first case (replaying\n * everything between the two playheads) and stall the switch outright in the second. Waiting on\n * the outgoing playhead always terminates: live recovers at wall-clock rate, and the incoming\n * rendition fills its buffer meanwhile.\n */\nexport function caughtUp(props: CaughtUp): boolean {\n\t// Nothing is rendering from the outgoing rendition, so there's no picture to step back from.\n\tif (props.active === undefined) return true;\n\n\treturn Time.Milli.add(props.playhead, SLACK) >= props.active;\n}\n\n/** The rendition delays that can be present during a track handoff. */\nexport interface SwitchJitter {\n\t/** The delay required by the rendition currently on screen. */\n\tactive?: Time.Milli;\n\n\t/** The delay required by the rendition preparing to replace it. */\n\tpending?: Time.Milli;\n}\n\n/** The delay Sync must cover while a rendition switch is in flight. */\nexport function switchJitter(props: SwitchJitter): Time.Milli | undefined {\n\tif (props.active === undefined) return props.pending;\n\tif (props.pending === undefined) return props.active;\n\treturn Time.Milli.max(props.active, props.pending);\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\n\ntype VideoRotation = 0 | 90 | 180 | 270;\n\ntype VideoDimensions = {\n\twidth: number;\n\theight: number;\n};\n\n/** Canvas transform and source dimensions for a video presentation. */\nexport type CanvasPresentationTransform = {\n\tmatrix: [number, number, number, number, number, number];\n\tsource: VideoDimensions;\n};\n\nfunction normalizeVideoRotation(rotation = 0): VideoRotation {\n\tconst normalized = ((rotation % 360) + 360) % 360;\n\treturn ((Math.round(normalized / 90) % 4) * 90) as VideoRotation;\n}\n\n/** Return dimensions after applying a clockwise quarter-turn. */\nexport function rotateVideoDimensions(dimensions: VideoDimensions, rotation = 0): VideoDimensions {\n\tconst normalized = normalizeVideoRotation(rotation);\n\treturn normalized === 90 || normalized === 270\n\t\t? { width: dimensions.height, height: dimensions.width }\n\t\t: { width: dimensions.width, height: dimensions.height };\n}\n\nfunction clean(value: number): number {\n\treturn Object.is(value, -0) ? 0 : value;\n}\n\nfunction flipMatrix(\n\tmatrix: [number, number, number, number, number, number],\n\twidth: number,\n): [number, number, number, number, number, number] {\n\tconst [a, b, c, d, e, f] = matrix;\n\treturn [clean(-a), clean(b), clean(-c), clean(d), clean(width - e), clean(f)];\n}\n\n/** Return the canvas transform needed to render a catalog video presentation. */\nexport function canvasPresentationTransform(\n\toutput: VideoDimensions,\n\tvideo?: Catalog.Video,\n): CanvasPresentationTransform {\n\tconst rotation = normalizeVideoRotation(video?.rotation);\n\tconst source = rotateVideoDimensions(output, rotation);\n\n\tlet matrix: [number, number, number, number, number, number];\n\tswitch (rotation) {\n\t\tcase 90:\n\t\t\tmatrix = [0, 1, -1, 0, output.width, 0];\n\t\t\tbreak;\n\t\tcase 180:\n\t\t\tmatrix = [-1, 0, 0, -1, output.width, output.height];\n\t\t\tbreak;\n\t\tcase 270:\n\t\t\tmatrix = [0, -1, 1, 0, 0, output.height];\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tmatrix = [1, 0, 0, 1, 0, 0];\n\t\t\tbreak;\n\t}\n\n\tif (video?.flip) matrix = flipMatrix(matrix, output.width);\n\treturn { matrix, source };\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 {\n\ttype Computed,\n\tEffect,\n\ttype Getter,\n\tgetter,\n\ttype Inputs,\n\ttype Readonlys,\n\treadonlys,\n\tSignal,\n} from \"@moq/signals\";\nimport { base64ToBytes } from \"../base64\";\nimport { nextMedia, subscribeMedia } from \"../media\";\n\nimport type { Sync } from \"../sync\";\nimport {\n\ttype DecoderConfig,\n\tdecoderConfigKey,\n\ttype PlaybackIdentity,\n\tplaybackIdentity,\n\tsupportCacheKey,\n} from \"./config\";\nimport { caughtUp, renditionJitter, switchJitter } from \"./playhead\";\nimport { rotateVideoDimensions } from \"./presentation\";\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);\n\nexport type DecoderInput = {\n\t/** Whether to download the video track. Defaults to true; the parent may wire it from the renderer's output. */\n\tenabled: Getter<boolean>;\n};\n\n/** Constructor properties for {@link Decoder}. */\nexport type DecoderProps = Inputs<DecoderInput> & {\n\t/** Rendition selector supplying encoded video. */\n\tsource: Source;\n\t/** Shared playback clock. */\n\tsync: Sync;\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// The rendition delay Sync must cover while switching tracks.\n\tjitter: Signal<Time.Milli | undefined>;\n\n\t// Combined buffered ranges (network jitter + decode buffer)\n\tbuffered: Signal<Container.BufferedRanges>;\n};\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\tjitter: new Signal<Time.Milli | 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\t// The rendition preparing to replace the active one, with the catalog of the broadcast it\n\t// subscribed to. One value so a later source that reuses the track name still notifies:\n\t// the name alone would compare equal, and the jitter effect would keep the previous catalog.\n\t#pending = new Signal<{ track: string; catalog: Getter<Catalog.Root | undefined> } | undefined>(undefined);\n\treadonly #identity: Computed<PlaybackIdentity | 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(props: DecoderProps) {\n\t\tthis.in = {\n\t\t\tenabled: getter(props?.enabled ?? true),\n\t\t};\n\n\t\tthis.source = props.source;\n\t\tthis.sync = props.sync;\n\t\tthis.#signals.cleanup(this.sync.register(this.out.jitter));\n\t\tthis.#identity = this.#signals.computed((effect) => {\n\t\t\tconst config = effect.get(this.source.out.config);\n\t\t\treturn config ? playbackIdentity(config) : undefined;\n\t\t});\n\n\t\tthis.#signals.run(this.#runJitter.bind(this));\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// Read from the full catalog entry, which the decoder config omits, so a rising entry resizes Sync.\n\t// Each in-flight rendition stays tied to the catalog it subscribed to: during a source switch the\n\t// shared catalog already describes the next broadcast, and the same track name there is a\n\t// different rendition.\n\t#runJitter(effect: Effect): void {\n\t\tconst floor = (track: string | undefined, catalog: Getter<Catalog.Root | undefined> | undefined) => {\n\t\t\tif (track === undefined || catalog === undefined) return undefined;\n\t\t\tconst config = effect.get(catalog)?.video?.renditions?.[track];\n\t\t\treturn config && renditionJitter(config);\n\t\t};\n\t\tconst activeTrack = effect.get(this.#active);\n\t\tconst active = floor(activeTrack?.track, activeTrack?.catalog);\n\t\tconst pendingTrack = effect.get(this.#pending);\n\t\tconst pending = floor(pendingTrack?.track, pendingTrack?.catalog);\n\t\teffect.set(this.#out.jitter, switchJitter({ active, pending }));\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.#identity,\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, identity] = 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, identity.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: identity.decoder,\n\t\t\tstats: this.#out.stats,\n\t\t\tcatalog: broadcast.out.catalog,\n\t\t});\n\t\teffect.set(this.#pending, { track, catalog: broadcast.out.catalog });\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\t// A zero timestamp is a rendered frame, not a missing one.\n\t\t\t\tconst pendingTimestamp = effect.get(pending.timestamp);\n\t\t\t\tif (pendingTimestamp === undefined) return;\n\n\t\t\t\t// Hold off until the new rendition has caught up to the picture it's replacing.\n\t\t\t\tif (!caughtUp({ playhead: pendingTimestamp, active: effect.get(current.timestamp) })) 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\tthis.#pending.set(undefined);\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\tif (!frame) return; // A promoted track holds the previous picture until its first frame.\n\t\t\tthis.#out.timestamp.set(Time.Milli.fromMicro(frame.timestamp as Time.Micro));\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.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, { width: display.width, height: display.height });\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(\n\t\t\tthis.#out.display,\n\t\t\trotateVideoDimensions({ width: frame.displayWidth, height: frame.displayHeight }, catalog.rotation),\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: DecoderConfig;\n\t// The broadcast catalog this subscription started from. Updates on that broadcast still apply;\n\t// a later source does not.\n\tcatalog: Getter<Catalog.Root | undefined>;\n\n\tstats: Signal<Stats | undefined>;\n}\n\nclass DecoderTrack {\n\tsync: Sync;\n\tbroadcast: Moq.Broadcast.Consumer;\n\ttrack: string;\n\tconfig: DecoderConfig;\n\tcatalog: Getter<Catalog.Root | undefined>;\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\tthis.sync = props.sync;\n\t\tthis.broadcast = props.broadcast;\n\t\tthis.track = props.track;\n\t\tthis.config = props.config;\n\t\tthis.catalog = props.catalog;\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 = subscribeMedia(effect, {\n\t\t\tbroadcast: this.broadcast,\n\t\t\ttrack: this.track,\n\t\t\tpriority: Catalog.PRIORITY.video,\n\t\t\tmaxAge: this.sync.out.maxAge,\n\t\t});\n\t\tif (!sub) return;\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\t// `received()` runs at submit, so a frame is anchored by the time it decodes.\n\t\t\t\t\t// Reaching here unanchored means a rewind reset the clock after this frame's\n\t\t\t\t\t// received(), so it predates the current timeline. Drop it: painting it would\n\t\t\t\t\t// set `timestamp` from the old timeline and late-reject the whole rewind.\n\t\t\t\t\tif (this.sync.out.reference.peek() === undefined) return;\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\t// Returns immediately when the latency is \"instant\".\n\t\t\t\t\tconst ok = await effect.race(this.sync.wait(timestamp).then(() => true));\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(\"video decoder 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\"\n\t\t\t\t? new Container.Loc.Format(\"video\")\n\t\t\t\t: new Container.Legacy.Format(this.config);\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\tmaxAge: this.sync.out.maxAge,\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\tcodec: this.config.codec,\n\t\t\tdescription: this.config.description ? Util.Hex.toBytes(this.config.description) : undefined,\n\t\t\tdisplayAspectWidth: this.config.displayAspectWidth,\n\t\t\tdisplayAspectHeight: this.config.displayAspectHeight,\n\t\t\toptimizeForLatency: this.config.optimizeForLatency,\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: Time.Micro | undefined;\n\t\tlet latest: number | undefined;\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await nextMedia(consumer);\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 } = next;\n\t\t\t\tif (!frame) continue; // The group is done\n\n\t\t\t\t// An older keyframe would replace the codec references needed by live deltas. Group\n\t\t\t\t// sequences only grow within a subscription, so a discontinuity (which a stale marker\n\t\t\t\t// can raise) never resets this guard. Rejected payloads still count as received bytes.\n\t\t\t\tconst stale = latest !== undefined && next.group < latest;\n\t\t\t\tthis.stats.update((current) => ({\n\t\t\t\t\tframeCount: (current?.frameCount ?? 0) + (stale ? 0 : 1),\n\t\t\t\t\tbytesReceived: (current?.bytesReceived ?? 0) + frame.payload.byteLength,\n\t\t\t\t}));\n\t\t\t\tif (stale) 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, \"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 decode buffer: frames sent to decoder but not yet rendered. Only bridge from\n\t\t\t\t// the previous frame when the consumer says nothing is missing in between. Group ids\n\t\t\t\t// can't answer that: they aren't required to be sequential (some encoders derive them\n\t\t\t\t// from DTS), so adjacency neither proves continuity nor rules out a gap the consumer\n\t\t\t\t// skipped, and reporting a skipped span as decoded overstates the buffer.\n\t\t\t\tif (previous !== undefined && next.continuous) {\n\t\t\t\t\tthis.#addBuffered(Time.Milli.fromMicro(previous), Time.Milli.fromMicro(frame.timestamp));\n\t\t\t\t}\n\n\t\t\t\tprevious = frame.timestamp;\n\n\t\t\t\tlatest = next.group;\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\tmaxAge: this.sync.out.maxAge,\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\tdisplayAspectWidth: this.config.displayAspectWidth,\n\t\t\tdisplayAspectHeight: this.config.displayAspectHeight,\n\t\t\toptimizeForLatency: this.config.optimizeForLatency,\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: Time.Micro | undefined;\n\t\tlet latest: number | undefined;\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await nextMedia(consumer);\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 } = next;\n\t\t\t\tif (!frame) continue;\n\n\t\t\t\t// An older keyframe would replace the codec references needed by live deltas. Group\n\t\t\t\t// sequences only grow within a subscription, so a discontinuity (which a stale marker\n\t\t\t\t// can raise) never resets this guard. Rejected payloads still count as received bytes.\n\t\t\t\tconst stale = latest !== undefined && next.group < latest;\n\t\t\t\tthis.stats.update((current) => ({\n\t\t\t\t\tframeCount: (current?.frameCount ?? 0) + (stale ? 0 : 1),\n\t\t\t\t\tbytesReceived: (current?.bytesReceived ?? 0) + frame.payload.byteLength,\n\t\t\t\t}));\n\t\t\t\tif (stale) 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);\n\t\t\t\tthis.sync.received(timestamp, \"video\");\n\n\t\t\t\t// Track decode buffer (see #runLegacy: bridge on the consumer's continuity signal,\n\t\t\t\t// never on group adjacency, which proves nothing about the timeline).\n\t\t\t\tif (previous !== undefined && next.continuous) {\n\t\t\t\t\tthis.#addBuffered(Time.Milli.fromMicro(previous), Time.Milli.fromMicro(frame.timestamp));\n\t\t\t\t}\n\n\t\t\t\tprevious = frame.timestamp;\n\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tlatest = next.group;\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\tdisplayAspectWidth: config.displayAspectWidth,\n\t\tdisplayAspectHeight: config.displayAspectHeight,\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\tdisplayAspectWidth: config.displayAspectWidth,\n\t\t\tdisplayAspectHeight: config.displayAspectHeight,\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\nObject.assign(supported, { [supportCacheKey]: decoderConfigKey });\n","import { Effect, type Getter, getter, Signal } from \"@moq/signals\";\n\n/** What TabVisible watches: `document`, or a stand-in with the same event and flag. */\nexport type VisibilitySource = EventTarget & { readonly hidden: boolean };\n\n/** Constructor properties for {@link TabVisible}. */\nexport type TabVisibleProps = {\n\t/** The document to watch. Defaults to the global `document`. */\n\tdoc?: VisibilitySource;\n\t/** How long, in milliseconds, the tab must stay hidden before it counts as invisible. Defaults to 5000. */\n\tgrace?: Getter<number> | number;\n};\n\n/**\n * Whether the tab counts as visible: true while it is shown, and false only once it has been\n * hidden for the grace period.\n *\n * A background tab is throttled, so video that keeps downloading cannot be rendered in time,\n * and on return the player fast-forwards through the backlog. Turning video off once the tab\n * has been away for a while means it comes back at the live edge, while the grace keeps a\n * quick flip to another tab and back from interrupting it.\n */\nexport class TabVisible {\n\treadonly visible: Signal<boolean>;\n\n\treadonly #hidden: Signal<boolean>;\n\treadonly #grace: Getter<number>;\n\treadonly #signals = new Effect();\n\n\tconstructor(props: TabVisibleProps = {}) {\n\t\tconst doc = props.doc ?? (globalThis.document as VisibilitySource | undefined);\n\t\tthis.#hidden = new Signal(doc?.hidden ?? false);\n\t\t// Nothing is playing yet when the tab starts hidden, so there is nothing to hold on to.\n\t\tthis.visible = new Signal(!this.#hidden.peek());\n\t\tthis.#grace = getter(props.grace ?? 5000);\n\n\t\tif (doc) {\n\t\t\tthis.#signals.event(doc, \"visibilitychange\", () => this.#hidden.set(doc.hidden));\n\t\t}\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst hidden = effect.get(this.#hidden);\n\t\t\tif (!hidden) {\n\t\t\t\tthis.visible.set(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\teffect.timer(() => this.visible.set(false), effect.get(this.#grace));\n\t\t});\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Decoder } from \"./decoder\";\nimport { canvasPresentationTransform } from \"./presentation\";\nimport { TabVisible, type VisibilitySource } from \"./tab-visible\";\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 * - `\"tab\"`: download wherever the canvas is, while the tab is visible (an off-screen tile keeps\n * playing; a background tab stops).\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 *\n * Whatever the policy, other than `\"always\"`, a hidden tab only stops video once it has been\n * hidden for `hiddenGrace`, so a quick flip to another tab and back does not interrupt it.\n */\nexport type Visible = \"never\" | \"always\" | \"tab\" | (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\t// How long, in milliseconds, the tab must stay hidden before video stops. Defaults to 5000.\n\thiddenGrace: Getter<number>;\n};\n\n/** Constructor properties for {@link Renderer}. */\nexport type RendererProps = Inputs<RendererInput> & {\n\t/** Decoder supplying video frames. */\n\tdecoder: Decoder;\n\t/** The document whose visibility counts. Defaults to the global `document`. */\n\tdocument?: VisibilitySource;\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\treadonly #tab: TabVisible;\n\n\tconstructor(props: RendererProps) {\n\t\tthis.decoder = props.decoder;\n\t\tthis.in = {\n\t\t\tcanvas: getter(props?.canvas),\n\t\t\tvisible: getter(props?.visible ?? \"20%\"),\n\t\t\thiddenGrace: getter(props?.hiddenGrace ?? 5000),\n\t\t};\n\t\tthis.#tab = new TabVisible({ doc: props.document, grace: this.in.hiddenGrace });\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\tconst tabVisible = effect.get(this.#tab.visible);\n\n\t\tif (visible === \"tab\") {\n\t\t\tthis.#out.visible.set(tabVisible);\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 && tabVisible);\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\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\t\tconst video = effect.get(this.decoder.source.out.catalog);\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, video);\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 !== undefined) cancelAnimationFrame(animate);\n\t\t});\n\t}\n\n\t#render(ctx: CanvasRenderingContext2D, frame?: VideoFrame, video?: Catalog.Video) {\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 rotation and horizontal flip from the video config.\n\t\tif (!video?.rotation) {\n\t\t\tif (video?.flip) {\n\t\t\t\tctx.scale(-1, 1);\n\t\t\t\tctx.translate(-ctx.canvas.width, 0);\n\t\t\t}\n\t\t\tctx.drawImage(frame, 0, 0, ctx.canvas.width, ctx.canvas.height);\n\t\t} else {\n\t\t\tconst transform = canvasPresentationTransform(ctx.canvas, video);\n\t\t\tctx.setTransform(...transform.matrix);\n\t\t\tctx.drawImage(frame, 0, 0, transform.source.width, transform.source.height);\n\t\t}\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\tthis.#tab.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport type * as Moq from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\nimport { supportCacheKey } from \"./config\";\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\ntype CacheableSupported = Supported & {\n\t[supportCacheKey]?: (config: Catalog.VideoConfig) => string;\n};\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\t// The connection's PROBE estimates, used to auto-select a rendition when the target has no\n\t// explicit bitrate. Usually wired from a `Connection`'s `probe`.\n\t// Optional: without it auto-selection falls back to the preference order alone.\n\tprobe: Getter<Moq.Connection.Probe | 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\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, so 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, so 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, so 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/** Return unstalled renditions, or the lowest bitrate or resolution when every option is stalled. */\nfunction selectableRenditions(renditions: Record<string, Catalog.VideoConfig>): Record<string, Catalog.VideoConfig> {\n\tconst active = Object.entries(renditions).filter(([, config]) => !config.stalled);\n\tif (active.length > 0) return Object.fromEntries(active);\n\n\tconst entries = Object.entries(renditions);\n\tif (entries.length === 0) return {};\n\tconst byRate = byBitrate(0)(entries);\n\tconst lowest = byRate.length === 1 ? byRate[0] : byDimensions(0, 0)(entries)[0];\n\treturn { [lowest]: renditions[lowest] };\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};\n\treadonly out = readonlys(this.#out);\n\n\t#signals = new Effect();\n\t#supportCache = new WeakMap<Supported, Map<string, { key: string; supported: boolean }>>();\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\tprobe: getter(props?.probe),\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\t\tlet cache = this.#supportCache.get(supported);\n\t\tif (!cache) {\n\t\t\tcache = new Map();\n\t\t\tthis.#supportCache.set(supported, cache);\n\t\t}\n\t\tconst names = new Set(Object.keys(renditions));\n\t\tfor (const name of cache.keys()) {\n\t\t\tif (!names.has(name)) cache.delete(name);\n\t\t}\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\tconst cacheKey = (supported as CacheableSupported)[supportCacheKey];\n\t\t\t\tconst key = cacheKey ? cacheKey(config) : JSON.stringify(config);\n\t\t\t\tconst cached = cache.get(name);\n\t\t\t\tlet isSupported: boolean | undefined = false;\n\t\t\t\tif (cached?.key === key) {\n\t\t\t\t\tisSupported = cached.supported;\n\t\t\t\t} else {\n\t\t\t\t\tlet failed = false;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// `supported` comes from the consumer, so we cannot assume it ever settles. A\n\t\t\t\t\t\t// rerun waits for the tasks it spawned, so an unraced probe would hold it shut.\n\t\t\t\t\t\tisSupported = await effect.race(supported(config));\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tfailed = true;\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`[Source] video rendition ${name} (${config.codec}) support probe failed; treating as unsupported`,\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!failed && isSupported !== undefined) {\n\t\t\t\t\t\tcache.set(name, {\n\t\t\t\t\t\t\tkey: cacheKey ? cacheKey(config) : JSON.stringify(config),\n\t\t\t\t\t\t\tsupported: isSupported,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Torn down: stop probing and publish nothing, since the rerun redoes this.\n\t\t\t\tif (effect.abort.aborted) return;\n\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 supported = effect.get(this.#out.available);\n\t\tconst target = effect.get(this.in.target);\n\n\t\t// A manual choice stays selected while stalled. `stalled` steers automatic adaptation; it\n\t\t// must not silently override an explicit user selection.\n\t\tif (target?.name && target.name in supported) {\n\t\t\tconst config = supported[target.name];\n\t\t\teffect.set(this.#out.track, target.name);\n\t\t\teffect.set(this.#out.config, config);\n\t\t\treturn;\n\t\t}\n\n\t\tconst available = selectableRenditions(supported);\n\t\tif (Object.keys(available).length === 0) return;\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 estimate = effect.get(this.in.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\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// With 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","import type * as Catalog from \"@moq/hang/catalog\";\nimport type * as Moq from \"@moq/net\";\nimport { Path, Time } from \"@moq/net\";\nimport { Effect, type Getter, getter, type Inputs, type Readonlys, readonlys, Signal } from \"@moq/signals\";\nimport * as Audio from \"./audio\";\nimport { Broadcast, type CatalogFormat } from \"./broadcast\";\nimport { type Delay, Sync } from \"./sync\";\nimport * as Text from \"./text\";\nimport * as Video from \"./video\";\n\n/** Inputs for a complete broadcast playback pipeline. */\nexport type PlayerInput = {\n\t/** Origin that supplies the broadcast. */\n\torigin: Getter<Moq.Origin.Table | undefined>;\n\t/** Broadcast path relative to the origin. */\n\tname: Getter<Moq.Path.Valid>;\n\t/** Whether the pipeline may subscribe. */\n\tenabled: Getter<boolean>;\n\t/** Wait for a broadcast announcement before subscribing. */\n\tannounced: Getter<boolean>;\n\t/** Catalog format override, or automatic detection when absent. */\n\tcatalogFormat: Getter<CatalogFormat | undefined>;\n\t/** Catalog supplied when the format is `manual`. */\n\tcatalog: Getter<Catalog.Root | undefined>;\n\t/** Connection probe used for rendition selection and delay. */\n\tprobe: Getter<Moq.Connection.Probe | undefined>;\n\t/** Canvas to paint video into. */\n\tcanvas: Getter<HTMLCanvasElement | undefined>;\n\t/** Container to render captions into. */\n\tcontainer: Getter<HTMLElement | undefined>;\n\t/** Pause media and captions. */\n\tpaused: Getter<boolean>;\n\t/** Speaker volume from zero to one. */\n\tvolume: Getter<number>;\n\t/** Silence audio and stop its download. */\n\tmuted: Getter<boolean>;\n\t/** Canvas visibility policy for video downloads. */\n\tvisible: Getter<Video.Visible>;\n\t/** Playback distance from the live edge. */\n\tdelay: Getter<Delay>;\n\t/** Future-dated media held beyond the live edge. */\n\tbuffer: Getter<Time.Milli>;\n\t/** Desired video rendition or quality limits. */\n\ttarget: Getter<Video.Target | undefined>;\n\t/** Desired audio rendition by track name (a language, say); unset takes the first playable. */\n\taudioTarget: Getter<Audio.Target | undefined>;\n\t/** Selected caption track, or undefined for off. */\n\tcaptions: Getter<string | undefined>;\n};\n\n/** Constructor options for {@link Player}; omitted controls use the element defaults. */\nexport type PlayerProps = Inputs<PlayerInput>;\n\n/** Owns the broadcast, synchronized media pipelines, and their download policy. */\nexport class Player {\n\t/** Inputs wired into the pipeline. Pass signals to change them after construction. */\n\treadonly in: Readonlys<PlayerInput>;\n\t/** Broadcast subscription and effective catalog. */\n\treadonly broadcast: Broadcast;\n\t/** Shared audio, video, and caption clock. */\n\treadonly sync: Sync;\n\t/** Selected caption rendition. */\n\treadonly text: Text.Source;\n\t/** Video decoder; `video.source` selects its rendition. */\n\treadonly video: Video.Decoder;\n\t/** Audio decoder; `audio.source` selects its rendition. */\n\treadonly audio: Audio.Decoder;\n\t/** Canvas video output. */\n\treadonly renderer: Video.Renderer;\n\t/** Speaker audio output. */\n\treadonly emitter: Audio.Emitter;\n\t/** Caption overlay output. */\n\treadonly textRenderer: Text.Renderer;\n\n\t#videoEnabled = new Signal(false);\n\t#audioEnabled = new Signal(false);\n\t#captionsEnabled = new Signal(false);\n\t#signals = new Effect();\n\n\tconstructor(props: PlayerProps = {}) {\n\t\tthis.in = readonlys({\n\t\t\torigin: getter(props.origin),\n\t\t\tname: getter(props.name ?? Path.empty()),\n\t\t\tenabled: getter(props.enabled ?? true),\n\t\t\tannounced: getter(props.announced ?? true),\n\t\t\tcatalogFormat: getter<CatalogFormat | undefined>(props.catalogFormat),\n\t\t\tcatalog: getter(props.catalog),\n\t\t\tprobe: getter(props.probe),\n\t\t\tcanvas: getter(props.canvas),\n\t\t\tcontainer: getter(props.container),\n\t\t\tpaused: getter(props.paused ?? false),\n\t\t\tvolume: getter(props.volume ?? 0.5),\n\t\t\tmuted: getter(props.muted ?? false),\n\t\t\tvisible: getter(props.visible ?? \"20%\"),\n\t\t\tdelay: getter(props.delay ?? \"auto\"),\n\t\t\tbuffer: getter(props.buffer ?? Time.Milli.zero),\n\t\t\ttarget: getter<Video.Target | undefined>(props.target),\n\t\t\taudioTarget: getter<Audio.Target | undefined>(props.audioTarget),\n\t\t\tcaptions: getter<string | undefined>(props.captions),\n\t\t});\n\n\t\tthis.broadcast = new Broadcast(this.in);\n\t\tthis.#signals.cleanup(() => this.broadcast.close());\n\n\t\tconst videoSource = new Video.Source({\n\t\t\tbroadcast: this.broadcast,\n\t\t\ttarget: this.in.target,\n\t\t\tsupported: Video.Decoder.supported,\n\t\t\tprobe: this.in.probe,\n\t\t});\n\t\tconst audioSource = new Audio.Source({\n\t\t\tbroadcast: this.broadcast,\n\t\t\ttarget: this.in.audioTarget,\n\t\t\tsupported: Audio.Decoder.supported,\n\t\t});\n\t\tthis.#signals.cleanup(() => {\n\t\t\tvideoSource.close();\n\t\t\taudioSource.close();\n\t\t});\n\n\t\tthis.text = new Text.Source({ broadcast: this.broadcast, target: this.in.captions });\n\t\tthis.#signals.cleanup(() => this.text.close());\n\n\t\tthis.sync = new Sync({ delay: this.in.delay, buffer: this.in.buffer, probe: this.in.probe });\n\t\tthis.#signals.cleanup(() => this.sync.close());\n\n\t\tthis.video = new Video.Decoder({ source: videoSource, sync: this.sync, enabled: this.#videoEnabled });\n\t\tthis.audio = new Audio.Decoder({ source: audioSource, sync: this.sync, enabled: this.#audioEnabled });\n\t\tthis.#signals.cleanup(() => {\n\t\t\tthis.video.close();\n\t\t\tthis.audio.close();\n\t\t});\n\n\t\tthis.emitter = new Audio.Emitter({\n\t\t\tsource: this.audio,\n\t\t\tvolume: this.in.volume,\n\t\t\tmuted: this.in.muted,\n\t\t\tpaused: this.in.paused,\n\t\t});\n\t\tthis.renderer = new Video.Renderer({\n\t\t\tdecoder: this.video,\n\t\t\tcanvas: this.in.canvas,\n\t\t\tvisible: this.in.visible,\n\t\t});\n\t\tthis.#signals.cleanup(() => {\n\t\t\tthis.emitter.close();\n\t\t\tthis.renderer.close();\n\t\t});\n\n\t\tthis.textRenderer = new Text.Renderer({\n\t\t\tsource: this.text,\n\t\t\tsync: this.sync,\n\t\t\tcontainer: this.in.container,\n\t\t\tenabled: this.#captionsEnabled,\n\t\t});\n\t\tthis.#signals.cleanup(() => this.textRenderer.close());\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tthis.#captionsEnabled.set(effect.get(this.in.enabled) && !effect.get(this.in.paused));\n\t\t});\n\t\tthis.#signals.run((effect) => {\n\t\t\tthis.#audioEnabled.set(effect.get(this.in.enabled) && effect.get(this.emitter.out.enabled));\n\t\t});\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst visible = effect.get(this.renderer.out.visible);\n\t\t\tif (!effect.get(this.in.enabled)) {\n\t\t\t\tthis.#videoEnabled.set(false);\n\t\t\t} else if (!effect.get(this.in.paused)) {\n\t\t\t\tthis.#videoEnabled.set(visible);\n\t\t\t} else {\n\t\t\t\t// Keep downloading a paused poster until a frame is painted, then stop.\n\t\t\t\tthis.#videoEnabled.set(visible && !effect.get(this.renderer.out.frame));\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Re-anchor buffered playback at a content boundary. */\n\treset(): void {\n\t\tthis.sync.reset();\n\t\tthis.audio.reset();\n\t}\n\n\t/** Release all subscriptions and media resources. */\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n"],"x_google_ignoreList":[16,17],"mappings":"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"}
|