@moq/watch 0.3.1 → 0.4.0

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 (73) hide show
  1. package/README.md +1 -2
  2. package/audio/decoder.d.ts +25 -15
  3. package/audio/decoder.d.ts.map +1 -1
  4. package/audio/emitter.d.ts +13 -10
  5. package/audio/emitter.d.ts.map +1 -1
  6. package/audio/index.d.ts +0 -2
  7. package/audio/index.d.ts.map +1 -1
  8. package/audio/source.d.ts +21 -17
  9. package/audio/source.d.ts.map +1 -1
  10. package/audio/unlock.d.ts +20 -0
  11. package/audio/unlock.d.ts.map +1 -0
  12. package/audio/unlock.test.d.ts +2 -0
  13. package/audio/unlock.test.d.ts.map +1 -0
  14. package/broadcast.d.ts +27 -57
  15. package/broadcast.d.ts.map +1 -1
  16. package/element.d.ts +42 -12
  17. package/element.d.ts.map +1 -1
  18. package/element.js +146 -90
  19. package/element.js.map +1 -1
  20. package/index.d.ts +9 -6
  21. package/index.d.ts.map +1 -1
  22. package/index.js +24 -5
  23. package/index.js.map +1 -0
  24. package/package.json +5 -6
  25. package/source-CWtgjSC_.js +1359 -0
  26. package/source-CWtgjSC_.js.map +1 -0
  27. package/support/element.d.ts.map +1 -1
  28. package/support/element.js.map +1 -1
  29. package/support/index.d.ts +3 -3
  30. package/support/index.d.ts.map +1 -1
  31. package/support/index.js +18 -17
  32. package/support/index.js.map +1 -1
  33. package/sync-zdwVEj9u.js +129 -0
  34. package/sync-zdwVEj9u.js.map +1 -0
  35. package/sync.d.ts +19 -18
  36. package/sync.d.ts.map +1 -1
  37. package/ui/components/buffer-control.d.ts +1 -1
  38. package/ui/components/buffer-control.d.ts.map +1 -1
  39. package/ui/components/fullscreen-button.d.ts +1 -2
  40. package/ui/components/fullscreen-button.d.ts.map +1 -1
  41. package/ui/components/play-pause.d.ts +1 -1
  42. package/ui/components/play-pause.d.ts.map +1 -1
  43. package/ui/element.d.ts.map +1 -1
  44. package/ui/element.js +269 -265
  45. package/ui/element.js.map +1 -1
  46. package/ui/fullscreen.d.ts +1 -2
  47. package/ui/fullscreen.d.ts.map +1 -1
  48. package/video/decoder.d.ts +32 -16
  49. package/video/decoder.d.ts.map +1 -1
  50. package/video/index.d.ts +0 -2
  51. package/video/index.d.ts.map +1 -1
  52. package/video/renderer.d.ts +14 -17
  53. package/video/renderer.d.ts.map +1 -1
  54. package/video/source.d.ts +23 -20
  55. package/video/source.d.ts.map +1 -1
  56. package/audio/backend.d.ts +0 -26
  57. package/audio/backend.d.ts.map +0 -1
  58. package/audio/mse.d.ts +0 -22
  59. package/audio/mse.d.ts.map +0 -1
  60. package/backend.d.ts +0 -65
  61. package/backend.d.ts.map +0 -1
  62. package/broadcast-C2qofUBv.js +0 -1701
  63. package/broadcast-C2qofUBv.js.map +0 -1
  64. package/buffered.d.ts +0 -19
  65. package/buffered.d.ts.map +0 -1
  66. package/mse.d.ts +0 -19
  67. package/mse.d.ts.map +0 -1
  68. package/sync-D67IxmxJ.js +0 -128
  69. package/sync-D67IxmxJ.js.map +0 -1
  70. package/video/backend.d.ts +0 -25
  71. package/video/backend.d.ts.map +0 -1
  72. package/video/mse.d.ts +0 -23
  73. package/video/mse.d.ts.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"broadcast-C2qofUBv.js","names":["#control","#samples","#anchored","#enabled","#headroom","#waiters","#timestamp","#stalled","#worklet","#backpressure","#ring","#signals","#context","#worklet","#sampleRate","#stats","#timestamp","#stalled","#buffered","#signals","#consumerLatency","#runWorklet","#runEnabled","#runLatency","#runDecoder","#ring","#trimDecodeBuffered","#runCmafDecoder","#runLegacyDecoder","#decodeBuffered","#emit","#onDiscontinuity","#addDecodeBuffered","#discontinuity","#signals","#unmuteVolume","#gain","#stats","#buffered","#signals","#runMedia","#runVolume","#runCmafMedia","#runLegacyMedia","#appendBuffer","#available","#track","#config","#signals","#runSupported","#runSelected","#select","#mediaSource","#sync","#signals","#runMediaSource","#runSkip","#runTrim","#runPaused","#runSync","#frame","#timestamp","#display","#stalled","#stats","#buffered","#signals","#runPending","#runActive","#runDisplay","#runBuffering","#active","#clearCurrentFrame","#run","#discontinuity","#trimBuffered","#runCmaf","#runLegacy","#onDiscontinuity","#addBuffered","#stats","#buffered","#stalled","#timestamp","#runMedia","#runStalled","#runTimestamp","#runCmafMedia","#runLegacyMedia","#appendBuffer","#signals","#ctx","#runVisible","#runEnabled","#runRender","#runResize","#visible","#render","#available","#error","#track","#config","#signals","#runSupported","#runSelected","#select","#videoSource","#audioSource","#runElement","#runWebcodecs","#runMse","#audioDecoder","#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/buffered.ts","../src/audio/mse.ts","../src/audio/source.ts","../src/audio/index.ts","../src/mse.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/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\t// Buffered mode: anchor to the first sample and never skip ahead on read.\n\tbuffered: boolean;\n}\n\nexport function allocSharedRingBuffer(\n\tchannels: number,\n\tcapacity: number,\n\trate: number,\n\tbuffered = false,\n): SharedRingBufferInit {\n\tif (channels <= 0) throw new Error(\"invalid channels\");\n\tif (capacity <= 0) throw new Error(\"invalid capacity\");\n\tif (rate <= 0) throw new Error(\"invalid sample rate\");\n\n\tconst samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\n\tconst control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\n\n\t// Initialize STALLED to 1\n\tconst ctrl = new Int32Array(control);\n\tAtomics.store(ctrl, STALLED, 1);\n\n\treturn { channels, capacity, rate, samples, control, buffered };\n}\n\n/** Modular i32 max: returns a if a is ahead of b, else b. */\nfunction i32Max(a: number, b: number): number {\n\treturn ((a - b) | 0) > 0 ? a : b;\n}\n\n/** Maps an absolute sample index to a [0, capacity) array slot. */\nfunction slot(idx: number, capacity: number): number {\n\treturn ((idx % capacity) + capacity) % capacity;\n}\n\n/**\n * Atomically advance `arr[idx]` to `candidate` iff `candidate` is strictly ahead\n * (in modular i32 ordering). Retries under contention so the slot only ever\n * moves forward and concurrent writers/readers can't clobber each other.\n */\nfunction casAdvance(arr: Int32Array, idx: number, candidate: number): number {\n\tfor (;;) {\n\t\tconst current = Atomics.load(arr, idx);\n\t\tif (((candidate - current) | 0) <= 0) return current;\n\t\tconst witnessed = Atomics.compareExchange(arr, idx, current, candidate);\n\t\tif (witnessed === current) return candidate;\n\t}\n}\n\nexport class SharedRingBuffer {\n\treadonly channels: number;\n\treadonly capacity: number;\n\treadonly rate: number;\n\treadonly buffered: boolean;\n\treadonly init: SharedRingBufferInit;\n\n\t#control: Int32Array;\n\t#samples: Float32Array[];\n\n\t// Whether READ/WRITE have been anchored to the first inserted sample (buffered mode).\n\t#anchored = false;\n\n\tconstructor(init: SharedRingBufferInit) {\n\t\tthis.channels = init.channels;\n\t\tthis.capacity = init.capacity;\n\t\tthis.rate = init.rate;\n\t\tthis.buffered = init.buffered;\n\t\tthis.init = init;\n\n\t\tthis.#control = new Int32Array(init.control);\n\t\tthis.#samples = [];\n\t\tfor (let i = 0; i < this.channels; i++) {\n\t\t\tthis.#samples.push(\n\t\t\t\tnew Float32Array(init.samples, i * this.capacity * Float32Array.BYTES_PER_ELEMENT, this.capacity),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Insert audio samples at the given timestamp.\n\t * Main thread only. Handles out-of-order writes, gap filling, and overflow.\n\t */\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tif (data.length !== this.channels) throw new Error(\"wrong number of channels\");\n\n\t\tlet start = Math.round(Time.Second.fromMicro(timestamp) * this.rate);\n\t\tconst originalLength = data[0].length;\n\t\tlet offset = 0;\n\n\t\t// Buffered mode: anchor READ/WRITE to the first sample so playback starts at its\n\t\t// timestamp, instead of skipping ahead or gap-filling silence from index 0.\n\t\tif (this.buffered && !this.#anchored) {\n\t\t\tAtomics.store(this.#control, READ, start | 0);\n\t\t\tAtomics.store(this.#control, WRITE, start | 0);\n\t\t\tthis.#anchored = true;\n\t\t}\n\n\t\tconst end = (start + originalLength) | 0;\n\n\t\t// Trim old: discard samples before the read index\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\tconst behind = (read - start) | 0;\n\t\tif (behind > 0) {\n\t\t\tif (behind >= originalLength) {\n\t\t\t\t// All samples are too old\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toffset = behind;\n\t\t\tstart = (start + behind) | 0;\n\t\t}\n\n\t\tconst samples = originalLength - offset;\n\n\t\t// Overflow: if the write would exceed capacity from current READ, advance READ.\n\t\t// Use CAS so a concurrent reader advance isn't clobbered backward.\n\t\tif (((end - read) | 0) > this.capacity) {\n\t\t\tcasAdvance(this.#control, READ, (end - this.capacity) | 0);\n\t\t}\n\n\t\t// Gap fill: zero-fill from current WRITE to start if there's a discontinuity\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst gap = (start - write) | 0;\n\t\tif (gap > 0) {\n\t\t\tconst gapSize = Math.min(gap, this.capacity);\n\t\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\t\tconst dst = this.#samples[channel];\n\t\t\t\tfor (let i = 0; i < gapSize; i++) {\n\t\t\t\t\tdst[slot((write + i) | 0, this.capacity)] = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Write sample data\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = data[channel];\n\t\t\tconst dst = this.#samples[channel];\n\t\t\tfor (let i = 0; i < samples; i++) {\n\t\t\t\tdst[slot((start + i) | 0, this.capacity)] = src[offset + i];\n\t\t\t}\n\t\t}\n\n\t\t// Advance WRITE (only forward)\n\t\tAtomics.store(this.#control, WRITE, i32Max(Atomics.load(this.#control, WRITE), end));\n\n\t\t// Un-stall: if buffered data >= LATENCY\n\t\tconst currentRead = Atomics.load(this.#control, READ);\n\t\tconst currentWrite = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tif (((currentWrite - currentRead) | 0) >= latency && latency > 0) {\n\t\t\tAtomics.store(this.#control, STALLED, 0);\n\t\t}\n\t}\n\n\t/**\n\t * Read audio samples into the output buffers.\n\t * AudioWorklet only. Returns the number of samples read.\n\t */\n\tread(output: Float32Array[]): number {\n\t\tif (Atomics.load(this.#control, STALLED) === 1) return 0;\n\n\t\tlet read = Atomics.load(this.#control, READ);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\n\t\t// Latency skip: if buffered data exceeds LATENCY, skip ahead.\n\t\t// CAS ensures we never step backward relative to a concurrent writer advance.\n\t\t// Disabled in buffered mode, where we deliberately play through the whole buffer.\n\t\tconst buffered = (write - read) | 0;\n\t\tif (!this.buffered && latency > 0 && buffered > latency) {\n\t\t\tconst skipTo = (write - latency) | 0;\n\t\t\tread = casAdvance(this.#control, READ, skipTo);\n\t\t}\n\n\t\tconst available = (write - read) | 0;\n\t\tconst count = Math.min(available, output[0].length);\n\t\tif (count <= 0) return 0;\n\n\t\t// Copy samples\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = this.#samples[channel];\n\t\t\tconst dst = output[channel];\n\t\t\tfor (let i = 0; i < count; i++) {\n\t\t\t\tdst[i] = src[slot((read + i) | 0, this.capacity)];\n\t\t\t}\n\t\t}\n\n\t\t// Advance READ via CAS so a concurrent writer overflow can't be undone.\n\t\tcasAdvance(this.#control, READ, (read + count) | 0);\n\n\t\treturn count;\n\t}\n\n\t/** Update the target latency in samples. */\n\tsetLatency(samples: number): void {\n\t\tAtomics.store(this.#control, LATENCY, samples);\n\t}\n\n\t/**\n\t * Flush buffered samples and re-stall, ready to anchor the next utterance (buffered mode).\n\t * Main thread only. The worklet reader sees STALLED and stops until the next insert.\n\t */\n\treset(): void {\n\t\tthis.#anchored = false;\n\t\tAtomics.store(this.#control, STALLED, 1);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tAtomics.store(this.#control, READ, write);\n\t}\n\n\t/**\n\t * Allocate a new ring with `newCapacity` samples and copy the unread window\n\t * [READ, WRITE) plus control state into it. Used when growing capacity so\n\t * we don't drop buffered audio. If `newCapacity` is smaller than the unread\n\t * span, the oldest samples are truncated.\n\t *\n\t * Main thread only. `resize()` reads from the source `SharedRingBuffer` and\n\t * writes into a freshly allocated buffer from `allocSharedRingBuffer`, so it\n\t * relies on the same invariant as `insert()`: no concurrent main-thread\n\t * writers. The AudioWorklet reader is tolerated via the CAS discipline used\n\t * by READ/WRITE elsewhere.\n\t */\n\tresize(newCapacity: number): SharedRingBuffer {\n\t\tconst init = allocSharedRingBuffer(this.channels, newCapacity, this.rate, this.buffered);\n\t\tconst dst = new SharedRingBuffer(init);\n\t\tdst.#anchored = this.#anchored;\n\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\tconst write = Atomics.load(this.#control, WRITE);\n\t\tconst latency = Atomics.load(this.#control, LATENCY);\n\t\tconst stalled = Atomics.load(this.#control, STALLED);\n\n\t\tconst available = (write - read) | 0;\n\t\tconst copyCount = Math.max(0, Math.min(available, dst.capacity));\n\t\tconst copyStart = (write - copyCount) | 0;\n\n\t\tfor (let channel = 0; channel < this.channels; channel++) {\n\t\t\tconst src = this.#samples[channel];\n\t\t\tconst out = dst.#samples[channel];\n\t\t\tfor (let i = 0; i < copyCount; i++) {\n\t\t\t\tconst idx = (copyStart + i) | 0;\n\t\t\t\tout[slot(idx, dst.capacity)] = src[slot(idx, this.capacity)];\n\t\t\t}\n\t\t}\n\n\t\tAtomics.store(dst.#control, READ, copyStart);\n\t\tAtomics.store(dst.#control, WRITE, write);\n\t\tAtomics.store(dst.#control, LATENCY, latency);\n\t\tAtomics.store(dst.#control, STALLED, stalled);\n\n\t\treturn dst;\n\t}\n\n\t/** Current playback timestamp derived from READ position. */\n\tget timestamp(): Time.Micro {\n\t\tconst read = Atomics.load(this.#control, READ);\n\t\treturn Time.Micro.fromSecond((read / this.rate) as Time.Second);\n\t}\n\n\t/** Whether the buffer is stalled (waiting to fill). */\n\tget stalled(): boolean {\n\t\treturn Atomics.load(this.#control, STALLED) === 1;\n\t}\n\n\t/**\n\t * Number of buffered samples (WRITE - READ).\n\t *\n\t * Non-atomic: WRITE and READ are loaded separately, so a concurrent\n\t * writer/reader can make the two loads inconsistent. Intended for\n\t * tests and diagnostics, not control-flow decisions.\n\t */\n\tget length(): number {\n\t\treturn (Atomics.load(this.#control, WRITE) - Atomics.load(this.#control, READ)) | 0;\n\t}\n}\n","import { Time } from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\nimport type { Data, InitPost, InitShared, Latency, Reset, State } from \"./render\";\nimport { allocSharedRingBuffer, SharedRingBuffer } from \"./shared-ring-buffer\";\n\n/**\n * Timestamp-based backpressure for buffered playback. The decoded PCM ring only holds the latency\n * floor; everything above it (the buffered lookahead, up to the ceiling) stays upstream as encoded\n * Opus. `wait(timestamp)` stays pending until the playhead is within `headroom` (the floor) of\n * `timestamp`, so the decode loop holds a frame as Opus instead of decoding it too far ahead of the\n * floor-sized ring. Both transports share this; they differ only in how they observe the playhead\n * (Atomics poll vs worklet state messages). A no-op when not buffered (the ring bounds itself).\n */\nclass Backpressure {\n\treadonly #enabled: boolean;\n\t#headroom: Time.Micro;\n\t#waiters: Array<{ timestamp: Time.Micro; resolve: () => void }> = [];\n\n\tconstructor(enabled: boolean, headroom: Time.Micro) {\n\t\tthis.#enabled = enabled;\n\t\tthis.#headroom = headroom;\n\t}\n\n\t// Move the gate as the floor changes (e.g. \"real-time\" jitter tracking RTT).\n\tsetHeadroom(headroom: Time.Micro): void {\n\t\tthis.#headroom = headroom;\n\t}\n\n\twait(timestamp: Time.Micro, playhead: Time.Micro): Promise<void> {\n\t\tif (!this.#enabled) return Promise.resolve();\n\t\tif (playhead >= ((timestamp - this.#headroom) | 0)) return Promise.resolve();\n\t\treturn new Promise((resolve) => this.#waiters.push({ timestamp, resolve }));\n\t}\n\n\t// Resolve every waiter the playhead has reached. Thresholds are recomputed live so a changed\n\t// headroom takes effect on queued waiters too.\n\tadvance(playhead: Time.Micro): void {\n\t\tif (this.#waiters.length === 0) return;\n\t\tthis.#waiters = this.#waiters.filter(({ timestamp, resolve }) => {\n\t\t\tif (playhead < ((timestamp - this.#headroom) | 0)) return true;\n\t\t\tresolve();\n\t\t\treturn false;\n\t\t});\n\t}\n\n\t// Resolve everything unconditionally (reset/close): never strand a decode loop.\n\tflush(): void {\n\t\tfor (const { resolve } of this.#waiters) resolve();\n\t\tthis.#waiters = [];\n\t}\n}\n\n/** Convert a sample count to a Time.Micro duration at the given sample rate. */\nfunction samplesToMicro(samples: number, rate: number): Time.Micro {\n\treturn Time.Micro.fromSecond((samples / rate) as Time.Second);\n}\n\n/**\n * Unified interface for the audio buffer between the main thread and the AudioWorklet.\n *\n * Two implementations exist:\n * - `SharedAudioBuffer`: backed by SharedArrayBuffer, lock-free writes via Atomics.\n * - `PostAudioBuffer`: backed by postMessage transfer (the fallback when SAB is unavailable).\n *\n * Use `createAudioBuffer()` to pick the right implementation automatically.\n */\nexport interface AudioBuffer {\n\treadonly rate: number;\n\treadonly channels: number;\n\n\t/** Insert audio samples at the given timestamp. Handles out-of-order writes. */\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void;\n\n\t/** Update the target latency in samples. */\n\tsetLatency(samples: number): void;\n\n\t/** Flush buffered samples and re-stall, ready to anchor the next utterance (buffered mode). */\n\treset(): void;\n\n\t/**\n\t * Resolve once the playhead is near enough to decode a frame at `timestamp`. In buffered mode this\n\t * applies backpressure: it stays pending while decoding `timestamp` would run more than the latency\n\t * floor ahead of the playhead, so the caller holds the (encoded) frame instead of decoding it too\n\t * far ahead of the floor-sized ring. Resolves immediately when not buffered (the ring bounds itself).\n\t */\n\twait(timestamp: Time.Micro): Promise<void>;\n\n\t/** Current playback timestamp (derived from reader position). */\n\treadonly timestamp: Getter<Time.Micro>;\n\n\t/** Whether the buffer is stalled (waiting to fill). */\n\treadonly stalled: Getter<boolean>;\n\n\t/** Release any resources (event listeners, intervals, etc.). */\n\tclose(): void;\n}\n\n/** Returns true when SharedArrayBuffer is available and usable in the current context. */\nexport function supportsSharedArrayBuffer(): boolean {\n\tif (typeof SharedArrayBuffer === \"undefined\") return false;\n\t// In browsers, SharedArrayBuffer requires cross-origin isolation (COOP/COEP).\n\t// crossOriginIsolated is a browser global; in Node/Bun it's undefined.\n\tif (typeof crossOriginIsolated !== \"undefined\" && !crossOriginIsolated) return false;\n\treturn true;\n}\n\n/**\n * Create the best audio buffer implementation for the current environment.\n * Picks `SharedAudioBuffer` when possible, falling back to `PostAudioBuffer`.\n */\nexport function createAudioBuffer(\n\tworklet: AudioWorkletNode,\n\tchannels: number,\n\trate: number,\n\tlatencySamples: number,\n\tbuffered = false,\n): AudioBuffer {\n\tif (supportsSharedArrayBuffer()) {\n\t\tconsole.log(\"[audio] using SharedArrayBuffer audio buffer\");\n\t\treturn new SharedAudioBuffer(worklet, channels, rate, latencySamples, buffered);\n\t}\n\tconsole.log(\"[audio] using postMessage audio buffer (SharedArrayBuffer unavailable)\");\n\treturn new PostAudioBuffer(worklet, channels, rate, latencySamples, buffered);\n}\n\n/** SharedArrayBuffer-backed implementation. Writes go directly into shared memory. */\nclass SharedAudioBuffer implements AudioBuffer {\n\treadonly rate: number;\n\treadonly channels: number;\n\t#worklet: AudioWorkletNode;\n\t#ring: SharedRingBuffer;\n\n\treadonly #timestamp = new Signal<Time.Micro>(0 as Time.Micro);\n\treadonly timestamp: Getter<Time.Micro> = this.#timestamp;\n\n\treadonly #stalled = new Signal<boolean>(true);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t#backpressure: Backpressure;\n\n\t#signals = new Effect();\n\n\tconstructor(worklet: AudioWorkletNode, channels: number, rate: number, latencySamples: number, buffered: boolean) {\n\t\tthis.#worklet = worklet;\n\t\tthis.channels = channels;\n\t\tthis.rate = rate;\n\n\t\t// The ring holds the latency floor as decoded PCM (headroom above it for overflow). In\n\t\t// buffered mode the lookahead above the floor stays encoded upstream, held back by `wait()`.\n\t\tconst capacity = Math.max(rate, latencySamples * 2);\n\t\tthis.#backpressure = new Backpressure(buffered, samplesToMicro(latencySamples, rate));\n\n\t\tconst init = allocSharedRingBuffer(channels, capacity, rate, buffered);\n\t\tthis.#ring = new SharedRingBuffer(init);\n\t\tthis.#ring.setLatency(latencySamples);\n\n\t\tconst msg: InitShared = { type: \"init-shared\", ...init };\n\t\tworklet.port.postMessage(msg);\n\n\t\t// Poll the shared control array and reflect it into signals.\n\t\tthis.#signals.interval(() => {\n\t\t\tconst stalled = this.#ring.stalled;\n\t\t\tthis.#timestamp.set(this.#ring.timestamp);\n\t\t\tthis.#stalled.set(stalled);\n\t\t\t// While stalled the playhead is parked, so release the decode loop to refill the floor;\n\t\t\t// once playing, hold it to ~the floor ahead.\n\t\t\tif (stalled) this.#backpressure.flush();\n\t\t\telse this.#backpressure.advance(this.#ring.timestamp);\n\t\t}, 50);\n\t}\n\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tthis.#ring.insert(timestamp, data);\n\t}\n\n\tsetLatency(samples: number): void {\n\t\tthis.#backpressure.setHeadroom(samplesToMicro(samples, this.rate));\n\n\t\t// Grow the ring (preserving the unread window) if it's too small for the new latency.\n\t\tif (this.#ring.capacity < samples * 1.5) {\n\t\t\tconst newCapacity = Math.max(this.rate, samples * 2);\n\t\t\tthis.#ring = this.#ring.resize(newCapacity);\n\t\t\tthis.#ring.setLatency(samples);\n\n\t\t\tconst msg: InitShared = { type: \"init-shared\", ...this.#ring.init };\n\t\t\tthis.#worklet.port.postMessage(msg);\n\t\t} else {\n\t\t\tthis.#ring.setLatency(samples);\n\t\t}\n\t}\n\n\treset(): void {\n\t\tthis.#ring.reset();\n\t\tthis.#backpressure.flush(); // the old timeline is gone; let the decode loop re-anchor\n\t}\n\n\twait(timestamp: Time.Micro): Promise<void> {\n\t\t// Stalled = still filling the floor (bootstrap or underflow): let frames through to refill.\n\t\tif (this.#ring.stalled) return Promise.resolve();\n\t\treturn this.#backpressure.wait(timestamp, this.#ring.timestamp);\n\t}\n\n\tclose(): void {\n\t\tthis.#backpressure.flush(); // never leave a decode loop awaiting a closed buffer\n\t\tthis.#signals.close();\n\t}\n}\n\n/** postMessage-backed fallback implementation. Samples are transferred, not shared. */\nclass PostAudioBuffer implements AudioBuffer {\n\treadonly rate: number;\n\treadonly channels: number;\n\t#worklet: AudioWorkletNode;\n\n\treadonly #timestamp = new Signal<Time.Micro>(0 as Time.Micro);\n\treadonly timestamp: Getter<Time.Micro> = this.#timestamp;\n\n\treadonly #stalled = new Signal<boolean>(true);\n\treadonly stalled: Getter<boolean> = this.#stalled;\n\n\t// Backpressure runs off the playhead the worklet reports in its state messages.\n\t#backpressure: Backpressure;\n\n\t#signals = new Effect();\n\n\tconstructor(worklet: AudioWorkletNode, channels: number, rate: number, latencySamples: number, buffered: boolean) {\n\t\tthis.#worklet = worklet;\n\t\tthis.channels = channels;\n\t\tthis.rate = rate;\n\n\t\tthis.#backpressure = new Backpressure(buffered, samplesToMicro(latencySamples, rate));\n\n\t\tconst latency = Time.Milli.fromSecond((latencySamples / rate) as Time.Second);\n\t\tconst msg: InitPost = { type: \"init-post\", channels, rate, latency, buffered };\n\t\tworklet.port.postMessage(msg);\n\n\t\t// Listen for state updates from the worklet.\n\t\tthis.#signals.event(worklet.port, \"message\", (ev: Event) => {\n\t\t\tconst data = (ev as MessageEvent<State>).data;\n\t\t\tif (data?.type === \"state\") {\n\t\t\t\tthis.#timestamp.set(data.timestamp);\n\t\t\t\tthis.#stalled.set(data.stalled);\n\t\t\t\t// While stalled the playhead is parked, so release the decode loop to refill the floor;\n\t\t\t\t// once playing, hold it to ~the floor ahead.\n\t\t\t\tif (data.stalled) this.#backpressure.flush();\n\t\t\t\telse this.#backpressure.advance(data.timestamp);\n\t\t\t}\n\t\t});\n\t\t// addEventListener on a MessagePort requires start() to begin delivery.\n\t\tworklet.port.start();\n\t}\n\n\tinsert(timestamp: Time.Micro, data: Float32Array[]): void {\n\t\tconst msg: Data = { type: \"data\", data, timestamp };\n\t\t// Transfer the ArrayBuffers to avoid a copy. This is why samples can be dropped\n\t\t// under load: the main thread loses access until the worklet drains the message queue.\n\t\tthis.#worklet.port.postMessage(\n\t\t\tmsg,\n\t\t\tdata.map((d) => d.buffer),\n\t\t);\n\t}\n\n\tsetLatency(samples: number): void {\n\t\tthis.#backpressure.setHeadroom(samplesToMicro(samples, this.rate));\n\n\t\tconst latency = Time.Milli.fromSecond((samples / this.rate) as Time.Second);\n\t\tconst msg: Latency = { type: \"latency\", latency };\n\t\tthis.#worklet.port.postMessage(msg);\n\t}\n\n\treset(): void {\n\t\tconst msg: Reset = { type: \"reset\" };\n\t\tthis.#worklet.port.postMessage(msg);\n\t\tthis.#backpressure.flush(); // the old timeline is gone; let the decode loop re-anchor\n\t}\n\n\twait(timestamp: Time.Micro): Promise<void> {\n\t\t// Stalled = still filling the floor (bootstrap or underflow): let frames through to refill.\n\t\tif (this.#stalled.peek()) return Promise.resolve();\n\t\t// Uses the worklet-reported playhead, which lags by a state-message interval; the floor's\n\t\t// headroom covers that. The worklet still drops the oldest if a frame slips through.\n\t\treturn this.#backpressure.wait(timestamp, this.#timestamp.peek());\n\t}\n\n\tclose(): void {\n\t\tthis.#backpressure.flush(); // never leave a decode loop awaiting a closed buffer\n\t\tthis.#signals.close();\n\t}\n}\n","const code = \"var __defProp = Object.defineProperty;\\nvar __export = (target, all) => {\\n for (var name in all)\\n __defProp(target, name, { get: all[name], enumerable: true });\\n};\\n\\n// ../net/src/time.ts\\nvar time_exports = {};\\n__export(time_exports, {\\n Micro: () => Micro,\\n Milli: () => Milli,\\n Nano: () => Nano,\\n Second: () => Second\\n});\\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// src/audio/ring-buffer.ts\\nvar AudioRingBuffer = class {\\n #buffer;\\n #writeIndex = 0;\\n #readIndex = 0;\\n rate;\\n channels;\\n #stalled = true;\\n // Buffered mode: anchor to the first sample, play through without skipping ahead.\\n #buffered;\\n // Un-stall threshold in samples (how much to buffer before playback starts).\\n #latencySamples;\\n // Whether the read/write indices have been anchored to the first inserted sample.\\n #anchored = false;\\n constructor(props) {\\n if (props.channels <= 0) throw new Error(\\\"invalid channels\\\");\\n if (props.rate <= 0) throw new Error(\\\"invalid sample rate\\\");\\n if (props.latency <= 0) throw new Error(\\\"invalid latency\\\");\\n this.#latencySamples = Math.ceil(props.rate * time_exports.Second.fromMilli(props.latency));\\n if (this.#latencySamples === 0) throw new Error(\\\"empty buffer\\\");\\n this.rate = props.rate;\\n this.channels = props.channels;\\n this.#buffered = props.buffered ?? false;\\n const capacity = this.#capacityFor(this.#latencySamples);\\n this.#buffer = [];\\n for (let i = 0; i < this.channels; i++) {\\n this.#buffer[i] = new Float32Array(capacity);\\n }\\n }\\n #capacityFor(latencySamples) {\\n return this.#buffered ? latencySamples * 2 : latencySamples;\\n }\\n get stalled() {\\n return this.#stalled;\\n }\\n get timestamp() {\\n return time_exports.Micro.fromSecond(this.#readIndex / this.rate);\\n }\\n get length() {\\n return this.#writeIndex - this.#readIndex;\\n }\\n get capacity() {\\n return this.#buffer[0]?.length;\\n }\\n resize(latency) {\\n this.#latencySamples = Math.ceil(this.rate * time_exports.Second.fromMilli(latency));\\n const newCapacity = this.#capacityFor(this.#latencySamples);\\n if (newCapacity === this.capacity) return;\\n if (newCapacity === 0) throw new Error(\\\"empty buffer\\\");\\n const newBuffer = [];\\n for (let i = 0; i < this.channels; i++) {\\n newBuffer[i] = new Float32Array(newCapacity);\\n }\\n const samplesToKeep = Math.min(this.length, newCapacity);\\n if (samplesToKeep > 0) {\\n const copyStart = this.#writeIndex - samplesToKeep;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#buffer[channel];\\n const dst = newBuffer[channel];\\n for (let i = 0; i < samplesToKeep; i++) {\\n const srcPos = (copyStart + i) % src.length;\\n const dstPos = (copyStart + i) % dst.length;\\n dst[dstPos] = src[srcPos];\\n }\\n }\\n }\\n this.#buffer = newBuffer;\\n this.#readIndex = this.#writeIndex - samplesToKeep;\\n if (samplesToKeep === 0) this.#stalled = true;\\n }\\n write(timestamp, data) {\\n if (data.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n let start = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n let samples = data[0].length;\\n if (this.#buffered && !this.#anchored) {\\n this.#readIndex = start;\\n this.#writeIndex = start;\\n this.#anchored = true;\\n }\\n let offset = this.#readIndex - start;\\n if (offset > samples) {\\n return;\\n } else if (offset > 0) {\\n samples -= offset;\\n start += offset;\\n } else {\\n offset = 0;\\n }\\n const end = start + samples;\\n const overflow = end - this.#readIndex - this.#buffer[0].length;\\n if (overflow >= 0) {\\n this.#stalled = false;\\n this.#readIndex += overflow;\\n }\\n if (start > this.#writeIndex) {\\n const gapSize = Math.min(start - this.#writeIndex, this.#buffer[0].length);\\n if (gapSize === 1) {\\n console.warn(\\\"floating point inaccuracy detected\\\");\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = this.#buffer[channel];\\n for (let i = 0; i < gapSize; i++) {\\n const writePos = (this.#writeIndex + i) % dst.length;\\n dst[writePos] = 0;\\n }\\n }\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n let src = data[channel];\\n src = src.subarray(src.length - samples);\\n const dst = this.#buffer[channel];\\n if (src.length !== samples) throw new Error(\\\"mismatching number of samples\\\");\\n for (let i = 0; i < samples; i++) {\\n const writePos = (start + i) % dst.length;\\n dst[writePos] = src[i];\\n }\\n }\\n if (end > this.#writeIndex) {\\n this.#writeIndex = end;\\n }\\n if (this.#buffered && this.length >= this.#latencySamples) {\\n this.#stalled = false;\\n }\\n }\\n // Flush all buffered samples and re-stall, ready to anchor the next utterance.\\n reset() {\\n this.#readIndex = 0;\\n this.#writeIndex = 0;\\n this.#stalled = true;\\n this.#anchored = false;\\n }\\n read(output) {\\n if (output.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n if (this.#stalled) return 0;\\n const samples = Math.min(this.#writeIndex - this.#readIndex, output[0].length);\\n if (samples === 0) return 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = output[channel];\\n const src = this.#buffer[channel];\\n if (dst.length !== output[0].length) throw new Error(\\\"mismatching number of samples\\\");\\n for (let i = 0; i < samples; i++) {\\n const readPos = (this.#readIndex + i) % src.length;\\n dst[i] = src[readPos];\\n }\\n }\\n this.#readIndex += samples;\\n return samples;\\n }\\n};\\n\\n// src/audio/shared-ring-buffer.ts\\nvar WRITE = 0;\\nvar READ = 1;\\nvar LATENCY = 2;\\nvar STALLED = 3;\\nvar CONTROL_SLOTS = 4;\\nfunction allocSharedRingBuffer(channels, capacity, rate, buffered = false) {\\n if (channels <= 0) throw new Error(\\\"invalid channels\\\");\\n if (capacity <= 0) throw new Error(\\\"invalid capacity\\\");\\n if (rate <= 0) throw new Error(\\\"invalid sample rate\\\");\\n const samples = new SharedArrayBuffer(channels * capacity * Float32Array.BYTES_PER_ELEMENT);\\n const control = new SharedArrayBuffer(CONTROL_SLOTS * Int32Array.BYTES_PER_ELEMENT);\\n const ctrl = new Int32Array(control);\\n Atomics.store(ctrl, STALLED, 1);\\n return { channels, capacity, rate, samples, control, buffered };\\n}\\nfunction i32Max(a, b) {\\n return (a - b | 0) > 0 ? a : b;\\n}\\nfunction slot(idx, capacity) {\\n return (idx % capacity + capacity) % capacity;\\n}\\nfunction casAdvance(arr, idx, candidate) {\\n for (; ; ) {\\n const current = Atomics.load(arr, idx);\\n if ((candidate - current | 0) <= 0) return current;\\n const witnessed = Atomics.compareExchange(arr, idx, current, candidate);\\n if (witnessed === current) return candidate;\\n }\\n}\\nvar SharedRingBuffer = class _SharedRingBuffer {\\n channels;\\n capacity;\\n rate;\\n buffered;\\n init;\\n #control;\\n #samples;\\n // Whether READ/WRITE have been anchored to the first inserted sample (buffered mode).\\n #anchored = false;\\n constructor(init) {\\n this.channels = init.channels;\\n this.capacity = init.capacity;\\n this.rate = init.rate;\\n this.buffered = init.buffered;\\n this.init = init;\\n this.#control = new Int32Array(init.control);\\n this.#samples = [];\\n for (let i = 0; i < this.channels; i++) {\\n this.#samples.push(\\n new Float32Array(init.samples, i * this.capacity * Float32Array.BYTES_PER_ELEMENT, this.capacity)\\n );\\n }\\n }\\n /**\\n * Insert audio samples at the given timestamp.\\n * Main thread only. Handles out-of-order writes, gap filling, and overflow.\\n */\\n insert(timestamp, data) {\\n if (data.length !== this.channels) throw new Error(\\\"wrong number of channels\\\");\\n let start = Math.round(time_exports.Second.fromMicro(timestamp) * this.rate);\\n const originalLength = data[0].length;\\n let offset = 0;\\n if (this.buffered && !this.#anchored) {\\n Atomics.store(this.#control, READ, start | 0);\\n Atomics.store(this.#control, WRITE, start | 0);\\n this.#anchored = true;\\n }\\n const end = start + originalLength | 0;\\n const read = Atomics.load(this.#control, READ);\\n const behind = read - start | 0;\\n if (behind > 0) {\\n if (behind >= originalLength) {\\n return;\\n }\\n offset = behind;\\n start = start + behind | 0;\\n }\\n const samples = originalLength - offset;\\n if ((end - read | 0) > this.capacity) {\\n casAdvance(this.#control, READ, end - this.capacity | 0);\\n }\\n const write = Atomics.load(this.#control, WRITE);\\n const gap = start - write | 0;\\n if (gap > 0) {\\n const gapSize = Math.min(gap, this.capacity);\\n for (let channel = 0; channel < this.channels; channel++) {\\n const dst = this.#samples[channel];\\n for (let i = 0; i < gapSize; i++) {\\n dst[slot(write + i | 0, this.capacity)] = 0;\\n }\\n }\\n }\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = data[channel];\\n const dst = this.#samples[channel];\\n for (let i = 0; i < samples; i++) {\\n dst[slot(start + i | 0, this.capacity)] = src[offset + i];\\n }\\n }\\n Atomics.store(this.#control, WRITE, i32Max(Atomics.load(this.#control, WRITE), end));\\n const currentRead = Atomics.load(this.#control, READ);\\n const currentWrite = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n if ((currentWrite - currentRead | 0) >= latency && latency > 0) {\\n Atomics.store(this.#control, STALLED, 0);\\n }\\n }\\n /**\\n * Read audio samples into the output buffers.\\n * AudioWorklet only. Returns the number of samples read.\\n */\\n read(output) {\\n if (Atomics.load(this.#control, STALLED) === 1) return 0;\\n let read = Atomics.load(this.#control, READ);\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n const buffered = write - read | 0;\\n if (!this.buffered && latency > 0 && buffered > latency) {\\n const skipTo = write - latency | 0;\\n read = casAdvance(this.#control, READ, skipTo);\\n }\\n const available = write - read | 0;\\n const count = Math.min(available, output[0].length);\\n if (count <= 0) return 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#samples[channel];\\n const dst = output[channel];\\n for (let i = 0; i < count; i++) {\\n dst[i] = src[slot(read + i | 0, this.capacity)];\\n }\\n }\\n casAdvance(this.#control, READ, read + count | 0);\\n return count;\\n }\\n /** Update the target latency in samples. */\\n setLatency(samples) {\\n Atomics.store(this.#control, LATENCY, samples);\\n }\\n /**\\n * Flush buffered samples and re-stall, ready to anchor the next utterance (buffered mode).\\n * Main thread only. The worklet reader sees STALLED and stops until the next insert.\\n */\\n reset() {\\n this.#anchored = false;\\n Atomics.store(this.#control, STALLED, 1);\\n const write = Atomics.load(this.#control, WRITE);\\n Atomics.store(this.#control, READ, write);\\n }\\n /**\\n * Allocate a new ring with `newCapacity` samples and copy the unread window\\n * [READ, WRITE) plus control state into it. Used when growing capacity so\\n * we don't drop buffered audio. If `newCapacity` is smaller than the unread\\n * span, the oldest samples are truncated.\\n *\\n * Main thread only. `resize()` reads from the source `SharedRingBuffer` and\\n * writes into a freshly allocated buffer from `allocSharedRingBuffer`, so it\\n * relies on the same invariant as `insert()`: no concurrent main-thread\\n * writers. The AudioWorklet reader is tolerated via the CAS discipline used\\n * by READ/WRITE elsewhere.\\n */\\n resize(newCapacity) {\\n const init = allocSharedRingBuffer(this.channels, newCapacity, this.rate, this.buffered);\\n const dst = new _SharedRingBuffer(init);\\n dst.#anchored = this.#anchored;\\n const read = Atomics.load(this.#control, READ);\\n const write = Atomics.load(this.#control, WRITE);\\n const latency = Atomics.load(this.#control, LATENCY);\\n const stalled = Atomics.load(this.#control, STALLED);\\n const available = write - read | 0;\\n const copyCount = Math.max(0, Math.min(available, dst.capacity));\\n const copyStart = write - copyCount | 0;\\n for (let channel = 0; channel < this.channels; channel++) {\\n const src = this.#samples[channel];\\n const out = dst.#samples[channel];\\n for (let i = 0; i < copyCount; i++) {\\n const idx = copyStart + i | 0;\\n out[slot(idx, dst.capacity)] = src[slot(idx, this.capacity)];\\n }\\n }\\n Atomics.store(dst.#control, READ, copyStart);\\n Atomics.store(dst.#control, WRITE, write);\\n Atomics.store(dst.#control, LATENCY, latency);\\n Atomics.store(dst.#control, STALLED, stalled);\\n return dst;\\n }\\n /** Current playback timestamp derived from READ position. */\\n get timestamp() {\\n const read = Atomics.load(this.#control, READ);\\n return time_exports.Micro.fromSecond(read / this.rate);\\n }\\n /** Whether the buffer is stalled (waiting to fill). */\\n get stalled() {\\n return Atomics.load(this.#control, STALLED) === 1;\\n }\\n /**\\n * Number of buffered samples (WRITE - READ).\\n *\\n * Non-atomic: WRITE and READ are loaded separately, so a concurrent\\n * writer/reader can make the two loads inconsistent. Intended for\\n * tests and diagnostics, not control-flow decisions.\\n */\\n get length() {\\n return Atomics.load(this.#control, WRITE) - Atomics.load(this.#control, READ) | 0;\\n }\\n};\\n\\n// src/audio/render-worklet.ts\\nvar Render = class extends AudioWorkletProcessor {\\n // Set after init, depending on which path the main thread chose.\\n #backend;\\n #underflow = 0;\\n #stateCounter = 0;\\n constructor() {\\n super();\\n this.port.onmessage = (event) => {\\n const msg = event.data;\\n if (msg.type === \\\"init-shared\\\") {\\n console.log(\\\"[audio-worklet] init-shared: using SharedArrayBuffer path\\\");\\n this.#backend = new SharedRingBuffer(msg);\\n this.#underflow = 0;\\n } else if (msg.type === \\\"init-post\\\") {\\n console.log(\\\"[audio-worklet] init-post: using postMessage path\\\");\\n this.#backend = new AudioRingBuffer(msg);\\n this.#underflow = 0;\\n } else if (msg.type === \\\"data\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.write(msg.timestamp, msg.data);\\n } else if (msg.type === \\\"latency\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.resize(msg.latency);\\n } else if (msg.type === \\\"reset\\\") {\\n if (this.#backend instanceof AudioRingBuffer) this.#backend.reset();\\n }\\n };\\n }\\n process(_inputs, outputs, _parameters) {\\n const output = outputs[0];\\n const backend = this.#backend;\\n const samplesRead = backend?.read(output) ?? 0;\\n if (samplesRead < output[0].length) {\\n this.#underflow += output[0].length - samplesRead;\\n } else if (this.#underflow > 0 && backend) {\\n console.debug(`audio underflow: ${Math.round(1e3 * this.#underflow / backend.rate)}ms`);\\n this.#underflow = 0;\\n }\\n if (backend instanceof AudioRingBuffer) {\\n this.#stateCounter++;\\n if (this.#stateCounter >= 5) {\\n this.#stateCounter = 0;\\n const state = {\\n type: \\\"state\\\",\\n timestamp: backend.timestamp,\\n stalled: backend.stalled\\n };\\n this.port.postMessage(state);\\n }\\n }\\n return true;\\n }\\n};\\nregisterProcessor(\\\"render\\\", Render);\\n\";\nconst blob = new Blob([code], { type: \"application/javascript\" });\nexport default URL.createObjectURL(blob);","import * 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 { base64ToBytes } from \"../base64\";\nimport type { BufferedRanges } from \"../buffered\";\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\t/** Number of encoded bytes received. */\n\tbytesReceived: number;\n}\n\n/**\n * Downloads audio from a track and emits it to an AudioContext.\n *\n * The user is responsible for hooking up audio to speakers, an analyzer, etc.\n */\nexport class Decoder {\n\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// The last discontinuity count seen from the container consumer. A change means the\n\t// publisher rewound the timeline (e.g. a voice agent interrupted) and we must flush.\n\t#discontinuity = 0;\n\n\t#signals = new Effect();\n\n\t// How much buffered audio the container consumer retains before skipping\n\t// ahead. This must be the latency CEILING (maxBuffer), not the floor\n\t// (buffer): in buffered playback the producer writes faster than real-time\n\t// with future PTS, so the group span legitimately exceeds the floor and\n\t// would otherwise be skipped. When collapsed, maxBuffer equals the floor.\n\t//\n\t// Held in a plain Signal driven by a running effect (below) rather than a\n\t// lazy `computed`: the container consumer only `.peek()`s this (it never\n\t// subscribes), and an unsubscribed computed peeks as `undefined`, which\n\t// would make the consumer's threshold NaN and skip every group.\n\t#consumerLatency = new Signal<Time.Milli>(Time.Milli.zero);\n\n\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((effect) => {\n\t\t\tthis.#consumerLatency.set(effect.get(this.source.sync.maxBuffer));\n\t\t});\n\n\t\tthis.#signals.run(this.#runWorklet.bind(this));\n\t\tthis.#signals.run(this.#runEnabled.bind(this));\n\t\tthis.#signals.run(this.#runLatency.bind(this));\n\t\tthis.#signals.run(this.#runDecoder.bind(this));\n\t}\n\n\t#runWorklet(effect: Effect): void {\n\t\t// It takes a second or so to initialize the AudioContext/AudioWorklet, so do it even if disabled.\n\t\t// This is less efficient for video-only playback but makes muting/unmuting instant.\n\n\t\t//const enabled = effect.get(this.enabled);\n\t\t//if (!enabled) return;\n\n\t\tconst config = effect.get(this.source.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 Promise.race([context.audioWorklet.addModule(RenderWorklet), effect.cancel]);\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\t\t\tconst buffered = this.source.sync.buffered.peek();\n\n\t\t\t// Let the factory pick the best transport (SharedArrayBuffer or postMessage).\n\t\t\tconst ring = createAudioBuffer(worklet, channelCount, sampleRate, latencySamples, buffered);\n\t\t\tthis.#ring = ring;\n\t\t\teffect.cleanup(() => {\n\t\t\t\tring.close();\n\t\t\t\tthis.#ring = undefined;\n\t\t\t});\n\n\t\t\t// Mirror ring state (timestamp/stalled) onto our public signals.\n\t\t\teffect.run((inner) => {\n\t\t\t\tconst ts = Time.Milli.fromMicro(inner.get(ring.timestamp));\n\t\t\t\tthis.#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.#consumerLatency,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#decodeBuffered);\n\t\t\tthis.#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\t// Publisher rewound the timeline: flush + re-anchor before decoding the new frame.\n\t\t\t\tthis.#onDiscontinuity(next.discontinuity);\n\n\t\t\t\tconst { frame } = next;\n\t\t\t\tif (!frame) continue;\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\tthis.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\t// Backpressure: in buffered mode this holds the encoded frame until the playhead nears\n\t\t\t\t// it, keeping the lookahead above the floor as Opus instead of decoded PCM. No-op live.\n\t\t\t\tawait this.#ring?.wait(frame.timestamp as Time.Micro);\n\n\t\t\t\tconst chunk = new EncodedAudioChunk({\n\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\tdata: frame.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.#consumerLatency,\n\t\t});\n\t\teffect.cleanup(() => consumer.close());\n\n\t\t// Combine network jitter buffer with decode buffer\n\t\teffect.run((inner) => {\n\t\t\tconst network = inner.get(consumer.buffered);\n\t\t\tconst decode = inner.get(this.#decodeBuffered);\n\t\t\tthis.#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\t// Publisher rewound the timeline: flush + re-anchor before decoding the new frame.\n\t\t\t\tthis.#onDiscontinuity(next.discontinuity);\n\n\t\t\t\tconst { frame } = next;\n\t\t\t\tif (!frame) continue;\n\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\tthis.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\t// Backpressure: in buffered mode this holds the encoded frame until the playhead nears\n\t\t\t\t// it, keeping the lookahead above the floor as Opus instead of decoded PCM. No-op live.\n\t\t\t\tawait this.#ring?.wait(frame.timestamp);\n\n\t\t\t\tif (decoder.state === \"closed\") break;\n\t\t\t\tdecoder.decode(\n\t\t\t\t\tnew EncodedAudioChunk({\n\t\t\t\t\t\ttype: frame.keyframe ? \"key\" : \"delta\",\n\t\t\t\t\t\tdata: frame.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\t// Flush the audio buffer and re-stall, re-anchoring playback to the next frame.\n\t// Use in buffered mode at an utterance boundary (see Sync.reset).\n\treset(): void {\n\t\tthis.#ring?.reset();\n\t}\n\n\t// React to the container consumer's discontinuity counter. When it changes the publisher\n\t// has rewound the timeline, so flush the queued PCM and re-anchor the shared clock before\n\t// the first frame of the new utterance is decoded. This makes the wire signal trigger the\n\t// same flush as a manual `reset()`, with no app involvement.\n\t#onDiscontinuity(count: number): void {\n\t\tif (count === this.#discontinuity) return;\n\t\tthis.#discontinuity = count;\n\t\tthis.#ring?.reset();\n\t\tthis.source.sync.reset();\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","/**\n * Buffered time-range helpers shared by the watch backends.\n *\n * Kept in a leaf module (depends only on `@moq/net`) so the backend and the\n * per-track MSE sources can share them without importing each other.\n *\n * @module\n */\nimport * as Moq from \"@moq/net\";\n\n/** A single buffered time range. */\nexport interface BufferedRange {\n\tstart: Moq.Time.Milli;\n\tend: Moq.Time.Milli;\n}\n\n/** Serializable representation of DOM `TimeRanges`. */\nexport type BufferedRanges = BufferedRange[];\n\n/** Convert a DOM `TimeRanges` into a {@link BufferedRanges}. */\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","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 { base64ToBytes } from \"../base64\";\nimport { type BufferedRanges, timeRangesToArray } from \"../buffered\";\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\tif (config.codec === \"mp3\") {\n\t\t// 1152 samples per frame for MPEG-1 Layer III; MPEG-2/2.5 use 576.\n\t\tconst samples = config.sampleRate >= 32000 ? 1152 : 576;\n\t\treturn Math.ceil((samples / config.sampleRate) * 1000);\n\t}\n\n\treturn undefined;\n}\n","export type * from \"./backend\";\nexport * from \"./decoder\";\nexport * from \"./emitter\";\nexport * from \"./mse\";\nexport * from \"./source\";\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(() => {\n\t\t\tfor (const sourceBuffer of media.sourceBuffers) {\n\t\t\t\t// Skip a buffer mid-update; the next tick (1s later) catches it.\n\t\t\t\tif (sourceBuffer.updating) continue;\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 * 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 { base64ToBytes } from \"../base64\";\nimport type { BufferedRanges } from \"../buffered\";\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\t// The last discontinuity count seen from the container consumer; doubles as a generation\n\t// so in-flight decodes from before a rewind can be dropped on output.\n\t#discontinuity = 0;\n\n\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\t// The generation this frame was decoded in. If a rewind bumps it while we wait\n\t\t\t\t\t// below, this frame belongs to the reneged timeline and must be dropped.\n\t\t\t\t\tconst generation = this.#discontinuity;\n\n\t\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\t\tif (timestamp < (this.timestamp.peek() ?? 0)) {\n\t\t\t\t\t\t// Late frame, don't render it.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this.frame.peek() === undefined) {\n\t\t\t\t\t\t// Render something while we wait for the sync to catch up.\n\t\t\t\t\t\tthis.frame.set(frame.clone());\n\t\t\t\t\t}\n\n\t\t\t\t\tconst wait = this.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\t\t\t\t\tif (generation !== this.#discontinuity) return; // a rewind happened while waiting\n\n\t\t\t\t\tif (timestamp < (this.timestamp.peek() ?? 0)) {\n\t\t\t\t\t\t// Late frame, don't render it.\n\t\t\t\t\t\t// NOTE: This can happen when the ref is updated, such as on playback start.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.timestamp.set(timestamp);\n\n\t\t\t\t\t// Trim the decode buffer as frames are rendered\n\t\t\t\t\tthis.#trimBuffered(timestamp);\n\n\t\t\t\t\tthis.frame.update((prev) => {\n\t\t\t\t\t\tprev?.close();\n\t\t\t\t\t\treturn frame.clone(); // avoid closing the frame here\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tframe.close();\n\t\t\t\t}\n\t\t\t},\n\t\t\t// TODO bubble up error\n\t\t\terror: (error) => {\n\t\t\t\tconsole.error(error);\n\t\t\t\teffect.close();\n\t\t\t},\n\t\t});\n\t\teffect.cleanup(() => {\n\t\t\tif (decoder.state !== \"closed\") decoder.close();\n\t\t});\n\n\t\t// Input processing - depends on container type\n\t\tif (this.config.container.kind === \"cmaf\") {\n\t\t\tthis.#runCmaf(effect, sub, decoder);\n\t\t} else {\n\t\t\tthis.#runLegacy(effect, sub, decoder);\n\t\t}\n\t}\n\n\t#runLegacy(effect: Effect, sub: Moq.Track, 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\t// Publisher rewound: flush queued/in-flight video and re-anchor before decoding.\n\t\t\t\tif (this.#onDiscontinuity(next.discontinuity)) previous = undefined;\n\n\t\t\t\tconst { frame, group } = next;\n\n\t\t\t\tif (!frame) {\n\t\t\t\t\tif (previous) {\n\t\t\t\t\t\tprevious.final = true;\n\t\t\t\t\t}\n\t\t\t\t\t// The group is done\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp as Time.Micro);\n\t\t\t\tthis.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\t// Publisher rewound: flush queued/in-flight video and re-anchor before decoding.\n\t\t\t\tif (this.#onDiscontinuity(next.discontinuity)) previous = undefined;\n\n\t\t\t\tconst { frame, group } = next;\n\n\t\t\t\tif (!frame) {\n\t\t\t\t\tif (previous) {\n\t\t\t\t\t\tprevious.final = true;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Mark that we received this frame right now.\n\t\t\t\tconst timestamp = Time.Milli.fromMicro(frame.timestamp);\n\t\t\t\tthis.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// React to the container consumer's discontinuity counter. On a change the publisher has\n\t// rewound the timeline, so drop what's queued downstream and re-anchor the shared clock\n\t// before the new utterance. Clearing `timestamp` is load-bearing: otherwise its stale high\n\t// value would late-reject the rewound (lower-timestamp) frames at the output guard. Bumping\n\t// the generation drops in-flight decodes on output. The held frame is left in place so the\n\t// last picture shows until the new keyframe renders, instead of flashing empty. Returns true\n\t// if a rewind was handled.\n\t#onDiscontinuity(count: number): boolean {\n\t\tif (count === this.#discontinuity) return false;\n\t\tthis.#discontinuity = count;\n\t\tthis.timestamp.set(undefined);\n\t\tthis.#buffered.set([]);\n\t\tthis.source.sync.reset();\n\t\treturn true;\n\t}\n\n\t// Add a range to the decode buffer (decoded, waiting to render)\n\t#addBuffered(start: Time.Milli, end: Time.Milli): void {\n\t\tif (start > end) return;\n\n\t\tthis.#buffered.mutate((current) => {\n\t\t\tfor (const range of current) {\n\t\t\t\t// Check if there's any overlap, then merge\n\t\t\t\tif (range.start <= end && range.end >= start) {\n\t\t\t\t\trange.start = Time.Milli.min(range.start, start);\n\t\t\t\t\trange.end = Time.Milli.max(range.end, end);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrent.push({ start, end });\n\t\t\tcurrent.sort((a, b) => a.start - b.start);\n\t\t});\n\t}\n\n\t// Trim the decode buffer up to the rendered timestamp\n\t#trimBuffered(timestamp: Time.Milli): void {\n\t\tthis.#buffered.mutate((current) => {\n\t\t\twhile (current.length > 0) {\n\t\t\t\tif (current[0].end >= timestamp) {\n\t\t\t\t\tcurrent[0].start = Time.Milli.max(current[0].start, timestamp);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent.shift();\n\t\t\t}\n\t\t});\n\t}\n\n\tclose(): void {\n\t\tthis.signals.close();\n\n\t\tthis.frame.update((prev) => {\n\t\t\tprev?.close();\n\t\t\treturn undefined;\n\t\t});\n\t}\n}\n\nasync function supported(config: Catalog.VideoConfig): Promise<boolean> {\n\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 { base64ToBytes } from \"../base64\";\nimport { type BufferedRanges, timeRangesToArray } from \"../buffered\";\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\n// Fraction of the canvas that must intersect the viewport before it counts as visible.\nconst INTERSECTION_THRESHOLD = 0.01;\n\n/**\n * Controls when video is downloaded relative to the canvas position.\n *\n * - `\"never\"`: never download video.\n * - `\"always\"`: always download video, regardless of the canvas position or tab visibility.\n * - a CSS length (`\"0px\"`, `\"200px\"`, `\"100%\"`, ...): download while the canvas is within\n * that distance of the viewport (used as the {@link IntersectionObserver} `rootMargin`) and\n * the tab is visible. `\"0px\"` means strictly on screen; larger values pre-warm the video\n * before it scrolls in.\n */\nexport type Visible = \"never\" | \"always\" | (string & {});\n\n/** Options for {@link Renderer}. */\nexport type RendererProps = {\n\t/** The canvas to render decoded frames to. */\n\tcanvas?: HTMLCanvasElement | Signal<HTMLCanvasElement | undefined>;\n\t/** Whether playback is paused; when paused only a single preview frame is fetched. */\n\tpaused?: boolean | Signal<boolean>;\n\t/** When video is downloaded relative to the canvas position. See {@link Visible}. Defaults to `\"20%\"`. */\n\tvisible?: Visible | Signal<Visible>;\n};\n\n/** Decodes a video track and paints it to a canvas, gating downloads on canvas visibility. */\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// When video is downloaded relative to the canvas position. See {@link Visible}.\n\tvisible: Signal<Visible>;\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// Whether video should currently download (within the configured margin and tab visible, or forced via \"always\").\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\t\tthis.visible = Signal.from(props?.visible ?? \"20%\");\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 video should currently download.\n\t#runVisible(effect: Effect): void {\n\t\tconst visible = effect.get(this.visible);\n\n\t\t// \"never\" forces the check off; \"always\" forces it on regardless of viewport or tab state.\n\t\tif (visible === \"never\") {\n\t\t\tthis.#visible.set(false);\n\t\t\treturn;\n\t\t}\n\n\t\tif (visible === \"always\") {\n\t\t\tthis.#visible.set(true);\n\t\t\teffect.cleanup(() => this.#visible.set(false));\n\t\t\treturn;\n\t\t}\n\n\t\t// A distance gates on the viewport (used as the rootMargin) and the tab being visible.\n\t\tconst canvas = effect.get(this.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\t\tconst update = () => {\n\t\t\tthis.#visible.set(intersecting && !document.hidden);\n\t\t};\n\n\t\tconst callback = (entries: IntersectionObserverEntry[]) => {\n\t\t\tfor (const entry of entries) {\n\t\t\t\tintersecting = entry.isIntersecting;\n\t\t\t\tupdate();\n\t\t\t}\n\t\t};\n\n\t\t// `visible` is a CSS length, but the programmatic API accepts arbitrary strings. An\n\t\t// invalid rootMargin throws a SyntaxError, so fall back to the default margin.\n\t\tlet observer: IntersectionObserver;\n\t\ttry {\n\t\t\tobserver = new IntersectionObserver(callback, { threshold: INTERSECTION_THRESHOLD, rootMargin: visible });\n\t\t} catch {\n\t\t\tconsole.warn(`moq-watch: invalid visible margin \"${visible}\", using \"0px\"`);\n\t\t\tobserver = new IntersectionObserver(callback, { threshold: INTERSECTION_THRESHOLD });\n\t\t}\n\n\t\tupdate();\n\t\teffect.event(document, \"visibilitychange\", update);\n\t\tobserver.observe(canvas);\n\t\teffect.cleanup(() => observer.disconnect());\n\t\teffect.cleanup(() => this.#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\n/** A video source error that prevents choosing a usable rendition. */\nexport type SourceError = \"unsupported\";\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#error = new Signal<SourceError | undefined>(undefined);\n\t/** The current source error, or undefined while healthy or still probing. */\n\treadonly error: Getter<SourceError | undefined> = this.#error;\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) {\n\t\t\tthis.#error.set(undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tconst renditions = effect.get(this.catalog)?.renditions ?? {};\n\t\tthis.#error.set(undefined);\n\n\t\teffect.spawn(async () => {\n\t\t\tconst available: Record<string, Catalog.VideoConfig> = {};\n\n\t\t\tfor (const [name, config] of Object.entries(renditions)) {\n\t\t\t\tlet isSupported = false;\n\t\t\t\ttry {\n\t\t\t\t\tisSupported = await supported(config);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Source] video rendition ${name} (${config.codec}) support probe failed; treating as unsupported`,\n\t\t\t\t\t\terr,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (isSupported) available[name] = config;\n\t\t\t}\n\n\t\t\tconst error =\n\t\t\t\tObject.keys(available).length === 0 && Object.keys(renditions).length > 0 ? \"unsupported\" : undefined;\n\t\t\tif (error === \"unsupported\") {\n\t\t\t\tconsole.warn(\"[Source] No supported video renditions found:\", renditions);\n\t\t\t}\n\n\t\t\tthis.#error.set(error);\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 type { BufferedRanges } from \"./buffered\";\nimport { Muxer } from \"./mse\";\nimport { type Latency, Sync } from \"./sync\";\nimport * as Video from \"./video\";\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 target: a scalar minimizes (collapsed range), an object opens a range. See {@link Latency}.\n\tlatency: Signal<Latency>;\n\n\t// The jitter buffer in milliseconds.\n\tjitter: Signal<Moq.Time.Milli>;\n\n\t// Derived: whether buffered playback is active (ceiling above floor).\n\tbuffered: Signal<boolean>;\n\n\t// Re-anchor playback (and flush the audio buffer) at an utterance boundary in buffered mode.\n\treset(): void;\n}\n\nexport interface MultiBackendProps {\n\telement?: HTMLCanvasElement | HTMLVideoElement | Signal<HTMLCanvasElement | HTMLVideoElement | undefined>;\n\tbroadcast?: Broadcast | Signal<Broadcast | undefined>;\n\n\t// Latency target. A scalar (or \"real-time\") minimizes; an object `{ min, max }` opens a range and\n\t// enables buffered playback: build up a buffer from future-dated frames (e.g. TTS) instead of\n\t// minimizing latency. Call `reset()` at each utterance boundary to re-anchor. See {@link Latency}.\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\t// When video is downloaded relative to the canvas position. See {@link Video.Visible}.\n\tvisible?: Video.Visible | Signal<Video.Visible>;\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/**\n * A generic backend that supports either MSE or WebCodecs based on the provided element.\n *\n * This primarily backs the `<moq-watch>` web component, but is useful standalone.\n */\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\tbuffered: Signal<boolean>;\n\tpaused: Signal<boolean>;\n\n\t// When video is downloaded relative to the canvas position. See {@link Video.Visible}.\n\tvisible: Signal<Video.Visible>;\n\n\tvideo: VideoBackend;\n\t#videoSource: Video.Source;\n\n\taudio: AudioBackend;\n\t#audioSource: Audio.Source;\n\n\t// The active WebCodecs audio decoder, used to flush the buffer on `reset()`.\n\t#audioDecoder?: Audio.Decoder;\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({\n\t\t\tlatency: props?.latency,\n\t\t\tconnection: props?.connection,\n\t\t});\n\t\tthis.latency = this.sync.latency;\n\t\tthis.jitter = this.sync.jitter;\n\t\tthis.buffered = this.sync.buffered;\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\t\tthis.visible = Signal.from(props?.visible ?? \"20%\");\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\t\tthis.#audioDecoder = 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\tvisible: this.visible,\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\tthis.#audioDecoder = undefined;\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\t// Re-anchor playback at an utterance boundary in buffered mode: reset the sync reference\n\t// and flush the audio buffer so the next utterance plays from its own first frame.\n\treset(): void {\n\t\tthis.sync.reset();\n\t\tthis.#audioDecoder?.reset();\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 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 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/** Consumes a custom track once subscribed, scoped to the subscription's lifetime. */\nexport type ConsumeTrack = (track: Moq.Track, effect: Effect) => void;\n\n/**\n * Catalog formats accepted by Watch.\n *\n * Watch supports the on-the-wire catalog formats from `@moq/hang`, plus\n * `\"hangz\"` (the DEFLATE-compressed `catalog.json.z` track) and `\"manual\"`, where\n * the user supplies the catalog directly without fetching. `\"hangz\"` is opt-in\n * only: it shares the `.hang` broadcast suffix and is never auto-detected, so set\n * it explicitly via `catalogFormat`.\n */\nexport const CATALOG_FORMATS = [...Catalog.FORMATS, \"hangz\", \"manual\"] as const;\n/** A catalog format accepted by Watch. */\nexport type CatalogFormat = (typeof CATALOG_FORMATS)[number];\n\n/** Parse a catalog format string, returning `undefined` for unknown values. */\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. If omitted, reload skips the\n\t// announcement gate and subscribes immediately.\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 true; pass false to subscribe immediately without waiting for an announcement.\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. `\"hangz\"` (the compressed\n\t// `catalog.json.z` track) is opt-in only and never auto-detected.\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 or 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 ?? true);\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;\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\tif (!this.#announced) {\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 default to subscribing immediately instead of waiting forever.\n\t\tconst conn = effect.get(this.connection);\n\t\tif (conn?.url.hostname.endsWith(\"mediaoverquic.com\")) {\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.TRACK : format === \"hangz\" ? Catalog.TRACK_COMPRESSED : \"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, with\n\t\t// \"hangz\" decompressing the `.z` track; MSF stays on its own one-blob-per-group fetch.\n\t\tlet fetchNext: () => Promise<Catalog.Root | undefined>;\n\t\tif (format === \"hang\" || format === \"hangz\") {\n\t\t\tconst consumer = new Catalog.Consumer(track, { compression: format === \"hangz\" });\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\t/**\n\t * Subscribe to a custom track within this broadcast, following the active broadcast across\n\t * reconnects. `consume` runs with a freshly-subscribed track and a subscription-scoped effect\n\t * each time a broadcast becomes active (re-running on reconnect).\n\t *\n\t * For a JSON track, wrap the track with a `@moq/json` `Consumer` and read it in a spawned loop\n\t * (e.g. into a Signal). An application advertises the track in its own catalog section, which it\n\t * reads back from {@link catalog} (unknown sections pass through the loose schema):\n\t *\n\t * ```ts\n\t * import * as Json from \"@moq/json\";\n\t * const scte35 = new Signal<{ splices: number[] } | undefined>(undefined);\n\t * broadcast.subscribeTrack(\"scte35.json\", Catalog.PRIORITY.catalog, (track, effect) => {\n\t * \tconst consumer = new Json.Snapshot.Consumer<{ splices: number[] }>(track);\n\t * \teffect.spawn(async () => {\n\t * \t\tfor (;;) {\n\t * \t\t\tconst next = await Promise.race([effect.cancel, consumer.next()]);\n\t * \t\t\tif (next === undefined) break;\n\t * \t\t\tscte35.set(next);\n\t * \t\t}\n\t * \t});\n\t * });\n\t * ```\n\t *\n\t * Returns a function to stop subscribing; also stopped when this broadcast closes.\n\t */\n\tsubscribeTrack(name: string, priority: number, consume: ConsumeTrack): () => void {\n\t\tconst signals = new Effect();\n\t\tsignals.run((effect) => {\n\t\t\tconst active = effect.get(this.active);\n\t\t\tif (!active) return;\n\n\t\t\tconst track = active.subscribe(name, priority);\n\t\t\teffect.cleanup(() => track.close());\n\n\t\t\tconsume(track, effect);\n\t\t});\n\t\tthis.signals.cleanup(() => signals.close());\n\t\treturn () => signals.close();\n\t}\n\n\tclose() {\n\t\tthis.signals.close();\n\t}\n}\n"],"mappings":"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"}
package/buffered.d.ts DELETED
@@ -1,19 +0,0 @@
1
- /**
2
- * Buffered time-range helpers shared by the watch backends.
3
- *
4
- * Kept in a leaf module (depends only on `@moq/net`) so the backend and the
5
- * per-track MSE sources can share them without importing each other.
6
- *
7
- * @module
8
- */
9
- import * as Moq from "@moq/net";
10
- /** A single buffered time range. */
11
- export interface BufferedRange {
12
- start: Moq.Time.Milli;
13
- end: Moq.Time.Milli;
14
- }
15
- /** Serializable representation of DOM `TimeRanges`. */
16
- export type BufferedRanges = BufferedRange[];
17
- /** Convert a DOM `TimeRanges` into a {@link BufferedRanges}. */
18
- export declare function timeRangesToArray(ranges: TimeRanges): BufferedRanges;
19
- //# sourceMappingURL=buffered.d.ts.map
package/buffered.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"buffered.d.ts","sourceRoot":"","sources":["../src/buffered.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,oCAAoC;AACpC,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;IACtB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;CACpB;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,aAAa,EAAE,CAAC;AAE7C,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,CAUpE"}
package/mse.d.ts DELETED
@@ -1,19 +0,0 @@
1
- import { type Getter, Signal } from "@moq/signals";
2
- import type { Sync } from "./sync";
3
- export type MuxerProps = {
4
- element?: HTMLMediaElement | Signal<HTMLMediaElement | undefined>;
5
- paused?: boolean | Signal<boolean>;
6
- };
7
- /**
8
- * MSE-based video source for CMAF/fMP4 fragments.
9
- * Uses Media Source Extensions to handle complete moof+mdat fragments.
10
- */
11
- export declare class Muxer {
12
- #private;
13
- element: Signal<HTMLMediaElement | undefined>;
14
- paused: Signal<boolean>;
15
- readonly mediaSource: Getter<MediaSource | undefined>;
16
- constructor(sync: Sync, props?: MuxerProps);
17
- close(): void;
18
- }
19
- //# sourceMappingURL=mse.d.ts.map
package/mse.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"mse.d.ts","sourceRoot":"","sources":["../src/mse.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,MAAM,MAAM,UAAU,GAAG;IACxB,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,qBAAa,KAAK;;IACjB,OAAO,EAAE,MAAM,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IAE9C,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAKxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,GAAG,SAAS,CAAC,CAAqB;IAI1E,YAAY,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,UAAU,EAUzC;IAgHD,KAAK,IAAI,IAAI,CAEZ;CACD"}