@moq/watch 0.2.15 → 0.2.16

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.
Files changed (58) hide show
  1. package/broadcast-CG8jZRfA.js +1651 -0
  2. package/broadcast-CG8jZRfA.js.map +1 -0
  3. package/element.js +1 -1
  4. package/index.js +1 -1
  5. package/package.json +3 -3
  6. package/support/element.js +39 -39
  7. package/support/element.js.map +1 -1
  8. package/ui/components/button.d.ts +3 -0
  9. package/ui/components/button.d.ts.map +1 -0
  10. package/ui/components/center-play.d.ts +5 -0
  11. package/ui/components/center-play.d.ts.map +1 -0
  12. package/ui/components/control-bar.d.ts +6 -0
  13. package/ui/components/control-bar.d.ts.map +1 -0
  14. package/ui/components/fullscreen-button.d.ts +2 -1
  15. package/ui/components/fullscreen-button.d.ts.map +1 -1
  16. package/ui/components/latency.d.ts +5 -0
  17. package/ui/components/latency.d.ts.map +1 -0
  18. package/ui/components/live-badge.d.ts +9 -0
  19. package/ui/components/live-badge.d.ts.map +1 -0
  20. package/ui/components/play-pause.d.ts +1 -0
  21. package/ui/components/play-pause.d.ts.map +1 -1
  22. package/ui/components/quality.d.ts +5 -0
  23. package/ui/components/quality.d.ts.map +1 -0
  24. package/ui/components/settings-button.d.ts +5 -0
  25. package/ui/components/settings-button.d.ts.map +1 -0
  26. package/ui/components/settings-panel.d.ts +6 -0
  27. package/ui/components/settings-panel.d.ts.map +1 -0
  28. package/ui/components/volume-slider.d.ts +1 -0
  29. package/ui/components/volume-slider.d.ts.map +1 -1
  30. package/ui/element.d.ts.map +1 -1
  31. package/ui/element.js +577 -260
  32. package/ui/element.js.map +1 -1
  33. package/ui/format.d.ts +11 -0
  34. package/ui/format.d.ts.map +1 -0
  35. package/ui/fullscreen.d.ts +16 -0
  36. package/ui/fullscreen.d.ts.map +1 -0
  37. package/ui/graph.d.ts +18 -0
  38. package/ui/graph.d.ts.map +1 -0
  39. package/ui/icons.d.ts +5 -1
  40. package/ui/icons.d.ts.map +1 -1
  41. package/ui/state.d.ts +9 -0
  42. package/ui/state.d.ts.map +1 -0
  43. package/ui/stats.d.ts +3 -2
  44. package/ui/stats.d.ts.map +1 -1
  45. package/broadcast-jtLUfuyL.js +0 -4291
  46. package/broadcast-jtLUfuyL.js.map +0 -1
  47. package/dom-Yz3UT5ap.js +0 -29
  48. package/dom-Yz3UT5ap.js.map +0 -1
  49. package/libav-opus-af-CzuaKpJb.js +0 -252
  50. package/libav-opus-af-CzuaKpJb.js.map +0 -1
  51. package/main-bFFHfuX4.js +0 -1999
  52. package/main-bFFHfuX4.js.map +0 -1
  53. package/ui/components/quality-selector.d.ts +0 -4
  54. package/ui/components/quality-selector.d.ts.map +0 -1
  55. package/ui/components/stats-button.d.ts +0 -3
  56. package/ui/components/stats-button.d.ts.map +0 -1
  57. package/ui/components/watch-status-indicator.d.ts +0 -4
  58. package/ui/components/watch-status-indicator.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broadcast-CG8jZRfA.js","names":["#control","#samples","#timestamp","#stalled","#worklet","#ring","#signals","#context","#worklet","#sampleRate","#stats","#timestamp","#stalled","#buffered","#signals","#runWorklet","#runEnabled","#runLatency","#runDecoder","#ring","#trimDecodeBuffered","#runCmafDecoder","#runLegacyDecoder","#decodeBuffered","#emit","#addDecodeBuffered","#signals","#unmuteVolume","#gain","#mediaSource","#sync","#signals","#runMediaSource","#runSkip","#runTrim","#runPaused","#runSync","#reference","#buffer","#connection","#update","#runJitter","#runBuffer","#minRtt","#late","#formatDuration","#frame","#timestamp","#display","#stalled","#stats","#buffered","#signals","#runPending","#runActive","#runDisplay","#runBuffering","#active","#clearCurrentFrame","#run","#trimBuffered","#runCmaf","#runLegacy","#addBuffered","#stats","#buffered","#stalled","#timestamp","#runMedia","#runStalled","#runTimestamp","#runCmafMedia","#runLegacyMedia","#appendBuffer","#signals","#ctx","#runVisible","#runEnabled","#runRender","#runResize","#visible","#render","#available","#track","#config","#signals","#runSupported","#runSelected","#select","#videoSource","#audioSource","#runElement","#runWebcodecs","#runMse","#stats","#buffered","#signals","#runMedia","#runVolume","#runCmafMedia","#runLegacyMedia","#appendBuffer","#available","#track","#config","#signals","#runSupported","#runSelected","#select","#active","#announced","#runAnnouncedNow","#runBroadcast","#runCatalog","#announcedNow"],"sources":["../src/base64.ts","../src/audio/shared-ring-buffer.ts","../src/audio/buffer.ts","../src/audio/render-worklet.ts?worklet","../src/audio/decoder.ts","../src/audio/emitter.ts","../src/mse.ts","../src/sync.ts","../src/video/decoder.ts","../src/video/mse.ts","../src/video/renderer.ts","../src/video/source.ts","../src/video/index.ts","../src/backend.ts","../src/audio/mse.ts","../src/audio/source.ts","../src/audio/index.ts","../src/msf.ts","../src/broadcast.ts"],"sourcesContent":["/** Decode a base64 string into bytes. Throws on invalid input. */\nexport function base64ToBytes(b64: string): Uint8Array {\n\tconst raw = atob(b64);\n\tconst bytes = new Uint8Array(raw.length);\n\tfor (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);\n\treturn bytes;\n}\n","import { Time } from \"@moq/net\";\n\n// Control array slot indices\nconst WRITE = 0;\nconst READ = 1;\nconst LATENCY = 2;\nconst STALLED = 3;\nconst CONTROL_SLOTS = 4;\n\nexport interface SharedRingBufferInit {\n\tchannels: number;\n\tcapacity: number; // samples per channel\n\trate: number;\n\tsamples: SharedArrayBuffer; // channels * capacity * Float32Array.BYTES_PER_ELEMENT bytes\n\tcontrol: SharedArrayBuffer; // CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT bytes\n}\n\nexport function allocSharedRingBuffer(channels: number, capacity: number, rate: number): SharedRingBufferInit {\n\tif (channels <= 0) throw new Error(\"invalid channels\");\n\tif (capacity <= 0) throw new Error(\"invalid capacity\");\n\tif (rate <= 0) throw new Error(\"invalid sample rate\");\n\n\tconst samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\n\tconst control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\n\n\t// Initialize STALLED to 1\n\tconst ctrl = new Int32Array(control);\n\tAtomics.store(ctrl, STALLED, 1);\n\n\treturn { channels, capacity, rate, samples, control };\n}\n\n/** Modular i32 max: returns a if a is ahead of b, else b. */\nfunction i32Max(a: number, b: number): number {\n\treturn ((a - b) | 0) > 0 ? a : b;\n}\n\n/** Maps an absolute sample index to a [0, capacity) array slot. */\nfunction slot(idx: number, capacity: number): number {\n\treturn ((idx % capacity) + capacity) % capacity;\n}\n\n/**\n * Atomically advance `arr[idx]` to `candidate` iff `candidate` is strictly ahead\n * (in modular i32 ordering). Retries under contention so the slot only ever\n * moves forward and concurrent writers/readers can't clobber each other.\n */\nfunction casAdvance(arr: Int32Array, idx: number, candidate: number): number {\n\tfor (;;) {\n\t\tconst current = Atomics.load(arr, idx);\n\t\tif (((candidate - current) | 0) <= 0) return current;\n\t\tconst witnessed = Atomics.compareExchange(arr, idx, current, candidate);\n\t\tif (witnessed === current) return candidate;\n\t}\n}\n\nexport class SharedRingBuffer {\n\treadonly channels: number;\n\treadonly capacity: number;\n\treadonly rate: number;\n\treadonly init: SharedRingBufferInit;\n\n\t#control: Int32Array;\n\t#samples: Float32Array[];\n\n\tconstructor(init: SharedRingBufferInit) {\n\t\tthis.channels = init.channels;\n\t\tthis.capacity = init.capacity;\n\t\tthis.rate = init.rate;\n\t\tthis.init = init;\n\n\t\tthis.#control = new Int32Array(init.control);\n\t\tthis.#samples = [];\n\t\tfor (let i = 0; i < this.channels; i++) {\n\t\t\tthis.#samples.push(\n\t\t\t\tnew Float32Array(init.samples, i * this.capacity * Float32Array.BYTES_PER_ELEMENT, this.capacity),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Insert audio samples at the given timestamp.\n\t * Main thread only. Handles out-of-order writes, gap filling, and overflow.\n\t */\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tif (data.length !== this.channels) throw new Error(\"wrong number of channels\");\n\n\t\tlet start = Math.round(Time.Second.fromMicro(timestamp) * this.rate);\n\t\tconst originalLength = data[0].length;\n\t\tlet offset = 0;\n\n\t\tconst end = (start + originalLength) | 0;\n\n\t\t// Trim old: discard samples before the read index\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\tconst behind = (read - start) | 0;\n\t\tif (behind > 0) {\n\t\t\tif (behind >= originalLength) {\n\t\t\t\t// All samples are too old\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toffset = behind;\n\t\t\tstart = (start + behind) | 0;\n\t\t}\n\n\t\tconst samples = originalLength - offset;\n\n\t\t// Overflow: if the write would exceed capacity from current READ, advance READ.\n\t\t// Use CAS so a concurrent reader advance isn't clobbered backward.\n\t\tif (((end - read) | 0) > this.capacity) {\n\t\t\tcasAdvance(this.#control, READ, (end - this.capacity) | 0);\n\t\t}\n\n\t\t// Gap fill: zero-fill from current WRITE to start if there's a discontinuity\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst gap = (start - write) | 0;\n\t\tif (gap > 0) {\n\t\t\tconst gapSize = Math.min(gap, this.capacity);\n\t\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\t\tconst dst = this.#samples[channel];\n\t\t\t\tfor (let i = 0; i < gapSize; i++) {\n\t\t\t\t\tdst[slot((write + i) | 0, this.capacity)] = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Write sample data\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = data[channel];\n\t\t\tconst dst = this.#samples[channel];\n\t\t\tfor (let i = 0; i < samples; i++) {\n\t\t\t\tdst[slot((start + i) | 0, this.capacity)] = src[offset + i];\n\t\t\t}\n\t\t}\n\n\t\t// Advance WRITE (only forward)\n\t\tAtomics.store(this.#control, WRITE, i32Max(Atomics.load(this.#control, WRITE), end));\n\n\t\t// Un-stall: if buffered data >= LATENCY\n\t\tconst currentRead = Atomics.load(this.#control, READ);\n\t\tconst currentWrite = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tif (((currentWrite - currentRead) | 0) >= latency && latency > 0) {\n\t\t\tAtomics.store(this.#control, STALLED, 0);\n\t\t}\n\t}\n\n\t/**\n\t * Read audio samples into the output buffers.\n\t * AudioWorklet only. Returns the number of samples read.\n\t */\n\tread(output: Float32Array[]): number {\n\t\tif (Atomics.load(this.#control, STALLED) === 1) return 0;\n\n\t\tlet read = Atomics.load(this.#control, READ);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\n\t\t// Latency skip: if buffered data exceeds LATENCY, skip ahead.\n\t\t// CAS ensures we never step backward relative to a concurrent writer advance.\n\t\tconst buffered = (write - read) | 0;\n\t\tif (latency > 0 && buffered > latency) {\n\t\t\tconst skipTo = (write - latency) | 0;\n\t\t\tread = casAdvance(this.#control, READ, skipTo);\n\t\t}\n\n\t\tconst available = (write - read) | 0;\n\t\tconst count = Math.min(available, output[0].length);\n\t\tif (count <= 0) return 0;\n\n\t\t// Copy samples\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = this.#samples[channel];\n\t\t\tconst dst = output[channel];\n\t\t\tfor (let i = 0; i < count; i++) {\n\t\t\t\tdst[i] = src[slot((read + i) | 0, this.capacity)];\n\t\t\t}\n\t\t}\n\n\t\t// Advance READ via CAS so a concurrent writer overflow can't be undone.\n\t\tcasAdvance(this.#control, READ, (read + count) | 0);\n\n\t\treturn count;\n\t}\n\n\t/** Update the target latency in samples. */\n\tsetLatency(samples: number): void {\n\t\tAtomics.store(this.#control, LATENCY, samples);\n\t}\n\n\t/**\n\t * 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);\n\t\tconst dst = new SharedRingBuffer(init);\n\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tconst stalled = Atomics.load(this.#control, STALLED);\n\n\t\tconst available = (write - read) | 0;\n\t\tconst copyCount = Math.max(0, Math.min(available, dst.capacity));\n\t\tconst copyStart = (write - copyCount) | 0;\n\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = this.#samples[channel];\n\t\t\tconst out = dst.#samples[channel];\n\t\t\tfor (let i = 0; i < copyCount; i++) {\n\t\t\t\tconst idx = (copyStart + i) | 0;\n\t\t\t\tout[slot(idx, dst.capacity)] = src[slot(idx, this.capacity)];\n\t\t\t}\n\t\t}\n\n\t\tAtomics.store(dst.#control, READ, copyStart);\n\t\tAtomics.store(dst.#control, WRITE, write);\n\t\tAtomics.store(dst.#control, LATENCY, latency);\n\t\tAtomics.store(dst.#control, STALLED, stalled);\n\n\t\treturn dst;\n\t}\n\n\t/** Current playback timestamp derived from READ position. */\n\tget timestamp(): Time.Micro {\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\treturn Time.Micro.fromSecond((read / this.rate) as Time.Second);\n\t}\n\n\t/** Whether the buffer is stalled (waiting to fill). */\n\tget stalled(): boolean {\n\t\treturn Atomics.load(this.#control, STALLED) === 1;\n\t}\n\n\t/**\n\t * Number of buffered samples (WRITE - READ).\n\t *\n\t * Non-atomic: WRITE and READ are loaded separately, so a concurrent\n\t * writer/reader can make the two loads inconsistent. Intended for\n\t * tests and diagnostics, not control-flow decisions.\n\t */\n\tget length(): number {\n\t\treturn (Atomics.load(this.#control, WRITE) - Atomics.load(this.#control, READ)) | 0;\n\t}\n}\n","import { Time } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { Data, InitPost, InitShared, Latency, State } from \"./render\";\nimport { allocSharedRingBuffer, SharedRingBuffer } from \"./shared-ring-buffer\";\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/** 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): AudioBuffer {\n\tif (supportsSharedArrayBuffer()) {\n\t\tconsole.log(\"[audio] using SharedArrayBuffer audio buffer\");\n\t\treturn new SharedAudioBuffer(worklet, channels, rate, latencySamples);\n\t}\n\tconsole.log(\"[audio] using postMessage audio buffer (SharedArrayBuffer unavailable)\");\n\treturn new PostAudioBuffer(worklet, channels, rate, latencySamples);\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#signals = new Effect();\n\n\tconstructor(worklet: AudioWorkletNode, channels: number, rate: number, latencySamples: number) {\n\t\tthis.#worklet = worklet;\n\t\tthis.channels = channels;\n\t\tthis.rate = rate;\n\n\t\t// Capacity needs headroom above LATENCY for overflow protection.\n\t\tconst capacity = Math.max(rate, latencySamples * 2);\n\t\tconst init = allocSharedRingBuffer(channels, capacity, rate);\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\tthis.#timestamp.set(this.#ring.timestamp);\n\t\t\tthis.#stalled.set(this.#ring.stalled);\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\t// Grow the ring (preserving the unread window) if it's too small for the new latency.\n\t\tif (this.#ring.capacity < samples * 1.5) {\n\t\t\tconst newCapacity = Math.max(this.rate, samples * 2);\n\t\t\tthis.#ring = this.#ring.resize(newCapacity);\n\t\t\tthis.#ring.setLatency(samples);\n\n\t\t\tconst msg: InitShared = { type: \"init-shared\", ...this.#ring.init };\n\t\t\tthis.#worklet.port.postMessage(msg);\n\t\t} else {\n\t\t\tthis.#ring.setLatency(samples);\n\t\t}\n\t}\n\n\tclose(): void {\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#signals = new Effect();\n\n\tconstructor(worklet: AudioWorkletNode, channels: number, rate: number, latencySamples: number) {\n\t\tthis.#worklet = worklet;\n\t\tthis.channels = channels;\n\t\tthis.rate = 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 };\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}\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\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\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n","const code = \"var __defProp = Object.defineProperty;\\nvar __getOwnPropNames = Object.getOwnPropertyNames;\\nvar __esm = (fn, res) => function __init() {\\n return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;\\n};\\nvar __export = (target, all) => {\\n for (var name in all)\\n __defProp(target, name, { get: all[name], enumerable: true });\\n};\\n\\n// ../net/src/path.ts\\nvar init_path = __esm({\\n \\\"../net/src/path.ts\\\"() {\\n \\\"use strict\\\";\\n }\\n});\\n\\n// ../net/src/varint.ts\\nvar MAX_U6, MAX_U14, MAX_U30, MAX_U53, MAX_U64, MAX_U62;\\nvar init_varint = __esm({\\n \\\"../net/src/varint.ts\\\"() {\\n \\\"use strict\\\";\\n MAX_U6 = 2 ** 6 - 1;\\n MAX_U14 = 2 ** 14 - 1;\\n MAX_U30 = 2 ** 30 - 1;\\n MAX_U53 = Number.MAX_SAFE_INTEGER;\\n MAX_U64 = (1n << 64n) - 1n;\\n MAX_U62 = 2n ** 62n - 1n;\\n }\\n});\\n\\n// ../net/src/index.ts\\ninit_path();\\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});\\nvar Nano = {\\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 = {\\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 = {\\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 Second = {\\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// ../net/src/index.ts\\ninit_varint();\\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 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 const samples = Math.ceil(props.rate * time_exports.Second.fromMilli(props.latency));\\n if (samples === 0) throw new Error(\\\"empty buffer\\\");\\n this.rate = props.rate;\\n this.channels = props.channels;\\n this.#buffer = [];\\n for (let i = 0; i < this.channels; i++) {\\n this.#buffer[i] = new Float32Array(samples);\\n }\\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 newCapacity = Math.ceil(this.rate * time_exports.Second.fromMilli(latency));\\n if (newCapacity === this.capacity) return;\\n if (newCapacity === 0) throw new Error(\\\"empty buffer\\\");\\n const newBuffer = [];\\n for (let i = 0; i < this.channels; i++) {\\n newBuffer[i] = new Float32Array(newCapacity);\\n }\\n const samplesToKeep = Math.min(this.length, newCapacity);\\n if (samplesToKeep > 0) {\\n const copyStart = this.#writeIndex - samplesToKeep;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#buffer[channel];\\n const dst = newBuffer[channel];\\n for (let i = 0; i < samplesToKeep; i++) {\\n const srcPos = (copyStart + i) % src.length;\\n const dstPos = (copyStart + i) % dst.length;\\n dst[dstPos] = src[srcPos];\\n }\\n }\\n }\\n this.#buffer = newBuffer;\\n this.#readIndex = this.#writeIndex - samplesToKeep;\\n if (samplesToKeep === 0) this.#stalled = true;\\n }\\n write(timestamp, data) {\\n if (data.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n let start = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n let samples = data[0].length;\\n 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 }\\n read(output) {\\n if (output.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n if (this.#stalled) return 0;\\n const samples = Math.min(this.#writeIndex - this.#readIndex, output[0].length);\\n if (samples === 0) return 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = output[channel];\\n const src = this.#buffer[channel];\\n if (dst.length !== output[0].length) throw new Error(\\\"mismatching number of samples\\\");\\n for (let i = 0; i < samples; i++) {\\n const readPos = (this.#readIndex + i) % src.length;\\n dst[i] = src[readPos];\\n }\\n }\\n this.#readIndex += samples;\\n return samples;\\n }\\n};\\n\\n// src/audio/shared-ring-buffer.ts\\nvar WRITE = 0;\\nvar READ = 1;\\nvar LATENCY = 2;\\nvar STALLED = 3;\\nvar CONTROL_SLOTS = 4;\\nfunction allocSharedRingBuffer(channels, capacity, rate) {\\n if (channels <= 0) throw new Error(\\\"invalid channels\\\");\\n if (capacity <= 0) throw new Error(\\\"invalid capacity\\\");\\n if (rate <= 0) throw new Error(\\\"invalid sample rate\\\");\\n const samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\\n const control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\\n const ctrl = new Int32Array(control);\\n Atomics.store(ctrl, STALLED, 1);\\n return { channels, capacity, rate, samples, control };\\n}\\nfunction i32Max(a, b) {\\n return (a - b | 0) > 0 ? a : b;\\n}\\nfunction slot(idx, capacity) {\\n return (idx % capacity + capacity) % capacity;\\n}\\nfunction casAdvance(arr, idx, candidate) {\\n for (; ; ) {\\n const current = Atomics.load(arr, idx);\\n if ((candidate - current | 0) <= 0) return current;\\n const witnessed = Atomics.compareExchange(arr, idx, current, candidate);\\n if (witnessed === current) return candidate;\\n }\\n}\\nvar SharedRingBuffer = class _SharedRingBuffer {\\n channels;\\n capacity;\\n rate;\\n init;\\n #control;\\n #samples;\\n constructor(init) {\\n this.channels = init.channels;\\n this.capacity = init.capacity;\\n this.rate = init.rate;\\n this.init = init;\\n this.#control = new Int32Array(init.control);\\n this.#samples = [];\\n for (let i = 0; i < this.channels; i++) {\\n this.#samples.push(\\n new Float32Array(init.samples, i * this.capacity * Float32Array.BYTES_PER_ELEMENT, this.capacity)\\n );\\n }\\n }\\n /**\\n * Insert audio samples at the given timestamp.\\n * Main thread only. Handles out-of-order writes, gap filling, and overflow.\\n */\\n insert(timestamp, data) {\\n if (data.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n let start = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n const originalLength = data[0].length;\\n let offset = 0;\\n const end = start + originalLength | 0;\\n const read = Atomics.load(this.#control, READ);\\n const behind = read - start | 0;\\n if (behind > 0) {\\n if (behind >= originalLength) {\\n return;\\n }\\n offset = behind;\\n start = start + behind | 0;\\n }\\n const samples = originalLength - offset;\\n if ((end - read | 0) > this.capacity) {\\n casAdvance(this.#control, READ, end - this.capacity | 0);\\n }\\n const write = Atomics.load(this.#control, WRITE);\\n const gap = start - write | 0;\\n if (gap > 0) {\\n const gapSize = Math.min(gap, this.capacity);\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = this.#samples[channel];\\n for (let i = 0; i < gapSize; i++) {\\n dst[slot(write + i | 0, this.capacity)] = 0;\\n }\\n }\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = data[channel];\\n const dst = this.#samples[channel];\\n for (let i = 0; i < samples; i++) {\\n dst[slot(start + i | 0, this.capacity)] = src[offset + i];\\n }\\n }\\n Atomics.store(this.#control, WRITE, i32Max(Atomics.load(this.#control, WRITE), end));\\n const currentRead = Atomics.load(this.#control, READ);\\n const currentWrite = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n if ((currentWrite - currentRead | 0) >= latency && latency > 0) {\\n Atomics.store(this.#control, STALLED, 0);\\n }\\n }\\n /**\\n * Read audio samples into the output buffers.\\n * AudioWorklet only. Returns the number of samples read.\\n */\\n read(output) {\\n if (Atomics.load(this.#control, STALLED) === 1) return 0;\\n let read = Atomics.load(this.#control, READ);\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n const buffered = write - read | 0;\\n if (latency > 0 && buffered > latency) {\\n const skipTo = write - latency | 0;\\n read = casAdvance(this.#control, READ, skipTo);\\n }\\n const available = write - read | 0;\\n const count = Math.min(available, output[0].length);\\n if (count <= 0) return 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#samples[channel];\\n const dst = output[channel];\\n for (let i = 0; i < count; i++) {\\n dst[i] = src[slot(read + i | 0, this.capacity)];\\n }\\n }\\n casAdvance(this.#control, READ, read + count | 0);\\n return count;\\n }\\n /** Update the target latency in samples. */\\n setLatency(samples) {\\n Atomics.store(this.#control, LATENCY, samples);\\n }\\n /**\\n * 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);\\n const dst = new _SharedRingBuffer(init);\\n const read = Atomics.load(this.#control, READ);\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n const stalled = Atomics.load(this.#control, STALLED);\\n const available = write - read | 0;\\n const copyCount = Math.max(0, Math.min(available, dst.capacity));\\n const copyStart = write - copyCount | 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#samples[channel];\\n const out = dst.#samples[channel];\\n for (let i = 0; i < copyCount; i++) {\\n const idx = copyStart + i | 0;\\n out[slot(idx, dst.capacity)] = src[slot(idx, this.capacity)];\\n }\\n }\\n Atomics.store(dst.#control, READ, copyStart);\\n Atomics.store(dst.#control, WRITE, write);\\n Atomics.store(dst.#control, LATENCY, latency);\\n Atomics.store(dst.#control, STALLED, stalled);\\n return dst;\\n }\\n /** Current playback timestamp derived from READ position. */\\n get timestamp() {\\n const read = Atomics.load(this.#control, READ);\\n return time_exports.Micro.fromSecond(read / this.rate);\\n }\\n /** Whether the buffer is stalled (waiting to fill). */\\n get stalled() {\\n return Atomics.load(this.#control, STALLED) === 1;\\n }\\n /**\\n * Number of buffered samples (WRITE - READ).\\n *\\n * Non-atomic: WRITE and READ are loaded separately, so a concurrent\\n * writer/reader can make the two loads inconsistent. Intended for\\n * tests and diagnostics, not control-flow decisions.\\n */\\n get length() {\\n return Atomics.load(this.#control, WRITE) - Atomics.load(this.#control, READ) | 0;\\n }\\n};\\n\\n// src/audio/render-worklet.ts\\nvar Render = class extends AudioWorkletProcessor {\\n // Set after init, depending on which path the main thread chose.\\n #backend;\\n #underflow = 0;\\n #stateCounter = 0;\\n constructor() {\\n super();\\n this.port.onmessage = (event) => {\\n const msg = event.data;\\n if (msg.type === \\\"init-shared\\\") {\\n console.log(\\\"[audio-worklet] init-shared: using SharedArrayBuffer path\\\");\\n this.#backend = new SharedRingBuffer(msg);\\n this.#underflow = 0;\\n } else if (msg.type === \\\"init-post\\\") {\\n console.log(\\\"[audio-worklet] init-post: using postMessage path\\\");\\n this.#backend = new AudioRingBuffer(msg);\\n this.#underflow = 0;\\n } else if (msg.type === \\\"data\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.write(msg.timestamp, msg.data);\\n } else if (msg.type === \\\"latency\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.resize(msg.latency);\\n }\\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 * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport * as Util from \"@moq/hang/util\";\nimport type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { BufferedRanges } from \"../backend\";\nimport { base64ToBytes } from \"../base64\";\nimport { type AudioBuffer, createAudioBuffer } from \"./buffer\";\n// Compiled and inlined as a blob URL via vite-plugin-worklet.\nimport RenderWorklet from \"./render-worklet.ts?worklet\";\nimport type { Source } from \"./source\";\n\nexport type DecoderProps = {\n\t// Enable to download the audio track.\n\tenabled?: boolean | Signal<boolean>;\n};\n\nexport interface AudioStats {\n\tbytesReceived: number;\n}\n\n// Downloads audio from a track and emits it to an AudioContext.\n// The user is responsible for hooking up audio to speakers, an analyzer, etc.\nexport class Decoder {\n\tsource: Source;\n\tenabled: Signal<boolean>;\n\n\t#context = new Signal<AudioContext | undefined>(undefined);\n\treadonly context: Getter<AudioContext | undefined> = this.#context;\n\n\t// The root of the audio graph, which can be used for custom visualizations.\n\t#worklet = new Signal<AudioWorkletNode | undefined>(undefined);\n\t// Downcast to AudioNode so it matches Publish.Audio\n\treadonly root = this.#worklet as Getter<AudioNode | undefined>;\n\n\t#sampleRate = new Signal<number | undefined>(undefined);\n\treadonly sampleRate: Getter<number | undefined> = this.#sampleRate;\n\n\t#stats = new Signal<AudioStats | undefined>(undefined);\n\treadonly stats: Getter<AudioStats | undefined> = this.#stats;\n\n\t// Current playback timestamp from worklet\n\t#timestamp = new Signal<Time.Milli | undefined>(undefined);\n\treadonly timestamp: Getter<Time.Milli | undefined> = this.#timestamp;\n\n\t// Whether the audio buffer is stalled (waiting to fill)\n\t#stalled = new Signal<boolean>(true);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t// Decode buffer: audio sent to worklet but not yet played\n\t#decodeBuffered = new Signal<BufferedRanges>([]);\n\n\t// Combined buffered ranges (network jitter + decode buffer)\n\t#buffered = new Signal<BufferedRanges>([]);\n\treadonly buffered: Getter<BufferedRanges> = this.#buffered;\n\n\t// Audio ring bridging main thread and worklet (shared memory or postMessage transport).\n\t#ring: AudioBuffer | undefined;\n\n\t#signals = new Effect();\n\n\tconstructor(source: Source, props?: DecoderProps) {\n\t\tthis.source = source;\n\t\tthis.source.supported.set(supported); // super hacky\n\n\t\tthis.enabled = Signal.from(props?.enabled ?? false);\n\n\t\tthis.#signals.run(this.#runWorklet.bind(this));\n\t\tthis.#signals.run(this.#runEnabled.bind(this));\n\t\tthis.#signals.run(this.#runLatency.bind(this));\n\t\tthis.#signals.run(this.#runDecoder.bind(this));\n\t}\n\n\t#runWorklet(effect: Effect): void {\n\t\t// It takes a second or so to initialize the AudioContext/AudioWorklet, so do it even if disabled.\n\t\t// This is less efficient for video-only playback but makes muting/unmuting instant.\n\n\t\t//const enabled = effect.get(this.enabled);\n\t\t//if (!enabled) return;\n\n\t\tconst config = effect.get(this.source.config);\n\t\tif (!config) return;\n\n\t\tconst sampleRate = config.sampleRate;\n\t\tconst channelCount = config.numberOfChannels;\n\n\t\t// NOTE: We still create an AudioContext even when muted.\n\t\t// This way we can process the audio for visualizations.\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.#context, context);\n\n\t\teffect.cleanup(() => context.close());\n\n\t\teffect.spawn(async () => {\n\t\t\t// Register the AudioWorklet processor\n\t\t\tawait context.audioWorklet.addModule(RenderWorklet);\n\n\t\t\t// Ensure the context is running before creating the worklet\n\t\t\tif (context.state === \"closed\") return;\n\n\t\t\t// Create the worklet node. outputChannelCount must be set explicitly\n\t\t\t// so the process() callback receives a matching channel layout —\n\t\t\t// Firefox defaults differently than Chrome otherwise.\n\t\t\tconst worklet = new AudioWorkletNode(context, \"render\", {\n\t\t\t\tchannelCount,\n\t\t\t\tchannelCountMode: \"explicit\",\n\t\t\t\toutputChannelCount: [channelCount],\n\t\t\t});\n\t\t\teffect.cleanup(() => worklet.disconnect());\n\n\t\t\t// Initial target latency in samples.\n\t\t\tconst latency = this.source.sync.buffer.peek();\n\t\t\tconst latencySamples = Math.ceil(sampleRate * Time.Second.fromMilli(latency));\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);\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.#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.#stalled.set(inner.get(ring.stalled));\n\t\t\t});\n\n\t\t\teffect.set(this.#worklet, worklet);\n\t\t});\n\t}\n\n\t#runEnabled(effect: Effect): void {\n\t\tconst values = effect.getAll([this.enabled, this.#context]);\n\t\tif (!values) return;\n\t\tconst [_, context] = values;\n\n\t\tcontext.resume();\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.#worklet);\n\t\tif (!worklet) return;\n\n\t\tconst ring = this.#ring;\n\t\tif (!ring) return;\n\n\t\tconst latency = effect.get(this.source.sync.buffer);\n\t\tconst latencySamples = Math.ceil(ring.rate * Time.Second.fromMilli(latency));\n\t\tring.setLatency(latencySamples);\n\t}\n\n\t#runDecoder(effect: Effect): void {\n\t\tconst enabled = effect.get(this.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst broadcast = effect.get(this.source.broadcast);\n\t\tif (!broadcast) return;\n\n\t\tconst track = effect.get(this.source.track);\n\t\tif (!track) return;\n\n\t\tconst config = effect.get(this.source.config);\n\t\tif (!config) return;\n\n\t\tconst active = effect.get(broadcast.active);\n\t\tif (!active) return;\n\n\t\tconst sub = active.subscribe(track, Catalog.PRIORITY.audio);\n\t\teffect.cleanup(() => sub.close());\n\n\t\tif (config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmafDecoder(effect, sub, config);\n\t\t} else {\n\t\t\tthis.#runLegacyDecoder(effect, sub, config);\n\t\t}\n\t}\n\n\t#runLegacyDecoder(effect: Effect, sub: Moq.Track, config: Catalog.AudioConfig): void {\n\t\tconst format = config.container.kind === \"loc\" ? new Container.Loc.Format() : new Container.Legacy.Format();\n\t\t// Create consumer with slightly less latency than the render worklet to avoid underflowing.\n\t\t// TODO include JITTER_UNDERHEAD\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat,\n\t\t\tlatency: this.source.sync.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#decodeBuffered);\n\t\t\tthis.#buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\teffect.spawn(async () => {\n\t\t\tconst loaded = await Util.Libav.polyfill();\n\t\t\tif (!loaded) return; // cancelled\n\n\t\t\tlet warmed = 0;\n\n\t\t\tconst decoder = new AudioDecoder({\n\t\t\t\toutput: (data) => {\n\t\t\t\t\twarmed++;\n\t\t\t\t\tif (warmed <= 3) {\n\t\t\t\t\t\t// Drop the first 3 frames to prime the decoder.\n\t\t\t\t\t\tdata.close();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.#emit(data);\n\t\t\t\t},\n\t\t\t\terror: (error) => console.error(error),\n\t\t\t});\n\t\t\teffect.cleanup(() => {\n\t\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t\t});\n\n\t\t\t// Opus in CMAF uses raw packets; dOps is not a valid OGG Identification Header.\n\t\t\tconst description =\n\t\t\t\tconfig.codec === \"opus\"\n\t\t\t\t\t? undefined\n\t\t\t\t\t: config.description\n\t\t\t\t\t\t? Util.Hex.toBytes(config.description)\n\t\t\t\t\t\t: undefined;\n\t\t\tdecoder.configure({\n\t\t\t\t...config,\n\t\t\t\tdescription,\n\t\t\t});\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\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.source.sync.received(timestamp, \"audio\");\n\n\t\t\t\tthis.#stats.update((stats) => ({\n\t\t\t\t\tbytesReceived: (stats?.bytesReceived ?? 0) + frame.data.byteLength,\n\t\t\t\t}));\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.data,\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t});\n\n\t\t\t\tdecoder.decode(chunk);\n\t\t\t}\n\t\t});\n\t}\n\n\t#runCmafDecoder(effect: Effect, sub: Moq.Track, config: Catalog.AudioConfig): void {\n\t\tif (config.container.kind !== \"cmaf\") return; // just to help typescript\n\n\t\tconst initSegment = base64ToBytes(config.container.init);\n\t\tconst init = Container.Cmaf.decodeInitSegment(initSegment);\n\t\t// Opus in CMAF uses raw packets (not OGG-wrapped), so description must be omitted.\n\t\t// The dOps box from the init segment is not a valid OGG Identification Header.\n\t\tconst description =\n\t\t\tconfig.codec === \"opus\"\n\t\t\t\t? undefined\n\t\t\t\t: config.description\n\t\t\t\t\t? Util.Hex.toBytes(config.description)\n\t\t\t\t\t: init.description;\n\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat: new Container.Cmaf.Format(init),\n\t\t\tlatency: this.source.sync.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#decodeBuffered);\n\t\t\tthis.#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(error),\n\t\t\t});\n\t\t\teffect.cleanup(() => {\n\t\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t\t});\n\n\t\t\t// Configure decoder with description from catalog\n\t\t\tdecoder.configure({\n\t\t\t\tcodec: config.codec,\n\t\t\t\tsampleRate: config.sampleRate,\n\t\t\t\tnumberOfChannels: config.numberOfChannels,\n\t\t\t\tdescription,\n\t\t\t});\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\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.source.sync.received(timestamp, \"audio\");\n\n\t\t\t\tthis.#stats.update((stats) => ({\n\t\t\t\t\tbytesReceived: (stats?.bytesReceived ?? 0) + frame.data.byteLength,\n\t\t\t\t}));\n\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tdecoder.decode(\n\t\t\t\t\tnew EncodedAudioChunk({\n\t\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\t\tdata: frame.data,\n\t\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\t#emit(sample: AudioData) {\n\t\tconst timestamp = sample.timestamp as Time.Micro;\n\t\tconst timestampMilli = Time.Milli.fromMicro(timestamp);\n\n\t\tconst ring = this.#ring;\n\t\tif (!ring) {\n\t\t\t// We're probably in the process of closing.\n\t\t\tsample.close();\n\t\t\treturn;\n\t\t}\n\n\t\t// Calculate end time from sample duration\n\t\tconst durationMicro = ((sample.numberOfFrames / sample.sampleRate) * 1_000_000) as Time.Micro;\n\t\tconst durationMilli = Time.Milli.fromMicro(durationMicro);\n\t\tconst end = Time.Milli.add(timestampMilli, durationMilli);\n\n\t\t// Add to decode buffer\n\t\tthis.#addDecodeBuffered(timestampMilli, end);\n\n\t\t// Firefox's Opus decoder sometimes outputs more channels than requested\n\t\t// (e.g. 6 for stereo). Clamp to the ring's channel count.\n\t\tconst channels = Math.min(sample.numberOfChannels, ring.channels);\n\t\tconst channelData: Float32Array[] = [];\n\t\tfor (let channel = 0; channel < channels; channel++) {\n\t\t\tconst data = new Float32Array(sample.numberOfFrames);\n\t\t\tsample.copyTo(data, { format: \"f32-planar\", planeIndex: channel });\n\t\t\tchannelData.push(data);\n\t\t}\n\n\t\t// Hand off to the ring. Shared transport writes directly; post transport\n\t\t// transfers the ArrayBuffers.\n\t\tring.insert(timestamp, channelData);\n\n\t\tsample.close();\n\t}\n\n\t#addDecodeBuffered(start: Time.Milli, end: Time.Milli): void {\n\t\tif (start > end) return;\n\n\t\tthis.#decodeBuffered.mutate((current) => {\n\t\t\tfor (const range of current) {\n\t\t\t\t// Extend range if new sample overlaps or is adjacent (1ms tolerance for float precision)\n\t\t\t\tif (start <= range.end + 1 && end >= range.start) {\n\t\t\t\t\trange.start = Time.Milli.min(range.start, start);\n\t\t\t\t\trange.end = Time.Milli.max(range.end, end);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrent.push({ start, end });\n\t\t\tcurrent.sort((a, b) => a.start - b.start);\n\t\t});\n\t}\n\n\t#trimDecodeBuffered(timestamp: Time.Milli): void {\n\t\tthis.#decodeBuffered.mutate((current) => {\n\t\t\twhile (current.length > 0) {\n\t\t\t\tif (current[0].end >= timestamp) {\n\t\t\t\t\tcurrent[0].start = Time.Milli.max(current[0].start, timestamp);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent.shift();\n\t\t\t}\n\t\t});\n\t}\n\n\tclose() {\n\t\tthis.#signals.close();\n\t}\n}\n\nasync function supported(config: Catalog.AudioConfig): Promise<boolean> {\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, Signal } from \"@moq/signals\";\nimport type { Decoder } from \"./decoder\";\n\nconst MIN_GAIN = 0.001;\nconst FADE_TIME = 0.2;\n\nexport type EmitterProps = {\n\tvolume?: number | Signal<number>;\n\tmuted?: boolean | Signal<boolean>;\n\tpaused?: boolean | Signal<boolean>;\n};\n\n// A helper that emits audio directly to the speakers.\nexport class Emitter {\n\tsource: Decoder;\n\tvolume: Signal<number>;\n\tmuted: Signal<boolean>;\n\n\t// Similar to muted, but controls whether we download audio at all.\n\t// That way we can be \"muted\" but also download audio for visualizations.\n\tpaused: Signal<boolean>;\n\n\t#signals = new Effect();\n\n\t// The volume to use when unmuted.\n\t#unmuteVolume = 0.5;\n\n\t// The gain node used to adjust the volume.\n\t#gain = new Signal<GainNode | undefined>(undefined);\n\n\tconstructor(source: Decoder, props?: EmitterProps) {\n\t\tthis.source = source;\n\t\tthis.volume = Signal.from(props?.volume ?? 0.5);\n\t\tthis.muted = Signal.from(props?.muted ?? false);\n\t\tthis.paused = Signal.from(props?.paused ?? props?.muted ?? false);\n\n\t\t// Set the volume to 0 when muted.\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst muted = effect.get(this.muted);\n\t\t\tif (muted) {\n\t\t\t\tthis.#unmuteVolume = this.volume.peek() || 0.5;\n\t\t\t\tthis.volume.set(0);\n\t\t\t} else {\n\t\t\t\tthis.volume.set(this.#unmuteVolume);\n\t\t\t}\n\t\t});\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst enabled = !effect.get(this.paused) && !effect.get(this.muted);\n\t\t\tthis.source.enabled.set(enabled);\n\t\t});\n\n\t\t// Set unmute when the volume is non-zero.\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst volume = effect.get(this.volume);\n\t\t\tthis.muted.set(volume === 0);\n\t\t});\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst root = effect.get(this.source.root);\n\t\t\tif (!root) return;\n\n\t\t\tconst gain = new GainNode(root.context, { gain: effect.get(this.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.source.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.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 { Time } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { Sync } from \"./sync\";\n\nexport type MuxerProps = {\n\telement?: HTMLMediaElement | Signal<HTMLMediaElement | undefined>;\n\tpaused?: boolean | Signal<boolean>;\n};\n\n/**\n * MSE-based video source for CMAF/fMP4 fragments.\n * Uses Media Source Extensions to handle complete moof+mdat fragments.\n */\nexport class Muxer {\n\telement: Signal<HTMLMediaElement | undefined>;\n\n\tpaused: Signal<boolean>;\n\n\t#sync: Sync;\n\n\t#mediaSource = new Signal<MediaSource | undefined>(undefined);\n\treadonly mediaSource: Getter<MediaSource | undefined> = this.#mediaSource;\n\n\t#signals = new Effect();\n\n\tconstructor(sync: Sync, props?: MuxerProps) {\n\t\tthis.element = Signal.from(props?.element);\n\t\tthis.paused = Signal.from(props?.paused ?? false);\n\t\tthis.#sync = sync;\n\n\t\tthis.#signals.run(this.#runMediaSource.bind(this));\n\t\tthis.#signals.run(this.#runSkip.bind(this));\n\t\tthis.#signals.run(this.#runTrim.bind(this));\n\t\tthis.#signals.run(this.#runPaused.bind(this));\n\t\tthis.#signals.run(this.#runSync.bind(this));\n\t}\n\n\t#runMediaSource(effect: Effect): void {\n\t\tconst element = effect.get(this.element);\n\t\tif (!element) return;\n\n\t\tconst mediaSource = new MediaSource();\n\n\t\telement.src = URL.createObjectURL(mediaSource);\n\t\teffect.cleanup(() => URL.revokeObjectURL(element.src));\n\n\t\teffect.event(\n\t\t\tmediaSource,\n\t\t\t\"sourceopen\",\n\t\t\t() => {\n\t\t\t\teffect.set(this.#mediaSource, mediaSource);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\n\t\teffect.event(mediaSource, \"error\", (e) => {\n\t\t\tconsole.error(\"[MSE] MediaSource error event:\", e);\n\t\t});\n\t}\n\n\t#runSkip(effect: Effect): void {\n\t\tconst element = effect.get(this.element);\n\t\tif (!element) return;\n\n\t\t// Don't skip when paused, otherwise we'll keep jerking forward.\n\t\tconst paused = effect.get(this.paused);\n\t\tif (paused) return;\n\n\t\t// Use the computed latency (catalog jitter + user jitter)\n\t\t// Convert to seconds since DOM APIs use seconds\n\t\tconst latency = Time.Milli.toSecond(effect.get(this.#sync.buffer));\n\n\t\teffect.interval(() => {\n\t\t\t// Skip over gaps based on the effective latency.\n\t\t\tconst buffered = element.buffered;\n\t\t\tif (buffered.length === 0) return;\n\n\t\t\tconst last = buffered.end(buffered.length - 1);\n\t\t\tconst target = last - latency;\n\t\t\tconst seek = target - element.currentTime;\n\n\t\t\t// Seek forward if we're too far behind, or backward if we're too far ahead (>100ms)\n\t\t\tif (seek > 0.1 || seek < -0.1) {\n\t\t\t\tconsole.warn(\"seeking\", seek > 0 ? \"forward\" : \"backward\", Math.abs(seek).toFixed(3), \"seconds\");\n\t\t\t\telement.currentTime = target;\n\t\t\t}\n\t\t}, 100);\n\t}\n\n\t#runTrim(effect: Effect): void {\n\t\tconst element = effect.get(this.element);\n\t\tif (!element) return;\n\n\t\tconst media = effect.get(this.mediaSource);\n\t\tif (!media) return;\n\n\t\t// Periodically clean up old buffered data.\n\t\teffect.interval(async () => {\n\t\t\tfor (const sourceBuffer of media.sourceBuffers) {\n\t\t\t\twhile (sourceBuffer.updating) {\n\t\t\t\t\tawait new Promise((resolve) => sourceBuffer.addEventListener(\"updateend\", resolve, { once: true }));\n\t\t\t\t}\n\n\t\t\t\t// Keep at least 10 seconds of buffered data to avoid removing I-frames.\n\t\t\t\tif (element.currentTime > 10) {\n\t\t\t\t\tsourceBuffer.remove(0, element.currentTime - 10);\n\t\t\t\t}\n\t\t\t}\n\t\t}, 1000);\n\t}\n\n\t#runPaused(effect: Effect): void {\n\t\tconst element = effect.get(this.element);\n\t\tif (!element) return;\n\n\t\tconst paused = effect.get(this.paused);\n\t\tif (paused && !element.paused) {\n\t\t\telement.pause();\n\t\t} else if (!paused && element.paused) {\n\t\t\telement.play().catch((e) => {\n\t\t\t\tconsole.error(\"[MSE] MediaElement play error:\", e);\n\t\t\t});\n\t\t}\n\t}\n\n\t// Seek to the target position based on the reference and latency.\n\t#runSync(effect: Effect): void {\n\t\tconst element = effect.get(this.element);\n\t\tif (!element) return;\n\n\t\t// Don't seek when paused, otherwise we'll keep jerking around.\n\t\tconst paused = effect.get(this.paused);\n\t\tif (paused) return;\n\n\t\tconst reference = effect.get(this.#sync.reference);\n\t\tif (reference === undefined) return;\n\n\t\tconst latency = effect.get(this.#sync.buffer);\n\n\t\t// Compute the target currentTime based on reference and latency.\n\t\t// reference = performance.now() - frameTimestamp (in ms) when we received the earliest frame\n\t\t// So the target media timestamp (in ms) at time `now` is: now - reference - latency\n\t\tconst target = Time.Milli.sub(Time.Milli.sub(Time.Milli.now(), reference), latency);\n\n\t\t// Seek to the target position.\n\t\telement.currentTime = Time.Milli.toSecond(target);\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, Signal } from \"@moq/signals\";\n\n/** Latency: `\"real-time\"` auto-computes jitter from RTT; a `Time.Milli` sets a fixed jitter. */\nexport type Latency = \"real-time\" | Time.Milli;\n\nconst MIN_JITTER = 20 as Time.Milli;\nconst FALLBACK_JITTER = 100 as Time.Milli;\n\nexport interface SyncProps {\n\tlatency?: Latency | Signal<Latency>;\n\tconnection?: Signal<Moq.Connection.Established | undefined>;\n\taudio?: Time.Milli | Signal<Time.Milli | undefined>;\n\tvideo?: Time.Milli | Signal<Time.Milli | undefined>;\n}\n\nexport class Sync {\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\t#reference = new Signal<Time.Milli | undefined>(undefined);\n\treadonly reference: Signal<Time.Milli | undefined> = this.#reference;\n\n\t// The latency setting: \"real-time\" auto-computes jitter from RTT, a number sets a fixed jitter.\n\tlatency: Signal<Latency>;\n\n\t// The jitter buffer in milliseconds (always numeric).\n\t// In \"real-time\" mode this is updated automatically from RTT.\n\t// When latency is a number, jitter equals that number.\n\tjitter: Signal<Time.Milli>;\n\n\t// Any additional delay required for audio or video.\n\taudio: Signal<Time.Milli | undefined>;\n\tvideo: Signal<Time.Milli | undefined>;\n\n\t// The total buffer required: jitter + max(audio, video).\n\t#buffer = new Signal<Time.Milli>(Time.Milli.zero);\n\treadonly buffer: Signal<Time.Milli> = this.#buffer;\n\n\t// A ghetto way to learn when the reference/buffer changes.\n\t// There's probably a way to use Effect, but lets keep it simple for now.\n\t#update: PromiseWithResolvers<void>;\n\n\t// The media timestamp of the most recently received frame.\n\treadonly timestamp = new Signal<Time.Milli | undefined>(undefined);\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// The connection used for \"real-time\" jitter: PROBE supplies RTT.\n\t#connection?: Signal<Moq.Connection.Established | undefined>;\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\n\tsignals = new Effect();\n\n\tconstructor(props?: SyncProps) {\n\t\tthis.latency = Signal.from(props?.latency ?? (\"real-time\" as Latency));\n\t\tthis.jitter = new Signal<Time.Milli>(FALLBACK_JITTER);\n\t\tthis.#connection = props?.connection;\n\t\tthis.audio = Signal.from(props?.audio);\n\t\tthis.video = Signal.from(props?.video);\n\n\t\tthis.#update = Promise.withResolvers();\n\n\t\tthis.signals.run(this.#runJitter.bind(this));\n\t\tthis.signals.run(this.#runBuffer.bind(this));\n\t}\n\n\t#runJitter(effect: Effect): void {\n\t\tconst latency = effect.get(this.latency);\n\n\t\tif (typeof latency === \"number\") {\n\t\t\t// Fixed mode: latency value is the jitter.\n\t\t\tthis.#minRtt = undefined;\n\t\t\tthis.jitter.set(latency);\n\t\t\treturn;\n\t\t}\n\n\t\t// \"real-time\" mode: compute jitter from RTT on the established connection.\n\t\tconst conn = this.#connection ? effect.get(this.#connection) : undefined;\n\t\tconst rttSignal = conn?.rtt;\n\t\tconst rtt = rttSignal ? effect.get(rttSignal) : undefined;\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 = Math.max(MIN_JITTER, this.#minRtt * 1.25) as Time.Milli;\n\t\t\tthis.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.jitter.set(FALLBACK_JITTER);\n\t}\n\n\t#runBuffer(effect: Effect): void {\n\t\tconst jitter = effect.get(this.jitter);\n\t\tconst video = effect.get(this.video) ?? Time.Milli.zero;\n\t\tconst audio = effect.get(this.audio) ?? Time.Milli.zero;\n\n\t\tconst buffer = Time.Milli.add(Time.Milli.max(video, audio), jitter);\n\t\tthis.#buffer.set(buffer);\n\n\t\tthis.#update.resolve();\n\t\tthis.#update = Promise.withResolvers();\n\t}\n\n\t// Update the reference if this is the earliest frame we've seen, relative to its timestamp.\n\treceived(timestamp: Time.Milli, label = \"\"): void {\n\t\tthis.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.#reference.peek();\n\n\t\tif (currentRef !== undefined) {\n\t\t\t// Check if `wait()` would not sleep at all.\n\t\t\t// NOTE: We check here instead of in `wait()` so we can identify when frames are received late.\n\t\t\t// Otherwise, chained `wait()` calls would cause a false-positive during CPU starvation.\n\t\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), this.#buffer.peek());\n\t\t\tif (sleep < 0) {\n\t\t\t\tconst entry = this.#late.get(label);\n\t\t\t\tif (entry) {\n\t\t\t\t\tentry.count++;\n\t\t\t\t\tentry.maxMs = Math.max(entry.maxMs, -sleep);\n\t\t\t\t} else {\n\t\t\t\t\tthis.#late.set(label, { count: 1, maxMs: -sleep });\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst entry = this.#late.get(label);\n\t\t\t\tif (entry) {\n\t\t\t\t\tconst prefix = label ? `sync[${label}]` : \"sync\";\n\t\t\t\t\tconst behind = Sync.#formatDuration(entry.maxMs);\n\t\t\t\t\tconsole.debug(`${prefix}: ${entry.count} late frame(s), max ${behind} behind`);\n\t\t\t\t\tthis.#late.delete(label);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (ref >= currentRef) {\n\t\t\t\t// Our frame was not relatively newer than any other frame.\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthis.#reference.set(ref);\n\t\tthis.#update.resolve();\n\t\tthis.#update = Promise.withResolvers();\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.#reference.peek();\n\t\tif (reference === undefined) return undefined;\n\t\treturn Time.Milli.sub(Time.Milli.sub(Time.Milli.now(), reference), this.#buffer.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\tconst reference = this.#reference.peek();\n\t\tif (reference === undefined) {\n\t\t\tthrow new Error(\"reference not set; call update() first\");\n\t\t}\n\n\t\tfor (;;) {\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.#reference.peek();\n\t\t\tif (currentRef === undefined) return;\n\n\t\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), this.#buffer.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\tconst wait = new Promise((resolve) => setTimeout(resolve, sleep)).then(() => true);\n\n\t\t\tconst ok = await Promise.race([this.#update.promise, wait]);\n\t\t\tif (ok) return;\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","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport * as Util from \"@moq/hang/util\";\nimport type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { BufferedRanges } from \"../backend\";\nimport { base64ToBytes } from \"../base64\";\nimport type { Backend, Stats } from \"./backend\";\nimport type { Source } from \"./source\";\n\n// The amount of time to wait before considering the video to be buffering.\nconst BUFFERING = 500 as Time.Milli;\nconst SWITCH = 100 as Time.Milli;\n\nexport type DecoderProps = {\n\tenabled?: boolean | Signal<boolean>;\n};\n\n// The types in VideoDecoderConfig that cause a hard reload.\n// ex. codedWidth/Height are optional and can be changed in-band, so we don't want to trigger a reload.\n// This way we can keep the current subscription active.\ntype RequiredDecoderConfig = Omit<Catalog.VideoConfig, \"codedWidth\" | \"codedHeight\">;\n\nexport class Decoder implements Backend {\n\tenabled: Signal<boolean>; // Don't download any longer\n\tsource: Source;\n\n\t// The current track running, held so we can cancel it when the new track is ready.\n\t#active = new Signal<DecoderTrack | undefined>(undefined);\n\n\t// Expose the current frame to render as a signal\n\t#frame = new Signal<VideoFrame | undefined>(undefined);\n\treadonly frame: Getter<VideoFrame | undefined> = this.#frame;\n\n\t// The timestamp of the current frame.\n\t#timestamp = new Signal<Time.Milli | undefined>(undefined);\n\treadonly timestamp: Getter<Time.Milli | undefined> = this.#timestamp;\n\n\t// The display size of the video in pixels, ideally sourced from the catalog.\n\t#display = new Signal<{ width: number; height: number } | undefined>(undefined);\n\treadonly display: Getter<{ width: number; height: number } | undefined> = this.#display;\n\n\t#stalled = new Signal<boolean>(false);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t#stats = new Signal<Stats | undefined>(undefined);\n\treadonly stats: Getter<Stats | undefined> = this.#stats;\n\n\t// Combined buffered ranges (network jitter + decode buffer)\n\t#buffered = new Signal<BufferedRanges>([]);\n\treadonly buffered: Getter<BufferedRanges> = this.#buffered;\n\n\t#signals = new Effect();\n\n\t#clearCurrentFrame(): void {\n\t\tthis.#frame.update((prev) => {\n\t\t\tprev?.close();\n\t\t\treturn undefined;\n\t\t});\n\t\tthis.#timestamp.set(undefined);\n\t}\n\n\tconstructor(source: Source, props?: DecoderProps) {\n\t\tthis.enabled = Signal.from(props?.enabled ?? false);\n\n\t\tthis.source = source;\n\t\tthis.source.supported.set(supported); // super hacky\n\n\t\tthis.#signals.run(this.#runPending.bind(this));\n\t\tthis.#signals.run(this.#runActive.bind(this));\n\t\tthis.#signals.run(this.#runDisplay.bind(this));\n\t\tthis.#signals.run(this.#runBuffering.bind(this));\n\t}\n\n\t#runPending(effect: Effect): void {\n\t\tconst values = effect.getAll([this.enabled, this.source.broadcast, this.source.track, this.source.config]);\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 [_, source, track, config] = values;\n\n\t\tconst broadcast: Moq.Broadcast | undefined = effect.get(source.active);\n\t\tif (!broadcast) {\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.#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\tsource: this.source,\n\t\t\tbroadcast,\n\t\t\ttrack,\n\t\t\tconfig,\n\t\t\tstats: this.#stats,\n\t\t});\n\n\t\teffect.cleanup(() => pending?.close());\n\n\t\teffect.run((effect) => {\n\t\t\tif (!pending) return;\n\n\t\t\tconst active = effect.get(this.#active);\n\t\t\tif (active) {\n\t\t\t\tconst pendingTimestamp = effect.get(pending.timestamp);\n\t\t\t\tconst activeTimestamp = effect.get(active.timestamp);\n\n\t\t\t\t// Switch to the new track if it's ready and we've caught up enough.\n\t\t\t\tif (!pendingTimestamp) return;\n\t\t\t\tif (activeTimestamp && activeTimestamp > pendingTimestamp + SWITCH) return;\n\t\t\t}\n\n\t\t\t// Upgrade the pending track to active.\n\t\t\t// #runActive will be in charge of it now.\n\t\t\tthis.#active.set(pending);\n\t\t\tpending = undefined;\n\n\t\t\t// This effect is done; close it to avoid a useless re-run.\n\t\t\teffect.close();\n\t\t});\n\t}\n\n\t#runActive(effect: Effect): void {\n\t\tconst active = effect.get(this.#active);\n\t\tif (!active) {\n\t\t\t// Clear stale data when disabled (e.g. paused or not visible).\n\t\t\tthis.#buffered.set([]);\n\t\t\treturn;\n\t\t}\n\n\t\teffect.cleanup(() => active.close());\n\n\t\t// Clone the frame so we own it independently of the DecoderTrack.\n\t\t// proxy() would share the same reference, allowing the source to close our frame.\n\t\teffect.run((inner) => {\n\t\t\tconst frame = inner.get(active.frame);\n\t\t\tthis.#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.#timestamp, active.timestamp);\n\t\teffect.proxy(this.#buffered, active.buffered);\n\t}\n\n\t#runDisplay(effect: Effect): void {\n\t\tconst catalog = effect.get(this.source.catalog);\n\t\tif (!catalog) return;\n\n\t\tconst display = catalog.display;\n\t\tif (display) {\n\t\t\teffect.set(this.#display, {\n\t\t\t\twidth: display.width,\n\t\t\t\theight: display.height,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst frame = effect.get(this.frame);\n\t\tif (!frame) return;\n\n\t\teffect.set(this.#display, {\n\t\t\twidth: frame.displayWidth,\n\t\t\theight: frame.displayHeight,\n\t\t});\n\t}\n\n\t#runBuffering(effect: Effect): void {\n\t\tconst enabled = effect.get(this.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst frame = effect.get(this.frame);\n\t\tif (!frame) {\n\t\t\tthis.#stalled.set(true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#stalled.set(false);\n\n\t\teffect.timer(() => {\n\t\t\tthis.#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\ninterface DecoderTrackProps {\n\tsource: Source;\n\tbroadcast: Moq.Broadcast;\n\ttrack: string;\n\tconfig: Catalog.VideoConfig;\n\n\tstats: Signal<Stats | undefined>;\n}\n\nclass DecoderTrack {\n\tsource: Source;\n\tbroadcast: Moq.Broadcast;\n\ttrack: string;\n\tconfig: RequiredDecoderConfig;\n\tstats: Signal<Stats | undefined>;\n\n\ttimestamp = new Signal<Time.Milli | undefined>(undefined);\n\tframe = new Signal<VideoFrame | undefined>(undefined);\n\n\t// Network jitter + decode buffer.\n\tbuffered = new Signal<BufferedRanges>([]);\n\n\t// Decoded frames waiting to be rendered.\n\t#buffered = new Signal<BufferedRanges>([]);\n\n\tsignals = new Effect();\n\n\tconstructor(props: DecoderTrackProps) {\n\t\t// Remove the codedWidth/Height from the config to avoid a hard reload if nothing else has changed.\n\t\tconst { codedWidth: _, codedHeight: __, ...requiredConfig } = props.config;\n\n\t\tthis.source = props.source;\n\t\tthis.broadcast = props.broadcast;\n\t\tthis.track = props.track;\n\t\tthis.config = requiredConfig;\n\t\tthis.stats = props.stats;\n\n\t\tthis.signals.run(this.#run.bind(this));\n\t}\n\n\t#run(effect: Effect): void {\n\t\tconst sub = this.broadcast.subscribe(this.track, Catalog.PRIORITY.video);\n\t\teffect.cleanup(() => sub.close());\n\n\t\tconst decoder = new VideoDecoder({\n\t\t\toutput: async (frame: VideoFrame) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\t\tif (timestamp < (this.timestamp.peek() ?? 0)) {\n\t\t\t\t\t\t// Late frame, don't render it.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this.frame.peek() === undefined) {\n\t\t\t\t\t\t// Render something while we wait for the sync to catch up.\n\t\t\t\t\t\tthis.frame.set(frame.clone());\n\t\t\t\t\t}\n\n\t\t\t\t\tconst wait = this.source.sync.wait(timestamp).then(() => true);\n\t\t\t\t\tconst ok = await Promise.race([wait, effect.cancel]);\n\t\t\t\t\tif (!ok) return;\n\n\t\t\t\t\tif (timestamp < (this.timestamp.peek() ?? 0)) {\n\t\t\t\t\t\t// Late frame, don't render it.\n\t\t\t\t\t\t// NOTE: This can happen when the ref is updated, such as on playback start.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.timestamp.set(timestamp);\n\n\t\t\t\t\t// Trim the decode buffer as frames are rendered\n\t\t\t\t\tthis.#trimBuffered(timestamp);\n\n\t\t\t\t\tthis.frame.update((prev) => {\n\t\t\t\t\t\tprev?.close();\n\t\t\t\t\t\treturn frame.clone(); // avoid closing the frame here\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tframe.close();\n\t\t\t\t}\n\t\t\t},\n\t\t\t// TODO bubble up error\n\t\t\terror: (error) => {\n\t\t\t\tconsole.error(error);\n\t\t\t\teffect.close();\n\t\t\t},\n\t\t});\n\t\teffect.cleanup(() => {\n\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t});\n\n\t\t// Input processing - depends on container type\n\t\tif (this.config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmaf(effect, sub, decoder);\n\t\t} else {\n\t\t\tthis.#runLegacy(effect, sub, decoder);\n\t\t}\n\t}\n\n\t#runLegacy(effect: Effect, sub: Moq.Track, decoder: VideoDecoder): void {\n\t\tconst format =\n\t\t\tthis.config.container.kind === \"loc\" ? new Container.Loc.Format() : new Container.Legacy.Format();\n\t\t// Create consumer that reorders groups/frames up to the provided latency.\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat,\n\t\t\tlatency: this.source.sync.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#buffered);\n\t\t\tthis.buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\tdecoder.configure({\n\t\t\t...this.config,\n\t\t\tdescription: this.config.description ? Util.Hex.toBytes(this.config.description) : undefined,\n\t\t\toptimizeForLatency: this.config.optimizeForLatency ?? true,\n\t\t\t// @ts-expect-error Only supported by Chrome, so the renderer has to flip manually.\n\t\t\tflip: false,\n\t\t});\n\n\t\tlet previous: { timestamp: Time.Micro; group: number; final: boolean } | undefined;\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\tconst { frame, group } = next;\n\n\t\t\t\tif (!frame) {\n\t\t\t\t\tif (previous) {\n\t\t\t\t\t\tprevious.final = true;\n\t\t\t\t\t}\n\t\t\t\t\t// The group is done\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\tthis.source.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.data,\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t});\n\n\t\t\t\t// Track both frame count and bytes received for stats in the UI\n\t\t\t\tthis.stats.update((current) => ({\n\t\t\t\t\tframeCount: (current?.frameCount ?? 0) + 1,\n\t\t\t\t\tbytesReceived: (current?.bytesReceived ?? 0) + frame.data.byteLength,\n\t\t\t\t}));\n\n\t\t\t\t// Track decode buffer: frames sent to decoder but not yet rendered\n\t\t\t\tconst prior = previous;\n\t\t\t\tif (prior && (prior.group === group || (prior.final && prior.group + 1 === group))) {\n\t\t\t\t\tconst start = Time.Milli.fromMicro(prior.timestamp);\n\t\t\t\t\tconst end = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\t\tthis.#addBuffered(start, end);\n\t\t\t\t}\n\n\t\t\t\tprevious = {\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\tgroup,\n\t\t\t\t\tfinal: false,\n\t\t\t\t};\n\n\t\t\t\tdecoder.decode(chunk);\n\t\t\t}\n\t\t});\n\t}\n\n\t#runCmaf(effect: Effect, sub: Moq.Track, decoder: VideoDecoder): void {\n\t\tif (this.config.container.kind !== \"cmaf\") return;\n\n\t\tconst initSegment = base64ToBytes(this.config.container.init);\n\t\tconst init = Container.Cmaf.decodeInitSegment(initSegment);\n\t\tconst description = this.config.description ? Util.Hex.toBytes(this.config.description) : init.description;\n\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat: new Container.Cmaf.Format(init),\n\t\t\tlatency: this.source.sync.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#buffered);\n\t\t\tthis.buffered.update(() => Container.mergeBufferedRanges(network, decode));\n\t\t});\n\n\t\t// Configure decoder with description from catalog\n\t\tdecoder.configure({\n\t\t\tcodec: this.config.codec,\n\t\t\tdescription,\n\t\t\toptimizeForLatency: this.config.optimizeForLatency ?? true,\n\t\t\t// @ts-expect-error Only supported by Chrome, so the renderer has to flip manually.\n\t\t\tflip: false,\n\t\t});\n\n\t\tlet previous: { timestamp: Time.Micro; group: number; final: boolean } | undefined;\n\n\t\teffect.spawn(async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) break;\n\n\t\t\t\tconst { frame, group } = next;\n\n\t\t\t\tif (!frame) {\n\t\t\t\t\tif (previous) {\n\t\t\t\t\t\tprevious.final = true;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\tthis.source.sync.received(timestamp, \"video\");\n\n\t\t\t\t// Track stats\n\t\t\t\tthis.stats.update((current) => ({\n\t\t\t\t\tframeCount: (current?.frameCount ?? 0) + 1,\n\t\t\t\t\tbytesReceived: (current?.bytesReceived ?? 0) + frame.data.byteLength,\n\t\t\t\t}));\n\n\t\t\t\t// Track decode buffer\n\t\t\t\tconst prior = previous;\n\t\t\t\tif (prior && (prior.group === group || (prior.final && prior.group + 1 === group))) {\n\t\t\t\t\tconst start = Time.Milli.fromMicro(prior.timestamp);\n\t\t\t\t\tconst end = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\t\tthis.#addBuffered(start, end);\n\t\t\t\t}\n\n\t\t\t\tprevious = {\n\t\t\t\t\ttimestamp: frame.timestamp,\n\t\t\t\t\tgroup,\n\t\t\t\t\tfinal: false,\n\t\t\t\t};\n\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tdecoder.decode(\n\t\t\t\t\tnew EncodedVideoChunk({\n\t\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\t\tdata: frame.data,\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// 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\tlet description: Uint8Array | undefined;\n\tif (config.description) {\n\t\tdescription = Util.Hex.toBytes(config.description);\n\t} else if (config.container.kind === \"cmaf\") {\n\t\ttry {\n\t\t\tdescription = Container.Cmaf.decodeInitSegment(base64ToBytes(config.container.init)).description;\n\t\t} catch (err) {\n\t\t\t// A malformed init segment means we can't extract the codec\n\t\t\t// description, so we can't probe support reliably. Reject the\n\t\t\t// track rather than letting isConfigSupported pass on a\n\t\t\t// description-less config and then having runCmaf fail later.\n\t\t\tconsole.warn(`video: malformed CMAF init segment for codec ${config.codec}`, err);\n\t\t\treturn false;\n\t\t}\n\t}\n\tconst { supported } = await VideoDecoder.isConfigSupported({\n\t\tcodec: config.codec,\n\t\tdescription,\n\t\toptimizeForLatency: config.optimizeForLatency ?? true,\n\t});\n\n\tif (supported) return true;\n\n\t// Safari rejects `avc3.*` codec strings even though its H.264 decoder handles\n\t// inline SPS/PPS. Rewrite to `avc1.*` and retry; mutate config.codec so the\n\t// later `decoder.configure()` call uses the accepted string too.\n\tif (config.codec.startsWith(\"avc3.\")) {\n\t\tconst avc1 = `avc1.${config.codec.slice(\"avc3.\".length)}`;\n\t\tconst retry = await VideoDecoder.isConfigSupported({\n\t\t\tcodec: avc1,\n\t\t\tdescription,\n\t\t\toptimizeForLatency: config.optimizeForLatency ?? true,\n\t\t});\n\t\tif (retry.supported) {\n\t\t\tconfig.codec = avc1;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport * as Moq from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport { type BufferedRanges, timeRangesToArray } from \"../backend\";\nimport { base64ToBytes } from \"../base64\";\nimport type { Muxer } from \"../mse\";\nimport type { Backend, Stats } from \"./backend\";\nimport type { Source } from \"./source\";\n\n/**\n * MSE-based video source for CMAF/fMP4 fragments.\n * Uses Media Source Extensions to handle complete moof+mdat fragments.\n */\nexport class Mse implements Backend {\n\tmuxer: Muxer;\n\tsource: Source;\n\n\t// TODO implement stats\n\t#stats = new Signal<Stats | undefined>(undefined);\n\treadonly stats: Getter<Stats | undefined> = this.#stats;\n\n\t#buffered = new Signal<BufferedRanges>([]);\n\treadonly buffered: Getter<BufferedRanges> = this.#buffered;\n\n\t#stalled = new Signal<boolean>(false);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t#timestamp = new Signal<Moq.Time.Milli>(Moq.Time.Milli.zero);\n\treadonly timestamp: Getter<Moq.Time.Milli> = this.#timestamp;\n\n\tsignals = new Effect();\n\n\tconstructor(muxer: Muxer, source: Source) {\n\t\tthis.muxer = muxer;\n\t\tthis.source = source;\n\t\tthis.source.supported.set(supported); // super hacky\n\n\t\tthis.signals.run(this.#runMedia.bind(this));\n\t\tthis.signals.run(this.#runStalled.bind(this));\n\t\tthis.signals.run(this.#runTimestamp.bind(this));\n\t}\n\n\t#runMedia(effect: Effect): void {\n\t\tconst element = effect.get(this.muxer.element);\n\t\tif (!element) return;\n\n\t\tconst mediaSource = effect.get(this.muxer.mediaSource);\n\t\tif (!mediaSource) return;\n\n\t\tconst broadcast = effect.get(this.source.broadcast);\n\t\tif (!broadcast) return;\n\n\t\tconst active = effect.get(broadcast.active);\n\t\tif (!active) return;\n\n\t\tconst track = effect.get(this.source.track);\n\t\tif (!track) return;\n\n\t\tconst config = effect.get(this.source.config);\n\t\tif (!config) return;\n\n\t\tconst mime = `video/mp4; codecs=\"${config.codec}\"`;\n\n\t\tconst sourceBuffer = mediaSource.addSourceBuffer(mime);\n\t\teffect.cleanup(() => {\n\t\t\tmediaSource.removeSourceBuffer(sourceBuffer);\n\t\t\tsourceBuffer.abort();\n\t\t});\n\n\t\teffect.event(sourceBuffer, \"error\", (e) => {\n\t\t\tconsole.error(\"[MSE] SourceBuffer error:\", e);\n\t\t});\n\n\t\teffect.event(sourceBuffer, \"updateend\", () => {\n\t\t\tthis.#buffered.set(timeRangesToArray(sourceBuffer.buffered));\n\t\t});\n\n\t\tif (config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmafMedia(effect, active, track, config, sourceBuffer, element);\n\t\t} else {\n\t\t\tthis.#runLegacyMedia(effect, active, track, config, sourceBuffer, element);\n\t\t}\n\t}\n\n\tasync #appendBuffer(sourceBuffer: SourceBuffer, buffer: Uint8Array): Promise<void> {\n\t\twhile (sourceBuffer.updating) {\n\t\t\tawait new Promise((resolve) => sourceBuffer.addEventListener(\"updateend\", resolve, { once: true }));\n\t\t}\n\n\t\tsourceBuffer.appendBuffer(buffer as BufferSource);\n\n\t\twhile (sourceBuffer.updating) {\n\t\t\tawait new Promise((resolve) => sourceBuffer.addEventListener(\"updateend\", resolve, { once: true }));\n\t\t}\n\t}\n\n\t#runCmafMedia(\n\t\teffect: Effect,\n\t\tactive: Moq.Broadcast,\n\t\ttrack: string,\n\t\tconfig: Catalog.VideoConfig,\n\t\tsourceBuffer: SourceBuffer,\n\t\telement: HTMLMediaElement,\n\t): void {\n\t\tif (config.container.kind !== \"cmaf\") throw new Error(\"unreachable\");\n\n\t\tconst data = active.subscribe(track, Catalog.PRIORITY.video);\n\t\teffect.cleanup(() => data.close());\n\n\t\t// Decode the catalog's authoritative init segment once and read the\n\t\t// timescale out of it. The catalog also gives us the bytes to feed\n\t\t// directly into MSE — no need to regenerate.\n\t\tconst initSegment = base64ToBytes(config.container.init);\n\t\tconst init = Container.Cmaf.decodeInitSegment(initSegment);\n\n\t\teffect.spawn(async () => {\n\t\t\tawait this.#appendBuffer(sourceBuffer, initSegment);\n\n\t\t\tfor (;;) {\n\t\t\t\t// TODO: Use Frame.Consumer for CMAF so we can support higher latencies.\n\t\t\t\t// It requires extracting the timestamp from the frame payload.\n\t\t\t\tconst frame = await data.readFrame();\n\t\t\t\tif (!frame) return;\n\n\t\t\t\t// Extract the timestamp from the CMAF segment and mark when we received it.\n\t\t\t\tconst timestamp = Container.Cmaf.decodeTimestamp(frame, init);\n\t\t\t\tthis.source.sync.received(Moq.Time.Milli.fromMicro(timestamp), \"video\");\n\n\t\t\t\tawait this.#appendBuffer(sourceBuffer, frame);\n\n\t\t\t\t// Seek to the start of the buffer if we're behind it (for startup).\n\t\t\t\tif (element.buffered.length > 0 && element.currentTime < element.buffered.start(0)) {\n\t\t\t\t\telement.currentTime = element.buffered.start(0);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t#runLegacyMedia(\n\t\teffect: Effect,\n\t\tactive: Moq.Broadcast,\n\t\ttrack: string,\n\t\tconfig: Catalog.VideoConfig,\n\t\tsourceBuffer: SourceBuffer,\n\t\telement: HTMLMediaElement,\n\t): void {\n\t\tconst data = active.subscribe(track, Catalog.PRIORITY.video);\n\t\teffect.cleanup(() => data.close());\n\n\t\tconst format = config.container.kind === \"loc\" ? new Container.Loc.Format() : new Container.Legacy.Format();\n\t\t// Create consumer that reorders groups/frames up to the provided latency.\n\t\tconst consumer = new Container.Consumer(data, {\n\t\t\tformat,\n\t\t\tlatency: this.source.sync.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\teffect.spawn(async () => {\n\t\t\t// Generate init segment from catalog config (timescale = 1,000,000 = microseconds)\n\t\t\tconst initSegment = Container.Cmaf.createVideoInitSegment(config);\n\t\t\tawait this.#appendBuffer(sourceBuffer, initSegment);\n\n\t\t\tlet sequence = 1;\n\t\t\tlet duration: Moq.Time.Micro | undefined;\n\n\t\t\t// Buffer one frame so we can compute accurate duration from the next frame's timestamp\n\t\t\tlet pending: Container.Frame;\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) return;\n\t\t\t\tif (!next.frame) continue; // Skip over group done notifications.\n\n\t\t\t\tpending = next.frame;\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Moq.Time.Milli.fromMicro(pending.timestamp as Moq.Time.Micro);\n\t\t\t\tthis.source.sync.received(timestamp, \"video\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (next && !next.frame) continue; // Skip over group done notifications.\n\t\t\t\tconst frame = next?.frame;\n\n\t\t\t\t// Compute duration from next frame's timestamp, or use last known duration if stream ended\n\t\t\t\tif (frame) {\n\t\t\t\t\tduration = Moq.Time.Micro.sub(frame.timestamp, pending.timestamp);\n\n\t\t\t\t\t// Mark that we received this frame right now for latency calculation.\n\t\t\t\t\tconst timestamp = Moq.Time.Milli.fromMicro(frame.timestamp as Moq.Time.Micro);\n\t\t\t\t\tthis.source.sync.received(timestamp, \"video\");\n\t\t\t\t}\n\n\t\t\t\t// Wrap raw frame in moof+mdat\n\t\t\t\tconst segment = Container.Cmaf.encodeDataSegment({\n\t\t\t\t\tdata: pending.data,\n\t\t\t\t\ttimestamp: pending.timestamp,\n\t\t\t\t\tduration: duration ?? 0, // Default to 0 duration if there's literally one frame then stream FIN.\n\t\t\t\t\tkeyframe: pending.keyframe,\n\t\t\t\t\tsequence: sequence++,\n\t\t\t\t});\n\n\t\t\t\tawait this.#appendBuffer(sourceBuffer, segment);\n\n\t\t\t\t// Seek to the start of the buffer if we're behind it (for startup).\n\t\t\t\tif (element.buffered.length > 0 && element.currentTime < element.buffered.start(0)) {\n\t\t\t\t\telement.currentTime = element.buffered.start(0);\n\t\t\t\t}\n\n\t\t\t\tif (!frame) return;\n\t\t\t\tpending = frame;\n\t\t\t}\n\t\t});\n\t}\n\n\t#runStalled(effect: Effect): void {\n\t\tconst element = effect.get(this.muxer.element);\n\t\tif (!element) return;\n\n\t\tconst update = () => {\n\t\t\tthis.#stalled.set(element.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA);\n\t\t};\n\n\t\t// Set initial state\n\t\tupdate();\n\n\t\t// TODO Are these the correct events to use?\n\t\teffect.event(element, \"waiting\", update);\n\t\teffect.event(element, \"playing\", update);\n\t\teffect.event(element, \"seeking\", update);\n\t}\n\n\t#runTimestamp(effect: Effect): void {\n\t\tconst element = effect.get(this.muxer.element);\n\t\tif (!element) return;\n\n\t\t// Use requestVideoFrameCallback if available (frame-accurate)\n\t\tif (\"requestVideoFrameCallback\" in element) {\n\t\t\tconst video = element as HTMLVideoElement;\n\n\t\t\tlet handle: number;\n\t\t\tconst onFrame = () => {\n\t\t\t\tconst timestamp = Moq.Time.Milli.fromSecond(video.currentTime as Moq.Time.Second);\n\t\t\t\tthis.#timestamp.set(timestamp);\n\t\t\t\thandle = video.requestVideoFrameCallback(onFrame);\n\t\t\t};\n\t\t\thandle = video.requestVideoFrameCallback(onFrame);\n\n\t\t\teffect.cleanup(() => video.cancelVideoFrameCallback(handle));\n\t\t} else {\n\t\t\t// Fallback to timeupdate event\n\t\t\teffect.event(element, \"timeupdate\", () => {\n\t\t\t\tconst timestamp = Moq.Time.Milli.fromSecond(element.currentTime as Moq.Time.Second);\n\t\t\t\tthis.#timestamp.set(timestamp);\n\t\t\t});\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tthis.source.close();\n\t\tthis.signals.close();\n\t}\n}\n\nasync function supported(config: Catalog.VideoConfig): Promise<boolean> {\n\treturn MediaSource.isTypeSupported(`video/mp4; codecs=\"${config.codec}\"`);\n}\n","import { Time } from \"@moq/net\";\nimport { Effect, Signal } from \"@moq/signals\";\nimport type { Decoder } from \"./decoder\";\n\nexport type RendererProps = {\n\tcanvas?: HTMLCanvasElement | Signal<HTMLCanvasElement | undefined>;\n\tpaused?: boolean | Signal<boolean>;\n};\n\n// An component to render a video to a canvas.\nexport class Renderer {\n\tdecoder: Decoder;\n\n\t// The canvas to render the video to.\n\tcanvas: Signal<HTMLCanvasElement | undefined>;\n\n\t// Whether the video is paused.\n\tpaused: Signal<boolean>;\n\n\t// The most recently rendered frame, updated after each rAF paint.\n\treadonly frame = new Signal<VideoFrame | undefined>(undefined);\n\n\t// The media timestamp of the most recently rendered frame.\n\treadonly timestamp = new Signal<Time.Milli | undefined>(undefined);\n\n\t#ctx = new Signal<CanvasRenderingContext2D | undefined>(undefined);\n\t#visible = new Signal(false);\n\t#signals = new Effect();\n\n\tconstructor(decoder: Decoder, props?: RendererProps) {\n\t\tthis.decoder = decoder;\n\t\tthis.canvas = Signal.from(props?.canvas);\n\t\tthis.paused = Signal.from(props?.paused ?? false);\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tconst canvas = effect.get(this.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.#runEnabled.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.canvas, this.decoder.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 is visible in the viewport and the tab is focused.\n\t#runVisible(effect: Effect): void {\n\t\tconst canvas = effect.get(this.canvas);\n\t\tif (!canvas) {\n\t\t\tthis.#visible.set(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet intersecting = false;\n\n\t\tconst update = () => {\n\t\t\tthis.#visible.set(intersecting && !document.hidden);\n\t\t};\n\n\t\tconst observer = new IntersectionObserver(\n\t\t\t(entries) => {\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tintersecting = entry.isIntersecting;\n\t\t\t\t\tupdate();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ threshold: 0.01 },\n\t\t);\n\n\t\teffect.event(document, \"visibilitychange\", update);\n\n\t\tobserver.observe(canvas);\n\t\teffect.cleanup(() => observer.disconnect());\n\t\teffect.cleanup(() => this.#visible.set(false));\n\t}\n\n\t// Detect when video should be downloaded.\n\t#runEnabled(effect: Effect): void {\n\t\tconst paused = effect.get(this.paused);\n\t\tconst visible = effect.get(this.#visible);\n\n\t\teffect.cleanup(() => this.decoder.enabled.set(false));\n\n\t\tif (!paused) {\n\t\t\tthis.decoder.enabled.set(visible);\n\t\t\treturn;\n\t\t}\n\n\t\t// When paused, fetch a single preview frame then disable.\n\t\tconst frame = effect.get(this.decoder.frame);\n\t\tthis.decoder.enabled.set(!frame);\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.frame);\n\n\t\t// Request a callback to render the frame based on the monitor's refresh rate.\n\t\t// Always render, even when paused (to show last frame).\n\t\tlet animate: number | undefined = requestAnimationFrame(() => {\n\t\t\tthis.#render(ctx, frame);\n\n\t\t\tif (frame) {\n\t\t\t\tthis.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.timestamp.set(Time.Milli.fromMicro(frame.timestamp as Time.Micro));\n\t\t\t} else {\n\t\t\t\tthis.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.timestamp.set(undefined);\n\t\t\t}\n\n\t\t\tanimate = undefined;\n\t\t});\n\n\t\t// Clean up any pending animation request.\n\t\teffect.cleanup(() => {\n\t\t\tif (animate) cancelAnimationFrame(animate);\n\t\t});\n\t}\n\n\t#render(ctx: CanvasRenderingContext2D, frame?: VideoFrame) {\n\t\tif (!frame) {\n\t\t\t// Clear canvas when no frame\n\t\t\tctx.fillStyle = \"#000\";\n\t\t\tctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\t\t\treturn;\n\t\t}\n\n\t\t// Prepare background and transformations for this draw\n\t\tctx.save();\n\t\tctx.fillStyle = \"#000\";\n\t\tctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\n\t\t// Apply horizontal flip if specified in the video config\n\t\tconst flip = this.decoder.source.catalog.peek()?.flip;\n\t\tif (flip) {\n\t\t\tctx.scale(-1, 1);\n\t\t\tctx.translate(-ctx.canvas.width, 0);\n\t\t}\n\n\t\tctx.drawImage(frame, 0, 0, ctx.canvas.width, ctx.canvas.height);\n\t\tctx.restore();\n\t}\n\n\t// Close the track and all associated resources.\n\tclose() {\n\t\tthis.frame.update((current) => {\n\t\t\tcurrent?.close();\n\t\t\treturn undefined;\n\t\t});\n\t\tthis.timestamp.set(undefined);\n\t\tthis.#signals.close();\n\t}\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport type * as Moq from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\nimport type { Sync } from \"../sync\";\n\n/**\n * A function that checks if a video configuration is supported by the backend.\n */\nexport type Supported = (config: Catalog.VideoConfig) => Promise<boolean>;\n\nexport type SourceProps = {\n\tbroadcast?: Broadcast | Signal<Broadcast | undefined>;\n\ttarget?: Target | Signal<Target | undefined>;\n\tsupported?: Supported;\n};\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\n/**\n * A filter that returns matching renditions sorted by preference (most preferred first).\n * Must return at least one rendition.\n */\ntype RenditionFilter = (entries: [string, Catalog.VideoConfig][]) => string[];\n\n/**\n * Filter and rank renditions by a maximum pixel count.\n * Returns renditions within budget (largest first for best quality).\n * Over-budget and unknown-resolution renditions are excluded.\n * If nothing is within budget, falls back to the single smallest rendition.\n */\nfunction byPixels(target: number): RenditionFilter {\n\treturn (entries) => {\n\t\tconst within: { name: string; size: number }[] = [];\n\t\tconst rest: { name: string; size: number }[] = [];\n\n\t\tfor (const [name, config] of entries) {\n\t\t\tif (config.codedWidth && config.codedHeight) {\n\t\t\t\tconst size = config.codedWidth * config.codedHeight;\n\t\t\t\tif (size <= target) {\n\t\t\t\t\twithin.push({ name, size });\n\t\t\t\t} else {\n\t\t\t\t\trest.push({ name, size });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Best quality within budget\n\t\twithin.sort((a, b) => b.size - a.size);\n\n\t\tif (within.length > 0) {\n\t\t\treturn within.map((e) => e.name);\n\t\t}\n\n\t\t// Degrade to smallest over-budget resolution.\n\t\tif (rest.length > 0) {\n\t\t\trest.sort((a, b) => a.size - b.size);\n\t\t\treturn [rest[0].name];\n\t\t}\n\n\t\t// No entries had resolution metadata — return all names unranked.\n\t\treturn entries.map(([name]) => name);\n\t};\n}\n\n/**\n * Filter and rank renditions by maximum coded dimensions.\n * Returns renditions where codedWidth <= width AND codedHeight <= height\n * (each cap is optional). Within-budget renditions rank by area (largest first).\n * If nothing fits, falls back to the single smallest over-budget rendition.\n */\nfunction byDimensions(width?: number, height?: number): RenditionFilter {\n\treturn (entries) => {\n\t\tconst within: { name: string; size: number }[] = [];\n\t\tconst rest: { name: string; size: number }[] = [];\n\n\t\tfor (const [name, config] of entries) {\n\t\t\tif (!config.codedWidth || !config.codedHeight) continue;\n\t\t\tconst size = config.codedWidth * config.codedHeight;\n\t\t\tconst fitsWidth = width == null || config.codedWidth <= width;\n\t\t\tconst fitsHeight = height == null || config.codedHeight <= height;\n\t\t\tif (fitsWidth && fitsHeight) {\n\t\t\t\twithin.push({ name, size });\n\t\t\t} else {\n\t\t\t\trest.push({ name, size });\n\t\t\t}\n\t\t}\n\n\t\t// Best quality within budget\n\t\twithin.sort((a, b) => b.size - a.size);\n\n\t\tif (within.length > 0) {\n\t\t\treturn within.map((e) => e.name);\n\t\t}\n\n\t\t// Degrade to smallest over-budget rendition.\n\t\tif (rest.length > 0) {\n\t\t\trest.sort((a, b) => a.size - b.size);\n\t\t\treturn [rest[0].name];\n\t\t}\n\n\t\t// No entries had resolution metadata — return all names unranked.\n\t\treturn entries.map(([name]) => name);\n\t};\n}\n\n/**\n * Filter and rank renditions by a maximum bitrate budget.\n * Returns renditions within budget (highest bitrate first for best quality).\n * Over-budget and unknown-bitrate renditions are excluded.\n * If nothing is within budget, falls back to the single lowest-bitrate rendition.\n */\nfunction byBitrate(target: number): RenditionFilter {\n\treturn (entries) => {\n\t\tconst within: { name: string; bitrate: number }[] = [];\n\t\tconst rest: { name: string; bitrate: number }[] = [];\n\n\t\tfor (const [name, config] of entries) {\n\t\t\tif (config.bitrate != null && config.bitrate <= target) {\n\t\t\t\twithin.push({ name, bitrate: config.bitrate });\n\t\t\t} else if (config.bitrate != null) {\n\t\t\t\trest.push({ name, bitrate: config.bitrate });\n\t\t\t}\n\t\t}\n\n\t\t// Best quality within budget\n\t\twithin.sort((a, b) => b.bitrate - a.bitrate);\n\n\t\tif (within.length > 0) {\n\t\t\treturn within.map((e) => e.name);\n\t\t}\n\n\t\t// Degrade to lowest over-budget bitrate.\n\t\tif (rest.length > 0) {\n\t\t\trest.sort((a, b) => a.bitrate - b.bitrate);\n\t\t\treturn [rest[0].name];\n\t\t}\n\n\t\t// No entries had bitrate metadata — return all names unranked.\n\t\treturn entries.map(([name]) => name);\n\t};\n}\n\n/**\n * Pick the best rendition when no filters are active.\n * Prefers the largest resolution, falls back to highest bitrate,\n * then falls back to the first entry.\n */\nfunction bestRendition(entries: [string, Catalog.VideoConfig][]): string {\n\tlet best = entries[0];\n\n\tfor (const entry of entries) {\n\t\tconst [, config] = entry;\n\t\tconst [, bestConfig] = best;\n\n\t\tconst size = (config.codedWidth ?? 0) * (config.codedHeight ?? 0);\n\t\tconst bestSize = (bestConfig.codedWidth ?? 0) * (bestConfig.codedHeight ?? 0);\n\n\t\tif (size !== bestSize) {\n\t\t\tif (size > bestSize) best = entry;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((config.bitrate ?? 0) > (bestConfig.bitrate ?? 0)) {\n\t\t\tbest = entry;\n\t\t}\n\t}\n\n\treturn best[0];\n}\n\n/**\n * Source handles catalog extraction, support checking, and rendition selection\n * for video playback. It is used by both MSE and Decoder backends.\n */\nexport class Source {\n\tbroadcast: Signal<Broadcast | undefined>;\n\ttarget: Signal<Target | undefined>;\n\n\treadonly catalog: Getter<Catalog.Video | undefined>;\n\n\t#available = new Signal<Record<string, Catalog.VideoConfig>>({});\n\treadonly available: Getter<Record<string, Catalog.VideoConfig>> = this.#available;\n\n\t// The name of the active rendition.\n\t#track = new Signal<string | undefined>(undefined);\n\treadonly track: Getter<string | undefined> = this.#track;\n\n\t#config = new Signal<Catalog.VideoConfig | undefined>(undefined);\n\treadonly config: Getter<Catalog.VideoConfig | undefined> = this.#config;\n\n\tsync: Sync;\n\tsupported: Signal<Supported | undefined>;\n\n\t#signals = new Effect();\n\n\tconstructor(sync: Sync, props?: SourceProps) {\n\t\tthis.broadcast = Signal.from(props?.broadcast);\n\t\tthis.target = Signal.from(props?.target);\n\t\tthis.sync = sync;\n\t\tthis.supported = Signal.from(props?.supported);\n\n\t\t// The video catalog, derived from the active broadcast.\n\t\tthis.catalog = this.#signals.computed((effect) => {\n\t\t\tconst broadcast = effect.get(this.broadcast);\n\t\t\treturn broadcast ? effect.get(broadcast.catalog)?.video : undefined;\n\t\t});\n\n\t\tthis.#signals.run(this.#runSupported.bind(this));\n\t\tthis.#signals.run(this.#runSelected.bind(this));\n\t}\n\n\t#runSupported(effect: Effect): void {\n\t\tconst supported = effect.get(this.supported);\n\t\tif (!supported) return;\n\n\t\tconst renditions = effect.get(this.catalog)?.renditions ?? {};\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 isSupported = await supported(config);\n\t\t\t\tif (isSupported) available[name] = config;\n\t\t\t}\n\n\t\t\tif (Object.keys(available).length === 0 && Object.keys(renditions).length > 0) {\n\t\t\t\tconsole.warn(\"[Source] No supported video renditions found:\", renditions);\n\t\t\t}\n\n\t\t\tthis.#available.set(available);\n\t\t});\n\t}\n\n\t#runSelected(effect: Effect): void {\n\t\tconst available = effect.get(this.#available);\n\t\tif (Object.keys(available).length === 0) return;\n\n\t\tconst target = effect.get(this.target);\n\n\t\t// Manual selection by name — skip all ABR logic.\n\t\tif (target?.name && target.name in available) {\n\t\t\tconst config = available[target.name];\n\t\t\teffect.set(this.#track, target.name);\n\t\t\teffect.set(this.#config, config);\n\t\t\teffect.set(this.sync.video, config.jitter as Moq.Time.Milli | undefined);\n\t\t\treturn;\n\t\t}\n\n\t\t// Auto-select: use recv bandwidth if no explicit bitrate target.\n\t\tlet effectiveTarget = target;\n\t\tif (!target?.bitrate) {\n\t\t\tconst broadcast = effect.get(this.broadcast);\n\t\t\tconst connection = broadcast ? effect.get(broadcast.connection) : undefined;\n\t\t\tconst recvBw = connection?.recvBandwidth;\n\t\t\tif (recvBw) {\n\t\t\t\tconst estimate = effect.get(recvBw);\n\t\t\t\tif (estimate != null) {\n\t\t\t\t\t// Apply a safety margin (80%) to avoid oscillation.\n\t\t\t\t\tconst safeBitrate = Math.round(estimate * 0.8);\n\t\t\t\t\teffectiveTarget = { ...target, bitrate: safeBitrate };\n\t\t\t\t}\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.#track, selected);\n\t\teffect.set(this.#config, config);\n\n\t\t// Use catalog jitter if available, otherwise estimate from framerate.\n\t\tconst jitter = config.jitter ?? (config.framerate ? Math.ceil(1000 / config.framerate) : undefined);\n\t\teffect.set(this.sync.video, jitter as Moq.Time.Milli | undefined);\n\t}\n\n\t/**\n\t * Select the best rendition using a generic filter system.\n\t *\n\t * Each enabled filter returns matching renditions sorted by preference.\n\t * The first rendition present in every filter's output is selected.\n\t * If no rendition satisfies all filters, a warning is logged.\n\t */\n\t#select(renditions: Record<string, Catalog.VideoConfig>, target?: Target): string | undefined {\n\t\tconst entries = Object.entries(renditions);\n\t\tif (entries.length === 0) return undefined;\n\t\tif (entries.length === 1) return entries[0][0];\n\n\t\t// Build enabled filters based on the target.\n\t\tconst filters: RenditionFilter[] = [];\n\n\t\tif (target?.pixels != null) {\n\t\t\tfilters.push(byPixels(target.pixels));\n\t\t}\n\t\tif (target?.width != null || target?.height != null) {\n\t\t\tfilters.push(byDimensions(target.width, target.height));\n\t\t}\n\t\tif (target?.bitrate != null) {\n\t\t\tfilters.push(byBitrate(target.bitrate));\n\t\t}\n\n\t\t// No filters — pick the best rendition by quality.\n\t\tif (filters.length === 0) {\n\t\t\treturn bestRendition(entries);\n\t\t}\n\n\t\t// Run each filter to get ranked preference lists.\n\t\tconst rankings = filters.map((f) => f(entries));\n\n\t\t// Select the first rendition (in the first ranking's order) present in all rankings.\n\t\tconst sets = rankings.map((r) => new Set(r));\n\n\t\tfor (const name of rankings[0]) {\n\t\t\tif (sets.every((s) => s.has(name))) {\n\t\t\t\treturn name;\n\t\t\t}\n\t\t}\n\n\t\tconsole.warn(\"conflicting rendition filters, no rendition satisfies all criteria\");\n\t\treturn undefined;\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n","export type * from \"./backend\";\nexport * from \"./decoder\";\nexport * from \"./mse\";\nexport * from \"./renderer\";\nexport * from \"./source\";\n","import * as Moq from \"@moq/net\";\nimport { Effect, Signal } from \"@moq/signals\";\nimport * as Audio from \"./audio\";\nimport type { Broadcast } from \"./broadcast\";\nimport { Muxer } from \"./mse\";\nimport { type Latency, Sync } from \"./sync\";\nimport * as Video from \"./video\";\n\n// Serializable representation of TimeRanges\nexport interface BufferedRange {\n\tstart: Moq.Time.Milli;\n\tend: Moq.Time.Milli;\n}\nexport type BufferedRanges = BufferedRange[];\n\n// Helper to convert DOM TimeRanges\nexport function timeRangesToArray(ranges: TimeRanges): BufferedRanges {\n\tconst result: BufferedRange[] = [];\n\n\tfor (let i = 0; i < ranges.length; i++) {\n\t\tconst start = Moq.Time.Milli.fromSecond(ranges.start(i) as Moq.Time.Second);\n\t\tconst end = Moq.Time.Milli.fromSecond(ranges.end(i) as Moq.Time.Second);\n\n\t\tresult.push({ start, end });\n\t}\n\treturn result;\n}\n\nexport interface Backend {\n\t// Whether audio/video playback is paused.\n\tpaused: Signal<boolean>;\n\n\t// Video specific signals.\n\tvideo?: Video.Backend;\n\n\t// Audio specific signals.\n\taudio?: Audio.Backend;\n\n\t// The latency setting: \"real-time\" auto-computes jitter, a number sets a fixed jitter.\n\tlatency: Signal<Latency>;\n\n\t// The jitter buffer in milliseconds.\n\tjitter: Signal<Moq.Time.Milli>;\n}\n\nexport interface MultiBackendProps {\n\telement?: HTMLCanvasElement | HTMLVideoElement | Signal<HTMLCanvasElement | HTMLVideoElement | undefined>;\n\tbroadcast?: Broadcast | Signal<Broadcast | undefined>;\n\n\t// Latency: \"real-time\" auto-computes jitter from RTT, a number sets a fixed jitter in ms.\n\tlatency?: Latency | Signal<Latency>;\n\n\t// Established connection, used by Sync to read RTT (PROBE) for dynamic jitter in \"real-time\" mode.\n\tconnection?: Signal<Moq.Connection.Established | undefined>;\n\n\tpaused?: boolean | Signal<boolean>;\n}\n\n// We have to proxy some of these signals because we support both the MSE and WebCodecs.\nclass VideoBackend implements Video.Backend {\n\t// The source of the video.\n\tsource: Video.Source;\n\n\t// The stats of the video.\n\tstats = new Signal<Video.Stats | undefined>(undefined);\n\n\t// We're currently stalled waiting for the next frame\n\tstalled = new Signal<boolean>(false);\n\n\t// Buffered time ranges\n\tbuffered = new Signal<BufferedRanges>([]);\n\n\t// The timestamp of the current frame\n\ttimestamp = new Signal<Moq.Time.Milli>(Moq.Time.Milli.zero);\n\n\tconstructor(source: Video.Source) {\n\t\tthis.source = source;\n\t}\n}\n\n// Audio specific signals that work regardless of the backend source (mse vs webcodecs).\nclass AudioBackend implements Audio.Backend {\n\tsource: Audio.Source;\n\n\t// The volume of the audio, between 0 and 1.\n\tvolume = new Signal<number>(0.5);\n\n\t// Whether the audio is muted.\n\tmuted = new Signal<boolean>(false);\n\n\t// The stats of the audio.\n\tstats = new Signal<Audio.Stats | undefined>(undefined);\n\n\t// Buffered time ranges\n\tbuffered = new Signal<BufferedRanges>([]);\n\n\t// The AudioContext used for playback (set by the WebCodecs backend; undefined under MSE).\n\tcontext = new Signal<AudioContext | undefined>(undefined);\n\n\tconstructor(source: Audio.Source) {\n\t\tthis.source = source;\n\t}\n}\n\n/// A generic backend that supports either MSE or WebCodecs based on the provided element.\n///\n/// This is primarily what backs the <moq-watch> web component, but it's useful as a standalone for other use cases.\nexport class MultiBackend implements Backend {\n\telement = new Signal<HTMLCanvasElement | HTMLVideoElement | undefined>(undefined);\n\tbroadcast: Signal<Broadcast | undefined>;\n\tlatency: Signal<Latency>;\n\tjitter: Signal<Moq.Time.Milli>;\n\tpaused: Signal<boolean>;\n\n\tvideo: VideoBackend;\n\t#videoSource: Video.Source;\n\n\taudio: AudioBackend;\n\t#audioSource: Audio.Source;\n\n\t// Used to sync audio and video playback at a target delay.\n\tsync: Sync;\n\n\tsignals = new Effect();\n\n\tconstructor(props?: MultiBackendProps) {\n\t\tthis.element = Signal.from(props?.element);\n\t\tthis.broadcast = Signal.from(props?.broadcast);\n\t\tthis.sync = new Sync({ latency: props?.latency, connection: props?.connection });\n\t\tthis.latency = this.sync.latency;\n\t\tthis.jitter = this.sync.jitter;\n\n\t\tthis.#videoSource = new Video.Source(this.sync, {\n\t\t\tbroadcast: this.broadcast,\n\t\t});\n\t\tthis.#audioSource = new Audio.Source(this.sync, {\n\t\t\tbroadcast: this.broadcast,\n\t\t});\n\n\t\tthis.video = new VideoBackend(this.#videoSource);\n\t\tthis.audio = new AudioBackend(this.#audioSource);\n\n\t\tthis.paused = Signal.from(props?.paused ?? false);\n\n\t\tthis.signals.run(this.#runElement.bind(this));\n\t}\n\n\t#runElement(effect: Effect): void {\n\t\tconst element = effect.get(this.element);\n\t\tif (!element) return;\n\n\t\tif (element instanceof HTMLCanvasElement) {\n\t\t\tthis.#runWebcodecs(effect, element);\n\t\t} else if (element instanceof HTMLVideoElement) {\n\t\t\tthis.#runMse(effect, element);\n\t\t}\n\t}\n\n\t#runWebcodecs(effect: Effect, element: HTMLCanvasElement): void {\n\t\tconst videoSource = new Video.Decoder(this.#videoSource);\n\t\tconst audioSource = new Audio.Decoder(this.#audioSource);\n\n\t\tconst audioEmitter = new Audio.Emitter(audioSource, {\n\t\t\tvolume: this.audio.volume,\n\t\t\tmuted: this.audio.muted,\n\t\t\tpaused: this.paused,\n\t\t});\n\n\t\tconst videoRenderer = new Video.Renderer(videoSource, {\n\t\t\tcanvas: element,\n\t\t\tpaused: this.paused,\n\t\t});\n\n\t\teffect.cleanup(() => {\n\t\t\tvideoSource.close();\n\t\t\taudioSource.close();\n\t\t\taudioEmitter.close();\n\t\t\tvideoRenderer.close();\n\t\t});\n\n\t\t// Proxy the read only signals to the backend.\n\t\teffect.proxy(this.video.stats, videoSource.stats);\n\t\teffect.proxy(this.video.buffered, videoSource.buffered);\n\t\teffect.proxy(this.video.stalled, videoSource.stalled);\n\t\teffect.proxy(this.video.timestamp, videoSource.timestamp);\n\n\t\teffect.proxy(this.audio.stats, audioSource.stats);\n\t\teffect.proxy(this.audio.buffered, audioSource.buffered);\n\t\teffect.proxy(this.audio.context, audioSource.context);\n\t}\n\n\t#runMse(effect: Effect, element: HTMLVideoElement): void {\n\t\tconst mse = new Muxer(this.sync, {\n\t\t\tpaused: this.paused,\n\t\t\telement,\n\t\t});\n\n\t\tconst video = new Video.Mse(mse, this.#videoSource);\n\t\tconst audio = new Audio.Mse(mse, this.#audioSource, {\n\t\t\tvolume: this.audio.volume,\n\t\t\tmuted: this.audio.muted,\n\t\t});\n\n\t\teffect.cleanup(() => {\n\t\t\tvideo.close();\n\t\t\taudio.close();\n\t\t\tmse.close();\n\t\t});\n\n\t\t// Proxy the read only signals to the backend.\n\t\teffect.proxy(this.video.stats, video.stats);\n\t\teffect.proxy(this.video.buffered, video.buffered);\n\t\teffect.proxy(this.video.stalled, video.stalled);\n\t\teffect.proxy(this.video.timestamp, video.timestamp);\n\n\t\teffect.proxy(this.audio.stats, audio.stats);\n\t\teffect.proxy(this.audio.buffered, audio.buffered);\n\t\teffect.proxy(this.audio.context, audio.context);\n\t}\n\n\tclose(): void {\n\t\tthis.signals.close();\n\t\tthis.#videoSource.close();\n\t\tthis.#audioSource.close();\n\t\tthis.sync.close();\n\t}\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Container from \"@moq/hang/container\";\nimport * as Moq from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport { type BufferedRanges, timeRangesToArray } from \"../backend\";\nimport { base64ToBytes } from \"../base64\";\nimport type { Muxer } from \"../mse\";\nimport type { Backend, Stats } from \"./backend\";\nimport type { Source } from \"./source\";\n\nexport type MseProps = {\n\tvolume?: number | Signal<number>;\n\tmuted?: boolean | Signal<boolean>;\n};\n\nexport class Mse implements Backend {\n\tmuxer: Muxer;\n\tsource: Source;\n\n\tvolume: Signal<number>;\n\tmuted: Signal<boolean>;\n\n\t#stats = new Signal<Stats | undefined>(undefined);\n\treadonly stats: Getter<Stats | undefined> = this.#stats;\n\n\t#buffered = new Signal<BufferedRanges>([]);\n\treadonly buffered: Getter<BufferedRanges> = this.#buffered;\n\n\t// MSE plays through the <video> element, not WebAudio.\n\treadonly context: Getter<AudioContext | undefined> = new Signal<AudioContext | undefined>(undefined);\n\n\t#signals = new Effect();\n\n\tconstructor(muxer: Muxer, source: Source, props?: MseProps) {\n\t\tthis.muxer = muxer;\n\t\tthis.source = source;\n\t\tthis.source.supported.set(supported); // super hacky\n\n\t\tthis.volume = Signal.from(props?.volume ?? 0.5);\n\t\tthis.muted = Signal.from(props?.muted ?? false);\n\n\t\tthis.#signals.run(this.#runMedia.bind(this));\n\t\tthis.#signals.run(this.#runVolume.bind(this));\n\t}\n\n\t#runMedia(effect: Effect): void {\n\t\tconst element = effect.get(this.muxer.element);\n\t\tif (!element) return;\n\n\t\tconst mediaSource = effect.get(this.muxer.mediaSource);\n\t\tif (!mediaSource) return;\n\n\t\tconst broadcast = effect.get(this.source.broadcast);\n\t\tif (!broadcast) return;\n\n\t\tconst active = effect.get(broadcast.active);\n\t\tif (!active) return;\n\n\t\tconst track = effect.get(this.source.track);\n\t\tif (!track) return;\n\n\t\tconst config = effect.get(this.source.config);\n\t\tif (!config) return;\n\n\t\tconst mime = `audio/mp4; codecs=\"${config.codec}\"`;\n\n\t\tconst sourceBuffer = mediaSource.addSourceBuffer(mime);\n\t\teffect.cleanup(() => {\n\t\t\tmediaSource.removeSourceBuffer(sourceBuffer);\n\t\t\tsourceBuffer.abort();\n\t\t});\n\n\t\teffect.event(sourceBuffer, \"error\", (e) => {\n\t\t\tconsole.error(\"[MSE] SourceBuffer error:\", e);\n\t\t});\n\n\t\teffect.event(sourceBuffer, \"updateend\", () => {\n\t\t\tthis.#buffered.set(timeRangesToArray(sourceBuffer.buffered));\n\t\t});\n\n\t\tconst sub = active.subscribe(track, Catalog.PRIORITY.audio);\n\t\teffect.cleanup(() => sub.close());\n\n\t\tif (config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmafMedia(effect, sub, config, sourceBuffer, element);\n\t\t} else {\n\t\t\tthis.#runLegacyMedia(effect, sub, config, sourceBuffer, element);\n\t\t}\n\t}\n\n\tasync #appendBuffer(sourceBuffer: SourceBuffer, buffer: Uint8Array): Promise<void> {\n\t\twhile (sourceBuffer.updating) {\n\t\t\tawait new Promise((resolve) => sourceBuffer.addEventListener(\"updateend\", resolve, { once: true }));\n\t\t}\n\t\tsourceBuffer.appendBuffer(buffer as BufferSource);\n\t\twhile (sourceBuffer.updating) {\n\t\t\tawait new Promise((resolve) => sourceBuffer.addEventListener(\"updateend\", resolve, { once: true }));\n\t\t}\n\t}\n\n\t#runCmafMedia(\n\t\teffect: Effect,\n\t\tsub: Moq.Track,\n\t\tconfig: Catalog.AudioConfig,\n\t\tsourceBuffer: SourceBuffer,\n\t\telement: HTMLMediaElement,\n\t): void {\n\t\tif (config.container.kind !== \"cmaf\") throw new Error(\"unreachable\");\n\n\t\tconst initSegment = base64ToBytes(config.container.init);\n\t\tconst init = Container.Cmaf.decodeInitSegment(initSegment);\n\n\t\teffect.spawn(async () => {\n\t\t\tawait this.#appendBuffer(sourceBuffer, initSegment);\n\n\t\t\tfor (;;) {\n\t\t\t\t// TODO: Use Frame.Consumer for CMAF so we can support higher latencies.\n\t\t\t\t// It requires extracting the timestamp from the frame payload.\n\t\t\t\tconst frame = await sub.readFrame();\n\t\t\t\tif (!frame) return;\n\n\t\t\t\t// Extract the timestamp from the CMAF segment and mark when we received it.\n\t\t\t\tconst timestamp = Container.Cmaf.decodeTimestamp(frame, init);\n\t\t\t\tthis.source.sync.received(Moq.Time.Milli.fromMicro(timestamp), \"audio\");\n\n\t\t\t\tawait this.#appendBuffer(sourceBuffer, frame);\n\n\t\t\t\t// Seek to the start of the buffer if we're behind it (for startup).\n\t\t\t\tif (element.buffered.length > 0 && element.currentTime < element.buffered.start(0)) {\n\t\t\t\t\telement.currentTime = element.buffered.start(0);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t#runLegacyMedia(\n\t\teffect: Effect,\n\t\tsub: Moq.Track,\n\t\tconfig: Catalog.AudioConfig,\n\t\tsourceBuffer: SourceBuffer,\n\t\telement: HTMLMediaElement,\n\t): void {\n\t\tconst format = config.container.kind === \"loc\" ? new Container.Loc.Format() : new Container.Legacy.Format();\n\t\t// Create consumer that reorders groups/frames up to the provided latency.\n\t\tconst consumer = new Container.Consumer(sub, {\n\t\t\tformat,\n\t\t\tlatency: this.source.sync.buffer,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\teffect.spawn(async () => {\n\t\t\t// Generate init segment from catalog config (timescale = 1,000,000 = microseconds)\n\t\t\tconst initSegment = Container.Cmaf.createAudioInitSegment(config);\n\t\t\tawait this.#appendBuffer(sourceBuffer, initSegment);\n\n\t\t\tlet sequence = 1;\n\t\t\tlet duration: Moq.Time.Micro | undefined;\n\n\t\t\t// Buffer one frame so we can compute accurate duration from the next frame's timestamp\n\t\t\tlet pending: Container.Frame;\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (!next) return;\n\t\t\t\tif (!next.frame) continue; // Skip over group done notifications.\n\n\t\t\t\tpending = next.frame;\n\n\t\t\t\t// Mark that we received this frame for latency calculation.\n\t\t\t\tconst timestamp = Moq.Time.Milli.fromMicro(pending.timestamp as Moq.Time.Micro);\n\t\t\t\tthis.source.sync.received(timestamp, \"audio\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await consumer.next();\n\t\t\t\tif (next && !next.frame) continue; // Skip over group done notifications.\n\n\t\t\t\tconst frame = next?.frame;\n\n\t\t\t\t// Compute duration from next frame's timestamp, or use last known duration if stream ended\n\t\t\t\tif (frame) {\n\t\t\t\t\tduration = Moq.Time.Micro.sub(frame.timestamp, pending.timestamp);\n\n\t\t\t\t\t// Mark that we received this frame for latency calculation.\n\t\t\t\t\tconst timestamp = Moq.Time.Milli.fromMicro(frame.timestamp as Moq.Time.Micro);\n\t\t\t\t\tthis.source.sync.received(timestamp, \"audio\");\n\t\t\t\t}\n\n\t\t\t\t// Wrap raw frame in moof+mdat\n\t\t\t\tconst segment = Container.Cmaf.encodeDataSegment({\n\t\t\t\t\tdata: pending.data,\n\t\t\t\t\ttimestamp: pending.timestamp,\n\t\t\t\t\tduration: duration ?? 0, // Default to 0 duration if there's literally one frame then stream FIN.\n\t\t\t\t\tkeyframe: pending.keyframe,\n\t\t\t\t\tsequence: sequence++,\n\t\t\t\t});\n\n\t\t\t\tawait this.#appendBuffer(sourceBuffer, segment);\n\n\t\t\t\t// Seek to the start of the buffer if we're behind it (for startup).\n\t\t\t\tif (element.buffered.length > 0 && element.currentTime < element.buffered.start(0)) {\n\t\t\t\t\telement.currentTime = element.buffered.start(0);\n\t\t\t\t}\n\n\t\t\t\tif (!frame) return;\n\t\t\t\tpending = frame;\n\t\t\t}\n\t\t});\n\t}\n\n\t#runVolume(effect: Effect): void {\n\t\tconst element = effect.get(this.muxer.element);\n\t\tif (!element) return;\n\n\t\tconst volume = effect.get(this.volume);\n\t\tconst muted = effect.get(this.muted);\n\n\t\tif (muted && !element.muted) {\n\t\t\telement.muted = true;\n\t\t} else if (!muted && element.muted) {\n\t\t\telement.muted = false;\n\t\t}\n\n\t\tif (volume !== element.volume) {\n\t\t\telement.volume = volume;\n\t\t}\n\n\t\teffect.event(element, \"volumechange\", () => {\n\t\t\tthis.volume.set(element.volume);\n\t\t});\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n\nasync function supported(config: Catalog.AudioConfig): Promise<boolean> {\n\treturn MediaSource.isTypeSupported(`audio/mp4; codecs=\"${config.codec}\"`);\n}\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport type * as Moq from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { Broadcast } from \"../broadcast\";\nimport type { Sync } from \"../sync\";\n\n// AudioWorklet always renders in 128-sample quanta.\nconst WORKLET_QUANTUM = 128;\n\nexport type Target = {\n\t// Optional manual override for the selected rendition name.\n\tname?: string;\n};\n\n/**\n * A function that checks if an audio configuration is supported by the backend.\n */\nexport type Supported = (config: Catalog.AudioConfig) => Promise<boolean>;\n\nexport type SourceProps = {\n\tbroadcast?: Broadcast | Signal<Broadcast | undefined>;\n\n\t// The desired rendition/bitrate of the audio.\n\ttarget?: Target | Signal<Target | undefined>;\n\n\t// A function that checks if an audio configuration is supported by the backend.\n\tsupported?: Supported;\n};\n\n/**\n * Source handles catalog extraction, support checking, and rendition selection\n * for audio playback. It is used by both MSE and Decoder backends.\n */\nexport class Source {\n\tbroadcast: Signal<Broadcast | undefined>;\n\ttarget: Signal<Target | undefined>;\n\n\treadonly catalog: Getter<Catalog.Audio | undefined>;\n\n\t#available = new Signal<Record<string, Catalog.AudioConfig>>({});\n\treadonly available: Getter<Record<string, Catalog.AudioConfig>> = this.#available;\n\n\t#track = new Signal<string | undefined>(undefined);\n\treadonly track: Signal<string | undefined> = this.#track;\n\n\t#config = new Signal<Catalog.AudioConfig | undefined>(undefined);\n\treadonly config: Getter<Catalog.AudioConfig | undefined> = this.#config;\n\n\tsupported: Signal<Supported | undefined>;\n\n\t// Used to target a latency and synchronize playback of video with audio.\n\tsync: Sync;\n\n\t#signals = new Effect();\n\n\tconstructor(sync: Sync, props?: SourceProps) {\n\t\tthis.sync = sync;\n\n\t\tthis.broadcast = Signal.from(props?.broadcast);\n\t\tthis.target = Signal.from(props?.target);\n\t\tthis.supported = Signal.from(props?.supported);\n\n\t\t// The audio catalog, derived from the active broadcast.\n\t\tthis.catalog = this.#signals.computed((effect) => {\n\t\t\tconst broadcast = effect.get(this.broadcast);\n\t\t\treturn broadcast ? effect.get(broadcast.catalog)?.audio : undefined;\n\t\t});\n\n\t\tthis.#signals.run(this.#runSupported.bind(this));\n\t\tthis.#signals.run(this.#runSelected.bind(this));\n\t}\n\n\t#runSupported(effect: Effect): void {\n\t\tconst renditions = effect.get(this.catalog)?.renditions ?? {};\n\t\tconst supported = effect.get(this.supported);\n\t\tif (!supported) return;\n\n\t\teffect.spawn(async () => {\n\t\t\tconst available: Record<string, Catalog.AudioConfig> = {};\n\n\t\t\tfor (const [name, config] of Object.entries(renditions)) {\n\t\t\t\tconst isSupported = await supported(config);\n\t\t\t\tif (isSupported) available[name] = config;\n\t\t\t}\n\n\t\t\tif (Object.keys(available).length === 0 && Object.keys(renditions).length > 0) {\n\t\t\t\tconsole.warn(\"no supported audio renditions found:\", renditions);\n\t\t\t}\n\n\t\t\tthis.#available.set(available);\n\t\t});\n\t}\n\n\t#runSelected(effect: Effect): void {\n\t\tconst available = effect.get(this.#available);\n\t\tif (Object.keys(available).length === 0) return;\n\n\t\tconst target = effect.get(this.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.#track, selected.track);\n\t\teffect.set(this.#config, selected.config);\n\n\t\t// Use catalog jitter if available, otherwise estimate from codec frame duration.\n\t\t// Add the worklet render quantum so the ring buffer has margin between frame arrivals.\n\t\tconst codecJitter = selected.config.jitter ?? defaultAudioJitter(selected.config) ?? 0;\n\t\tconst overhead = Math.ceil((WORKLET_QUANTUM / selected.config.sampleRate) * 1000);\n\t\tconst jitter = codecJitter + overhead;\n\t\teffect.set(this.sync.audio, jitter as Moq.Time.Milli);\n\t}\n\n\t/**\n\t * Select rendition based on the configured strategy.\n\t */\n\t#select(\n\t\trenditions: Record<string, Catalog.AudioConfig>,\n\t): { track: string; config: Catalog.AudioConfig } | undefined {\n\t\tconst entries = Object.entries(renditions);\n\t\tif (entries.length === 0) return undefined;\n\n\t\tfor (const [track, config] of entries) {\n\t\t\tif (config.container.kind === \"legacy\") {\n\t\t\t\treturn { track, config };\n\t\t\t}\n\t\t}\n\n\t\tfor (const [track, config] of entries) {\n\t\t\tif (config.container.kind === \"loc\") {\n\t\t\t\treturn { track, config };\n\t\t\t}\n\t\t}\n\n\t\tfor (const [track, config] of entries) {\n\t\t\tif (config.container.kind === \"cmaf\") {\n\t\t\t\treturn { track, config };\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n\n// Estimate the minimum jitter (frame duration) based on the audio codec.\n// TODO these are defaults; the actual frame duration depends on encoder config.\nfunction defaultAudioJitter(config: Catalog.AudioConfig): number | undefined {\n\tif (config.codec.startsWith(\"opus\")) {\n\t\t// Opus supports 2.5–60ms but 20ms is the real-time default.\n\t\treturn 20;\n\t}\n\n\tif (config.codec.startsWith(\"mp4a\")) {\n\t\t// 1024 samples for LC-AAC; HE-AAC/AAC-LD use different sizes.\n\t\treturn Math.ceil((1024 / config.sampleRate) * 1000);\n\t}\n\n\treturn undefined;\n}\n","export type * from \"./backend\";\nexport * from \"./decoder\";\nexport * from \"./emitter\";\nexport * from \"./mse\";\nexport * from \"./source\";\n","import type * as Catalog from \"@moq/hang/catalog\";\nimport { u53 } from \"@moq/hang/catalog\";\nimport type * as Msf from \"@moq/msf\";\nimport { base64ToBytes } from \"./base64\";\n\nconst DEFAULT_SAMPLE_RATE = 48000;\nconst DEFAULT_NUMBER_OF_CHANNELS = 2;\n\nfunction bytesToHex(bytes: Uint8Array): string {\n\tlet hex = \"\";\n\tfor (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, \"0\");\n\treturn hex;\n}\n\ninterface ContainerInfo {\n\tcontainer: Catalog.Container;\n\tdescription?: string;\n}\n\nfunction toContainer(track: Msf.Track): ContainerInfo {\n\tlet initBytes: Uint8Array | undefined;\n\ttry {\n\t\tinitBytes = track.initData ? base64ToBytes(track.initData) : undefined;\n\t} catch {\n\t\tinitBytes = undefined;\n\t}\n\n\tif (track.packaging === \"cmaf\" && track.initData && initBytes) {\n\t\treturn {\n\t\t\tcontainer: { kind: \"cmaf\", init: track.initData },\n\t\t\t// hang's CMAF path reads codec metadata from the init segment, so we\n\t\t\t// don't need to surface a separate description here.\n\t\t\tdescription: undefined,\n\t\t};\n\t}\n\n\treturn {\n\t\tcontainer: { kind: \"legacy\" },\n\t\tdescription: initBytes ? bytesToHex(initBytes) : undefined,\n\t};\n}\n\nfunction toVideoConfig(track: Msf.Track): Catalog.VideoConfig | undefined {\n\tif (!track.codec) return undefined;\n\n\tconst { container, description } = toContainer(track);\n\treturn {\n\t\tcodec: track.codec,\n\t\tcontainer,\n\t\tdescription,\n\t\tcodedWidth: track.width != null ? u53(track.width) : undefined,\n\t\tcodedHeight: track.height != null ? u53(track.height) : undefined,\n\t\tframerate: track.framerate,\n\t\tbitrate: track.bitrate != null ? u53(track.bitrate) : undefined,\n\t\tjitter: track.jitter != null ? u53(track.jitter) : undefined,\n\t};\n}\n\nfunction toAudioConfig(track: Msf.Track): Catalog.AudioConfig | undefined {\n\tif (!track.codec) return undefined;\n\n\tconst channels = (() => {\n\t\tif (!track.channelConfig) return DEFAULT_NUMBER_OF_CHANNELS;\n\t\tconst parsed = Number.parseInt(track.channelConfig, 10);\n\t\treturn Number.isFinite(parsed) ? parsed : DEFAULT_NUMBER_OF_CHANNELS;\n\t})();\n\n\tconst { container, description } = toContainer(track);\n\treturn {\n\t\tcodec: track.codec,\n\t\tcontainer,\n\t\tdescription,\n\t\tsampleRate: u53(track.samplerate ?? DEFAULT_SAMPLE_RATE),\n\t\tnumberOfChannels: u53(channels),\n\t\tbitrate: track.bitrate != null ? u53(track.bitrate) : undefined,\n\t\tjitter: track.jitter != null ? u53(track.jitter) : undefined,\n\t};\n}\n\n/** Convert an MSF catalog to a hang catalog Root. */\nexport function toHang(msf: Msf.Catalog): Catalog.Root {\n\tconst videoRenditions: Record<string, Catalog.VideoConfig> = {};\n\tconst audioRenditions: Record<string, Catalog.AudioConfig> = {};\n\n\tfor (const track of msf.tracks) {\n\t\tif (track.role === \"video\") {\n\t\t\tconst config = toVideoConfig(track);\n\t\t\tif (config) videoRenditions[track.name] = config;\n\t\t} else if (track.role === \"audio\") {\n\t\t\tconst config = toAudioConfig(track);\n\t\t\tif (config) audioRenditions[track.name] = config;\n\t\t}\n\t}\n\n\tconst root: Catalog.Root = {};\n\n\tif (Object.keys(videoRenditions).length > 0) {\n\t\troot.video = { renditions: videoRenditions };\n\t}\n\n\tif (Object.keys(audioRenditions).length > 0) {\n\t\troot.audio = { renditions: audioRenditions };\n\t}\n\n\treturn root;\n}\n","import * as Catalog from \"@moq/hang/catalog\";\nimport * as Json from \"@moq/json\";\nimport * as Msf from \"@moq/msf\";\nimport type * as Moq from \"@moq/net\";\nimport { Path } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\n\nimport { toHang } from \"./msf\";\n\n// Watch supports the two on-the-wire catalog formats from @moq/hang plus a\n// \"manual\" mode where the user supplies the catalog directly without fetching.\nexport const CATALOG_FORMATS = [...Catalog.FORMATS, \"manual\"] as const;\nexport type CatalogFormat = (typeof CATALOG_FORMATS)[number];\n\nexport function parseCatalogFormat(value: string | null): CatalogFormat | undefined {\n\tif (value === null) return undefined;\n\treturn CATALOG_FORMATS.find((f) => f === value);\n}\n\nexport interface BroadcastProps {\n\tconnection?: Moq.Connection.Established | Signal<Moq.Connection.Established | undefined>;\n\n\t// All actively announced broadcast paths from the connection.\n\tannounced?: Getter<Set<Moq.Path.Valid>>;\n\n\t// Whether to start downloading the broadcast.\n\t// Defaults to false so you can make sure everything is ready before starting.\n\tenabled?: boolean | Signal<boolean>;\n\n\t// The broadcast name.\n\tname?: Moq.Path.Valid | Signal<Moq.Path.Valid>;\n\n\t// Whether to reload the broadcast when it goes offline.\n\t// Defaults to false; pass true to wait for an announcement before subscribing.\n\treload?: boolean | Signal<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.\n\tcatalogFormat?: CatalogFormat | Signal<CatalogFormat | undefined>;\n\n\t// Initial catalog. Used directly when catalogFormat is \"manual\"; otherwise it's\n\t// overwritten by whatever the fetched catalog track produces. Note: switching\n\t// catalogFormat between \"manual\" and a fetched format will reset this signal\n\t// to undefined when the fetched-format spawn tears down. Set the catalog\n\t// after switching formats, not before.\n\tcatalog?: Catalog.Root | Signal<Catalog.Root | undefined>;\n}\n\n// A catalog source that (optionally) reloads automatically when live/offline.\nexport class Broadcast {\n\tconnection: Signal<Moq.Connection.Established | undefined>;\n\n\tenabled: Signal<boolean>;\n\tname: Signal<Moq.Path.Valid>;\n\tstatus = new Signal<\"offline\" | \"loading\" | \"live\">(\"offline\");\n\treload: Signal<boolean>;\n\n\t// `undefined` means auto-detect from the broadcast name extension.\n\tcatalogFormat: Signal<CatalogFormat | undefined>;\n\n\t#active = new Signal<Moq.Broadcast | undefined>(undefined);\n\treadonly active: Getter<Moq.Broadcast | undefined> = this.#active;\n\n\t// The active catalog. Writable so users can supply it directly when\n\t// catalogFormat is \"manual\"; otherwise the fetch loop owns writes.\n\tcatalog: Signal<Catalog.Root | undefined>;\n\n\t// All actively announced broadcast paths from the connection.\n\t#announced: Getter<Set<Moq.Path.Valid>>;\n\n\t// Whether `name` is currently in the announced set (or skipping the check).\n\t// Derived in its own effect so that flaps for unrelated broadcasts don't\n\t// retrigger the broadcast/catalog subscriptions.\n\t#announcedNow = new Signal(false);\n\n\tsignals = new Effect();\n\n\tconstructor(props?: BroadcastProps) {\n\t\tthis.connection = Signal.from(props?.connection);\n\t\tthis.name = Signal.from(props?.name ?? Path.empty());\n\t\tthis.enabled = Signal.from(props?.enabled ?? false);\n\t\tthis.reload = Signal.from(props?.reload ?? false);\n\t\tthis.catalogFormat = Signal.from<CatalogFormat | undefined>(props?.catalogFormat);\n\t\tthis.catalog = Signal.from(props?.catalog);\n\n\t\tthis.#announced = props?.announced ?? new Signal(new Set());\n\n\t\tthis.signals.run(this.#runAnnouncedNow.bind(this));\n\t\tthis.signals.run(this.#runBroadcast.bind(this));\n\t\tthis.signals.run(this.#runCatalog.bind(this));\n\t}\n\n\t#runAnnouncedNow(effect: Effect): void {\n\t\tconst reload = effect.get(this.reload);\n\t\tif (!reload) {\n\t\t\tthis.#announcedNow.set(true);\n\t\t\treturn;\n\t\t}\n\n\t\t// Cloudflare's relay does not yet support announcement subscriptions,\n\t\t// so an announcement will never arrive. Fall back to subscribing\n\t\t// immediately (reload=false behaviour) instead of waiting forever.\n\t\tconst conn = effect.get(this.connection);\n\t\tif (conn?.url.hostname.endsWith(\"mediaoverquic.com\")) {\n\t\t\tconsole.warn(\"Cloudflare relay does not support broadcast discovery yet; ignoring reload signal.\");\n\t\t\tthis.#announcedNow.set(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst name = effect.get(this.name);\n\t\tconst announced = effect.get(this.#announced);\n\t\tthis.#announcedNow.set(announced.has(name));\n\t}\n\n\t#runBroadcast(effect: Effect): void {\n\t\tconst enabled = effect.get(this.enabled);\n\t\tif (!enabled) return;\n\n\t\tif (!effect.get(this.#announcedNow)) return;\n\n\t\tconst conn = effect.get(this.connection);\n\t\tif (!conn) return;\n\n\t\tconst name = effect.get(this.name);\n\t\tconst broadcast = conn.consume(name);\n\t\teffect.cleanup(() => broadcast.close());\n\n\t\teffect.set(this.#active, broadcast, undefined);\n\t}\n\n\t#runCatalog(effect: Effect): void {\n\t\tconst enabled = effect.get(this.enabled);\n\t\tif (!enabled) return;\n\n\t\tconst catalogFormat = effect.get(this.catalogFormat);\n\t\tconst name = effect.get(this.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// User-supplied catalog; no track to fetch.\n\t\t\tconst catalog = effect.get(this.catalog);\n\t\t\tthis.status.set(catalog ? \"live\" : \"loading\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst broadcast = effect.get(this.active);\n\t\tif (!broadcast) return;\n\n\t\tthis.status.set(\"loading\");\n\n\t\tconst trackName = format === \"hang\" ? \"catalog.json\" : \"catalog\";\n\t\tconst track = broadcast.subscribe(trackName, 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;\n\t\t// MSF stays on its own one-blob-per-group fetch.\n\t\tlet fetchNext: () => Promise<Catalog.Root | undefined>;\n\t\tif (format === \"hang\") {\n\t\t\tconst consumer = new Json.Consumer<Catalog.Root>(track, { schema: Catalog.RootSchema });\n\t\t\tfetchNext = () => consumer.next();\n\t\t} else {\n\t\t\tfetchNext = async () => {\n\t\t\t\tconst update = await Msf.fetch(track);\n\t\t\t\treturn update ? toHang(update) : undefined;\n\t\t\t};\n\t\t}\n\n\t\teffect.spawn(async () => {\n\t\t\ttry {\n\t\t\t\tfor (;;) {\n\t\t\t\t\tconst update = await Promise.race([effect.cancel, fetchNext()]);\n\t\t\t\t\tif (!update) break;\n\n\t\t\t\t\tconsole.debug(\"received catalog\", format, this.name.peek(), update);\n\n\t\t\t\t\tthis.catalog.set(update);\n\t\t\t\t\tthis.status.set(\"live\");\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.warn(\"error fetching catalog\", this.name.peek(), err);\n\t\t\t} finally {\n\t\t\t\tthis.catalog.set(undefined);\n\t\t\t\tthis.status.set(\"offline\");\n\t\t\t}\n\t\t});\n\t}\n\n\tclose() {\n\t\tthis.signals.close();\n\t}\n}\n"],"mappings":"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"}
package/element.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, r as t, t as n } from "./broadcast-jtLUfuyL.js";
1
+ import { a as e, r as t, t as n } from "./broadcast-CG8jZRfA.js";
2
2
  import * as r from "@moq/net";
3
3
  import { Effect as i, Signal as a } from "@moq/signals";
4
4
  //#region src/element.ts
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c } from "./broadcast-jtLUfuyL.js";
1
+ import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c } from "./broadcast-CG8jZRfA.js";
2
2
  import * as l from "@moq/hang";
3
3
  import * as u from "@moq/net";
4
4
  import * as d from "@moq/signals";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@moq/watch",
3
3
  "type": "module",
4
- "version": "0.2.15",
4
+ "version": "0.2.16",
5
5
  "description": "Watch Media over QUIC broadcasts",
6
6
  "license": "(MIT OR Apache-2.0)",
7
7
  "repository": "github:moq-dev/moq",
@@ -33,9 +33,9 @@
33
33
  "./support/element.js"
34
34
  ],
35
35
  "dependencies": {
36
- "@moq/hang": "^0.2.8",
36
+ "@moq/hang": "^0.2.9",
37
37
  "@moq/json": "^0.0.2",
38
- "@moq/net": "^0.1.3",
38
+ "@moq/net": "^0.1.4",
39
39
  "@moq/msf": "^0.1.1",
40
40
  "@moq/signals": "^0.1.8"
41
41
  }