@camstack/addon-pipeline 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/stream-broker/@mf-types.zip +0 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-ChoHjdk6.mjs +17 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-DbMNirr7.mjs +20 -0
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BTj1RQPs.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BuV9ar3i.mjs} +6 -6
- package/dist/stream-broker/{hostInit-wnZIaWA5.mjs → hostInit-CjVI5LuK.mjs} +6 -6
- package/dist/stream-broker/{index-DadYrR5H.mjs → index-BF5Qr03x.mjs} +1 -1
- package/dist/stream-broker/{index-BQ_NNiX0.mjs → index-DUJwOcGq.mjs} +5230 -5150
- package/dist/stream-broker/{index-Dwc0KrUN.mjs → index-DuBCn5us.mjs} +4244 -4164
- package/dist/stream-broker/index.js +425 -14
- package/dist/stream-broker/index.js.map +1 -1
- package/dist/stream-broker/index.mjs +425 -14
- package/dist/stream-broker/index.mjs.map +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-ezH__dL2.mjs +0 -17
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-ocGWYEqu.mjs +0 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/stream-broker/stream-broker/decoder-session-proxy.ts","../../src/stream-broker/rtsp/audio/aac-config.ts","../../src/stream-broker/stream-broker/audio-codec-session.ts","../../src/stream-broker/stream-broker/frame-dropper.ts","../../src/stream-broker/stream-broker/stream-pipe-server.ts","../../src/stream-broker/stream-broker/encoded-ring-buffer.ts","../../src/stream-broker/rtsp/rtsp-types.ts","../../src/stream-broker/rtsp/rtp-packetizer.ts","../../src/stream-broker/rtsp/annexb-deframer.ts","../../src/stream-broker/rtsp/sdp-builder.ts","../../src/stream-broker/rtsp/rtsp-restreamer.ts","../../src/stream-broker/rtsp/sdp-parser.ts","../../src/stream-broker/rtsp/rtsp-client.ts","../../src/stream-broker/rtsp/rfc4571-reader.ts","../../src/stream-broker/rtmp/flv-parser.ts","../../src/stream-broker/rtmp/rtmp-client.ts","../../src/stream-broker/rtmp/rtmp-reader.ts","../../src/stream-broker/rtsp/rtp-depacketizer.ts","../../src/stream-broker/rtsp/audio-rtp-decoder.ts","../../src/stream-broker/stream-broker/g711-mulaw.ts","../../src/stream-broker/stream-broker/placeholder-frames.ts","../../src/stream-broker/stream-broker/stream-broker.ts","../../src/stream-broker/stream-broker/audio-codec-proxy.ts","../../src/stream-broker/rtsp/rtsp-session.ts","../../src/stream-broker/rtsp/rtsp-listen-server.ts","../../src/stream-broker/rtsp/rtsp-restream-provider.ts","../../src/stream-broker/stream-broker/stream-broker-manager.ts","../../src/stream-broker/webrtc/nal-utils.ts","../../src/stream-broker/webrtc/h264-utils.ts","../../src/stream-broker/webrtc/h265-utils.ts","../../src/stream-broker/webrtc/mdns-resolve.ts","../../src/stream-broker/webrtc/jitter-buffer.ts","../../src/stream-broker/webrtc/h265-repacketizer.ts","../../src/stream-broker/webrtc/session.ts","../../src/stream-broker/webrtc/broker-webrtc-server.ts","../../src/stream-broker/device-scoped-providers.ts","../../src/stream-broker/addon.ts","../../src/stream-broker/stream-broker/ffmpeg-decoder-session.ts","../../src/stream-broker/stream-broker/ffmpeg-decoder-provider.ts","../../src/stream-broker/frame-transport/codecs/length-prefix-codec.ts","../../src/stream-broker/frame-transport/codecs/decoded-frame-codec.ts","../../src/stream-broker/frame-transport/transports/tcp-transport.ts","../../src/stream-broker/frame-transport/transports/uds-transport.ts","../../src/stream-broker/frame-transport/transports/inprocess-transport.ts","../../src/stream-broker/frame-transport/frame-server.ts","../../src/stream-broker/frame-transport/frame-client.ts","../../src/stream-broker/frame-transport/auth.ts"],"sourcesContent":["import type { EncodedPacket, DecodedFrame, DecoderSessionConfig, DecoderStats } from '@camstack/types'\n\nexport interface DecoderCapApi {\n supportsCodec(input: { readonly codec: string }): Promise<boolean>\n getInfo(): Promise<{ readonly id: string; readonly name: string; readonly isPullMode?: boolean; readonly priority?: number }>\n createSession(config: DecoderSessionConfig): Promise<{ readonly sessionId: string; readonly nodeId: string }>\n pushPacket(input: { readonly sessionId: string; readonly packet: EncodedPacket }): Promise<void>\n pullFrames(input: { readonly sessionId: string; readonly maxCount: number }): Promise<readonly DecodedFrame[]>\n destroySession(input: { readonly sessionId: string }): Promise<void>\n openStream(input: { readonly sessionId: string; readonly url: string }): Promise<void>\n updateConfig(input: { readonly sessionId: string; readonly config: Partial<DecoderSessionConfig> }): Promise<void>\n getStats(input: { readonly sessionId: string }): Promise<DecoderStats>\n}\n\nexport class DecoderSessionProxy {\n private polling = false\n\n constructor(\n private readonly api: DecoderCapApi,\n readonly sessionId: string,\n ) {}\n\n async pushPacket(packet: EncodedPacket): Promise<void> {\n await this.api.pushPacket({ sessionId: this.sessionId, packet })\n }\n\n async openStream(url: string): Promise<void> {\n await this.api.openStream({ sessionId: this.sessionId, url })\n }\n\n async startPolling(onFrame: (frame: DecodedFrame) => void): Promise<void> {\n this.polling = true\n while (this.polling) {\n const frames = await this.api.pullFrames({ sessionId: this.sessionId, maxCount: 4 })\n for (const frame of frames) onFrame(frame)\n if (frames.length === 0) await new Promise((r) => setTimeout(r, 1))\n }\n }\n\n stopPolling(): void {\n this.polling = false\n }\n\n async updateConfig(config: Partial<DecoderSessionConfig>): Promise<void> {\n await this.api.updateConfig({ sessionId: this.sessionId, config })\n }\n\n async getStats(): Promise<DecoderStats> {\n return this.api.getStats({ sessionId: this.sessionId })\n }\n\n async destroy(): Promise<void> {\n this.stopPolling()\n await this.api.destroySession({ sessionId: this.sessionId })\n }\n}\n","/**\n * Helpers for AAC stream framing.\n *\n * Two pieces:\n *\n * 1. `parseAudioSpecificConfig` — decode the hex `config=` blob from the\n * SDP `fmtp:` line for `mpeg4-generic`/`MP4A-LATM`. Returns the\n * samplerate + channel config the camera advertises.\n *\n * 2. `depacketizeRfc3640` — strip AU-headers from an RTP `mode=AAC-hbr`\n * payload and return one or more raw AAC AccessUnits ready to feed\n * libavcodec. Reolink uses `sizeLength=13; indexLength=3` which is\n * the RFC 3640 default for AAC-hbr.\n *\n * Both are pure functions over `Uint8Array` — keep them free of\n * libav/node-av deps so they're easy to unit-test.\n */\n\n// AAC sample-rate frequency table (ISO/IEC 14496-3 §1.6.3.4).\nconst SAMPLERATE_TABLE: readonly number[] = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,\n 16000, 12000, 11025, 8000, 7350,\n]\n\nexport interface AudioSpecificConfig {\n /** Object type (e.g. 2 = AAC-LC, 5 = HE-AAC, 29 = HE-AAC v2). */\n readonly objectType: number\n /** Decoded sample rate in Hz. */\n readonly sampleRate: number\n /** Channel configuration (1 = mono, 2 = stereo, 6 = 5.1, …). */\n readonly channelConfig: number\n}\n\nclass BitReader {\n private bitPos = 0\n constructor(private readonly buf: Uint8Array) {}\n read(n: number): number {\n let v = 0\n for (let i = 0; i < n; i++) {\n const bytePos = this.bitPos >> 3\n const bit = 7 - (this.bitPos & 7)\n const b = bytePos < this.buf.length ? this.buf[bytePos]! : 0\n v = (v << 1) | ((b >> bit) & 1)\n this.bitPos++\n }\n return v\n }\n remainingBits(): number {\n return this.buf.length * 8 - this.bitPos\n }\n}\n\n/**\n * Decode hex `config=` blob (or raw `Uint8Array`) into an\n * `AudioSpecificConfig`. Throws on malformed input.\n *\n * Layout per ISO/IEC 14496-3 §1.6.2.1:\n * - 5 bits: audioObjectType (extension-escape if 31)\n * - 4 bits: samplingFrequencyIndex (0xf = explicit 24-bit value follows)\n * - 4 bits: channelConfiguration\n */\nexport function parseAudioSpecificConfig(input: string | Uint8Array): AudioSpecificConfig {\n const buf = typeof input === 'string' ? hexToBytes(input) : input\n if (buf.length < 2) {\n throw new Error(`audio-codec: AudioSpecificConfig too short (${buf.length} bytes)`)\n }\n const reader = new BitReader(buf)\n let objectType = reader.read(5)\n if (objectType === 31) {\n objectType = 32 + reader.read(6)\n }\n const sampleRateIndex = reader.read(4)\n let sampleRate: number\n if (sampleRateIndex === 0xf) {\n sampleRate = reader.read(24)\n } else {\n const v = SAMPLERATE_TABLE[sampleRateIndex]\n if (!v) throw new Error(`audio-codec: invalid samplingFrequencyIndex ${sampleRateIndex}`)\n sampleRate = v\n }\n const channelConfig = reader.read(4)\n return { objectType, sampleRate, channelConfig }\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const trimmed = hex.replace(/\\s+/g, '')\n if (trimmed.length % 2 !== 0) {\n throw new Error(`audio-codec: hex string has odd length (${trimmed.length})`)\n }\n const out = new Uint8Array(trimmed.length / 2)\n for (let i = 0; i < out.length; i++) {\n const byte = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16)\n if (Number.isNaN(byte)) {\n throw new Error(`audio-codec: invalid hex at offset ${i * 2}`)\n }\n out[i] = byte\n }\n return out\n}\n\n/**\n * Depacketize an RFC 3640 `mode=AAC-hbr` RTP payload (typical\n * `sizeLength=13; indexLength=3`) into one or more raw AAC AccessUnits.\n *\n * RFC 3640 §3.2 layout:\n * AU Headers Length (16 bits, big-endian) — total bits in the AU\n * headers section.\n * AU headers — `sizeLength` bits AU-size, `indexLength` bits AU-index\n * (or AU-index-delta after the first). Padding aligns to byte.\n * AU data — `auCount` access units back-to-back, each `auSize` bytes.\n *\n * `payload` is the RTP payload (after the 12+ byte RTP header, i.e.\n * what callers pass via `onAudioRtp` minus the header strip).\n */\nexport interface Rfc3640Options {\n readonly sizeLength: number // bits per AU-size — default 13\n readonly indexLength: number // bits per AU-index — default 3\n readonly indexDeltaLength?: number\n}\n\nexport function depacketizeRfc3640(\n payload: Uint8Array,\n opts: Rfc3640Options = { sizeLength: 13, indexLength: 3 },\n): Uint8Array[] {\n if (payload.length < 2) return []\n const sizeLen = opts.sizeLength\n const idxLen = opts.indexLength\n const idxDeltaLen = opts.indexDeltaLength ?? idxLen\n const headersBitLength = (payload[0]! << 8) | payload[1]!\n if (headersBitLength === 0) return []\n\n const headersStartByte = 2\n const headersByteLength = (headersBitLength + 7) >> 3\n const headersEndByte = headersStartByte + headersByteLength\n if (headersEndByte > payload.length) return []\n\n const headersSlice = payload.subarray(headersStartByte, headersEndByte)\n const reader = new BitReader(headersSlice)\n const sizes: number[] = []\n let consumed = 0\n let firstAu = true\n while (consumed < headersBitLength) {\n if (reader.remainingBits() < sizeLen + (firstAu ? idxLen : idxDeltaLen)) break\n const auSize = reader.read(sizeLen)\n reader.read(firstAu ? idxLen : idxDeltaLen) // index/delta — ignored\n sizes.push(auSize)\n consumed += sizeLen + (firstAu ? idxLen : idxDeltaLen)\n firstAu = false\n }\n\n const units: Uint8Array[] = []\n let offset = headersEndByte\n for (const sz of sizes) {\n if (offset + sz > payload.length) break\n units.push(payload.subarray(offset, offset + sz))\n offset += sz\n }\n return units\n}\n","/**\n * Per-broker handle around an audio-codec decode session. Owns:\n * - sessionId (lazily created on first push)\n * - depacketizer choice based on RTP payload format\n * - polling timer that pulls PCM from the cap and fans it out via\n * a caller-supplied AudioRtpDecoder-shaped callback.\n *\n * Stays self-contained so the broker only needs to forward\n * `processPacket(rtpData)` and `close()` calls. Mirrors the surface\n * of `AudioRtpDecoder` (the legacy PCMU/PCMA path) so the broker's\n * call sites can swap implementations without further branching.\n */\nimport type { DecodedAudioChunk, IScopedLogger } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport type { AudioCodecCapApi } from './audio-codec-proxy.js'\nimport { depacketizeRfc3640, type Rfc3640Options } from '../rtsp/audio/aac-config.js'\n\nexport interface AudioCodecSessionConfig {\n readonly codec: string // canonical lowercase: 'aac', 'opus', ...\n readonly sourceSampleRate: number\n readonly sourceChannels: number\n readonly extraData?: Uint8Array // AAC AudioSpecificConfig\n readonly targetSampleRate: number // typically 16000 for ASA / yamnet\n readonly targetChannels: number // typically 1\n readonly tag?: string\n /** Set when the source uses MPEG4-GENERIC mode=AAC-hbr framing. */\n readonly rfc3640?: Rfc3640Options\n}\n\nconst POLL_INTERVAL_MS = 40\nconst MAX_PCM_PER_POLL = 16\nconst RTP_HEADER_LENGTH = 12\nconst WARN_THROTTLE_MS = 5_000\n// Match AudioRtpDecoder's emit cadence so YAMNet/Apple SoundAnalysis\n// see the same window size regardless of source codec. A YAMNet hop is\n// ~0.48s and Apple SA classifies on rolling ~0.96s windows; 0.5s of\n// target-rate samples gives both classifiers a stable input. Codec\n// frames are typically 64–128ms so ~4–8 frames coalesce into one chunk.\nconst TARGET_CHUNK_MS = 500\nconst F32_BYTES = 4\n// Matches the \"decode session 'dec-...' not found\" thrown by the\n// audio-codec addon when the upstream session has been reaped (idle\n// timeout, addon restart, host process resumed from sleep). We use it\n// to detect \"the cap forgot us\" vs a transient decode error.\nconst SESSION_NOT_FOUND_RE = /decode session\\b.*\\bnot found\\b/\n\nfunction isSessionGone(err: unknown): boolean {\n return SESSION_NOT_FOUND_RE.test(errMsg(err))\n}\n\nexport class AudioCodecSession {\n private sessionId: string | null = null\n private sessionNodeId: string | null = null\n private creating: Promise<string | null> | null = null\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private closed = false\n private packetsIn = 0\n private packetsDropped = 0\n // Coalesce warnings — without this a single reaped session produces\n // ~1000 warn/s (every RTP audio packet + every poll tick), and the\n // synchronous winston writes block the broker's event loop enough\n // to visibly slow the video forwarders sharing the process.\n private warnState = new Map<string, { lastLoggedAt: number; suppressed: number }>()\n // Accumulator for the ~0.5s emit window. The codec cap returns a PCM\n // frame per decoded codec packet (~21–128ms depending on codec), which\n // is too short for the audio classifiers downstream. We coalesce\n // until we've buffered TARGET_CHUNK_MS worth of samples, then emit\n // one chunk with the earliest packet's timestamp.\n private pendingPcm: Buffer[] = []\n private pendingBytes = 0\n private pendingSampleRate = 0\n private pendingChannels = 0\n private pendingFirstTs = 0\n private readonly chunkBytesTarget: number\n\n constructor(\n private readonly api: AudioCodecCapApi,\n private readonly cfg: AudioCodecSessionConfig,\n private readonly emit: (chunk: DecodedAudioChunk) => void,\n private readonly logger?: IScopedLogger,\n ) {\n this.chunkBytesTarget =\n Math.max(1, Math.round(cfg.targetSampleRate * (TARGET_CHUNK_MS / 1000))) *\n Math.max(1, cfg.targetChannels) *\n F32_BYTES\n }\n\n processPacket(rtpData: Buffer): void {\n if (this.closed) return\n if (rtpData.length <= RTP_HEADER_LENGTH) return\n const payload = rtpData.subarray(RTP_HEADER_LENGTH)\n this.packetsIn++\n\n void this.ensureSession().then((sid) => {\n if (!sid || this.closed) return\n const nodeId = this.sessionNodeId ?? undefined\n const units = this.cfg.rfc3640\n ? depacketizeRfc3640(payload, this.cfg.rfc3640)\n : [Uint8Array.from(payload)]\n for (const unit of units) {\n void this.api.pushEncodedFrame({\n sessionId: sid,\n ...(nodeId ? { nodeId } : {}),\n data: unit,\n }).catch((err) => {\n this.packetsDropped++\n this.handlePushError('audio-codec: pushEncodedFrame failed', sid, err)\n })\n }\n })\n }\n\n /**\n * Push a single pre-depacketized codec frame. Used by push-source\n * providers (Reolink Baichuan, Frigate) that emit raw codec frames\n * directly — no RTP wrapping, no rfc3640 depacketize. The payload\n * MUST be one complete frame the underlying decoder can consume\n * (e.g. an AAC raw frame with or without ADTS header per the\n * codec's AudioSpecificConfig).\n */\n pushRawFrame(unit: Uint8Array): void {\n if (this.closed) return\n if (unit.length === 0) return\n this.packetsIn++\n void this.ensureSession().then((sid) => {\n if (!sid || this.closed) return\n const nodeId = this.sessionNodeId ?? undefined\n void this.api.pushEncodedFrame({\n sessionId: sid,\n ...(nodeId ? { nodeId } : {}),\n data: unit,\n }).catch((err) => {\n this.packetsDropped++\n this.handlePushError('audio-codec: pushEncodedFrame (raw) failed', sid, err)\n })\n })\n }\n\n /** Stats surfaced by the broker for the streams tab / logs. */\n getStats(): { packetsIn: number; packetsDropped: number; sessionId: string | null } {\n return {\n packetsIn: this.packetsIn,\n packetsDropped: this.packetsDropped,\n sessionId: this.sessionId,\n }\n }\n\n async close(): Promise<void> {\n if (this.closed) return\n this.closed = true\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n // Emit whatever's in the accumulator so downstream audio\n // analyzers don't miss the last partial window. Skipped if the\n // accumulator is empty.\n this.flushPending()\n if (this.sessionId) {\n const sid = this.sessionId\n const nodeId = this.sessionNodeId ?? undefined\n try { await this.api.closeSession({ sessionId: sid, ...(nodeId ? { nodeId } : {}) }) }\n catch (err) {\n this.logger?.warn('audio-codec: closeSession failed', {\n meta: { error: errMsg(err) },\n })\n }\n this.sessionId = null\n this.sessionNodeId = null\n }\n }\n\n private async ensureSession(): Promise<string | null> {\n if (this.sessionId) return this.sessionId\n if (this.creating) return this.creating\n this.creating = (async () => {\n try {\n const res = await this.api.createDecodeSession({\n codec: this.cfg.codec,\n sourceSampleRate: this.cfg.sourceSampleRate,\n sourceChannels: this.cfg.sourceChannels,\n ...(this.cfg.extraData ? { extraData: this.cfg.extraData } : {}),\n targetSampleRate: this.cfg.targetSampleRate,\n targetChannels: this.cfg.targetChannels,\n targetFormat: 'f32le',\n ...(this.cfg.tag ? { tag: this.cfg.tag } : {}),\n })\n this.sessionId = res.sessionId\n this.sessionNodeId = res.nodeId\n this.startPollLoop()\n this.logger?.info('audio-codec: decode session ready', {\n meta: {\n sessionId: res.sessionId,\n nodeId: res.nodeId,\n codec: this.cfg.codec,\n target: `${this.cfg.targetSampleRate}Hz×${this.cfg.targetChannels}`,\n },\n })\n return res.sessionId\n } catch (err) {\n this.logger?.error('audio-codec: createDecodeSession failed', {\n meta: { codec: this.cfg.codec, error: errMsg(err) },\n })\n return null\n } finally {\n this.creating = null\n }\n })()\n return this.creating\n }\n\n private startPollLoop(): void {\n if (this.pollTimer) return\n this.pollTimer = setInterval(() => {\n void this.pollOnce()\n }, POLL_INTERVAL_MS)\n if (typeof this.pollTimer.unref === 'function') this.pollTimer.unref()\n }\n\n private async pollOnce(): Promise<void> {\n const sid = this.sessionId\n if (!sid || this.closed) return\n const nodeId = this.sessionNodeId ?? undefined\n try {\n const pcms = await this.api.pullPcm({\n sessionId: sid,\n ...(nodeId ? { nodeId } : {}),\n maxCount: MAX_PCM_PER_POLL,\n })\n for (const pcm of pcms) {\n this.appendPcm(pcm)\n }\n } catch (err) {\n // Stop polling and forget the dead session id when the upstream\n // cap reaped it; the next pushed RTP packet will reopen via\n // ensureSession(). Without this the timer fires every 40ms forever\n // and the broker logs ~25 warn/s per dead session per camera.\n if (isSessionGone(err)) {\n this.handleSessionGone(sid)\n return\n }\n this.warnThrottled('audio-codec: pullPcm failed', errMsg(err))\n }\n }\n\n // ── Sample accumulator ──────────────────────────────────────────────────\n\n /**\n * Stage a freshly-pulled PCM frame into the accumulator. When we've\n * buffered enough samples for the configured ~0.5s window we\n * concatenate, copy out, and emit a single DecodedAudioChunk so\n * downstream audio classifiers see a stable input length regardless\n * of source codec frame size (G.711 packets ≈ 20ms, AAC ≈ 21–43ms,\n * Opus ≈ 20ms).\n *\n * Format changes (sampleRate/channels) flush the previous window\n * first so a chunk never spans two formats.\n */\n private appendPcm(pcm: { data: Uint8Array; sampleRate: number; channels: number; pts: number }): void {\n const incoming = pcm.data.byteLength\n if (incoming === 0) return\n\n // Format change → flush the previous window before swapping params.\n if (\n this.pendingBytes > 0 &&\n (pcm.sampleRate !== this.pendingSampleRate ||\n pcm.channels !== this.pendingChannels)\n ) {\n this.flushPending()\n }\n\n if (this.pendingBytes === 0) {\n this.pendingSampleRate = pcm.sampleRate\n this.pendingChannels = pcm.channels\n // pcm.pts is a codec-relative PTS (often starting at 0), not a\n // wall-clock timestamp. Downstream consumers (audio-metrics\n // snapshot, UI \"X seconds ago\" chip) interpret the chunk\n // timestamp as ms-since-epoch — emitting `pts` produced\n // bogus values like \"1777832862s ago\" because Date.now() - 0\n // ≈ Date.now(). AudioRtpDecoder always uses Date.now() too,\n // so the codec path now matches.\n this.pendingFirstTs = Date.now()\n }\n // Buffer.from(uint8array) deep-copies — safe to keep across the\n // RPC tick that owns the source buffer.\n this.pendingPcm.push(Buffer.from(pcm.data))\n this.pendingBytes += incoming\n\n if (this.pendingBytes >= this.chunkBytesTarget) {\n this.flushPending()\n }\n }\n\n private flushPending(): void {\n if (this.pendingBytes === 0) return\n const merged = this.pendingPcm.length === 1\n ? this.pendingPcm[0]!\n : Buffer.concat(this.pendingPcm, this.pendingBytes)\n const sampleRate = this.pendingSampleRate\n const channels = this.pendingChannels\n const timestamp = this.pendingFirstTs\n this.pendingPcm = []\n this.pendingBytes = 0\n this.pendingSampleRate = 0\n this.pendingChannels = 0\n this.pendingFirstTs = 0\n this.emit({\n data: merged,\n sampleRate,\n channels,\n timestamp,\n })\n }\n\n // ── Recovery + warn coalescing ──────────────────────────────────────────\n\n /**\n * Invoked from any push/pull path when the cap returns\n * `decode session not found`. Drops our cached session id + tears\n * down the poll loop so subsequent traffic re-creates the session\n * lazily. Safe to call multiple times: second caller sees a null\n * sessionId and noops.\n */\n private handleSessionGone(staleSid: string): void {\n if (this.sessionId !== staleSid) return\n this.sessionId = null\n this.sessionNodeId = null\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n // Drain whatever survived from the dead session — the new session\n // may negotiate a different sampleRate/channels, and emitting the\n // tail keeps timeline continuity for the classifier.\n this.flushPending()\n this.warnThrottled(\n 'audio-codec: session reaped upstream — will re-create on next packet',\n `staleSessionId=${staleSid}`,\n )\n }\n\n private handlePushError(label: string, sid: string, err: unknown): void {\n if (isSessionGone(err)) {\n this.handleSessionGone(sid)\n return\n }\n this.warnThrottled(label, errMsg(err))\n }\n\n /**\n * Logs at most once per WARN_THROTTLE_MS per (label,detail) pair and\n * folds suppressed counts into the next log line so we keep visibility\n * into chronic failures without flooding winston (whose JSON encode +\n * sync write blocks the broker's event loop at >100/s).\n */\n private warnThrottled(label: string, detail: string): void {\n const key = `${label}|${detail}`\n const now = Date.now()\n const prev = this.warnState.get(key)\n if (prev && now - prev.lastLoggedAt < WARN_THROTTLE_MS) {\n prev.suppressed++\n return\n }\n const suppressed = prev?.suppressed ?? 0\n this.warnState.set(key, { lastLoggedAt: now, suppressed: 0 })\n this.logger?.warn(label, {\n meta: {\n codec: this.cfg.codec,\n error: detail,\n ...(suppressed > 0 ? { suppressedSinceLastLog: suppressed } : {}),\n },\n })\n }\n}\n","export class FrameDropper {\n private intervalMs: number\n private lastPassedAt = -Infinity\n\n constructor(maxFps: number) {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n\n shouldKeep(): boolean {\n if (this.intervalMs === 0) return true\n\n const now = Date.now()\n if (now - this.lastPassedAt >= this.intervalMs) {\n this.lastPassedAt = now\n return true\n }\n return false\n }\n\n setMaxFps(maxFps: number): void {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n}\n","import net from 'node:net'\nimport type { IScopedLogger } from '@camstack/types'\n\n/**\n * A lightweight TCP server that broadcasts raw encoded video bytes\n * (H.264/H.265 Annex-B) to all connected clients.\n *\n * Each StreamBroker owns one StreamPipeServer. Restreamers (e.g. go2rtc)\n * connect via `tcp://127.0.0.1:{port}` and receive the same byte stream\n * that the broker reads from the camera, eliminating duplicate RTSP\n * connections.\n */\n/** Max bytes queued in a client's write buffer before we consider it too slow */\nconst MAX_WRITE_BUFFER_BYTES = 2 * 1024 * 1024 // 2 MB\n\nexport class StreamPipeServer {\n private readonly server: net.Server\n private readonly clients: Set<net.Socket> = new Set()\n private port = 0\n private started = false\n private readonly logger: IScopedLogger | undefined\n private _onClientCountChanged: (() => void) | null = null\n\n /** Register a callback invoked when pipe client count changes. */\n onClientCountChanged(cb: () => void): void { this._onClientCountChanged = cb }\n\n constructor(logger?: IScopedLogger) {\n this.logger = logger\n this.server = net.createServer((socket) => {\n this.handleConnection(socket)\n })\n\n this.server.on('error', (err) => {\n this.logger?.error('StreamPipeServer error', { meta: { error: err.message } })\n })\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n this.server.once('error', reject)\n this.server.listen(0, '127.0.0.1', () => {\n this.server.removeListener('error', reject)\n const address = this.server.address() as net.AddressInfo\n this.port = address.port\n this.started = true\n this.logger?.debug('StreamPipeServer listening', { meta: { port: this.port } })\n resolve()\n })\n })\n }\n\n /**\n * Broadcast raw encoded bytes to all connected clients.\n * Silently drops data for clients that are slow or disconnected.\n */\n broadcast(data: Buffer): void {\n for (const client of this.clients) {\n if (client.destroyed) {\n this.clients.delete(client)\n continue\n }\n\n // Drop data for slow clients instead of letting buffers grow unbounded\n if (client.writableLength > MAX_WRITE_BUFFER_BYTES) {\n this.logger?.warn(\n 'StreamPipeServer client buffer overflow — dropping client',\n { meta: { bufferMB: Number((client.writableLength / 1024 / 1024).toFixed(1)) } },\n )\n this.removeClient(client)\n continue\n }\n\n client.write(data, (err) => {\n if (err) {\n this.logger?.debug(\n 'StreamPipeServer write error, removing client',\n { meta: { error: err.message } },\n )\n this.removeClient(client)\n }\n })\n }\n }\n\n getPort(): number {\n return this.port\n }\n\n getUrl(): string {\n return `tcp://127.0.0.1:${this.port}`\n }\n\n getClientCount(): number {\n return this.clients.size\n }\n\n isStarted(): boolean {\n return this.started\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return\n }\n\n this.started = false\n this._onClientCountChanged = null\n\n for (const client of this.clients) {\n client.destroy()\n }\n this.clients.clear()\n\n await new Promise<void>((resolve) => {\n this.server.close(() => {\n this.logger?.debug('StreamPipeServer stopped')\n resolve()\n })\n })\n }\n\n private handleConnection(socket: net.Socket): void {\n this.clients.add(socket)\n this.logger?.debug(\n 'StreamPipeServer client connected',\n { meta: { total: this.clients.size } },\n )\n this._onClientCountChanged?.()\n\n socket.on('close', () => {\n this.removeClient(socket)\n })\n\n socket.on('error', () => {\n this.removeClient(socket)\n })\n }\n\n private removeClient(socket: net.Socket): void {\n const existed = this.clients.delete(socket)\n if (existed) {\n this.logger?.debug(\n 'StreamPipeServer client disconnected',\n { meta: { total: this.clients.size } },\n )\n this._onClientCountChanged?.()\n }\n if (!socket.destroyed) {\n socket.destroy()\n }\n }\n}\n","import type { EncodedPacket } from '@camstack/types'\n\n/**\n * Time-based ring buffer for encoded video packets.\n *\n * Keeps the most recent N seconds of encoded packets, evicting old ones\n * based on timestamp. Used for pre-buffering: when an addon needs the last\n * N seconds of video (e.g., for clip creation), it calls `getPackets()`.\n *\n * The buffer always starts from the most recent keyframe to ensure\n * consumers can decode the returned data.\n */\nexport class EncodedRingBuffer {\n private readonly packets: EncodedPacket[] = []\n private maxDurationMs: number\n\n constructor(maxDurationSec: number = 10) {\n this.maxDurationMs = maxDurationSec * 1000\n }\n\n /** Update the buffer duration (in seconds). Evicts old packets immediately. */\n setDuration(seconds: number): void {\n this.maxDurationMs = seconds * 1000\n this.evict()\n }\n\n /** Get the current buffer duration in seconds. */\n getDuration(): number {\n return this.maxDurationMs / 1000\n }\n\n /** Push a new packet into the buffer and evict expired ones. */\n push(packet: EncodedPacket): void {\n this.packets.push(packet)\n this.evict()\n }\n\n /**\n * Get buffered packets, starting from the most recent keyframe.\n * Returns packets in chronological order (oldest first).\n * Returns an empty array if no keyframe is found.\n */\n getPackets(): readonly EncodedPacket[] {\n // Find the last keyframe index — consumers need a keyframe to start decoding\n let lastKeyframeIdx = -1\n for (let i = this.packets.length - 1; i >= 0; i--) {\n if (this.packets[i]!.keyframe && this.packets[i]!.type === 'video') {\n lastKeyframeIdx = i\n break\n }\n }\n\n if (lastKeyframeIdx < 0) return []\n\n return this.packets.slice(lastKeyframeIdx)\n }\n\n /** Get the approximate duration of buffered content in seconds. */\n getBufferedDurationMs(): number {\n if (this.packets.length < 2) return 0\n const first = this.packets[0]!\n const last = this.packets[this.packets.length - 1]!\n return last.pts - first.pts\n }\n\n /** Get the number of packets in the buffer. */\n getPacketCount(): number {\n return this.packets.length\n }\n\n /** Clear all buffered packets. */\n clear(): void {\n this.packets.length = 0\n }\n\n /** Remove packets older than maxDurationMs based on PTS. */\n private evict(): void {\n if (this.packets.length === 0) return\n\n const cutoff = this.packets[this.packets.length - 1]!.pts - this.maxDurationMs\n let removeCount = 0\n\n while (removeCount < this.packets.length && this.packets[removeCount]!.pts < cutoff) {\n removeCount++\n }\n\n if (removeCount > 0) {\n this.packets.splice(0, removeCount)\n }\n }\n}\n","/** RTP header + payload ready to send over TCP interleaved. */\nexport interface RtpPacket {\n /** RTP payload including 12-byte header. */\n readonly data: Buffer\n /** Whether this packet starts an access unit (for keyframe detection). */\n readonly marker: boolean\n}\n\n/** Parsed RTSP request from a client. */\nexport interface RtspRequest {\n readonly method: string\n readonly uri: string\n readonly cseq: number\n readonly headers: ReadonlyMap<string, string>\n readonly body?: string\n}\n\n/** Track setup state per client session. */\nexport interface TrackSetup {\n readonly trackId: number\n readonly rtpChannel: number\n readonly rtcpChannel: number\n}\n\n/** Client session state. */\nexport interface ClientSession {\n readonly sessionId: string\n readonly tracks: ReadonlyArray<TrackSetup>\n readonly playing: boolean\n}\n\n/** Codec parameters extracted from the stream. */\nexport interface CodecParams {\n readonly codec: 'h264' | 'h265'\n /** Parameter set NALs (SPS+PPS for H.264, VPS+SPS+PPS for H.265). */\n readonly parameterSets: ReadonlyArray<Buffer>\n readonly width?: number\n readonly height?: number\n}\n\n/** RTSP method constants. */\nexport const RTSP_METHODS = ['OPTIONS', 'DESCRIBE', 'SETUP', 'PLAY', 'TEARDOWN', 'GET_PARAMETER'] as const\n\n/** RTP constants. */\nexport const RTP_VERSION = 2\nexport const RTP_HEADER_SIZE = 12\nexport const RTP_MAX_PAYLOAD = 1400 // Safe MTU for TCP interleaved\n\n/** H.264 NAL type masks. */\nexport const H264_NAL_TYPE_MASK = 0x1f\nexport const H264_NAL_FUA = 28\n\n/** H.265 NAL type extraction: (byte0 >> 1) & 0x3f. */\nexport const H265_NAL_TYPE_SHIFT = 1\nexport const H265_NAL_TYPE_MASK = 0x3f\nexport const H265_NAL_FU = 49\n\n/** TCP interleaved framing: magic byte. */\nexport const INTERLEAVED_MAGIC = 0x24\n\n/** Max write buffer before dropping slow client. */\nexport const MAX_WRITE_BUFFER = 2 * 1024 * 1024\n","import {\n RTP_VERSION,\n RTP_HEADER_SIZE,\n RTP_MAX_PAYLOAD,\n H264_NAL_TYPE_MASK,\n H264_NAL_FUA,\n H265_NAL_TYPE_SHIFT,\n H265_NAL_TYPE_MASK,\n H265_NAL_FU,\n type RtpPacket,\n} from './rtsp-types.js'\n\nexport class RtpPacketizer {\n private sequenceNumber = 0\n private readonly ssrc: number\n\n constructor(\n private readonly codec: 'h264' | 'h265',\n private readonly payloadType: number,\n ) {\n this.ssrc = Math.floor(Math.random() * 0xffffffff)\n }\n\n /**\n * Split Annex-B byte stream into individual NAL units.\n * Strips 3-byte (0x000001) and 4-byte (0x00000001) start codes.\n */\n static splitAnnexB(data: Buffer): ReadonlyArray<Buffer> {\n const nals: Buffer[] = []\n let start = -1\n\n for (let i = 0; i < data.length - 2; i++) {\n const isStartCode3 = data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 1\n const isStartCode4 = i + 3 < data.length &&\n data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0 && data[i + 3] === 1\n\n if (isStartCode3 || isStartCode4) {\n if (start >= 0) {\n // Strip trailing zero bytes — they belong to the next start code prefix.\n // Emulation prevention (00 00 03) guarantees no false start codes in NAL payload.\n let end = i\n while (end > start && data[end - 1] === 0) end--\n if (end > start) nals.push(data.subarray(start, end))\n }\n start = isStartCode4 ? i + 4 : i + 3\n if (isStartCode4) i += 3\n else i += 2\n }\n }\n\n if (start >= 0 && start < data.length) {\n nals.push(data.subarray(start))\n }\n\n return nals\n }\n\n /**\n * Packetize a single NAL unit into one or more RTP packets.\n * @param nal NAL unit WITHOUT start code\n * @param timestamp RTP timestamp (90kHz clock)\n * @param marker true if this is the last NAL of the access unit\n */\n packetize(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n if (this.codec === 'h264') {\n return this.packetizeH264(nal, timestamp, marker)\n }\n return this.packetizeH265(nal, timestamp, marker)\n }\n\n private packetizeH264(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n if (nal.length <= RTP_MAX_PAYLOAD) {\n return [this.buildRtpPacket(nal, timestamp, marker)]\n }\n return this.fragmentH264FUA(nal, timestamp, marker)\n }\n\n private packetizeH265(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n if (nal.length <= RTP_MAX_PAYLOAD) {\n return [this.buildRtpPacket(nal, timestamp, marker)]\n }\n return this.fragmentH265FU(nal, timestamp, marker)\n }\n\n /** H.264 FU-A fragmentation (RFC 6184 Section 5.8). */\n private fragmentH264FUA(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n const nalHeader = nal[0]!\n const fnri = nalHeader & 0xe0\n const nalType = nalHeader & H264_NAL_TYPE_MASK\n const payload = nal.subarray(1)\n\n const maxFragment = RTP_MAX_PAYLOAD - 2\n const packets: RtpPacket[] = []\n let offset = 0\n\n while (offset < payload.length) {\n const end = Math.min(offset + maxFragment, payload.length)\n const isFirst = offset === 0\n const isLast = end === payload.length\n\n const fuIndicator = fnri | H264_NAL_FUA\n const fuHeader = (isFirst ? 0x80 : 0) | (isLast ? 0x40 : 0) | nalType\n\n const fragment = Buffer.allocUnsafe(2 + (end - offset))\n fragment[0] = fuIndicator\n fragment[1] = fuHeader\n payload.copy(fragment, 2, offset, end)\n\n packets.push(this.buildRtpPacket(fragment, timestamp, isLast && marker))\n offset = end\n }\n\n return packets\n }\n\n /** H.265 FU fragmentation (RFC 7798 Section 4.4.3). */\n private fragmentH265FU(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n const nalType = (nal[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n const tidLayerId = nal[1]!\n const payload = nal.subarray(2)\n\n const maxFragment = RTP_MAX_PAYLOAD - 3\n const packets: RtpPacket[] = []\n let offset = 0\n\n while (offset < payload.length) {\n const end = Math.min(offset + maxFragment, payload.length)\n const isFirst = offset === 0\n const isLast = end === payload.length\n\n const payloadHdr0 = (H265_NAL_FU << H265_NAL_TYPE_SHIFT) | (nal[0]! & 0x81)\n const payloadHdr1 = tidLayerId\n const fuHeader = (isFirst ? 0x80 : 0) | (isLast ? 0x40 : 0) | nalType\n\n const fragment = Buffer.allocUnsafe(3 + (end - offset))\n fragment[0] = payloadHdr0\n fragment[1] = payloadHdr1\n fragment[2] = fuHeader\n payload.copy(fragment, 3, offset, end)\n\n packets.push(this.buildRtpPacket(fragment, timestamp, isLast && marker))\n offset = end\n }\n\n return packets\n }\n\n private buildRtpPacket(payload: Buffer, timestamp: number, marker: boolean): RtpPacket {\n const header = Buffer.allocUnsafe(RTP_HEADER_SIZE)\n header[0] = RTP_VERSION << 6\n header[1] = (marker ? 0x80 : 0) | (this.payloadType & 0x7f)\n header.writeUInt16BE(this.sequenceNumber & 0xffff, 2)\n this.sequenceNumber = (this.sequenceNumber + 1) & 0xffff\n header.writeUInt32BE(timestamp >>> 0, 4)\n header.writeUInt32BE(this.ssrc >>> 0, 8)\n\n return {\n data: Buffer.concat([header, payload]),\n marker,\n }\n }\n}\n","/**\n * Annex-B NAL unit deframer.\n *\n * ffmpeg outputs Annex-B via stdout in arbitrary chunk sizes — a single\n * `data` event may contain partial NALs, multiple NALs, or NALs split\n * across events. This class accumulates bytes and emits complete NAL units.\n *\n * Usage:\n * const deframer = new AnnexBDeframer((nal, isKeyframe) => { ... })\n * proc.stdout.on('data', chunk => deframer.push(chunk))\n */\nexport class AnnexBDeframer {\n private buffer: Buffer<ArrayBufferLike> = Buffer.alloc(0)\n private readonly codec: 'h264' | 'h265'\n\n constructor(\n codec: 'h264' | 'h265',\n private readonly onNal: (nal: Buffer, keyframe: boolean) => void,\n ) {\n this.codec = codec\n }\n\n /** Push raw Annex-B bytes. Complete NAL units are emitted via onNal callback. */\n push(chunk: Buffer): void {\n this.buffer = this.buffer.length > 0\n ? Buffer.concat([this.buffer, chunk])\n : chunk\n\n this.drain()\n }\n\n /** Flush remaining buffer (call on stream end). */\n flush(): void {\n if (this.buffer.length > 0) {\n const nal = this.stripLeadingStartCode(this.buffer)\n if (nal.length > 0) {\n this.onNal(nal, this.isKeyframeNal(nal))\n }\n this.buffer = Buffer.alloc(0)\n }\n }\n\n /** Extract complete NAL units from the buffer, leaving partial data for next push. */\n private drain(): void {\n while (true) {\n // Find first start code in buffer\n const firstSc = this.findStartCode(0)\n if (firstSc < 0) {\n // No start code found at all — keep everything (partial NAL or leading junk)\n return\n }\n\n // Find the NEXT start code after the first one\n const nalStart = firstSc + (this.isStartCode4(firstSc) ? 4 : 3)\n const nextSc = this.findStartCode(nalStart)\n\n if (nextSc < 0) {\n // Only one start code — NAL is not yet complete, wait for more data.\n // Keep buffer from firstSc onwards (discard junk before first start code).\n if (firstSc > 0) {\n this.buffer = Buffer.from(this.buffer.subarray(firstSc))\n }\n return\n }\n\n // We have a complete NAL between firstSc and nextSc\n const nal = this.buffer.subarray(nalStart, nextSc)\n if (nal.length > 0) {\n this.onNal(nal, this.isKeyframeNal(nal))\n }\n\n // Advance buffer past this NAL (keep from nextSc onwards)\n this.buffer = Buffer.from(this.buffer.subarray(nextSc))\n }\n }\n\n /** Find the byte offset of the next start code (00 00 01 or 00 00 00 01) at or after `from`. */\n private findStartCode(from: number): number {\n const buf = this.buffer\n const len = buf.length - 2\n for (let i = from; i < len; i++) {\n // Fast skip: if buf[i+2] is not 0 or 1, no start code can begin at i\n if (buf[i + 2]! > 1) {\n i += 2\n continue\n }\n if (buf[i] === 0 && buf[i + 1] === 0) {\n if (buf[i + 2] === 1) return i\n if (buf[i + 2] === 0 && i + 3 < buf.length && buf[i + 3] === 1) return i\n }\n }\n return -1\n }\n\n /** Check if start code at `pos` is 4-byte (00 00 00 01) vs 3-byte (00 00 01). */\n private isStartCode4(pos: number): boolean {\n return pos + 3 < this.buffer.length &&\n this.buffer[pos] === 0 &&\n this.buffer[pos + 1] === 0 &&\n this.buffer[pos + 2] === 0 &&\n this.buffer[pos + 3] === 1\n }\n\n /** Strip leading start code from a buffer (if present). */\n private stripLeadingStartCode(buf: Buffer): Buffer {\n if (buf.length >= 4 && buf[0] === 0 && buf[1] === 0 && buf[2] === 0 && buf[3] === 1) {\n return buf.subarray(4)\n }\n if (buf.length >= 3 && buf[0] === 0 && buf[1] === 0 && buf[2] === 1) {\n return buf.subarray(3)\n }\n return buf\n }\n\n /** Check if a NAL unit (without start code) is a keyframe. */\n private isKeyframeNal(nal: Buffer): boolean {\n if (nal.length === 0) return false\n if (this.codec === 'h264') {\n const type = nal[0]! & 0x1f\n return type === 5 // IDR\n }\n const type = (nal[0]! >> 1) & 0x3f\n return type >= 16 && type <= 21 // IRAP\n }\n}\n","export interface SdpOptions {\n readonly codec: 'h264' | 'h265'\n readonly parameterSets: ReadonlyArray<Buffer>\n readonly streamName: string\n}\n\nexport function buildSdp(options: SdpOptions): string {\n const { codec, parameterSets, streamName } = options\n const lines: string[] = [\n 'v=0',\n `o=- ${Date.now()} 1 IN IP4 0.0.0.0`,\n `s=${streamName}`,\n 't=0 0',\n 'a=tool:camstack-rtsp-restream',\n 'm=video 0 RTP/AVP 96',\n 'c=IN IP4 0.0.0.0',\n 'b=AS:10000',\n ]\n\n if (codec === 'h264') {\n lines.push('a=rtpmap:96 H264/90000')\n const sps = parameterSets[0]\n const spropSets = parameterSets.map(ps => ps.toString('base64')).join(',')\n const profileLevelId = sps && sps.length >= 4\n ? Buffer.from([sps[1]!, sps[2]!, sps[3]!]).toString('hex')\n : '42001e'\n lines.push(`a=fmtp:96 packetization-mode=1;profile-level-id=${profileLevelId};sprop-parameter-sets=${spropSets}`)\n } else {\n lines.push('a=rtpmap:96 H265/90000')\n const fmtpParts = parameterSets.map((ps, i) => {\n const prefix = i === 0 ? 'sprop-vps' : i === 1 ? 'sprop-sps' : 'sprop-pps'\n return `${prefix}=${ps.toString('base64')}`\n })\n lines.push(`a=fmtp:96 ${fmtpParts.join(';')}`)\n }\n\n lines.push('a=control:trackID=0')\n lines.push('')\n return lines.join('\\r\\n')\n}\n","import type { EncodedPacket, IScopedLogger } from '@camstack/types'\nimport { RtpPacketizer } from './rtp-packetizer.js'\nimport { AnnexBDeframer } from './annexb-deframer.js'\nimport { buildSdp } from './sdp-builder.js'\nimport { RtspSession } from './rtsp-session.js'\nimport type { CodecParams, RtpPacket } from './rtsp-types.js'\n\n/**\n * Per-broker RTSP restreamer.\n *\n * Operates in two modes:\n *\n * **Annex-B mode** (legacy, ffmpeg-based):\n * Receives raw Annex-B chunks via pushPacket(), deframes into NALs,\n * RTP-packetizes, and broadcasts to RTSP sessions.\n *\n * **RTP passthrough mode** (native RTSP client):\n * Receives the camera's SDP via setCameraSdp() and raw RTP packets\n * via pushRtpPassthrough(). Forwards RTP directly to clients — zero\n * re-packetization, preserving the camera's original framing.\n */\nexport class RtspRestreamer {\n private readonly sessions = new Map<string, RtspSession>()\n /** Sessions that need a keyframe before receiving regular frames. */\n private readonly pendingKeyframe = new Set<string>()\n private packetizer: RtpPacketizer | null = null\n private codecParams: CodecParams | null = null\n private sdp: string | null = null\n private mutedSdp: string | null = null\n private deframer: AnnexBDeframer | null = null\n private detectedCodec: 'h264' | 'h265' | null = null\n\n /** Cached last keyframe NALs (SPS/PPS + IDR) for late-joining clients. */\n private lastKeyframeNals: Buffer[] = []\n private lastKeyframeTimestamp = 0\n /** True if we're currently accumulating a keyframe's NALs. */\n private collectingKeyframe = false\n\n /** Current RTP timestamp for the batch of NALs being processed. */\n private currentTimestamp = 0\n\n /** RTP passthrough: cached last keyframe RTP packets for late-joining clients. */\n private lastKeyframeRtpPackets: RtpPacket[] = []\n /** True when in RTP passthrough mode (setCameraSdp was called). */\n private _rtpPassthroughMode = false\n /** Accumulating keyframe RTP packets in passthrough mode. */\n private collectingKeyframeRtp = false\n\n private logger: IScopedLogger | null = null\n private _onSessionCountChanged: (() => void) | null = null\n\n constructor(private readonly streamName: string) {}\n\n /** True when using RTP passthrough (native client) instead of Annex-B re-packetization. */\n get rtpPassthroughMode(): boolean { return this._rtpPassthroughMode }\n\n /** Register a callback invoked when RTSP client count changes. */\n onSessionCountChanged(cb: () => void): void { this._onSessionCountChanged = cb }\n\n setLogger(logger: IScopedLogger): void {\n this.logger = logger\n }\n\n /**\n * Reset all stream-session state. Called on broker suspend so stale\n * keyframe caches / codec params from the previous session don't leak\n * into the next one after resume.\n */\n resetStreamState(): void {\n this.lastKeyframeNals = []\n this.lastKeyframeTimestamp = 0\n this.collectingKeyframe = false\n this.currentTimestamp = 0\n this.packetizer = null\n this.codecParams = null\n this.sdp = null\n this.mutedSdp = null\n this.deframer = null\n this.detectedCodec = null\n this.lastKeyframeRtpPackets = []\n this._rtpPassthroughMode = false\n this.collectingKeyframeRtp = false\n }\n\n /** True when codec params are detected and SDP is available — safe to accept PLAY. */\n isReady(): boolean { return this.codecParams !== null && this.packetizer !== null }\n\n getCodecParams(): CodecParams | null { return this.codecParams }\n getSdp(): string | null { return this.sdp }\n getMutedSdp(): string | null { return this.mutedSdp ?? this.sdp }\n getSessionCount(): number { return this.sessions.size }\n\n /** Per-session diagnostic snapshots consumed by `StreamBroker.listClients`. */\n listSessionInfos(): readonly ReturnType<RtspSession['getInfo']>[] {\n return [...this.sessions.values()].map((s) => s.getInfo())\n }\n\n addSession(session: RtspSession): void {\n this.sessions.set(session.getSessionId(), session)\n this.pendingKeyframe.add(session.getSessionId())\n this.logger?.info('RTSP client connected', {\n meta: { streamName: this.streamName, total: this.sessions.size },\n })\n this._onSessionCountChanged?.()\n\n if (this._rtpPassthroughMode) {\n // RTP passthrough: serve cached keyframe RTP packets to late-joining clients\n if (this.lastKeyframeRtpPackets.length > 0) {\n setTimeout(() => this.servePendingSessionsRtp(), 0)\n }\n } else {\n // Annex-B mode: serve cached keyframe NALs via packetizer\n if (this.lastKeyframeNals.length > 0 && this.packetizer) {\n setTimeout(() => this.servePendingSessions(), 0)\n }\n }\n }\n\n // ── RTP Passthrough Mode ──────────��───────────────────────────────\n\n /**\n * Set the camera's SDP directly (from DESCRIBE response).\n * Switches the restreamer to RTP passthrough mode — subsequent calls\n * should use pushRtpPassthrough() instead of pushPacket().\n */\n setCameraSdp(sdpText: string, codec: 'h264' | 'h265', codecParams: CodecParams | null): void {\n this._rtpPassthroughMode = true\n this.detectedCodec = codec\n this.codecParams = codecParams\n this.sdp = sdpText\n // Build a video-only SDP for muted sessions (strip all m=audio sections)\n this.mutedSdp = stripAudioFromSdp(sdpText)\n this.logger?.info('RTP passthrough mode — SDP set', {\n meta: { streamName: this.streamName, codec },\n })\n }\n\n /**\n * Push a raw RTP packet from the native RTSP client.\n * Forwarded directly to all connected RTSP sessions — zero re-packetization.\n *\n * @param rtpData Complete RTP packet (header + payload)\n * @param isKeyframe True if this packet is part of a keyframe access unit\n * @param isParameterSet True if this packet contains SPS/PPS/VPS\n */\n pushRtpPassthrough(rtpData: Buffer, isKeyframe: boolean, isParameterSet: boolean): void {\n if (this.sessions.size === 0 && !isKeyframe && !isParameterSet) return\n\n const rtpPacket: RtpPacket = { data: rtpData, marker: (rtpData[1]! & 0x80) !== 0 }\n\n // Cache keyframe RTP packets for late-joining clients\n if (isKeyframe || isParameterSet) {\n if (!this.collectingKeyframeRtp) {\n this.collectingKeyframeRtp = true\n this.lastKeyframeRtpPackets = []\n }\n }\n\n if (this.collectingKeyframeRtp) {\n this.lastKeyframeRtpPackets.push({ data: Buffer.from(rtpData), marker: rtpPacket.marker })\n if (!isKeyframe && !isParameterSet) {\n this.collectingKeyframeRtp = false\n }\n }\n\n if (this.sessions.size === 0) return\n\n // Serve pending sessions (late joiners waiting for keyframe)\n this.servePendingSessionsRtp()\n\n // Broadcast to all active sessions\n for (const session of this.sessions.values()) {\n if (this.pendingKeyframe.has(session.getSessionId())) continue\n if (session.isPlaying()) {\n session.sendRtp(rtpPacket)\n }\n }\n }\n\n /** Send cached keyframe RTP packets to sessions that transitioned to PLAY. */\n private servePendingSessionsRtp(): void {\n if (this.pendingKeyframe.size === 0) return\n if (this.lastKeyframeRtpPackets.length === 0) return\n\n const ready: string[] = []\n for (const sessionId of this.pendingKeyframe) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) ready.push(sessionId)\n }\n\n if (ready.length === 0) return\n\n for (const rtpPacket of this.lastKeyframeRtpPackets) {\n for (const sessionId of ready) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) session.sendRtp(rtpPacket)\n }\n }\n\n for (const id of ready) this.pendingKeyframe.delete(id)\n }\n\n /**\n * Push a raw audio RTP packet from the native RTSP client.\n * Forwarded to all non-muted sessions. Muted sessions receive no audio.\n */\n pushAudioRtpPassthrough(rtpData: Buffer): void {\n if (this.sessions.size === 0) return\n\n const rtpPacket: RtpPacket = { data: rtpData, marker: (rtpData[1]! & 0x80) !== 0 }\n\n for (const session of this.sessions.values()) {\n if (session.isMuted()) continue\n if (this.pendingKeyframe.has(session.getSessionId())) continue\n if (session.isPlaying()) {\n session.sendAudioRtp(rtpPacket)\n }\n }\n }\n\n removeSession(sessionId: string): void {\n this.sessions.delete(sessionId)\n this.pendingKeyframe.delete(sessionId)\n this.logger?.info('RTSP client disconnected', {\n meta: { streamName: this.streamName, total: this.sessions.size },\n })\n this._onSessionCountChanged?.()\n }\n\n /**\n * Force-close + remove a session by id. Used by the admin `killClient`\n * cap to let operators kick a stuck holder. Returns `true` if the\n * session existed and was dropped, `false` if the id doesn't match.\n */\n killSession(sessionId: string): boolean {\n const session = this.sessions.get(sessionId)\n if (!session) return false\n try { session.close() } catch { /* best-effort close */ }\n this.removeSession(sessionId)\n return true\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (packet.type !== 'video') return\n\n this.currentTimestamp = Math.floor(packet.pts * 90) // ms → 90kHz\n\n // Initialize deframer on first packet (need codec to detect keyframes)\n if (!this.deframer) {\n this.detectedCodec = (packet.codec === 'h265' || packet.codec === 'hevc') ? 'h265' : 'h264'\n this.deframer = new AnnexBDeframer(this.detectedCodec, (nal, isKeyframe) => {\n this.handleNal(nal, isKeyframe)\n })\n }\n\n // NOTE: keyframe collection is triggered inside handleNal() when the\n // deframer emits a complete keyframe NAL — NOT from packet.keyframe,\n // which is unreliable because ffmpeg chunks don't align to NAL boundaries.\n\n // Push raw bytes — the deframer emits complete NALs via handleNal callback\n this.deframer.push(packet.data)\n }\n\n destroy(): void {\n this._onSessionCountChanged = null\n this.deframer?.flush()\n for (const session of this.sessions.values()) {\n session.close()\n }\n this.sessions.clear()\n this.packetizer = null\n this.deframer = null\n }\n\n /** Called by deframer for each complete NAL unit. */\n private handleNal(nal: Buffer, isKeyframe: boolean): void {\n // Initialize codec params from parameter set NALs (SPS/PPS/VPS)\n if (!this.codecParams) {\n this.tryInitCodec(nal)\n }\n\n // Start keyframe collection when the deframer emits a keyframe or\n // parameter set NAL. This is reliable because the deframer works on\n // complete NAL units — unlike packet.keyframe which scans raw chunks\n // that may split a keyframe across two ffmpeg data events.\n if (isKeyframe || this.isParameterSetNal(nal)) {\n if (!this.collectingKeyframe) {\n this.collectingKeyframe = true\n this.lastKeyframeNals = []\n this.lastKeyframeTimestamp = this.currentTimestamp\n }\n }\n\n // Cache keyframe NALs\n if (this.collectingKeyframe) {\n this.lastKeyframeNals.push(Buffer.from(nal))\n // Stop collecting after the first non-parameter-set, non-keyframe NAL\n // (parameter sets + IDR = the keyframe, then P/B frames start)\n if (!isKeyframe && !this.isParameterSetNal(nal)) {\n this.collectingKeyframe = false\n }\n }\n\n // Skip RTP if no clients connected or no packetizer yet\n if (this.sessions.size === 0) return\n if (!this.packetizer) return\n\n // Serve pending sessions: send cached keyframe when they transition to PLAY\n this.servePendingSessions()\n\n // RTP packetize and broadcast\n const rtpPackets = this.packetizer.packetize(nal, this.currentTimestamp, true)\n for (const rtp of rtpPackets) {\n for (const session of this.sessions.values()) {\n if (this.pendingKeyframe.has(session.getSessionId())) continue\n if (session.isPlaying()) {\n session.sendRtp(rtp)\n }\n }\n }\n }\n\n /** Send cached keyframe to sessions that have transitioned to PLAY. */\n private servePendingSessions(): void {\n if (this.pendingKeyframe.size === 0) return\n\n const ready: string[] = []\n for (const sessionId of this.pendingKeyframe) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) ready.push(sessionId)\n }\n\n if (ready.length === 0) return\n if (this.lastKeyframeNals.length === 0 || !this.packetizer) return\n\n // Send cached keyframe NALs to each ready session\n for (const keyNal of this.lastKeyframeNals) {\n const rtpPackets = this.packetizer.packetize(keyNal, this.lastKeyframeTimestamp, true)\n for (const rtp of rtpPackets) {\n for (const sessionId of ready) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) session.sendRtp(rtp)\n }\n }\n }\n\n for (const id of ready) this.pendingKeyframe.delete(id)\n }\n\n /** Try to initialize codec params from a parameter set NAL (SPS/PPS for H264, VPS/SPS/PPS for H265). */\n private tryInitCodec(nal: Buffer): void {\n if (!this.isParameterSetNal(nal)) return\n if (!this.detectedCodec) return\n\n // Accumulate parameter sets until we have enough\n if (!this.codecParams) {\n // Start a new collection\n const existing = this.lastKeyframeNals.filter(n => this.isParameterSetNal(n))\n const all = [...existing, nal]\n\n const codec = this.detectedCodec\n const needed = codec === 'h264' ? 2 : 3 // SPS+PPS or VPS+SPS+PPS\n if (all.length >= needed) {\n this.codecParams = { codec, parameterSets: all.slice(0, needed) }\n this.packetizer = new RtpPacketizer(codec, 96)\n this.sdp = buildSdp({ codec, parameterSets: all.slice(0, needed), streamName: this.streamName })\n this.mutedSdp = buildSdp({ codec, parameterSets: all.slice(0, needed), streamName: `${this.streamName} (muted)` })\n }\n }\n }\n\n /** Check if a NAL is a parameter set (SPS, PPS, or VPS). */\n private isParameterSetNal(nal: Buffer): boolean {\n if (nal.length === 0) return false\n if (this.detectedCodec === 'h264') {\n const type = nal[0]! & 0x1f\n return type === 7 || type === 8 // SPS, PPS\n }\n const type = (nal[0]! >> 1) & 0x3f\n return type === 32 || type === 33 || type === 34 // VPS, SPS, PPS\n }\n}\n\n/**\n * Strip audio media sections from an SDP string.\n * Returns a video-only SDP for muted RTSP sessions.\n *\n * SDP structure: session-level lines, then m= blocks.\n * We keep session-level lines and all m= blocks that aren't audio.\n */\nfunction stripAudioFromSdp(sdpText: string): string {\n const lines = sdpText.split(/\\r?\\n/)\n const result: string[] = []\n let inAudioSection = false\n\n for (const line of lines) {\n if (line.startsWith('m=')) {\n inAudioSection = line.startsWith('m=audio')\n }\n if (!inAudioSection) {\n result.push(line)\n }\n }\n\n return result.join('\\r\\n')\n}\n","import type { CodecParams } from './rtsp-types.js'\n\n/** Parsed representation of an SDP media section (m= block). */\nexport interface SdpMediaSection {\n /** Media type: \"video\", \"audio\", \"application\". */\n readonly type: string\n /** RTP port (0 for TCP interleaved). */\n readonly port: number\n /** Transport protocol (e.g. \"RTP/AVP\"). */\n readonly protocol: string\n /** Payload types listed on the m= line. */\n readonly payloadTypes: ReadonlyArray<number>\n /** Codec name from a=rtpmap (e.g. \"H264\", \"H265\", \"PCMU\"). */\n readonly codec: string | null\n /** Clock rate from a=rtpmap (e.g. 90000 for video). */\n readonly clockRate: number | null\n /** Audio channel count from a=rtpmap (null for video). */\n readonly channels: number | null\n /** Parsed a=fmtp parameters as key-value pairs. */\n readonly fmtp: Readonly<Record<string, string>>\n /** Track control URL from a=control (may be relative or absolute). */\n readonly control: string | null\n /** Raw SDP lines for this section. */\n readonly lines: ReadonlyArray<string>\n}\n\n/** Result of parsing a full SDP response. */\nexport interface ParsedSdp {\n /** Session-level lines (before the first m= line). */\n readonly sessionLines: ReadonlyArray<string>\n /** Session-level a=control (base URL for relative track controls). */\n readonly sessionControl: string | null\n /** All parsed media sections. */\n readonly mediaSections: ReadonlyArray<SdpMediaSection>\n}\n\n/** Parsed video track info ready for native RTSP client use. */\nexport interface SdpVideoTrack {\n /** Index of this media section in the SDP. */\n readonly sectionIndex: number\n /** Codec: \"h264\" or \"h265\". */\n readonly codec: 'h264' | 'h265'\n /** RTP payload type (usually 96+). */\n readonly payloadType: number\n /** Clock rate (90000 for video). */\n readonly clockRate: number\n /** Track control URL from a=control. */\n readonly control: string | null\n /** H.264 profile-level-id (hex string, e.g. \"42001e\"). Null for H.265. */\n readonly profileLevelId: string | null\n /** Codec parameters extracted from SDP fmtp (SPS/PPS for H264, VPS/SPS/PPS for H265). */\n readonly codecParams: CodecParams | null\n}\n\n/** Parsed audio track info. */\nexport interface SdpAudioTrack {\n /** Index of this media section in the SDP. */\n readonly sectionIndex: number\n /** Codec name (e.g. \"PCMU\", \"PCMA\", \"MPEG4-GENERIC\", \"opus\"). */\n readonly codec: string\n /** RTP payload type. */\n readonly payloadType: number\n /** Clock rate (e.g. 8000, 48000). */\n readonly clockRate: number\n /** Channel count. */\n readonly channels: number\n /** Track control URL from a=control. */\n readonly control: string | null\n /** Raw fmtp parameters. */\n readonly fmtp: Readonly<Record<string, string>>\n}\n\n/**\n * Parse a raw SDP text into structured sections.\n *\n * Handles:\n * - Session-level and media-level attributes\n * - Multiple media sections (video, audio, application)\n * - a=rtpmap, a=fmtp, a=control extraction\n * - Tolerant of \\r\\n and \\n line endings\n */\nexport function parseSdp(sdpText: string): ParsedSdp {\n const lines = sdpText.split(/\\r?\\n/).filter(l => l.length > 0)\n\n const sessionLines: string[] = []\n const sections: Array<{ mLine: string; lines: string[] }> = []\n let currentSection: { mLine: string; lines: string[] } | null = null\n\n for (const line of lines) {\n if (line.startsWith('m=')) {\n currentSection = { mLine: line, lines: [line] }\n sections.push(currentSection)\n } else if (currentSection) {\n currentSection.lines.push(line)\n } else {\n sessionLines.push(line)\n }\n }\n\n const sessionControl = extractAttribute(sessionLines, 'control')\n\n const mediaSections = sections.map(s => parseMediaSection(s.mLine, s.lines))\n\n return { sessionLines, sessionControl, mediaSections }\n}\n\n/**\n * Extract the first video track from a parsed SDP.\n * Returns null if no supported video codec found.\n */\nexport function extractVideoTrack(parsed: ParsedSdp): SdpVideoTrack | null {\n for (let i = 0; i < parsed.mediaSections.length; i++) {\n const section = parsed.mediaSections[i]!\n if (section.type !== 'video') continue\n\n const codecUpper = section.codec?.toUpperCase() ?? null\n if (codecUpper !== 'H264' && codecUpper !== 'H265') continue\n\n const codec: 'h264' | 'h265' = codecUpper === 'H264' ? 'h264' : 'h265'\n const payloadType = section.payloadTypes[0] ?? 96\n const clockRate = section.clockRate ?? 90000\n\n const codecParams = extractCodecParams(codec, section.fmtp)\n const profileLevelId = codec === 'h264'\n ? (section.fmtp['profile-level-id'] ?? null)\n : null\n\n return {\n sectionIndex: i,\n codec,\n payloadType,\n clockRate,\n control: section.control,\n profileLevelId,\n codecParams,\n }\n }\n return null\n}\n\n/**\n * Extract the first audio track from a parsed SDP.\n * Returns null if no audio section found.\n */\nexport function extractAudioTrack(parsed: ParsedSdp): SdpAudioTrack | null {\n for (let i = 0; i < parsed.mediaSections.length; i++) {\n const section = parsed.mediaSections[i]!\n if (section.type !== 'audio') continue\n\n return {\n sectionIndex: i,\n codec: section.codec ?? 'unknown',\n payloadType: section.payloadTypes[0] ?? 0,\n clockRate: section.clockRate ?? 8000,\n channels: section.channels ?? 1,\n control: section.control,\n fmtp: section.fmtp,\n }\n }\n return null\n}\n\n/**\n * Resolve a track control URL against the session-level control / RTSP base URL.\n *\n * Cameras vary wildly:\n * - Absolute: \"rtsp://192.168.1.100/trackID=1\"\n * - Relative: \"trackID=1\" or \"track1\"\n * - Session control: \"rtsp://192.168.1.100/stream1\" with track \"trackID=1\"\n */\nexport function resolveTrackUrl(\n baseUrl: string,\n sessionControl: string | null,\n trackControl: string | null,\n): string {\n if (!trackControl) return baseUrl\n\n // Absolute URL — use as-is\n if (trackControl.startsWith('rtsp://') || trackControl.startsWith('rtsps://')) {\n return trackControl\n }\n\n // Use session control as base if it's an absolute URL\n const base = (sessionControl && (sessionControl.startsWith('rtsp://') || sessionControl.startsWith('rtsps://')))\n ? sessionControl\n : baseUrl\n\n // Append track control to base\n const separator = base.endsWith('/') ? '' : '/'\n return `${base}${separator}${trackControl}`\n}\n\n// ── Internal helpers ────────────────────────────────────────────────\n\nfunction parseMediaSection(mLine: string, lines: ReadonlyArray<string>): SdpMediaSection {\n // m=<type> <port> <protocol> <payloadType1> [payloadType2 ...]\n const mParts = mLine.substring(2).split(/\\s+/)\n const type = mParts[0] ?? 'unknown'\n const port = parseInt(mParts[1] ?? '0', 10)\n const protocol = mParts[2] ?? 'RTP/AVP'\n const payloadTypes = mParts.slice(3).map(p => parseInt(p, 10)).filter(n => !isNaN(n))\n\n const primaryPt = payloadTypes[0]\n\n // Parse a=rtpmap:<pt> <codec>/<clockRate>[/<channels>]\n let codec: string | null = null\n let clockRate: number | null = null\n let channels: number | null = null\n\n for (const line of lines) {\n const rtpmapMatch = line.match(/^a=rtpmap:(\\d+)\\s+([^/]+)\\/(\\d+)(?:\\/(\\d+))?/)\n if (rtpmapMatch) {\n const pt = parseInt(rtpmapMatch[1]!, 10)\n if (pt === primaryPt) {\n codec = rtpmapMatch[2]!\n clockRate = parseInt(rtpmapMatch[3]!, 10)\n channels = rtpmapMatch[4] ? parseInt(rtpmapMatch[4], 10) : null\n }\n }\n }\n\n // Fallback for well-known static payload types (no rtpmap line)\n if (!codec && primaryPt !== undefined) {\n const staticCodec = STATIC_PAYLOAD_TYPES.get(primaryPt)\n if (staticCodec) {\n codec = staticCodec.codec\n clockRate = staticCodec.clockRate\n channels = staticCodec.channels\n }\n }\n\n // Parse a=fmtp:<pt> <key>=<value>;<key>=<value>;...\n const fmtp = parseFmtp(lines, primaryPt)\n\n // Parse a=control:<url>\n const control = extractAttribute(lines, 'control')\n\n return { type, port, protocol, payloadTypes, codec, clockRate, channels, fmtp, control, lines }\n}\n\nfunction parseFmtp(\n lines: ReadonlyArray<string>,\n payloadType: number | undefined,\n): Record<string, string> {\n const result: Record<string, string> = {}\n\n for (const line of lines) {\n const fmtpMatch = line.match(/^a=fmtp:(\\d+)\\s+(.+)/)\n if (!fmtpMatch) continue\n const pt = parseInt(fmtpMatch[1]!, 10)\n if (payloadType !== undefined && pt !== payloadType) continue\n\n const paramsStr = fmtpMatch[2]!\n // Split on ';' then on first '='\n for (const part of paramsStr.split(/;\\s*/)) {\n const eqIdx = part.indexOf('=')\n if (eqIdx > 0) {\n const key = part.substring(0, eqIdx).trim().toLowerCase()\n const value = part.substring(eqIdx + 1).trim()\n result[key] = value\n }\n }\n }\n\n return result\n}\n\nfunction extractAttribute(lines: ReadonlyArray<string>, name: string): string | null {\n for (const line of lines) {\n if (line.startsWith(`a=${name}:`)) {\n return line.substring(name.length + 3).trim()\n }\n }\n return null\n}\n\n/**\n * Extract CodecParams (parameter set buffers) from SDP fmtp.\n *\n * H.264: sprop-parameter-sets=<base64-SPS>,<base64-PPS>\n * H.265: sprop-vps=<base64>; sprop-sps=<base64>; sprop-pps=<base64>\n *\n * Returns null if parameter sets are missing from SDP (some cameras\n * only send them in-band via RTP).\n */\nfunction extractCodecParams(\n codec: 'h264' | 'h265',\n fmtp: Readonly<Record<string, string>>,\n): CodecParams | null {\n if (codec === 'h264') {\n const spropSets = fmtp['sprop-parameter-sets']\n if (!spropSets) return null\n\n const parts = spropSets.split(',').filter(s => s.length > 0)\n if (parts.length < 2) return null\n\n const parameterSets = parts.map(p => Buffer.from(p, 'base64'))\n return { codec, parameterSets }\n }\n\n // H.265: need all three\n const vpsB64 = fmtp['sprop-vps']\n const spsB64 = fmtp['sprop-sps']\n const ppsB64 = fmtp['sprop-pps']\n if (!vpsB64 || !spsB64 || !ppsB64) return null\n\n const parameterSets = [\n Buffer.from(vpsB64, 'base64'),\n Buffer.from(spsB64, 'base64'),\n Buffer.from(ppsB64, 'base64'),\n ]\n return { codec, parameterSets }\n}\n\n/** Well-known static RTP payload types (RFC 3551). */\nconst STATIC_PAYLOAD_TYPES = new Map<number, { codec: string; clockRate: number; channels: number }>([\n [0, { codec: 'PCMU', clockRate: 8000, channels: 1 }],\n [8, { codec: 'PCMA', clockRate: 8000, channels: 1 }],\n])\n","import * as net from 'node:net'\nimport { createHash, randomUUID } from 'node:crypto'\nimport type { IScopedLogger } from '@camstack/types'\nimport { INTERLEAVED_MAGIC } from './rtsp-types.js'\nimport { parseSdp, extractVideoTrack, extractAudioTrack, resolveTrackUrl } from './sdp-parser.js'\nimport type { ParsedSdp, SdpVideoTrack, SdpAudioTrack } from './sdp-parser.js'\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface RtspClientOptions {\n /** RTSP URL (rtsp://user:pass@host:port/path). */\n readonly url: string\n /** Override username (takes precedence over URL credentials). */\n readonly username?: string\n /** Override password (takes precedence over URL credentials). */\n readonly password?: string\n /** TCP connect timeout in ms (default 10000). */\n readonly connectTimeoutMs?: number\n /** Keepalive interval in ms (default 30000). GET_PARAMETER sent periodically. */\n readonly keepaliveIntervalMs?: number\n /** Logger instance. */\n readonly logger?: IScopedLogger\n}\n\nexport interface RtspClientCallbacks {\n /** Called for every RTP packet received on a video track. */\n readonly onVideoRtp?: (packet: Buffer, channel: number) => void\n /** Called for every RTP packet received on an audio track. */\n readonly onAudioRtp?: (packet: Buffer, channel: number) => void\n /** Called when video SDP info is available (after DESCRIBE). */\n readonly onVideoTrack?: (track: SdpVideoTrack, sdpText: string) => void\n /** Called when audio SDP info is available (after DESCRIBE). */\n readonly onAudioTrack?: (track: SdpAudioTrack) => void\n /** Called when connection is fully established (PLAY response received). */\n readonly onPlaying?: () => void\n /** Called on unrecoverable error or connection lost. */\n readonly onError?: (error: Error) => void\n /** Called when the client is cleanly torn down. */\n readonly onTeardown?: () => void\n}\n\ntype RtspState = 'idle' | 'connecting' | 'options' | 'describe' | 'setup-video' | 'setup-audio' | 'play' | 'streaming' | 'teardown' | 'closed'\n\ninterface DigestChallenge {\n readonly realm: string\n readonly nonce: string\n readonly qop?: string\n readonly opaque?: string\n}\n\n// ── Constants ───────────────────────────────────────────────────────\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 10_000\nconst DEFAULT_KEEPALIVE_INTERVAL_MS = 30_000\nconst RTSP_PORT = 554\n\n// ── Implementation ──────────────────────────────────────────────────\n\n/**\n * Native RTSP client that connects directly to IP cameras via TCP.\n *\n * Replaces the ffmpeg RTSP reader. Instead of spawning ffmpeg to read\n * RTSP → Annex-B pipe, this client speaks RTSP natively and delivers\n * raw RTP packets — preserving RTP framing that ffmpeg's Annex-B output\n * destroys.\n *\n * State machine: connect → OPTIONS → DESCRIBE → SETUP(video) → SETUP(audio) → PLAY → streaming\n *\n * Features:\n * - TCP interleaved transport (RTP over RTSP connection)\n * - Basic + Digest authentication (auto-detect from 401 response)\n * - GET_PARAMETER keepalive\n * - Clean teardown\n */\nexport class NativeRtspClient {\n private state: RtspState = 'idle'\n private socket: net.Socket | null = null\n private cseq = 0\n private sessionId: string | null = null\n\n private readonly url: string\n private readonly host: string\n private readonly port: number\n private readonly username: string\n private readonly password: string\n private readonly connectTimeoutMs: number\n private readonly keepaliveIntervalMs: number\n private readonly logger: IScopedLogger | undefined\n private readonly callbacks: RtspClientCallbacks\n\n /** Parsed SDP from DESCRIBE response. */\n private parsedSdp: ParsedSdp | null = null\n private rawSdpText: string | null = null\n private videoTrack: SdpVideoTrack | null = null\n private audioTrack: SdpAudioTrack | null = null\n\n /** Channel assignments for TCP interleaved transport. */\n private videoRtpChannel = 0\n private videoRtcpChannel = 1\n private audioRtpChannel = 2\n private audioRtcpChannel = 3\n\n /** Authentication state. */\n private authMethod: 'none' | 'basic' | 'digest' = 'none'\n private digestChallenge: DigestChallenge | null = null\n private digestNc = 0\n\n /** TCP read buffer for interleaved framing + RTSP response parsing. */\n private readBuffer: Buffer = Buffer.alloc(0)\n\n /** Pending RTSP response handler (one request at a time). */\n private pendingResponse: ((statusCode: number, headers: ReadonlyMap<string, string>, body: string) => void) | null = null\n\n /** Keepalive timer. */\n private keepaliveTimer: ReturnType<typeof setInterval> | undefined\n\n /** Connect timeout. */\n private connectTimer: ReturnType<typeof setTimeout> | undefined\n\n /** Whether a teardown was requested (prevents reconnect on close). */\n private tornDown = false\n\n constructor(options: RtspClientOptions, callbacks: RtspClientCallbacks) {\n this.callbacks = callbacks\n this.logger = options.logger\n this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS\n this.keepaliveIntervalMs = options.keepaliveIntervalMs ?? DEFAULT_KEEPALIVE_INTERVAL_MS\n\n // Parse URL\n const parsed = new URL(options.url)\n this.url = options.url\n this.host = parsed.hostname\n this.port = parsed.port ? parseInt(parsed.port, 10) : RTSP_PORT\n this.username = options.username ?? decodeURIComponent(parsed.username)\n this.password = options.password ?? decodeURIComponent(parsed.password)\n }\n\n /** Start the RTSP session: connect → negotiate → stream. */\n async connect(): Promise<void> {\n if (this.state !== 'idle' && this.state !== 'closed') {\n throw new Error(`Cannot connect in state ${this.state}`)\n }\n\n this.tornDown = false\n this.state = 'connecting'\n this.cseq = 0\n this.sessionId = null\n this.authMethod = 'none'\n this.digestChallenge = null\n this.digestNc = 0\n this.readBuffer = Buffer.alloc(0)\n\n return new Promise<void>((resolve, reject) => {\n const socket = net.createConnection({ host: this.host, port: this.port })\n this.socket = socket\n\n this.connectTimer = setTimeout(() => {\n this.connectTimer = undefined\n socket.destroy(new Error(`RTSP connect timeout after ${this.connectTimeoutMs}ms`))\n }, this.connectTimeoutMs)\n\n socket.on('connect', () => {\n if (this.connectTimer) {\n clearTimeout(this.connectTimer)\n this.connectTimer = undefined\n }\n this.logger?.info('Connected', { meta: { host: this.host, port: this.port } })\n\n this.negotiate()\n .then(() => resolve())\n .catch((err) => {\n this.destroy()\n reject(err)\n })\n })\n\n socket.on('data', (chunk: Buffer) => {\n this.onData(chunk)\n })\n\n socket.on('close', () => {\n if (this.state === 'streaming' && !this.tornDown) {\n this.callbacks.onError?.(new Error('RTSP connection closed unexpectedly'))\n }\n this.cleanup()\n })\n\n socket.on('error', (err) => {\n if (this.connectTimer) {\n clearTimeout(this.connectTimer)\n this.connectTimer = undefined\n }\n if (this.state !== 'closed' && this.state !== 'teardown') {\n this.callbacks.onError?.(err)\n }\n this.cleanup()\n reject(err)\n })\n })\n }\n\n /** Gracefully tear down the RTSP session. */\n async teardown(): Promise<void> {\n if (this.state === 'closed' || this.state === 'idle') return\n this.tornDown = true\n this.state = 'teardown'\n\n try {\n if (this.socket && !this.socket.destroyed) {\n await this.sendRequest('TEARDOWN', this.url)\n }\n } catch {\n // Best-effort teardown\n }\n\n this.destroy()\n this.callbacks.onTeardown?.()\n }\n\n /** Forcefully destroy the connection without TEARDOWN. */\n destroy(): void {\n this.tornDown = true\n this.cleanup()\n }\n\n /** Get the parsed SDP text (available after DESCRIBE). */\n getSdpText(): string | null { return this.rawSdpText }\n\n /** Get the parsed SDP (available after DESCRIBE). */\n getParsedSdp(): ParsedSdp | null { return this.parsedSdp }\n\n /** Get the video track info (available after DESCRIBE). */\n getVideoTrack(): SdpVideoTrack | null { return this.videoTrack }\n\n /** Current client state. */\n getState(): RtspState { return this.state }\n\n // ── RTSP Negotiation ──────────────────────────────────────────────\n\n private async negotiate(): Promise<void> {\n // Step 1: OPTIONS (optional but standard — probes server capabilities)\n this.state = 'options'\n await this.sendRequest('OPTIONS', this.url)\n\n // Step 2: DESCRIBE — get SDP\n this.state = 'describe'\n const describeResult = await this.sendRequest('DESCRIBE', this.url, {\n 'Accept': 'application/sdp',\n })\n\n if (describeResult.statusCode === 401) {\n // Parse auth challenge and retry\n this.parseAuthChallenge(describeResult.headers)\n const retryResult = await this.sendRequest('DESCRIBE', this.url, {\n 'Accept': 'application/sdp',\n })\n if (retryResult.statusCode !== 200) {\n throw new Error(`DESCRIBE failed after auth: ${retryResult.statusCode}`)\n }\n this.processSdp(retryResult.body)\n } else if (describeResult.statusCode === 200) {\n this.processSdp(describeResult.body)\n } else {\n throw new Error(`DESCRIBE failed: ${describeResult.statusCode}`)\n }\n\n if (!this.videoTrack) {\n throw new Error('No supported video track in SDP')\n }\n\n // Step 3: SETUP video track\n this.state = 'setup-video'\n const videoControlUrl = resolveTrackUrl(\n this.url,\n this.parsedSdp?.sessionControl ?? null,\n this.videoTrack.control,\n )\n\n const videoSetupResult = await this.sendRequest('SETUP', videoControlUrl, {\n 'Transport': `RTP/AVP/TCP;unicast;interleaved=${this.videoRtpChannel}-${this.videoRtcpChannel}`,\n })\n\n if (videoSetupResult.statusCode !== 200) {\n throw new Error(`SETUP video failed: ${videoSetupResult.statusCode}`)\n }\n\n // Extract session ID from first SETUP response\n const sessionHeader = videoSetupResult.headers.get('session')\n if (sessionHeader) {\n // Session header may contain timeout: \"12345678;timeout=60\"\n this.sessionId = sessionHeader.split(';')[0]!.trim()\n }\n\n // Step 4: SETUP audio track (optional)\n if (this.audioTrack) {\n this.state = 'setup-audio'\n const audioControlUrl = resolveTrackUrl(\n this.url,\n this.parsedSdp?.sessionControl ?? null,\n this.audioTrack.control,\n )\n\n const audioSetupResult = await this.sendRequest('SETUP', audioControlUrl, {\n 'Transport': `RTP/AVP/TCP;unicast;interleaved=${this.audioRtpChannel}-${this.audioRtcpChannel}`,\n })\n\n if (audioSetupResult.statusCode !== 200) {\n this.logger?.warn('SETUP audio failed — continuing without audio', {\n meta: { statusCode: audioSetupResult.statusCode },\n })\n this.audioTrack = null\n }\n }\n\n // Step 5: PLAY\n this.state = 'play'\n const playResult = await this.sendRequest('PLAY', this.url, {\n 'Range': 'npt=0.000-',\n })\n\n if (playResult.statusCode !== 200) {\n throw new Error(`PLAY failed: ${playResult.statusCode}`)\n }\n\n this.state = 'streaming'\n this.logger?.info('RTSP streaming', { meta: { host: this.host, port: this.port } })\n\n // Start keepalive\n this.keepaliveTimer = setInterval(() => {\n this.sendKeepalive().catch((err) => {\n this.logger?.warn('Keepalive failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }, this.keepaliveIntervalMs)\n\n this.callbacks.onPlaying?.()\n }\n\n private processSdp(sdpText: string): void {\n this.rawSdpText = sdpText\n this.parsedSdp = parseSdp(sdpText)\n this.videoTrack = extractVideoTrack(this.parsedSdp)\n this.audioTrack = extractAudioTrack(this.parsedSdp)\n\n if (this.videoTrack) {\n this.callbacks.onVideoTrack?.(this.videoTrack, sdpText)\n this.logger?.info('Video track', {\n meta: {\n codec: this.videoTrack.codec,\n payloadType: this.videoTrack.payloadType,\n clockRate: this.videoTrack.clockRate,\n },\n })\n }\n\n if (this.audioTrack) {\n this.callbacks.onAudioTrack?.(this.audioTrack)\n this.logger?.info('Audio track', {\n meta: {\n codec: this.audioTrack.codec,\n payloadType: this.audioTrack.payloadType,\n clockRate: this.audioTrack.clockRate,\n },\n })\n }\n }\n\n // ── TCP Data Handling ─────────────────────────────────────────────\n\n /**\n * Process incoming TCP data. RTSP TCP interleaved transport mixes:\n * - RTSP text responses (e.g. \"RTSP/1.0 200 OK\\r\\n...\")\n * - Interleaved binary frames ($channel + 2-byte length + payload)\n */\n private onData(chunk: Buffer): void {\n this.readBuffer = this.readBuffer.length > 0\n ? Buffer.concat([this.readBuffer, chunk])\n : chunk\n\n while (this.readBuffer.length > 0) {\n if (this.readBuffer[0] === INTERLEAVED_MAGIC) {\n // Binary interleaved frame: $ + channel(1) + length(2) + payload\n if (this.readBuffer.length < 4) return // Need more data\n\n const channel = this.readBuffer[1]!\n const length = this.readBuffer.readUInt16BE(2)\n\n if (this.readBuffer.length < 4 + length) return // Need more data\n\n const payload = Buffer.from(this.readBuffer.subarray(4, 4 + length))\n this.readBuffer = this.readBuffer.subarray(4 + length)\n\n this.handleInterleavedPacket(channel, payload)\n } else {\n // RTSP text response\n const headerEnd = this.readBuffer.indexOf('\\r\\n\\r\\n')\n if (headerEnd < 0) return // Need more data\n\n const headerText = this.readBuffer.subarray(0, headerEnd).toString('ascii')\n let bodyStart = headerEnd + 4\n\n // Check for Content-Length\n const clMatch = headerText.match(/Content-Length:\\s*(\\d+)/i)\n const contentLength = clMatch ? parseInt(clMatch[1]!, 10) : 0\n\n if (this.readBuffer.length < bodyStart + contentLength) return // Need more data\n\n const body = contentLength > 0\n ? this.readBuffer.subarray(bodyStart, bodyStart + contentLength).toString('utf-8')\n : ''\n\n this.readBuffer = this.readBuffer.subarray(bodyStart + contentLength)\n\n this.handleRtspResponse(headerText, body)\n }\n }\n }\n\n private handleInterleavedPacket(channel: number, payload: Buffer): void {\n if (channel === this.videoRtpChannel) {\n this.callbacks.onVideoRtp?.(payload, channel)\n } else if (channel === this.audioRtpChannel) {\n this.callbacks.onAudioRtp?.(payload, channel)\n }\n // RTCP channels (odd) are silently ignored for now\n }\n\n private handleRtspResponse(headerText: string, body: string): void {\n const lines = headerText.split('\\r\\n')\n const statusLine = lines[0] ?? ''\n const statusMatch = statusLine.match(/RTSP\\/1\\.\\d\\s+(\\d+)/)\n const statusCode = statusMatch ? parseInt(statusMatch[1]!, 10) : 0\n\n const headers = new Map<string, string>()\n for (let i = 1; i < lines.length; i++) {\n const colonIdx = lines[i]!.indexOf(':')\n if (colonIdx < 0) continue\n const key = lines[i]!.substring(0, colonIdx).trim().toLowerCase()\n const value = lines[i]!.substring(colonIdx + 1).trim()\n headers.set(key, value)\n }\n\n if (this.pendingResponse) {\n const handler = this.pendingResponse\n this.pendingResponse = null\n handler(statusCode, headers, body)\n }\n }\n\n // ── RTSP Request Building ─────────────────────────────────────────\n\n private sendRequest(\n method: string,\n uri: string,\n extraHeaders?: Record<string, string>,\n ): Promise<{ statusCode: number; headers: ReadonlyMap<string, string>; body: string }> {\n return new Promise((resolve, reject) => {\n if (!this.socket || this.socket.destroyed) {\n reject(new Error('Socket not connected'))\n return\n }\n\n this.cseq++\n const headers: Record<string, string> = {\n 'CSeq': String(this.cseq),\n 'User-Agent': 'camstack-rtsp-client',\n ...extraHeaders,\n }\n\n if (this.sessionId) {\n headers['Session'] = this.sessionId\n }\n\n // Add authentication header\n const authHeader = this.buildAuthHeader(method, uri)\n if (authHeader) {\n headers['Authorization'] = authHeader\n }\n\n let request = `${method} ${uri} RTSP/1.0\\r\\n`\n for (const [key, value] of Object.entries(headers)) {\n request += `${key}: ${value}\\r\\n`\n }\n request += '\\r\\n'\n\n this.pendingResponse = (statusCode, responseHeaders, body) => {\n resolve({ statusCode, headers: responseHeaders, body })\n }\n\n // Timeout for response\n const timeout = setTimeout(() => {\n if (this.pendingResponse) {\n this.pendingResponse = null\n reject(new Error(`RTSP ${method} response timeout`))\n }\n }, this.connectTimeoutMs)\n\n const originalResolve = this.pendingResponse\n this.pendingResponse = (statusCode, responseHeaders, body) => {\n clearTimeout(timeout)\n originalResolve(statusCode, responseHeaders, body)\n }\n\n this.socket.write(request)\n })\n }\n\n private async sendKeepalive(): Promise<void> {\n if (this.state !== 'streaming' || !this.socket) return\n await this.sendRequest('GET_PARAMETER', this.url)\n }\n\n // ── Authentication ────────────────────────────────────────────────\n\n private parseAuthChallenge(headers: ReadonlyMap<string, string>): void {\n const wwwAuth = headers.get('www-authenticate')\n if (!wwwAuth) return\n\n if (wwwAuth.toLowerCase().startsWith('digest')) {\n this.authMethod = 'digest'\n const realm = extractQuoted(wwwAuth, 'realm') ?? ''\n const nonce = extractQuoted(wwwAuth, 'nonce') ?? ''\n const qop = extractQuoted(wwwAuth, 'qop')\n const opaque = extractQuoted(wwwAuth, 'opaque')\n this.digestChallenge = { realm, nonce, qop, opaque }\n this.digestNc = 0\n } else if (wwwAuth.toLowerCase().startsWith('basic')) {\n this.authMethod = 'basic'\n }\n }\n\n private buildAuthHeader(method: string, uri: string): string | null {\n if (!this.username && !this.password) return null\n\n if (this.authMethod === 'basic') {\n const encoded = Buffer.from(`${this.username}:${this.password}`).toString('base64')\n return `Basic ${encoded}`\n }\n\n if (this.authMethod === 'digest' && this.digestChallenge) {\n return this.buildDigestAuth(method, uri)\n }\n\n // First request — try Basic (some cameras accept it without 401)\n if (this.username || this.password) {\n const encoded = Buffer.from(`${this.username}:${this.password}`).toString('base64')\n return `Basic ${encoded}`\n }\n\n return null\n }\n\n private buildDigestAuth(method: string, uri: string): string {\n const { realm, nonce, qop, opaque } = this.digestChallenge!\n const ha1 = md5(`${this.username}:${realm}:${this.password}`)\n\n let response: string\n let authParams: string\n\n if (qop === 'auth' || qop?.includes('auth')) {\n this.digestNc++\n const nc = this.digestNc.toString(16).padStart(8, '0')\n const cnonce = randomUUID().replace(/-/g, '').slice(0, 16)\n const ha2 = md5(`${method}:${uri}`)\n response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:auth:${ha2}`)\n authParams = `username=\"${this.username}\", realm=\"${realm}\", nonce=\"${nonce}\", uri=\"${uri}\", qop=auth, nc=${nc}, cnonce=\"${cnonce}\", response=\"${response}\"`\n } else {\n const ha2 = md5(`${method}:${uri}`)\n response = md5(`${ha1}:${nonce}:${ha2}`)\n authParams = `username=\"${this.username}\", realm=\"${realm}\", nonce=\"${nonce}\", uri=\"${uri}\", response=\"${response}\"`\n }\n\n if (opaque) {\n authParams += `, opaque=\"${opaque}\"`\n }\n\n return `Digest ${authParams}`\n }\n\n // ── Cleanup ───────────────────────────────────────────────────────\n\n private cleanup(): void {\n this.state = 'closed'\n\n if (this.keepaliveTimer) {\n clearInterval(this.keepaliveTimer)\n this.keepaliveTimer = undefined\n }\n\n if (this.connectTimer) {\n clearTimeout(this.connectTimer)\n this.connectTimer = undefined\n }\n\n if (this.socket) {\n try { this.socket.destroy() } catch { /* already closed */ }\n this.socket = null\n }\n\n this.pendingResponse = null\n }\n}\n\n// ── Utilities ───────────────────────────────────────────────────────\n\nfunction md5(input: string): string {\n return createHash('md5').update(input).digest('hex')\n}\n\nfunction extractQuoted(header: string, key: string): string | undefined {\n // Match key=\"value\" or key=value\n const regex = new RegExp(`${key}=\"([^\"]*)\"`, 'i')\n const match = header.match(regex)\n if (match) return match[1]\n\n // Try unquoted\n const unquotedRegex = new RegExp(`${key}=([^,\\\\s]+)`, 'i')\n const unquotedMatch = header.match(unquotedRegex)\n return unquotedMatch?.[1]\n}\n","/**\n * RFC 4571 framed-RTP-over-TCP reader.\n *\n * Format on the wire (per RFC 4571):\n *\n * ┌─────────────┬────────────────────────────┐\n * │ length (BE) │ RTP packet (length bytes) │\n * │ uint16 │ │\n * └─────────────┴────────────────────────────┘\n *\n * No RTSP signalling, no '$' magic byte, no interleaved channel id —\n * just length-prefixed framed RTP. Stream descriptions arrive\n * out-of-band as an SDP string (handed in by the publisher), and\n * the reader routes packets to video / audio depacketizers based on\n * the RTP payload type each track advertised in its `m=` / rtpmap line.\n *\n * Used to ingest the local TCP server spawned by the Reolink lib's\n * `createRfc4571TcpServer` — same wire format the scrypted Reolink\n * plugin consumes through its `x-scrypted/x-rfc4571` MediaObject.\n *\n * The callbacks here mirror `NativeRtspClient` so the broker's\n * `startNativeRtspReader` glue code can be reused unchanged: same\n * `onVideoTrack` / `onVideoRtp` / `onAudioRtp` / `onConnected` /\n * `onError` / `onDisconnected` handles.\n */\nimport * as net from 'node:net'\nimport { parseSdp, extractVideoTrack, extractAudioTrack } from './sdp-parser.js'\nimport type { ParsedSdp, SdpVideoTrack, SdpAudioTrack } from './sdp-parser.js'\nimport type { IScopedLogger } from '@camstack/types'\n\nexport interface Rfc4571ReaderOptions {\n /** Loopback URL the lib's RFC 4571 server emitted (`tcp://[user:pass@]host:port`). */\n readonly url: string\n /** SDP describing the video + (optional) audio track, returned by the lib's server. */\n readonly sdp: string\n readonly logger?: IScopedLogger\n}\n\n/**\n * Reader callbacks. Signature deliberately mirrors `RtspClientCallbacks`\n * (channel arg is always 0 here — RFC 4571 has no interleaved channel\n * concept, but keeping the shape lets the broker reuse the same handler\n * objects across both readers without an adapter layer).\n */\nexport interface Rfc4571ReaderCallbacks {\n readonly onVideoTrack?: (track: SdpVideoTrack, sdpText: string) => void\n readonly onAudioTrack?: (track: SdpAudioTrack) => void\n readonly onVideoRtp?: (rtpData: Buffer, channel: number) => void\n readonly onAudioRtp?: (rtpData: Buffer, channel: number) => void\n readonly onPlaying?: () => void\n readonly onError?: (error: Error) => void\n readonly onTeardown?: () => void\n}\n\nexport class Rfc4571Reader {\n private readonly options: Rfc4571ReaderOptions\n private readonly callbacks: Rfc4571ReaderCallbacks\n private readonly logger: IScopedLogger | undefined\n private readonly host: string\n private readonly port: number\n private readonly authLine: Buffer | null\n private socket: net.Socket | null = null\n /** Concatenated TCP read buffer awaiting full frames. */\n private readBuffer: Buffer = Buffer.alloc(0)\n private parsedSdp: ParsedSdp | null = null\n private videoTrack: SdpVideoTrack | null = null\n private audioTrack: SdpAudioTrack | null = null\n private destroyed = false\n\n constructor(options: Rfc4571ReaderOptions, callbacks: Rfc4571ReaderCallbacks) {\n this.options = options\n this.callbacks = callbacks\n this.logger = options.logger\n const parsed = new URL(options.url)\n this.host = parsed.hostname\n this.port = parseInt(parsed.port || '0', 10)\n if (this.port === 0) {\n throw new Error(`Rfc4571Reader: URL must include a port — got '${options.url}'`)\n }\n // The lib's RFC 4571 server (when spawned with `requireAuth: true`)\n // expects `username:password\\n` as the very first bytes on the socket\n // before any framed RTP. After 5s without a match it drops the\n // connection. Username/password are percent-encoded in the URL —\n // decode them back to their raw form before composing the auth line.\n if (parsed.username || parsed.password) {\n const u = decodeURIComponent(parsed.username)\n const p = decodeURIComponent(parsed.password)\n this.authLine = Buffer.from(`${u}:${p}\\n`, 'utf8')\n } else {\n this.authLine = null\n }\n }\n\n async connect(): Promise<void> {\n // Parse the SDP up front. RFC 4571 has no DESCRIBE phase; the publisher\n // handed us the stream description out-of-band so the broker knows\n // codec + payload-type → track routing before the first byte arrives.\n this.parsedSdp = parseSdp(this.options.sdp)\n this.videoTrack = extractVideoTrack(this.parsedSdp)\n this.audioTrack = extractAudioTrack(this.parsedSdp)\n\n if (this.videoTrack) {\n this.callbacks.onVideoTrack?.(this.videoTrack, this.options.sdp)\n }\n if (this.audioTrack) {\n this.callbacks.onAudioTrack?.(this.audioTrack)\n }\n\n await this.openSocket()\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n if (this.socket) {\n try { this.socket.destroy() } catch { /* ignore */ }\n this.socket = null\n }\n }\n\n private openSocket(): Promise<void> {\n return new Promise((resolve, reject) => {\n let settled = false\n const socket = net.createConnection({ host: this.host, port: this.port }, () => {\n this.logger?.info('rfc4571: tcp connected', { meta: { host: this.host, port: this.port } })\n // The lib's server requires `username:password\\n` as the first\n // bytes when started with `requireAuth: true`. Send it before\n // anything else — the server starts framed-RTP delivery only\n // after a successful auth line.\n if (this.authLine) socket.write(this.authLine)\n // RFC 4571 has no PLAY phase — `onPlaying` fires the moment the TCP\n // socket is up, signalling \"data is about to flow\". Mirrors the\n // semantic the broker's RTSP path expects post-PLAY.\n this.callbacks.onPlaying?.()\n settled = true\n resolve()\n })\n this.socket = socket\n // Disable Nagle: video packets are small, latency matters more than\n // coalescing for a loopback connection.\n socket.setNoDelay(true)\n\n socket.on('data', (chunk) => this.onData(chunk))\n socket.on('error', (err) => {\n this.logger?.warn('rfc4571: tcp error', { meta: { error: err.message } })\n if (!this.destroyed) {\n this.callbacks.onError?.(err)\n }\n if (!settled) reject(err)\n })\n socket.on('close', () => {\n this.logger?.info('rfc4571: tcp closed')\n if (!this.destroyed) {\n this.callbacks.onTeardown?.()\n }\n })\n })\n }\n\n private onData(chunk: Buffer): void {\n this.readBuffer = this.readBuffer.length > 0\n ? Buffer.concat([this.readBuffer, chunk])\n : chunk\n\n while (this.readBuffer.length >= 2) {\n const length = this.readBuffer.readUInt16BE(0)\n if (this.readBuffer.length < 2 + length) return // need more bytes\n\n const rtpPacket = Buffer.from(this.readBuffer.subarray(2, 2 + length))\n this.readBuffer = this.readBuffer.subarray(2 + length)\n\n this.dispatchRtp(rtpPacket)\n }\n }\n\n private dispatchRtp(rtpPacket: Buffer): void {\n if (rtpPacket.length < 2) return\n // RTP payload type is the lower 7 bits of byte 1.\n const payloadType = rtpPacket[1]! & 0x7f\n if (this.videoTrack && payloadType === this.videoTrack.payloadType) {\n this.callbacks.onVideoRtp?.(rtpPacket, 0)\n return\n }\n if (this.audioTrack && payloadType === this.audioTrack.payloadType) {\n this.callbacks.onAudioRtp?.(rtpPacket, 0)\n return\n }\n // Unknown PT — drop silently. Cameras occasionally send RTCP-style\n // packets multiplexed on the same channel; the broker doesn't care.\n }\n}\n","/**\n * FLV Audio/Video tag payload parser (RTMP messages).\n *\n * Standard FLV (Adobe spec) supports H.264 (codecId=7) and AAC.\n *\n * Enhanced RTMP / Enhanced FLV (Veovera spec, used by YouTube Live and most\n * modern Reolink firmware for HEVC streams) extends the layout:\n * byte 0:\n * bit 7 : IsExHeader (1 = enhanced layout follows)\n * bits 4..6 : PacketType (0=SequenceStart, 1=CodedFrames, 2=SequenceEnd,\n * 3=CodedFramesX, 4=Metadata, 5=MPEG2TS-SequenceStart)\n * bits 0..3 : FrameType (1=key, 2=inter, ...)\n * bytes 1..4 : FOURCC ('hvc1' / 'hev1' = H.265, 'av01' = AV1, 'vp09' = VP9)\n * remainder : codec/packet-type-specific payload\n *\n * Ported from `koush/scrypted/plugins/prebuffer-mixin/src/flv.ts` (ISC).\n * H.265 / enhanced-FLV branches added here.\n */\n\n// ── Standard FLV enums ────────────────────────────────────────────────\n\nexport enum VideoCodecId {\n JPEG = 1,\n SORENSON_H263 = 2,\n SCREEN_VIDEO = 3,\n ON2_VP6 = 4,\n ON2_VP6_WITH_ALPHA = 5,\n SCREEN_VIDEO_V2 = 6,\n H264 = 7,\n /**\n * Non-standard codecId for HEVC, predating the Veovera Enhanced-FLV\n * spec. Used by Reolink RLC-810/820/etc., ZLMediaKit, ffmpeg builds\n * with HEVC FLV patches, and MediaMTX. Layout matches codecId=7\n * (H.264) — the only difference is that SEQUENCE_HEADER carries\n * HEVCDecoderConfigurationRecord and NALU payloads contain HEVC NAL\n * units.\n */\n H265_LEGACY = 12,\n}\n\nexport enum VideoFrameType {\n KEY = 1,\n INTER = 2,\n DISPOSABLE_INTER = 3,\n GENERATED_KEYFRAME = 4,\n VIDEO_INFO = 5,\n}\n\nexport enum AvcPacketType {\n SEQUENCE_HEADER = 0,\n NALU = 1,\n END_OF_SEQUENCE = 2,\n}\n\nexport enum AudioSoundFormat {\n PCM_BE = 0,\n ADPCM = 1,\n MP3 = 2,\n PCM_LE = 3,\n NELLYMOSER_16K = 4,\n NELLYMOSER_8K = 5,\n NELLYMOSER = 6,\n G711_A = 7,\n G711_U = 8,\n AAC = 10,\n SPEEX = 11,\n MP3_8K = 14,\n}\n\nexport enum AacPacketType {\n SEQUENCE_HEADER = 0,\n RAW = 1,\n}\n\n// ── Enhanced FLV (Veovera) ────────────────────────────────────────────\n\nexport enum EnhancedPacketType {\n SEQUENCE_START = 0,\n CODED_FRAMES = 1,\n SEQUENCE_END = 2,\n CODED_FRAMES_X = 3,\n METADATA = 4,\n MPEG2TS_SEQUENCE_START = 5,\n}\n\n/** Canonical codec discriminator for the parsed video tag. */\nexport type VideoCodec = 'h264' | 'h265'\n\n// ── Decoder configuration record types ────────────────────────────────\n\nexport interface AvcDecoderConfigurationRecord {\n readonly configurationVersion: number\n readonly avcProfileIndication: number\n readonly profileCompatibility: number\n readonly avcLevelIndication: number\n /** NALU length-size = lengthSizeMinusOne + 1 (1, 2, or 4) */\n readonly lengthSizeMinusOne: number\n readonly sps: ReadonlyArray<Buffer>\n readonly pps: ReadonlyArray<Buffer>\n}\n\nexport interface HevcDecoderConfigurationRecord {\n readonly configurationVersion: number\n readonly generalProfileSpace: number\n readonly generalTierFlag: number\n readonly generalProfileIdc: number\n readonly generalProfileCompatibilityFlags: number\n readonly generalLevelIdc: number\n /** NALU length-size = lengthSizeMinusOne + 1 (1, 2, or 4) */\n readonly lengthSizeMinusOne: number\n readonly vps: ReadonlyArray<Buffer>\n readonly sps: ReadonlyArray<Buffer>\n readonly pps: ReadonlyArray<Buffer>\n}\n\n// ── Audio decoder configuration ───────────────────────────────────────\n\nexport interface AudioSpecificConfig {\n readonly audioObjectType: number\n readonly samplingFrequencyIndex: number\n readonly channelConfiguration: number\n}\n\n// ── Parsed tag types ──────────────────────────────────────────────────\n\ninterface FlvVideoTagBase {\n readonly frameType: VideoFrameType\n readonly codec: VideoCodec\n /** Composition-time offset in milliseconds (0 for enhanced FLV CODED_FRAMES_X). */\n readonly compositionTime: number\n}\n\nexport interface FlvVideoSequenceHeaderH264 extends FlvVideoTagBase {\n readonly kind: 'video-sequence-header'\n readonly codec: 'h264'\n readonly avcConfig: AvcDecoderConfigurationRecord\n}\n\nexport interface FlvVideoSequenceHeaderH265 extends FlvVideoTagBase {\n readonly kind: 'video-sequence-header'\n readonly codec: 'h265'\n readonly hevcConfig: HevcDecoderConfigurationRecord\n}\n\nexport interface FlvVideoCodedFrames extends FlvVideoTagBase {\n readonly kind: 'video-coded-frames'\n readonly nalus: ReadonlyArray<Buffer>\n}\n\nexport interface FlvVideoSequenceEnd extends FlvVideoTagBase {\n readonly kind: 'video-sequence-end'\n}\n\nexport type FlvVideoTag =\n | FlvVideoSequenceHeaderH264\n | FlvVideoSequenceHeaderH265\n | FlvVideoCodedFrames\n | FlvVideoSequenceEnd\n\nexport interface FlvAudioSequenceHeader {\n readonly kind: 'audio-sequence-header'\n readonly soundFormat: AudioSoundFormat\n readonly audioSpecificConfig: AudioSpecificConfig\n /** Raw 2-byte AudioSpecificConfig — useful for SDP / pushAudioInfo. */\n readonly audioSpecificConfigBytes: Buffer\n}\n\nexport interface FlvAudioRaw {\n readonly kind: 'audio-raw'\n readonly soundFormat: AudioSoundFormat\n readonly data: Buffer\n}\n\nexport type FlvAudioTag = FlvAudioSequenceHeader | FlvAudioRaw\n\n// ── Errors ────────────────────────────────────────────────────────────\n\nexport class FlvParseError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'FlvParseError'\n }\n}\n\nexport class UnsupportedCodecError extends FlvParseError {\n constructor(message: string) {\n super(message)\n this.name = 'UnsupportedCodecError'\n }\n}\n\n// ── Configuration record parsers ──────────────────────────────────────\n\nfunction parseAvcDecoderConfigurationRecord(buffer: Buffer): AvcDecoderConfigurationRecord {\n if (buffer.length < 7) {\n throw new FlvParseError('AVCDecoderConfigurationRecord too short')\n }\n\n const configurationVersion = buffer.readUInt8(0)\n const avcProfileIndication = buffer.readUInt8(1)\n const profileCompatibility = buffer.readUInt8(2)\n const avcLevelIndication = buffer.readUInt8(3)\n const lengthSizeMinusOne = buffer.readUInt8(4) & 0x03\n\n const numSPS = buffer.readUInt8(5) & 0x1f\n let pos = 6\n\n const sps: Buffer[] = []\n for (let i = 0; i < numSPS; i++) {\n if (pos + 2 > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord truncated reading SPS length')\n const len = buffer.readUInt16BE(pos)\n pos += 2\n if (pos + len > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord SPS exceeds buffer')\n sps.push(buffer.subarray(pos, pos + len))\n pos += len\n }\n\n const pps: Buffer[] = []\n if (pos < buffer.length) {\n const numPPS = buffer.readUInt8(pos)\n pos += 1\n for (let i = 0; i < numPPS; i++) {\n if (pos + 2 > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord truncated reading PPS length')\n const len = buffer.readUInt16BE(pos)\n pos += 2\n if (pos + len > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord PPS exceeds buffer')\n pps.push(buffer.subarray(pos, pos + len))\n pos += len\n }\n }\n\n return {\n configurationVersion,\n avcProfileIndication,\n profileCompatibility,\n avcLevelIndication,\n lengthSizeMinusOne,\n sps,\n pps,\n }\n}\n\n/**\n * Parse HEVCDecoderConfigurationRecord (HVCC) per ISO/IEC 14496-15 §8.\n * Layout (relevant fields only — reserved bits skipped):\n *\n * u8 configurationVersion (= 1)\n * u8 general_profile_space(2) | general_tier_flag(1) | general_profile_idc(5)\n * u32 general_profile_compatibility_flags\n * u48 general_constraint_indicator_flags\n * u8 general_level_idc\n * u16 reserved(4) | min_spatial_segmentation_idc(12)\n * u8 reserved(6) | parallelismType(2)\n * u8 reserved(6) | chromaFormat(2)\n * u8 reserved(5) | bitDepthLumaMinus8(3)\n * u8 reserved(5) | bitDepthChromaMinus8(3)\n * u16 avgFrameRate\n * u8 constantFrameRate(2) | numTemporalLayers(3) | temporalIdNested(1) | lengthSizeMinusOne(2)\n * u8 numOfArrays\n * for each array:\n * u8 array_completeness(1) | reserved(1) | NAL_unit_type(6)\n * u16 numNalus\n * for each nalu: u16 nalUnitLength, then nalUnit bytes\n *\n * The fixed-size header is exactly 22 bytes; arrays start at offset 22.\n * NAL unit types of interest: 32 = VPS, 33 = SPS, 34 = PPS.\n */\nfunction parseHevcDecoderConfigurationRecord(buffer: Buffer): HevcDecoderConfigurationRecord {\n if (buffer.length < 23) {\n throw new FlvParseError('HEVCDecoderConfigurationRecord too short')\n }\n\n const configurationVersion = buffer.readUInt8(0)\n const byte1 = buffer.readUInt8(1)\n const generalProfileSpace = (byte1 >> 6) & 0x03\n const generalTierFlag = (byte1 >> 5) & 0x01\n const generalProfileIdc = byte1 & 0x1f\n const generalProfileCompatibilityFlags = buffer.readUInt32BE(2)\n // bytes 6..11 = general_constraint_indicator_flags (48 bits) — preserved but not exposed\n const generalLevelIdc = buffer.readUInt8(12)\n // bytes 13..14 = min_spatial_segmentation_idc (low 12 bits)\n // byte 15 = parallelismType\n // byte 16 = chromaFormat\n // byte 17 = bitDepthLumaMinus8\n // byte 18 = bitDepthChromaMinus8\n // bytes 19..20 = avgFrameRate\n // byte 21 = constantFrameRate(2) | numTemporalLayers(3) | temporalIdNested(1) | lengthSizeMinusOne(2)\n const lengthSizeMinusOne = buffer.readUInt8(21) & 0x03\n\n const numOfArrays = buffer.readUInt8(22)\n let pos = 23\n\n const vps: Buffer[] = []\n const sps: Buffer[] = []\n const pps: Buffer[] = []\n\n for (let a = 0; a < numOfArrays; a++) {\n if (pos + 3 > buffer.length) throw new FlvParseError('HEVCDecoderConfigurationRecord truncated reading array header')\n const nalUnitType = buffer.readUInt8(pos) & 0x3f\n const numNalus = buffer.readUInt16BE(pos + 1)\n pos += 3\n\n for (let n = 0; n < numNalus; n++) {\n if (pos + 2 > buffer.length) throw new FlvParseError('HEVCDecoderConfigurationRecord truncated reading NALU length')\n const len = buffer.readUInt16BE(pos)\n pos += 2\n if (pos + len > buffer.length) throw new FlvParseError('HEVCDecoderConfigurationRecord NALU exceeds buffer')\n const nal = buffer.subarray(pos, pos + len)\n pos += len\n\n if (nalUnitType === 32) vps.push(nal)\n else if (nalUnitType === 33) sps.push(nal)\n else if (nalUnitType === 34) pps.push(nal)\n // SEI / other NAL arrays are dropped — not needed for decoder init.\n }\n }\n\n return {\n configurationVersion,\n generalProfileSpace,\n generalTierFlag,\n generalProfileIdc,\n generalProfileCompatibilityFlags,\n generalLevelIdc,\n lengthSizeMinusOne,\n vps,\n sps,\n pps,\n }\n}\n\nfunction parseAudioSpecificConfig(buffer: Buffer): AudioSpecificConfig {\n if (buffer.length < 2) throw new FlvParseError('AudioSpecificConfig too short')\n const b0 = buffer.readUInt8(0)\n const b1 = buffer.readUInt8(1)\n return {\n audioObjectType: (b0 >> 3) & 0x1f,\n samplingFrequencyIndex: ((b0 & 0x07) << 1) | ((b1 >> 7) & 0x01),\n channelConfiguration: (b1 >> 3) & 0x0f,\n }\n}\n\n// ── NALU extraction (length-prefixed → list of NAL bodies) ────────────\n\nfunction extractLengthPrefixedNalus(payload: Buffer, lengthSize: number): Buffer[] {\n if (lengthSize < 1 || lengthSize > 4) {\n throw new FlvParseError(`Invalid NALU length-size: ${lengthSize}`)\n }\n const out: Buffer[] = []\n let pos = 0\n while (pos + lengthSize <= payload.length) {\n const len = payload.readUIntBE(pos, lengthSize)\n pos += lengthSize\n if (len === 0) continue\n if (pos + len > payload.length) {\n throw new FlvParseError(`NALU exceeds payload at pos=${pos} len=${len}`)\n }\n out.push(payload.subarray(pos, pos + len))\n pos += len\n }\n return out\n}\n\n// ── AAC ADTS sniffing (audio path runtime helper) ─────────────────────\n\nconst ADTS_SYNC_WORD = 0xfff\n\n/**\n * Returns true when `buffer` starts with the 12-bit AAC ADTS sync word\n * (0xFFF). Used by the RTMP reader to disambiguate AAC raw frames coming\n * directly from cameras that bypass the AAC sequence header step.\n */\nexport function looksLikeAdts(buffer: Buffer): boolean {\n if (buffer.length < 2) return false\n return ((buffer.readUInt8(0) << 4) | (buffer.readUInt8(1) >> 4)) === ADTS_SYNC_WORD\n}\n\n// ── Public entry points ───────────────────────────────────────────────\n\n/**\n * Parse an RTMP video tag (FLV message-type 9) payload.\n *\n * Throws `UnsupportedCodecError` for codecs we cannot route to AnnexB\n * downstream (anything other than H.264 / H.265).\n */\nexport function parseFlvVideoTag(buffer: Buffer): FlvVideoTag {\n if (buffer.length < 1) throw new FlvParseError('Video tag empty')\n const byte0 = buffer.readUInt8(0)\n const isExHeader = (byte0 & 0x80) !== 0\n\n if (isExHeader) {\n return parseEnhancedVideoTag(buffer)\n }\n\n // Standard FLV layout (codecId=7 H.264, or codecId=12 H.265-legacy)\n const frameType = (byte0 >> 4) as VideoFrameType\n const codecId = byte0 & 0x0f\n\n const codec: VideoCodec | null =\n codecId === VideoCodecId.H264 ? 'h264'\n : codecId === VideoCodecId.H265_LEGACY ? 'h265'\n : null\n if (codec === null) {\n throw new UnsupportedCodecError(`FLV video codecId=${codecId} not supported (only H.264 / H.265-legacy / H.265-enhanced)`)\n }\n\n return parseStandardLayoutVideoTag(buffer, codec, frameType, 4)\n}\n\n/**\n * Shared parser for the codecId=7 (H.264) / codecId=12 (H.265 legacy)\n * layout: byte0 frameType+codecId, byte1 packetType, bytes2..4 composition\n * time offset (signed), then payload (decoder config or length-prefixed\n * NALUs).\n */\nfunction parseStandardLayoutVideoTag(\n buffer: Buffer,\n codec: VideoCodec,\n frameType: VideoFrameType,\n lengthSize: number,\n): FlvVideoTag {\n if (buffer.length < 5) throw new FlvParseError(`${codec.toUpperCase()} video tag header truncated`)\n const packetType = buffer.readUInt8(1) as AvcPacketType\n const compositionTime = buffer.readIntBE(2, 3)\n\n if (packetType === AvcPacketType.SEQUENCE_HEADER) {\n if (codec === 'h264') {\n const avcConfig = parseAvcDecoderConfigurationRecord(buffer.subarray(5))\n return { kind: 'video-sequence-header', codec: 'h264', frameType, compositionTime, avcConfig }\n }\n const hevcConfig = parseHevcDecoderConfigurationRecord(buffer.subarray(5))\n return { kind: 'video-sequence-header', codec: 'h265', frameType, compositionTime, hevcConfig }\n }\n\n if (packetType === AvcPacketType.NALU) {\n const nalus = extractLengthPrefixedNalus(buffer.subarray(5), lengthSize)\n return { kind: 'video-coded-frames', codec, frameType, compositionTime, nalus }\n }\n\n if (packetType === AvcPacketType.END_OF_SEQUENCE) {\n return { kind: 'video-sequence-end', codec, frameType, compositionTime }\n }\n\n throw new FlvParseError(`Unknown AVC packet type: ${packetType}`)\n}\n\n/**\n * Same as `parseFlvVideoTag` but uses the supplied NALU length-size\n * when extracting CodedFrames payloads. Callers that have parsed a\n * prior SequenceHeader should prefer this overload for correctness on\n * cameras that ship a non-default `lengthSizeMinusOne`.\n */\nexport function parseFlvVideoTagWithLengthSize(buffer: Buffer, lengthSize: number): FlvVideoTag {\n if (buffer.length < 1) throw new FlvParseError('Video tag empty')\n const byte0 = buffer.readUInt8(0)\n const isExHeader = (byte0 & 0x80) !== 0\n\n if (isExHeader) {\n return parseEnhancedVideoTag(buffer, lengthSize)\n }\n\n const frameType = (byte0 >> 4) as VideoFrameType\n const codecId = byte0 & 0x0f\n const codec: VideoCodec | null =\n codecId === VideoCodecId.H264 ? 'h264'\n : codecId === VideoCodecId.H265_LEGACY ? 'h265'\n : null\n if (codec === null) {\n throw new UnsupportedCodecError(`FLV video codecId=${codecId} not supported`)\n }\n return parseStandardLayoutVideoTag(buffer, codec, frameType, lengthSize)\n}\n\nfunction parseEnhancedVideoTag(buffer: Buffer, lengthSize: number = 4): FlvVideoTag {\n // byte 0 layout: bit7 IsExHeader | bits4..6 PacketType | bits0..3 FrameType\n if (buffer.length < 5) throw new FlvParseError('Enhanced FLV video tag truncated (need FOURCC)')\n const byte0 = buffer.readUInt8(0)\n const packetType = ((byte0 >> 4) & 0x07) as EnhancedPacketType\n const frameType = (byte0 & 0x0f) as VideoFrameType\n\n const fourcc = buffer.subarray(1, 5).toString('ascii')\n const codec = mapFourCcToCodec(fourcc)\n\n if (codec !== 'h265') {\n throw new UnsupportedCodecError(`Enhanced FLV FOURCC '${fourcc}' not supported (only hvc1/hev1)`)\n }\n\n if (packetType === EnhancedPacketType.SEQUENCE_START) {\n const hevcConfig = parseHevcDecoderConfigurationRecord(buffer.subarray(5))\n return {\n kind: 'video-sequence-header',\n codec: 'h265',\n frameType,\n compositionTime: 0,\n hevcConfig,\n }\n }\n\n if (packetType === EnhancedPacketType.CODED_FRAMES) {\n if (buffer.length < 8) throw new FlvParseError('Enhanced FLV CODED_FRAMES truncated')\n const compositionTime = buffer.readIntBE(5, 3)\n const nalus = extractLengthPrefixedNalus(buffer.subarray(8), lengthSize)\n return { kind: 'video-coded-frames', codec: 'h265', frameType, compositionTime, nalus }\n }\n\n if (packetType === EnhancedPacketType.CODED_FRAMES_X) {\n const nalus = extractLengthPrefixedNalus(buffer.subarray(5), lengthSize)\n return { kind: 'video-coded-frames', codec: 'h265', frameType, compositionTime: 0, nalus }\n }\n\n if (packetType === EnhancedPacketType.SEQUENCE_END) {\n return { kind: 'video-sequence-end', codec: 'h265', frameType, compositionTime: 0 }\n }\n\n throw new FlvParseError(`Unsupported enhanced FLV packet type: ${packetType}`)\n}\n\nfunction mapFourCcToCodec(fourcc: string): VideoCodec | null {\n if (fourcc === 'hvc1' || fourcc === 'hev1') return 'h265'\n if (fourcc === 'avc1') return 'h264'\n return null\n}\n\n/** Parse an RTMP audio tag (FLV message-type 8) payload. */\nexport function parseFlvAudioTag(buffer: Buffer): FlvAudioTag {\n if (buffer.length < 1) throw new FlvParseError('Audio tag empty')\n const byte0 = buffer.readUInt8(0)\n const soundFormat = ((byte0 >> 4) & 0x0f) as AudioSoundFormat\n\n if (soundFormat !== AudioSoundFormat.AAC) {\n return { kind: 'audio-raw', soundFormat, data: buffer.subarray(1) }\n }\n\n if (buffer.length < 2) throw new FlvParseError('AAC audio tag truncated')\n const aacPacketType = buffer.readUInt8(1) as AacPacketType\n\n if (aacPacketType === AacPacketType.SEQUENCE_HEADER) {\n const ascBytes = buffer.subarray(2)\n const audioSpecificConfig = parseAudioSpecificConfig(ascBytes)\n return {\n kind: 'audio-sequence-header',\n soundFormat,\n audioSpecificConfig,\n audioSpecificConfigBytes: Buffer.from(ascBytes),\n }\n }\n\n return {\n kind: 'audio-raw',\n soundFormat,\n data: buffer.subarray(2),\n }\n}\n\n// ── AnnexB assembly helper ────────────────────────────────────────────\n\n/** 4-byte AnnexB start code. */\nconst ANNEXB_START_CODE = Buffer.from([0x00, 0x00, 0x00, 0x01])\n\n/**\n * Assemble a sequence of NAL unit bodies into a single AnnexB buffer.\n * Each NAL is prefixed with `00 00 00 01`.\n */\nexport function nalusToAnnexB(nalus: ReadonlyArray<Buffer>): Buffer {\n if (nalus.length === 0) return Buffer.alloc(0)\n const parts: Buffer[] = []\n for (const nal of nalus) {\n parts.push(ANNEXB_START_CODE, nal)\n }\n return Buffer.concat(parts)\n}\n\n/**\n * Build the AnnexB parameter-set prefix for an H.264 IDR — concatenates\n * SPS + PPS NAL bodies with start codes.\n */\nexport function buildH264ParamSetsAnnexB(record: AvcDecoderConfigurationRecord): Buffer {\n return nalusToAnnexB([...record.sps, ...record.pps])\n}\n\n/**\n * Build the AnnexB parameter-set prefix for an H.265 IDR — concatenates\n * VPS + SPS + PPS NAL bodies with start codes.\n */\nexport function buildH265ParamSetsAnnexB(record: HevcDecoderConfigurationRecord): Buffer {\n return nalusToAnnexB([...record.vps, ...record.sps, ...record.pps])\n}\n\n// ── Frequency-index → sample-rate (AAC) ───────────────────────────────\n\nconst AAC_SAMPLE_RATES: ReadonlyArray<number> = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,\n 16000, 12000, 11025, 8000, 7350,\n]\n\n/**\n * Resolve the sample-rate for an AAC `samplingFrequencyIndex`. Returns\n * `null` for the reserved indices 13–15 so the caller can decide whether\n * to fall back or reject the stream.\n */\nexport function aacSamplingIndexToHz(index: number): number | null {\n return AAC_SAMPLE_RATES[index] ?? null\n}\n","/**\n * RTMP client (TCP, handshake, AMF0 connect/createStream/play, chunk parser).\n *\n * Ported from `koush/scrypted/plugins/prebuffer-mixin/src/rtmp-client.ts`\n * (ISC). Adaptations:\n * - `console` replaced with project `IScopedLogger`.\n * - Inlined `readLength` helper (no dependency on @scrypted/common).\n * - Strict TS — AMF0 encoder accepts a tagged union instead of `any`.\n * - Async-friendly `destroy()` for the broker's reconnect cycle.\n */\n\nimport { Socket, type SocketConnectOpts } from 'net'\nimport { Readable } from 'stream'\nimport { once } from 'events'\nimport type { IScopedLogger } from '@camstack/types'\n\n// ── Inlined readLength helper (replaces @scrypted/common/src/read-stream) ──\n\nclass StreamEndError extends Error {\n constructor(where: string) {\n super(`stream ended: ${where}`)\n this.name = 'StreamEndError'\n }\n}\n\nasync function readLength(readable: Readable, length: number): Promise<Buffer> {\n if (readable.readableEnded || readable.destroyed) {\n throw new StreamEndError('readLength start')\n }\n if (!length) return Buffer.alloc(0)\n\n const initial = readable.read(length) as Buffer | null\n if (initial) return initial\n\n return new Promise<Buffer>((resolve, reject) => {\n const onReadable = (): void => {\n const chunk = readable.read(length) as Buffer | null\n if (chunk) {\n cleanup()\n resolve(chunk)\n return\n }\n if (readable.readableEnded || readable.destroyed) {\n cleanup()\n reject(new StreamEndError('readLength readable'))\n }\n }\n const onEnd = (): void => {\n cleanup()\n reject(new StreamEndError('readLength end'))\n }\n const cleanup = (): void => {\n readable.removeListener('readable', onReadable)\n readable.removeListener('end', onEnd)\n }\n readable.on('readable', onReadable)\n readable.on('end', onEnd)\n })\n}\n\n// ── RTMP wire enums ────────────────────────────────────────────────────\n\nconst HANDSHAKE_SIZE = 1536\nconst RTMP_VERSION = 3\nconst DEFAULT_RTMP_PORT = 1935\n\nenum ChunkFormat {\n TYPE_0 = 0,\n TYPE_1 = 1,\n TYPE_2 = 2,\n TYPE_3 = 3,\n}\n\nenum RtmpMessageType {\n CHUNK_SIZE = 1,\n ABORT = 2,\n ACKNOWLEDGEMENT = 3,\n USER_CONTROL = 4,\n WINDOW_ACKNOWLEDGEMENT_SIZE = 5,\n SET_PEER_BANDWIDTH = 6,\n AUDIO = 8,\n VIDEO = 9,\n DATA_AMF0 = 18,\n COMMAND_AMF0 = 20,\n}\n\ninterface ChunkStreamState {\n chunkStreamId: number\n messageStreamId: number\n messageLength: number\n messageTypeId: number\n timestamp: number\n messageData: Buffer[]\n totalReceived: number\n hasExtendedTimestamp: boolean\n}\n\n// ── AMF0 encoder (strict types) ────────────────────────────────────────\n\ntype Amf0Value =\n | number\n | string\n | boolean\n | null\n | undefined\n | { readonly [key: string]: Amf0Value }\n\nfunction encodeAmf0(value: Amf0Value): Buffer {\n if (typeof value === 'number') {\n const buf = Buffer.alloc(9)\n buf[0] = 0x00 // Number marker\n buf.writeDoubleBE(value, 1)\n return buf\n }\n if (typeof value === 'string') {\n const buf = Buffer.alloc(3 + value.length)\n buf[0] = 0x02 // String marker\n buf.writeUInt16BE(value.length, 1)\n buf.write(value, 3, 'utf8')\n return buf\n }\n if (typeof value === 'boolean') {\n const buf = Buffer.alloc(2)\n buf[0] = 0x01 // Boolean marker\n buf[1] = value ? 1 : 0\n return buf\n }\n if (value === null || value === undefined) {\n return Buffer.from([0x05]) // Null marker\n }\n // Object\n const parts: Buffer[] = [Buffer.from([0x03])] // Object marker\n for (const [key, val] of Object.entries(value)) {\n const keyBuf = Buffer.alloc(2 + key.length)\n keyBuf.writeUInt16BE(key.length, 0)\n keyBuf.write(key, 2, 'utf8')\n parts.push(keyBuf)\n parts.push(encodeAmf0(val))\n }\n parts.push(Buffer.from([0x00, 0x00, 0x09])) // End-of-object marker\n return Buffer.concat(parts)\n}\n\nfunction encodeAmf0Command(\n commandName: string,\n transactionId: number,\n commandObject: Amf0Value,\n ...args: ReadonlyArray<Amf0Value>\n): Buffer {\n const parts: Buffer[] = [\n encodeAmf0(commandName),\n encodeAmf0(transactionId),\n encodeAmf0(commandObject),\n ]\n for (const arg of args) parts.push(encodeAmf0(arg))\n return Buffer.concat(parts)\n}\n\nfunction writeUInt24BE(buffer: Buffer, value: number, offset: number): void {\n buffer[offset] = (value >> 16) & 0xff\n buffer[offset + 1] = (value >> 8) & 0xff\n buffer[offset + 2] = value & 0xff\n}\n\n// ── Public RTMP packet shape ───────────────────────────────────────────\n\nexport type RtmpMediaCodec = 'video' | 'audio'\n\nexport interface RtmpMediaPacket {\n readonly codec: RtmpMediaCodec\n readonly packet: Buffer\n /** Timestamp in milliseconds, as advertised by the server. */\n readonly timestamp: number\n}\n\nexport class RtmpClientClosedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'RtmpClientClosedError'\n }\n}\n\n// ── RtmpClient ─────────────────────────────────────────────────────────\n\nexport class RtmpClient {\n readonly url: string\n private readonly logger: IScopedLogger | undefined\n private socket: Socket\n private chunkSize = 128\n private outgoingChunkSize = 128\n private windowAckSize = 5_000_000\n private streamId = 0\n private lastAcknowledgementBytes = 0\n private totalBytesReceived = 0\n private transactionId = 1\n private chunkStreams: Map<number, ChunkStreamState> = new Map()\n private destroyed = false\n\n constructor(url: string, logger?: IScopedLogger) {\n this.url = url\n this.logger = logger\n this.socket = new Socket()\n }\n\n /** Connect, handshake, and play. Throws on any failure. */\n async setup(): Promise<void> {\n await this.connect()\n\n this.logger?.debug('rtmp connect command sent')\n await this.sendConnect()\n\n while (true) {\n const msg = await this.readMessage()\n const { messageTypeId } = msg.chunkStream\n if (messageTypeId === RtmpMessageType.WINDOW_ACKNOWLEDGEMENT_SIZE) continue\n if (messageTypeId === RtmpMessageType.SET_PEER_BANDWIDTH) continue\n if (messageTypeId === RtmpMessageType.CHUNK_SIZE) {\n this.chunkSize = msg.message.readUInt32BE(0)\n this.logger?.debug('rtmp server chunk size', { meta: { chunkSize: this.chunkSize } })\n continue\n }\n if (messageTypeId === RtmpMessageType.COMMAND_AMF0) {\n const commandName = msg.message.subarray(3, 10).toString('utf8')\n if (commandName === '_result') {\n this.logger?.debug('rtmp connect _result received')\n break\n }\n throw new Error(`Unexpected RTMP command: ${commandName}`)\n }\n throw new Error(`Unexpected RTMP message type: ${messageTypeId}`)\n }\n\n this.sendWindowAckSize(5_000_000)\n\n this.streamId = await this.sendCreateStream()\n const createStreamResult = await this.readMessage()\n const { messageTypeId } = createStreamResult.chunkStream\n if (messageTypeId !== RtmpMessageType.COMMAND_AMF0) {\n throw new Error(`Unexpected message type for createStream result: ${messageTypeId}`)\n }\n this.logger?.debug('rtmp createStream _result received')\n\n const parsedUrl = new URL(this.url)\n const parts = parsedUrl.pathname.split('/')\n const streamName = parts.length > 2 ? parts.slice(2).join('/') : ''\n const playPath = streamName + parsedUrl.search\n\n const getStreamLengthData = encodeAmf0Command('getStreamLength', this.transactionId++, null, playPath)\n this.sendMessage(5, 0, RtmpMessageType.COMMAND_AMF0, 0, getStreamLengthData)\n\n this.sendPlay(this.streamId, playPath)\n this.setBufferLength(this.streamId, 3000)\n\n this.logger?.info('rtmp play sent', { meta: { playPath } })\n }\n\n /** Async-iterator over received video/audio packets. */\n async *readLoop(): AsyncGenerator<RtmpMediaPacket, void, void> {\n while (!this.destroyed) {\n const msg = await this.readMessage()\n const typeId = msg.chunkStream.messageTypeId\n if (typeId === RtmpMessageType.VIDEO) {\n yield { codec: 'video', packet: msg.message, timestamp: msg.chunkStream.timestamp }\n } else if (typeId === RtmpMessageType.AUDIO) {\n yield { codec: 'audio', packet: msg.message, timestamp: msg.chunkStream.timestamp }\n } else {\n this.logger?.debug('rtmp ignoring message', { meta: { messageTypeId: typeId } })\n }\n }\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n try {\n this.socket.destroy()\n } catch {\n // Socket already destroyed — ignore.\n }\n }\n\n // ── Connection / handshake ────────────────────────────────────────────\n\n private async connect(): Promise<void> {\n const parsedUrl = new URL(this.url)\n const host = parsedUrl.hostname\n const port = parseInt(parsedUrl.port || `${DEFAULT_RTMP_PORT}`, 10) || DEFAULT_RTMP_PORT\n\n this.logger?.debug('rtmp tcp connecting', { meta: { host, port } })\n\n this.socket.on('error', (err) => {\n this.logger?.warn('rtmp socket error', { meta: { error: err.message } })\n })\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => reject(err)\n this.socket.once('error', onError)\n const opts: SocketConnectOpts = { host, port }\n this.socket.connect(opts, () => {\n this.socket.removeListener('error', onError)\n resolve()\n })\n })\n\n await this.performHandshake()\n }\n\n private async performHandshake(): Promise<void> {\n // C0\n const c0 = Buffer.from([RTMP_VERSION])\n // C1: timestamp(4) + zero(4) + random(1528)\n const c1 = Buffer.alloc(HANDSHAKE_SIZE)\n c1.writeUInt32BE(Math.floor(Date.now() / 1000), 0)\n c1.writeUInt32BE(0, 4)\n this.socket.write(Buffer.concat([c0, c1]))\n\n const s0 = await readLength(this.socket, 1)\n const serverVersion = s0.readUInt8(0)\n if (serverVersion !== RTMP_VERSION) {\n throw new Error(`Unsupported RTMP server version: ${serverVersion}`)\n }\n const s1 = await readLength(this.socket, HANDSHAKE_SIZE)\n await readLength(this.socket, HANDSHAKE_SIZE) // S2 (discarded)\n\n // C2 = echo of S1\n this.socket.write(s1)\n }\n\n // ── Chunk parser ──────────────────────────────────────────────────────\n\n private async readMessage(): Promise<{ message: Buffer; chunkStream: ChunkStreamState }> {\n const stream = this.socket\n while (true) {\n const basicHeader = await readLength(stream, 1)\n const basic0 = basicHeader.readUInt8(0)\n const fmt = (basic0 >> 6) & 0x03\n let csId = basic0 & 0x3f\n\n if (csId === 0) {\n const secondByte = await readLength(stream, 1)\n csId = secondByte.readUInt8(0) + 64\n } else if (csId === 1) {\n const bytes = await readLength(stream, 2)\n csId = (bytes.readUInt8(1) << 8) | (bytes.readUInt8(0) + 64)\n }\n\n let chunkStream = this.chunkStreams.get(csId)\n if (!chunkStream) {\n chunkStream = {\n chunkStreamId: csId,\n messageStreamId: 0,\n messageLength: 0,\n messageTypeId: 0,\n timestamp: 0,\n messageData: [],\n totalReceived: 0,\n hasExtendedTimestamp: false,\n }\n this.chunkStreams.set(csId, chunkStream)\n }\n\n let hasExtendedTimestamp = false\n let headerSize: number\n\n if (fmt === ChunkFormat.TYPE_0) {\n headerSize = 11\n const header = await readLength(stream, 11)\n const timestamp = header.readUIntBE(0, 3)\n const messageLength = header.readUIntBE(3, 3)\n const messageTypeId = header.readUInt8(6)\n const messageStreamId = header.readUInt32LE(7)\n\n chunkStream.messageStreamId = messageStreamId\n chunkStream.messageLength = messageLength\n chunkStream.messageTypeId = messageTypeId\n chunkStream.timestamp = timestamp\n chunkStream.totalReceived = 0\n chunkStream.messageData = []\n\n if (timestamp >= 0xffffff) {\n hasExtendedTimestamp = true\n chunkStream.hasExtendedTimestamp = true\n }\n } else if (fmt === ChunkFormat.TYPE_1) {\n headerSize = 7\n const header = await readLength(stream, 7)\n const timestampDelta = header.readUIntBE(0, 3)\n const messageLength = header.readUIntBE(3, 3)\n const messageTypeId = header.readUInt8(6)\n chunkStream.messageLength = messageLength\n chunkStream.messageTypeId = messageTypeId\n chunkStream.timestamp += timestampDelta\n chunkStream.totalReceived = 0\n chunkStream.messageData = []\n if (timestampDelta >= 0xffffff) {\n hasExtendedTimestamp = true\n chunkStream.hasExtendedTimestamp = true\n }\n } else if (fmt === ChunkFormat.TYPE_2) {\n headerSize = 3\n const header = await readLength(stream, 3)\n const timestampDelta = header.readUIntBE(0, 3)\n chunkStream.timestamp += timestampDelta\n chunkStream.totalReceived = 0\n chunkStream.messageData = []\n if (timestampDelta >= 0xffffff) {\n hasExtendedTimestamp = true\n chunkStream.hasExtendedTimestamp = true\n }\n } else {\n headerSize = 0\n if (chunkStream.totalReceived === 0) {\n throw new Error('Type 3 chunk but no previous chunk in stream')\n }\n }\n\n if (hasExtendedTimestamp || chunkStream.hasExtendedTimestamp) {\n const extTs = await readLength(stream, 4)\n const extendedTimestamp = extTs.readUInt32BE(0)\n if (fmt === ChunkFormat.TYPE_0) {\n chunkStream.timestamp = extendedTimestamp\n }\n }\n\n const remainingInMessage = chunkStream.messageLength - chunkStream.totalReceived\n const chunkDataSize = Math.min(this.chunkSize, remainingInMessage)\n\n const MAX_CHUNK_SIZE = 1024 * 1024\n if (chunkDataSize > MAX_CHUNK_SIZE) {\n throw new Error(`Chunk size ${chunkDataSize} exceeds max ${MAX_CHUNK_SIZE}`)\n }\n\n const chunkData = await readLength(stream, chunkDataSize)\n chunkStream.messageData.push(chunkData)\n chunkStream.totalReceived += chunkDataSize\n\n const extTimestampSize = (hasExtendedTimestamp || chunkStream.hasExtendedTimestamp) ? 4 : 0\n this.totalBytesReceived += 1 + headerSize + extTimestampSize + chunkDataSize\n this.sendAcknowledgementIfNeeded()\n\n if (chunkStream.totalReceived >= chunkStream.messageLength) {\n const message = Buffer.concat(chunkStream.messageData)\n chunkStream.messageData = []\n chunkStream.totalReceived = 0\n chunkStream.hasExtendedTimestamp = false\n return { chunkStream, message }\n }\n }\n }\n\n private sendAcknowledgementIfNeeded(): void {\n const bytesToAck = this.totalBytesReceived - this.lastAcknowledgementBytes\n if (bytesToAck >= this.windowAckSize) {\n this.lastAcknowledgementBytes = this.totalBytesReceived\n const data = Buffer.alloc(4)\n data.writeUInt32BE(this.lastAcknowledgementBytes & 0xffffffff, 0)\n this.sendMessage(2, 0, RtmpMessageType.ACKNOWLEDGEMENT, 0, data)\n }\n }\n\n // ── Outgoing message helpers ─────────────────────────────────────────\n\n private sendMessage(\n chunkStreamId: number,\n messageStreamId: number,\n messageTypeId: number,\n timestamp: number,\n data: Buffer,\n ): void {\n const chunks: Buffer[] = []\n let offset = 0\n\n while (offset < data.length) {\n const chunkDataSize = Math.min(this.outgoingChunkSize, data.length - offset)\n const isType0 = offset === 0\n const headerSize = isType0 ? 12 : 1\n const header = Buffer.alloc(headerSize)\n\n if (chunkStreamId < 64) {\n header[0] = ((isType0 ? ChunkFormat.TYPE_0 : ChunkFormat.TYPE_3) << 6) | chunkStreamId\n } else {\n header[0] = ((isType0 ? ChunkFormat.TYPE_0 : ChunkFormat.TYPE_3) << 6) | 1\n }\n\n if (isType0) {\n writeUInt24BE(header, timestamp, 1)\n writeUInt24BE(header, data.length, 4)\n header[7] = messageTypeId\n header.writeUInt32LE(messageStreamId, 8)\n }\n\n chunks.push(header, data.subarray(offset, offset + chunkDataSize))\n offset += chunkDataSize\n }\n\n for (const chunk of chunks) this.socket.write(chunk)\n }\n\n private async sendConnect(): Promise<void> {\n const parsedUrl = new URL(this.url)\n const tcUrl = `${parsedUrl.protocol}//${parsedUrl.host}/${parsedUrl.pathname.split('/')[1]}`\n const connectObject = {\n app: parsedUrl.pathname.split('/')[1],\n flashVer: 'LNX 9,0,124,2',\n tcUrl,\n fpad: false,\n capabilities: 15,\n audioCodecs: 4071,\n videoCodecs: 252,\n videoFunction: 1,\n }\n const data = encodeAmf0Command('connect', this.transactionId++, connectObject)\n this.sendMessage(3, 0, RtmpMessageType.COMMAND_AMF0, 0, data)\n }\n\n private async sendCreateStream(): Promise<number> {\n const data = encodeAmf0Command('createStream', this.transactionId++, null)\n this.sendMessage(3, 0, RtmpMessageType.COMMAND_AMF0, 0, data)\n return 1\n }\n\n private sendPlay(streamId: number, playPath: string): void {\n const data = encodeAmf0Command('play', this.transactionId++, null, playPath, -2000)\n this.sendMessage(4, streamId, RtmpMessageType.COMMAND_AMF0, 0, data)\n }\n\n private setBufferLength(streamId: number, bufferLength: number): void {\n const data = Buffer.alloc(10)\n data.writeUInt16BE(3, 0)\n data.writeUInt32BE(streamId, 2)\n data.writeUInt32BE(bufferLength, 6)\n this.sendMessage(2, 0, RtmpMessageType.USER_CONTROL, 1, data)\n }\n\n private sendWindowAckSize(windowSize: number): void {\n const data = Buffer.alloc(4)\n data.writeUInt32BE(windowSize, 0)\n this.sendMessage(2, 0, RtmpMessageType.WINDOW_ACKNOWLEDGEMENT_SIZE, 0, data)\n }\n\n /** Wait for the underlying socket to close. */\n async waitForClose(): Promise<void> {\n if (this.socket.destroyed) return\n await once(this.socket, 'close')\n }\n\n get isDestroyed(): boolean {\n return this.destroyed\n }\n}\n","/**\n * RtmpReader — drives an `RtmpClient`, parses FLV tags from each RTMP\n * VIDEO/AUDIO message, builds AnnexB access units, and surfaces them to\n * the stream-broker via the supplied callbacks.\n *\n * Codec support:\n * - Video: H.264 (standard FLV) and H.265 (Enhanced FLV / Veovera spec\n * with FOURCC 'hvc1' or 'hev1'). Param sets are kept as the\n * AnnexB prefix and prepended to every IDR access unit so\n * Chrome's HEVC decoder can initialise from a single packet.\n * - Audio: AAC only. Other codecs are skipped with a warn log.\n *\n * The reader does NOT own the broker's reconnect/backoff loop — it\n * exposes `connect()` / `destroy()` and reports terminal failures via\n * `onError`. The broker re-instantiates a fresh reader on reconnect.\n */\n\nimport type { EncodedPacket, IScopedLogger, PushAudioInfo } from '@camstack/types'\nimport {\n parseFlvVideoTagWithLengthSize,\n parseFlvAudioTag,\n buildH264ParamSetsAnnexB,\n buildH265ParamSetsAnnexB,\n nalusToAnnexB,\n aacSamplingIndexToHz,\n AudioSoundFormat,\n UnsupportedCodecError,\n FlvParseError,\n} from './flv-parser.js'\nimport { RtmpClient, RtmpClientClosedError } from './rtmp-client.js'\n\nexport interface RtmpReaderCallbacks {\n /** Called when codec is first identified from the FLV sequence header. */\n onVideoCodec?: (codec: 'h264' | 'h265') => void\n /** Called for every assembled AnnexB access unit. */\n onEncodedPacket: (packet: EncodedPacket) => void\n /** Called once when the AAC sequence header is parsed. */\n onAudioInfo?: (info: PushAudioInfo) => void\n /** Called the first time the connection successfully starts streaming media. */\n onPlaying?: () => void\n /** Called on any terminal failure (TCP close, parse error, unsupported codec). */\n onError: (error: Error) => void\n /** Called once after the read-loop exits (successfully or otherwise). */\n onTeardown?: () => void\n}\n\nexport class RtmpReader {\n private readonly url: string\n private readonly logger: IScopedLogger | undefined\n private readonly callbacks: RtmpReaderCallbacks\n private client: RtmpClient | null = null\n private destroyed = false\n\n // Per-stream codec state — captured from the first SequenceHeader we see.\n private videoCodec: 'h264' | 'h265' | null = null\n private h264ParamPrefix: Buffer | null = null\n private h265ParamPrefix: Buffer | null = null\n private naluLengthSize = 4\n\n private audioInfoEmitted = false\n private firstMediaPacketSeen = false\n private packetCounter = 0\n\n constructor(url: string, logger: IScopedLogger | undefined, callbacks: RtmpReaderCallbacks) {\n this.url = url\n this.logger = logger\n this.callbacks = callbacks\n }\n\n async connect(): Promise<void> {\n const client = new RtmpClient(this.url, this.logger?.child('rtmp-client'))\n this.client = client\n\n try {\n await client.setup()\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n this.logger?.warn('rtmp setup failed', { meta: { error: error.message } })\n this.fail(error)\n return\n }\n\n void this.runReadLoop(client)\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.client?.destroy()\n }\n\n private async runReadLoop(client: RtmpClient): Promise<void> {\n try {\n for await (const media of client.readLoop()) {\n if (this.destroyed) break\n\n if (media.codec === 'video') {\n this.handleVideoPacket(media.packet, media.timestamp)\n } else {\n this.handleAudioPacket(media.packet, media.timestamp)\n }\n\n if (!this.firstMediaPacketSeen) {\n this.firstMediaPacketSeen = true\n this.callbacks.onPlaying?.()\n }\n }\n // readLoop exited cleanly — the only way this happens is destroy()\n this.callbacks.onTeardown?.()\n } catch (err) {\n if (this.destroyed) {\n this.callbacks.onTeardown?.()\n return\n }\n const error = err instanceof Error ? err : new Error(String(err))\n this.fail(error)\n }\n }\n\n private fail(error: Error): void {\n if (this.destroyed) return\n this.destroyed = true\n this.client?.destroy()\n this.callbacks.onError(error)\n }\n\n // ── Video path ────────────────────────────────────────────────────────\n\n private handleVideoPacket(payload: Buffer, timestamp: number): void {\n let tag\n try {\n tag = parseFlvVideoTagWithLengthSize(payload, this.naluLengthSize)\n } catch (err) {\n if (err instanceof UnsupportedCodecError) {\n this.fail(err)\n return\n }\n if (err instanceof FlvParseError) {\n this.logger?.warn('rtmp: skipping malformed video tag', { meta: { error: err.message } })\n return\n }\n throw err\n }\n\n if (tag.kind === 'video-sequence-header') {\n this.handleVideoSequenceHeader(tag.codec, tag)\n return\n }\n if (tag.kind === 'video-sequence-end') {\n // Treated as an end-of-stream signal — let the read loop exit naturally.\n this.logger?.info('rtmp: video sequence end received')\n return\n }\n // video-coded-frames\n if (!this.videoCodec) {\n // Coded frames before sequence header — skip silently. Some servers\n // start mid-GOP; the next SequenceHeader (or restart) will recover.\n return\n }\n\n const annexB = this.buildAnnexBAccessUnit(tag.codec, tag.nalus, this.isKeyframeFrameType(tag.frameType))\n if (annexB.length === 0) return\n\n this.packetCounter++\n const packet: EncodedPacket = {\n type: 'video',\n data: annexB,\n pts: timestamp + tag.compositionTime,\n dts: timestamp,\n keyframe: this.isKeyframeFrameType(tag.frameType),\n codec: tag.codec,\n }\n this.callbacks.onEncodedPacket(packet)\n }\n\n private handleVideoSequenceHeader(\n codec: 'h264' | 'h265',\n tag: ReturnType<typeof parseFlvVideoTagWithLengthSize>,\n ): void {\n if (tag.kind !== 'video-sequence-header') return\n\n if (this.videoCodec !== null && this.videoCodec !== codec) {\n this.fail(new Error(`RTMP video codec changed mid-stream: ${this.videoCodec} → ${codec}`))\n return\n }\n\n if (codec === 'h264' && tag.codec === 'h264') {\n this.videoCodec = 'h264'\n this.naluLengthSize = tag.avcConfig.lengthSizeMinusOne + 1\n this.h264ParamPrefix = buildH264ParamSetsAnnexB(tag.avcConfig)\n this.logger?.info('rtmp: H.264 sequence header', {\n meta: {\n profile: tag.avcConfig.avcProfileIndication,\n level: tag.avcConfig.avcLevelIndication,\n spsCount: tag.avcConfig.sps.length,\n ppsCount: tag.avcConfig.pps.length,\n lengthSize: this.naluLengthSize,\n },\n })\n this.callbacks.onVideoCodec?.('h264')\n return\n }\n\n if (codec === 'h265' && tag.codec === 'h265') {\n this.videoCodec = 'h265'\n this.naluLengthSize = tag.hevcConfig.lengthSizeMinusOne + 1\n this.h265ParamPrefix = buildH265ParamSetsAnnexB(tag.hevcConfig)\n this.logger?.info('rtmp: H.265 sequence header', {\n meta: {\n profile: tag.hevcConfig.generalProfileIdc,\n level: tag.hevcConfig.generalLevelIdc,\n vpsCount: tag.hevcConfig.vps.length,\n spsCount: tag.hevcConfig.sps.length,\n ppsCount: tag.hevcConfig.pps.length,\n lengthSize: this.naluLengthSize,\n },\n })\n this.callbacks.onVideoCodec?.('h265')\n }\n }\n\n /**\n * Build the AnnexB access unit. Prepends VPS/SPS/PPS prefix when the\n * frame is a keyframe so downstream consumers (decoder, prebuffer,\n * WebRTC) receive a self-contained access unit on every IDR.\n */\n private buildAnnexBAccessUnit(\n codec: 'h264' | 'h265',\n nalus: ReadonlyArray<Buffer>,\n keyframe: boolean,\n ): Buffer {\n if (nalus.length === 0) return Buffer.alloc(0)\n const body = nalusToAnnexB(nalus)\n if (!keyframe) return body\n\n const prefix = codec === 'h264' ? this.h264ParamPrefix : this.h265ParamPrefix\n if (!prefix || prefix.length === 0) return body\n return Buffer.concat([prefix, body])\n }\n\n private isKeyframeFrameType(frameType: number): boolean {\n // FrameType 1 = KEY, 4 = GENERATED_KEYFRAME\n return frameType === 1 || frameType === 4\n }\n\n // ── Audio path ────────────────────────────────────────────────────────\n\n private handleAudioPacket(payload: Buffer, timestamp: number): void {\n let tag\n try {\n tag = parseFlvAudioTag(payload)\n } catch (err) {\n if (err instanceof FlvParseError) {\n this.logger?.warn('rtmp: skipping malformed audio tag', { meta: { error: err.message } })\n return\n }\n throw err\n }\n\n if (tag.kind === 'audio-sequence-header') {\n if (tag.soundFormat !== AudioSoundFormat.AAC) {\n this.logger?.warn('rtmp: non-AAC sequence header — audio skipped', {\n meta: { soundFormat: tag.soundFormat },\n })\n return\n }\n const sampleRate = aacSamplingIndexToHz(tag.audioSpecificConfig.samplingFrequencyIndex)\n const channels = tag.audioSpecificConfig.channelConfiguration === 0\n ? 1\n : tag.audioSpecificConfig.channelConfiguration\n if (sampleRate === null) {\n this.logger?.warn('rtmp: AAC reserved sample-rate index — audio skipped', {\n meta: { samplingFrequencyIndex: tag.audioSpecificConfig.samplingFrequencyIndex },\n })\n return\n }\n this.audioInfoEmitted = true\n this.callbacks.onAudioInfo?.({\n codec: 'aac',\n sampleRate,\n channels,\n extraData: new Uint8Array(tag.audioSpecificConfigBytes),\n })\n this.logger?.info('rtmp: AAC sequence header', {\n meta: {\n audioObjectType: tag.audioSpecificConfig.audioObjectType,\n sampleRate,\n channels,\n },\n })\n return\n }\n\n if (tag.soundFormat !== AudioSoundFormat.AAC) {\n // Non-AAC audio (G.711, ADPCM, …) is not supported on the RTMP path.\n // Drop silently after the first warning to avoid log floods.\n return\n }\n\n if (!this.audioInfoEmitted) {\n // AAC raw arrived before the sequence header — uncommon but legal.\n // Skip until we see the header (decoder needs ASC to initialise).\n return\n }\n\n const packet: EncodedPacket = {\n type: 'audio',\n data: tag.data,\n pts: timestamp,\n dts: timestamp,\n keyframe: false,\n codec: 'aac',\n }\n this.callbacks.onEncodedPacket(packet)\n }\n}\n\nexport { RtmpClientClosedError }\n","import {\n RTP_HEADER_SIZE,\n H264_NAL_TYPE_MASK,\n H264_NAL_FUA,\n H265_NAL_TYPE_SHIFT,\n H265_NAL_TYPE_MASK,\n H265_NAL_FU,\n} from './rtsp-types.js'\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface DepacketizedNal {\n /** Complete NAL unit (without start code). */\n readonly nal: Buffer\n /** Whether this NAL is a keyframe (IDR for H.264, IRAP for H.265). */\n readonly keyframe: boolean\n /** Whether this NAL is a parameter set (SPS/PPS/VPS). */\n readonly parameterSet: boolean\n /** RTP timestamp (90kHz clock). */\n readonly timestamp: number\n /** True if the RTP marker bit was set (last packet of access unit). */\n readonly marker: boolean\n}\n\nexport type NalCallback = (nal: DepacketizedNal) => void\n\n// ── H.264 NAL types ────────────────────────────────────────────────\n\nconst H264_NAL_SPS = 7\nconst H264_NAL_PPS = 8\nconst H264_NAL_IDR = 5\nconst H264_NAL_STAP_A = 24\n\n// ── H.265 NAL types ────────────────────────────────────────────────\n\nconst H265_NAL_VPS = 32\nconst H265_NAL_SPS = 33\nconst H265_NAL_PPS = 34\nconst H265_NAL_IRAP_MIN = 16\nconst H265_NAL_IRAP_MAX = 21\nconst H265_NAL_AP = 48\n\n// ── Implementation ──────────────────────────────────────────────────\n\n/**\n * RTP → Annex-B NAL depacketizer.\n *\n * Takes raw RTP packets (as received from TCP interleaved RTSP) and\n * reassembles complete NAL units. Handles:\n *\n * H.264 (RFC 6184):\n * - Single NAL unit packets (type 1-23)\n * - STAP-A aggregation packets (type 24)\n * - FU-A fragmentation packets (type 28)\n *\n * H.265 (RFC 7798):\n * - Single NAL unit packets (type 0-47, except 48/49)\n * - AP aggregation packets (type 48)\n * - FU fragmentation packets (type 49)\n */\nexport class RtpDepacketizer {\n private readonly codec: 'h264' | 'h265'\n private readonly onNal: NalCallback\n\n /** FU reassembly state. */\n private fuBuffer: Buffer[] = []\n private fuTimestamp = 0\n /** H.264: stores the reconstructed first byte (NRI + type). */\n private fuFirstByte = 0\n /** H.265: stores the 2-byte NAL header from the FU payload header. */\n private fuH265Header: Buffer | null = null\n\n constructor(codec: 'h264' | 'h265', onNal: NalCallback) {\n this.codec = codec\n this.onNal = onNal\n }\n\n /** Reset reassembly state (e.g. on stream restart). */\n reset(): void {\n this.fuBuffer = []\n this.fuTimestamp = 0\n\n this.fuFirstByte = 0\n this.fuH265Header = null\n }\n\n /**\n * Process a raw RTP packet (including 12-byte RTP header).\n * Emits zero or more complete NAL units via the callback.\n */\n processPacket(rtpData: Buffer): void {\n if (rtpData.length < RTP_HEADER_SIZE) return\n\n const marker = (rtpData[1]! & 0x80) !== 0\n const timestamp = rtpData.readUInt32BE(4)\n const payload = rtpData.subarray(RTP_HEADER_SIZE)\n\n if (payload.length === 0) return\n\n if (this.codec === 'h264') {\n this.processH264(payload, timestamp, marker)\n } else {\n this.processH265(payload, timestamp, marker)\n }\n }\n\n // ── H.264 ─────────────────────────────────────────────────────────\n\n private processH264(payload: Buffer, timestamp: number, marker: boolean): void {\n const firstByte = payload[0]!\n const nalType = firstByte & H264_NAL_TYPE_MASK\n\n if (nalType >= 1 && nalType <= 23) {\n // Single NAL unit packet\n this.emitNal(payload, timestamp, marker)\n } else if (nalType === H264_NAL_STAP_A) {\n this.processH264StapA(payload, timestamp, marker)\n } else if (nalType === H264_NAL_FUA) {\n this.processH264FuA(payload, timestamp, marker)\n }\n // Types 25-27 (STAP-B, MTAP16, MTAP24) and 29 (FU-B) are extremely rare\n // in IP camera streams — ignore them.\n }\n\n /** STAP-A: Single-Time Aggregation Packet (multiple NALs in one RTP). */\n private processH264StapA(payload: Buffer, timestamp: number, marker: boolean): void {\n let offset = 1 // Skip STAP-A header byte\n while (offset + 2 <= payload.length) {\n const nalSize = payload.readUInt16BE(offset)\n offset += 2\n if (offset + nalSize > payload.length) break\n\n const nal = Buffer.from(payload.subarray(offset, offset + nalSize))\n offset += nalSize\n this.emitNal(nal, timestamp, marker && offset >= payload.length)\n }\n }\n\n /** FU-A: Fragmentation Unit (large NAL split across multiple RTP packets). */\n private processH264FuA(payload: Buffer, timestamp: number, marker: boolean): void {\n if (payload.length < 2) return\n\n const fuIndicator = payload[0]!\n const fuHeader = payload[1]!\n const isStart = (fuHeader & 0x80) !== 0\n const isEnd = (fuHeader & 0x40) !== 0\n const nalType = fuHeader & H264_NAL_TYPE_MASK\n\n if (isStart) {\n // Start fragment — reconstruct NAL first byte: NRI from indicator + type from header\n this.fuBuffer = [payload.subarray(2)]\n this.fuTimestamp = timestamp\n\n this.fuFirstByte = (fuIndicator & 0xe0) | nalType\n } else if (this.fuBuffer.length > 0 && this.fuTimestamp === timestamp) {\n // Middle or end fragment\n this.fuBuffer.push(payload.subarray(2))\n\n if (isEnd) {\n // Reassemble: prepend reconstructed NAL header byte\n const totalSize = this.fuBuffer.reduce((sum, b) => sum + b.length, 0)\n const nal = Buffer.allocUnsafe(1 + totalSize)\n nal[0] = this.fuFirstByte\n let offset = 1\n for (const fragment of this.fuBuffer) {\n fragment.copy(nal, offset)\n offset += fragment.length\n }\n this.fuBuffer = []\n this.emitNal(nal, timestamp, marker)\n }\n } else {\n // Out-of-order or gap — discard partial reassembly\n this.fuBuffer = []\n }\n }\n\n // ── H.265 ─────────────────────────────────────────────────────────\n\n private processH265(payload: Buffer, timestamp: number, marker: boolean): void {\n if (payload.length < 2) return\n\n const nalType = (payload[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n\n if (nalType < 48) {\n // Single NAL unit packet (types 0-47)\n this.emitNal(payload, timestamp, marker)\n } else if (nalType === H265_NAL_AP) {\n this.processH265Ap(payload, timestamp, marker)\n } else if (nalType === H265_NAL_FU) {\n this.processH265Fu(payload, timestamp, marker)\n }\n }\n\n /** AP: Aggregation Packet (multiple NALs in one RTP). */\n private processH265Ap(payload: Buffer, timestamp: number, marker: boolean): void {\n let offset = 2 // Skip 2-byte AP payload header\n while (offset + 2 <= payload.length) {\n const nalSize = payload.readUInt16BE(offset)\n offset += 2\n if (offset + nalSize > payload.length) break\n\n const nal = Buffer.from(payload.subarray(offset, offset + nalSize))\n offset += nalSize\n this.emitNal(nal, timestamp, marker && offset >= payload.length)\n }\n }\n\n /** FU: Fragmentation Unit (large NAL split across multiple RTP packets). */\n private processH265Fu(payload: Buffer, timestamp: number, marker: boolean): void {\n if (payload.length < 3) return\n\n const fuHeader = payload[2]!\n const isStart = (fuHeader & 0x80) !== 0\n const isEnd = (fuHeader & 0x40) !== 0\n const nalType = fuHeader & H265_NAL_TYPE_MASK\n\n if (isStart) {\n // Start fragment — store payload header bytes for NAL header reconstruction\n this.fuBuffer = [payload.subarray(3)]\n this.fuTimestamp = timestamp\n\n // Reconstruct 2-byte NAL header: replace FU type with actual NAL type\n const header = Buffer.allocUnsafe(2)\n header[0] = (payload[0]! & 0x81) | (nalType << H265_NAL_TYPE_SHIFT)\n header[1] = payload[1]!\n this.fuH265Header = header\n } else if (this.fuBuffer.length > 0 && this.fuTimestamp === timestamp) {\n // Middle or end fragment\n this.fuBuffer.push(payload.subarray(3))\n\n if (isEnd && this.fuH265Header) {\n // Reassemble: prepend 2-byte NAL header\n const totalSize = this.fuBuffer.reduce((sum, b) => sum + b.length, 0)\n const nal = Buffer.allocUnsafe(2 + totalSize)\n this.fuH265Header.copy(nal, 0)\n let offset = 2\n for (const fragment of this.fuBuffer) {\n fragment.copy(nal, offset)\n offset += fragment.length\n }\n this.fuBuffer = []\n this.fuH265Header = null\n this.emitNal(nal, timestamp, marker)\n }\n } else {\n // Gap — discard\n this.fuBuffer = []\n this.fuH265Header = null\n }\n }\n\n // ── Emit ──────────────────────────────────────────────────────────\n\n private emitNal(nal: Buffer, timestamp: number, marker: boolean): void {\n const keyframe = this.codec === 'h264'\n ? this.isH264Keyframe(nal)\n : this.isH265Keyframe(nal)\n\n const parameterSet = this.codec === 'h264'\n ? this.isH264ParameterSet(nal)\n : this.isH265ParameterSet(nal)\n\n this.onNal({ nal, keyframe, parameterSet, timestamp, marker })\n }\n\n private isH264Keyframe(nal: Buffer): boolean {\n if (nal.length === 0) return false\n return (nal[0]! & H264_NAL_TYPE_MASK) === H264_NAL_IDR\n }\n\n private isH264ParameterSet(nal: Buffer): boolean {\n if (nal.length === 0) return false\n const type = nal[0]! & H264_NAL_TYPE_MASK\n return type === H264_NAL_SPS || type === H264_NAL_PPS\n }\n\n private isH265Keyframe(nal: Buffer): boolean {\n if (nal.length < 2) return false\n const type = (nal[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n return type >= H265_NAL_IRAP_MIN && type <= H265_NAL_IRAP_MAX\n }\n\n private isH265ParameterSet(nal: Buffer): boolean {\n if (nal.length < 2) return false\n const type = (nal[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n return type === H265_NAL_VPS || type === H265_NAL_SPS || type === H265_NAL_PPS\n }\n}\n","/**\n * Native audio RTP decoder for G.711 PCMU (μ-law) and PCMA (A-law).\n *\n * IP cameras almost universally use G.711 for audio (8kHz, mono).\n * This decoder converts raw RTP payloads → PCM float32 samples,\n * matching the format of the ffmpeg audio path (f32le mono 16kHz)\n * so downstream consumers (audio-classifier, VAD) receive identical data.\n *\n * Output: 16kHz mono f32le — upsampled from 8kHz via linear interpolation.\n */\n\nimport type { DecodedAudioChunk } from '@camstack/types'\n\nexport type AudioCodec = 'PCMU' | 'PCMA'\n\n/** Target sample rate for decoded audio (matches ffmpeg path). */\nconst TARGET_SAMPLE_RATE = 16000\n/** Buffer ~0.5s of decoded audio before emitting a chunk (matches ffmpeg). */\nconst CHUNK_DURATION_MS = 500\nconst CHUNK_SAMPLES = (TARGET_SAMPLE_RATE * CHUNK_DURATION_MS) / 1000 // 8000 samples\n\n/**\n * Decodes G.711 audio RTP packets into PCM f32le chunks.\n *\n * Buffers decoded samples and emits DecodedAudioChunk when enough\n * samples accumulate (~0.5s), matching the ffmpeg audio output cadence.\n */\nexport class AudioRtpDecoder {\n private readonly codec: AudioCodec\n private readonly onChunk: (chunk: DecodedAudioChunk) => void\n private buffer: Float32Array\n private bufferOffset = 0\n\n constructor(codec: AudioCodec, onChunk: (chunk: DecodedAudioChunk) => void) {\n this.codec = codec\n this.onChunk = onChunk\n this.buffer = new Float32Array(CHUNK_SAMPLES)\n }\n\n /**\n * Process a raw RTP packet (header + payload).\n * Strips the 12-byte RTP header, decodes G.711 payload, upsamples 8→16kHz.\n */\n processPacket(rtpData: Buffer): void {\n // RTP header is 12 bytes minimum (no CSRC, no extensions handled here)\n if (rtpData.length <= 12) return\n\n const payload = rtpData.subarray(12)\n const decode = this.codec === 'PCMU' ? decodeUlaw : decodeAlaw\n\n // Each byte = one G.711 sample at 8kHz.\n // Upsample 8→16kHz via linear interpolation (2x).\n for (let i = 0; i < payload.length; i++) {\n const current = decode(payload[i]!)\n const next = i + 1 < payload.length ? decode(payload[i + 1]!) : current\n\n // Original sample\n this.pushSample(current)\n // Interpolated sample (midpoint)\n this.pushSample((current + next) * 0.5)\n }\n }\n\n /** Flush any remaining buffered samples as a partial chunk. */\n flush(): void {\n if (this.bufferOffset === 0) return\n\n const data = Buffer.from(\n this.buffer.buffer,\n this.buffer.byteOffset,\n this.bufferOffset * 4,\n )\n\n this.onChunk({\n data: Buffer.from(data), // copy — buffer will be reused\n sampleRate: TARGET_SAMPLE_RATE,\n channels: 1,\n timestamp: Date.now(),\n })\n\n this.bufferOffset = 0\n }\n\n /** Reset the decoder state. */\n reset(): void {\n this.bufferOffset = 0\n }\n\n private pushSample(sample: number): void {\n this.buffer[this.bufferOffset++] = sample\n\n if (this.bufferOffset >= CHUNK_SAMPLES) {\n // Emit full chunk\n const data = Buffer.from(\n this.buffer.buffer,\n this.buffer.byteOffset,\n CHUNK_SAMPLES * 4,\n )\n\n this.onChunk({\n data: Buffer.from(data), // copy\n sampleRate: TARGET_SAMPLE_RATE,\n channels: 1,\n timestamp: Date.now(),\n })\n\n this.bufferOffset = 0\n }\n }\n}\n\n// ── G.711 μ-law decoding ──────────────────────────────────────────────\n\n/** μ-law compressed byte → normalized float [-1.0, 1.0]. */\nfunction decodeUlaw(byte: number): number {\n return ULAW_TABLE[byte]!\n}\n\n/** A-law compressed byte → normalized float [-1.0, 1.0]. */\nfunction decodeAlaw(byte: number): number {\n return ALAW_TABLE[byte]!\n}\n\n/**\n * Build the μ-law decode table (ITU-T G.711).\n * Each of the 256 possible byte values maps to a 16-bit PCM sample,\n * which we normalize to [-1.0, 1.0] for f32le output.\n */\nfunction buildUlawTable(): Float32Array {\n const table = new Float32Array(256)\n for (let i = 0; i < 256; i++) {\n const complemented = ~i & 0xff\n const sign = (complemented & 0x80) !== 0 ? -1 : 1\n const exponent = (complemented >> 4) & 0x07\n const mantissa = complemented & 0x0f\n const magnitude = ((2 * mantissa + 33) << exponent) - 33\n table[i] = (sign * magnitude) / 32768\n }\n return table\n}\n\n/**\n * Build the A-law decode table (ITU-T G.711).\n */\nfunction buildAlawTable(): Float32Array {\n const table = new Float32Array(256)\n for (let i = 0; i < 256; i++) {\n const xored = i ^ 0x55\n const sign = (xored & 0x80) !== 0 ? -1 : 1\n const exponent = (xored >> 4) & 0x07\n const mantissa = xored & 0x0f\n let magnitude: number\n if (exponent === 0) {\n magnitude = (2 * mantissa + 1)\n } else {\n magnitude = (2 * mantissa + 33) << (exponent - 1)\n }\n table[i] = (sign * magnitude) / 32768\n }\n return table\n}\n\nconst ULAW_TABLE = buildUlawTable()\nconst ALAW_TABLE = buildAlawTable()\n","/**\n * G.711 µ-law encoder — converts 16-bit signed PCM samples to µ-law (ITU-T G.711).\n *\n * Used to bridge non-native audio codecs (AAC, Opus) into WebRTC's\n * PCMU negotiated track. The audio-codec cap decodes to linear PCM;\n * this module encodes the PCM to µ-law for the RTP payload.\n */\n\n// µ-law compression bias\nconst MULAW_BIAS = 0x84\nconst MULAW_CLIP = 32635\n\n/** Encode a single 16-bit signed PCM sample to µ-law byte. */\nfunction linearToMulaw(sample: number): number {\n const sign = (sample >> 8) & 0x80\n if (sign !== 0) sample = -sample\n if (sample > MULAW_CLIP) sample = MULAW_CLIP\n sample = sample + MULAW_BIAS\n\n const exponent = MU_LAW_COMPRESS_TABLE[(sample >> 7) & 0xff]!\n const mantissa = (sample >> (exponent + 3)) & 0x0f\n const byte = ~(sign | (exponent << 4) | mantissa) & 0xff\n return byte\n}\n\n// Lookup table: maps (sample >> 7) to exponent (0-7)\nconst MU_LAW_COMPRESS_TABLE = new Uint8Array(256)\n;(() => {\n for (let i = 0; i < 256; i++) {\n let val = i\n let exp = 0\n val >>= 1\n while (val > 0 && exp < 7) { val >>= 1; exp++ }\n MU_LAW_COMPRESS_TABLE[i] = exp\n }\n})()\n\n/** Convert an Int16Array of PCM samples to a Uint8Array of µ-law bytes. */\nexport function pcmToMulaw(pcm: Int16Array): Uint8Array {\n const result = new Uint8Array(pcm.length)\n for (let i = 0; i < pcm.length; i++) {\n result[i] = linearToMulaw(pcm[i]!)\n }\n return result\n}\n\n/**\n * Downsample µ-law by picking every Nth sample. Used when source\n * sample rate exceeds 8kHz (e.g. AAC at 16kHz → G.711 at 8kHz).\n * Simple decimation is acceptable for speech/ambient audio.\n */\nexport function downsampleUlaw(data: Uint8Array, ratio: number): Uint8Array {\n const outLen = Math.floor(data.length / ratio)\n const result = new Uint8Array(outLen)\n for (let i = 0; i < outLen; i++) {\n result[i] = data[i * ratio]!\n }\n return result\n}\n","/**\n * Pre-encoded Annex-B placeholder keyframes for both H.264 and H.265.\n * Single 1280×720 black frame with a centred text label\n * (\"RECONNECTING\", \"SLEEPING\", …), encoded with x264/x265 ultrafast.\n * Embedded as base64 to avoid spawning ffmpeg at runtime.\n *\n * Frames carry SPS+PPS+IDR (H.264) or VPS+SPS+PPS+IDR (H.265) in a\n * single Annex-B blob so a fresh consumer (RTSP restream, WebRTC\n * AnnexB feed) decodes them without waiting for the next \"real\"\n * keyframe.\n *\n * Codec selection happens at the broker level: a broker carrying an\n * H.265 source emits H.265 placeholders so a WebRTC session\n * negotiated for H.265 sees correctly-coded fill while waking up.\n *\n * To regenerate (regression/refresh after font change):\n * for label in reconnecting sleeping offline disabled waking; do\n * upper=$(echo $label | tr '[:lower:]' '[:upper:]')\n * # H.264\n * ffmpeg -hide_banner -loglevel error -y -f lavfi \\\n * -i \"color=c=0x101010:s=1280x720:r=1:d=1\" \\\n * -vf \"drawtext=fontfile=/System/Library/Fonts/Supplemental/Arial.ttf:text='${upper}':fontsize=64:fontcolor=0xb8b8b8:x=(w-text_w)/2:y=(h-text_h)/2\" \\\n * -frames:v 1 -c:v libx264 -preset ultrafast -tune stillimage \\\n * -profile:v baseline -x264-params \"nal-hrd=none:aud=0:repeat-headers=1\" \\\n * -f h264 -bsf:v \"h264_mp4toannexb,filter_units=remove_types=6\" \\\n * ${label}.h264\n * # H.265\n * ffmpeg -hide_banner -loglevel error -y -f lavfi \\\n * -i \"color=c=0x101010:s=1280x720:r=1:d=1\" \\\n * -vf \"drawtext=fontfile=/System/Library/Fonts/Supplemental/Arial.ttf:text='${upper}':fontsize=64:fontcolor=0xb8b8b8:x=(w-text_w)/2:y=(h-text_h)/2\" \\\n * -frames:v 1 -c:v libx265 -preset ultrafast \\\n * -x265-params \"keyint=1:repeat-headers=1\" \\\n * -f hevc ${label}.h265\n * done\n */\n\n/**\n * State the broker is currently in. Drives which placeholder frame is\n * emitted to encoded subscribers (RTSP restream, WebRTC AnnexB feed)\n * while no live RTP is flowing. Default `reconnecting` is the\n * legacy behaviour for the dial-fail/reconnect path.\n */\nexport type PlaceholderKind =\n | 'reconnecting'\n | 'sleeping'\n | 'offline'\n | 'disabled'\n | 'waking'\n\n/**\n * Codec the placeholder frame is encoded as. Selection lives at the\n * broker level — an H.265 source surfaces H.265 placeholders so a\n * negotiated WebRTC session decodes them with the same codec as the\n * live stream. Only `'h264'` and `'h265'` are supported (the only\n * codecs that flow through the broker's encoded subscribers today).\n */\nexport type PlaceholderCodec = 'h264' | 'h265'\n\nconst FRAME_B64: Readonly<Record<PlaceholderCodec, Readonly<Record<PlaceholderKind, string>>>> = {\n h264: {\n reconnecting: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgARwAaAfgvaKICMwoLAOgADxEIjArGFj0HzJsgAA2AHATLIAAMgBQE3ACIwbKGUWeUI9Xg4ABsAOAmWHAANgBwEyywADYAAgMGRwADYAAgMGQ4ABkAKAm4QAAYAHAq4HAAMgBQE3A4ABgAcCrn4/ABwjgGMCXnxbX/yxvWnTbBwke+QAqCJtK/C/ANHYwBiaibAhFAwNAYBETMKEp/DdMAAVluoAAgKyf8BWwZFmQC+raGBG1IAwEgPQdQ7AEAYHCVS5MuqyLBIm21NwPwEdMjMJ8h2KQPTQfEi5poyZEw/1NvqAA8ZCKyiNLHIZ6XyAADIAQDbgYAro/YCAZstAw/vNEAqAAIBiyrw4ABkAIBtzDgAGQAgG3B/4BjvB/2xbteXFxm4BcTAgkL5YdfLwCIgyByUFDRzz1eDr5YdfLLAANgACAwZHAANgACAwZyAACgAgCrmHAAKACAKufvZY7AA7AACAQAQPJEWx8kFDGRyZ/eABHf9E/EeMqpJFVe/gAeJVOoQrZdIPEev4AH8J5YVwwpkr3+37/+/wGgClGk1MvAN9VJ6BIUWVMODsABAPgACACEiJKqGeGhQqwRGuD0AAYoXqu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RLHYAEH1zdzf/gBMRiKyHMpRkqWH47AARBqhDjKTaFvcon/4AD5EY1QhM4voRkAAGgA4CJYAQtDIw6J8ppDx2AAiMyIBjO3ee9zH/+GBI/NvyAADQAcBEsADBYIweqs4k4sfgAIADAgkqwysyUKlCrRIqQ/wACEUSqy5aKeEb/0JWYE4gmijPjBpr7TQ78AAkQAAQEhUmDIwVtWD8meJumEurCowj2gJxHrWF5hvElDlMS7OasJqgA2Y5AHeoAAgKVzv/oNWfEBRXSABffbEAAE1AAEBS4wAAQAAEA24AE0TAihBEW1YKjTLkAAGgA4HS8fgAIGHBQoVzpjA7v1bsT4PxEgrgFIFpyZIAPcgPZk4xdCozhgB6De+qQEPg0EOzwYwJxxFVGfMGQfJFhwG0eU+Q8DSdo86EALwQR/bIyIDJXBFlFl4E3kNhenCVOaDsBQAIBAkmNOyGGSSVsAjkDV3wI6MjMEXMOhrAxpjMxp4ijbpzzSk+A87YQeoiABfu/sQpLw2zMgCE+/pUAntEGlsl0IgB+UAQBdCsHAQuUFDsqfjw6ZYdNVslPB2AAgYAfhIwvZMuvFIEKDvPyBwAKeiEp3qDIWsbh9ENEH5R+FmHPA0cqMNUxNgC/mp4bj7pbSXp9DEAiQUaWx2rYrlD3E2AIS++5yZmgmAw7d8BLALbDw8skKUFK/v1qEQbDgUp5QNIQAAbACAfcXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXeG+/367w3+/33+/37777799d99/v994b77799/v9dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeA=',\n sleeping: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgAIBgAEPwDIl6AJKw3fccBLCZwAHKurzmDTjrCx0AC1cGKrrkpgC3X4FFZAP0Uaf7/6gZgeKmof6pKQDFucb/wYp75WNKMZMNLhTAUAVnFsltEAGDB64A7LxPeqngCF4/AEAgcCPCbK0wt32oU2+7FSzAg1zwoGYY6J5NiACWYMvJ4xAAWoI254HCYUE/1doEB1DA/VNnAlhlGCkobZhpf/euUyMgit1MDgPz//6ewJdgXXJoQEX0DzktM5h4U0h2AkYAK8DY5OrsAFl7Vq4BIA2V6cjTQdhKNqXYkqw0Ut8ACnIZCjJ8wppIEyAEBACbgYAH5v2DCCkY/LAsAA2AAIDBmjkDKJTBzRBfXvDgICAE3PDgICAE3B34bu7nOdwABBIAAEAze9gASAiIAANgBwEywAnMzGqlKMc30jsgAAoAwIuB/hwACgDAi5hwACgDAi5+uOwAGBRTAPA9QGwrbYCgnAuAAUaIZHGW0Qlv50gAA2AHATLIAAIgGAbcw4ABsAOAmWHAANgBwEywCIgbApKijwCmPV4sAA2AAIDBkOAARAMA25hwACIBgG3MgAAwAIBFzDgAGABAIub8dgAIAs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iKqL+gYHiILNZcsFvCN/7TQOklXDjIuln8BV+nTmgMEwFLygbIkJCH35eOw8AAiD7JgAcORoP8BzLgiAWU+vfhmNvSMEUAwAPN5nmcCBboVnqVIQUTiFiZzyAACoBQbcxDgGAB4jM8SgBBakVng6AwrALSNMvToZltGEAAFQCg25gp2jAGB/jQ8e8dgAoAAIEIAAgCAERjADRCK2tqF/+B1ncwFQADVtoAQDoi4gAA0AGARLH4ACAEGLASmcBNaIPDDzUpHKT//+ACWBDkZTThJEXIDgBDiLmoAAbELBYzxmDgAGwA4CZa6666666666666666666666666666666666666666666666666666666666647AAYAGHWAELGCgIvxicO8WuFYAJSiqBLnAC954PAZRlkFw48wBjdNbJpgAGcthxDGwsUX4QK6KKdCKCxy/3/1B31BhNY1iYGYYYAFC2ILP5UGFgJYAAvPYYiPuTXAIdfsbiMBS0wDgZ3fD8ABAAwDCoFzPz1a4pq2Y//wAKbIkxlVhMSvMoIDpiNGax3lAABAPADGCdsQasEP2eEAAEQIFywDKAAh1THGcvPUQswHTI3ppv9SpZikOQXjfYXAIcuYlKByRh2AEggQPVOkjRH9A4nMhK2/+AHJSIAGAXqwEjy0AEtVZVTO8VKyZas51kkk4/kTLs4fFtoEbODpKFTEWkYbgxSiAiM8BYToQcwwVQgABYAAQCYfgAVpjKQZEeW0hw7AAQBBCQMRYRZFGrUswC1NJn/AB+YSgzkvKANT9XiAADQAcBEsSAAaBR5sYU5iwADQAAgMmQcAA0AAIDJkgAA0AHARLDgAGgA4CJY/AAQ2YAF9alAw4dH63z5///ABzDTiIaSwr1eLAANgACAwZAhshJylENViRhAABsAMAmWOwAGhxDANI9WqgHIDEWqH/gBFIxlYZVJLW5hAABoAOAiWAD8wTghCyTgC1/V4sAA0AAIDJkSAAaAoabGGOYgAA0AHARLfHYAFgYwPTqkREPiuR6WOrf4OAAaAAEBkyAD4gNkBWGC1WvV4DgAGgA4CJYCxGIrIKwwchnhj4YAFeYihnJjixpTw7AAQBBCQORQZRFm7dtwCXuZP8AHYS4X2OaT6vFgAGgABAZMqAAGgoo2MI8xAABoAOAiWDgAGgABAZMg4ABoAAQGTIcAA0AHARLDgAGgA4CJY/AAiA/REZ5Ef/+ADmawkKKhnq9AhtEszCH7PMHYACIzIgGM7dx73Mf//D4HAANABwESwCMhiMNwUq0nu0u+OwAEYGiiY7W07Hu0n/wAQXkrekEAAGgA4CJeAFU8EYPFSITYTDHYACYD3Gp1vplPdb//wAMwpgASLMoA4FU+uOBDFzECF3kwx+AAhOAwe+XLfLD74RzmJ7dP4ALJXJiAtpYEDu37tFrI5i6oBGY0+cykFxS7tZgTiiKqL+nhgeIgt1lywW8I3/vjvwzgOkbjQnaYGUzWh46wSG9hcMNk2S6/jjNXFAA91AAEAgPyAgcAq5gBJIpYUQVuRu/xTQgAA0AHARLwAhNNIybF0UMfgKcAqAQLCQh7WF+Rdn0fvgMIJ7NygR8kAeB1f9ABOwXNNMoUZFwyBrAT30yAj4NAYPw7WIXGiLrreAU7MEWEAqjzF8XO4M3MBUHHw8GZJBpXARoxvrlyMTifGkLawCM2RobdCoz2wo1BNm8AEK/fD8AFAACoD4AAbWtAFsNT9/zfAkF+gkFgO1QGJqIoc6gYNQ5ZwABAKfk8x8oBLaIkXvvNCQEAAIAdzwcGUAAIFgtp+A4coBHVIY7/74tIBAACAPFh2IEQ/AHR3g7AAYAAIA6DgQoPIwhiOjamwn6R8ADGUsz0LKujNEL01cCoErP/f+oMo92eSJTz3cXyTAl+AAEBK2sFwtxqwBCH32v2QQkWYIScAg7ALfYHfrssBHC8gAEMJua6666666666666666666666666666666666666666666666666666666666677/f7994a79+++uu/313hvv9/rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrw',\n offline: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJydddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgAIAQcsOSSTTLlyxZIuXLf/wANhc8uVQgps2//QWgElpnArSQHn3h2AA4ADBMADxxeJWO1OLfdS9Bs/QADCh193AxANb38ABYlU8UMVsskHiLn+/7YELICKS04HAb9fDqNXOPveYDnMTLoBSwIaP2fQPHkQGAM13p+//nbhoKtUCn5CaBgpIA2DKJLlxxnYqkpEkg/Dj8AQjAAGAQTCxyPvtmpwKevTsuUcA0IZIYc1H2mrLxnjMgBArJmNE66YDDcB4PVTiQCUnBCAVG3DyiSs+w2ogYtWIF/9++5cjIADMXOEB4ds/8CMzI0GTsLyk4MwuSpRR03i/4GBtiexCgh8GAnuHYCwAwAOaLf0Ksjw9oPzoJL+EIuLJSAFwLKft8ACmkaZGGN26bIABBBdzGuxC807SzIA/GTxFRjoUgdpAK8JbPkkAQ4GHABBBdzw4AIILuD/wGK0P66UdLhIiO/gyLGwgAEFHyw4AIKPl4BoAQqZQFiSKq8HABBR8sOACCj5ZYABsAAQGDI4ABsAAQGDOQgCAAEAy5h0AQAAgGXPx+ADzQAX3wKYaZJZhv4AJIGQLltPIMA4oagCR64Bgkl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uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuv',\n disabled: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgAIDBmgatGsRz4EOeLeNIaoAK4IoTYYcSaB6SIAgABANuEAAGwA4CZYcAA2AHATLwCkaABlQgoArnq9DwBAACAbch4AgABANuZAAQQVcw4AQQVcH4AOBDut5h5lXWn9tfoTCAFpcXTwIwAIBExOCkcZ4QItHXzzA41AEvZgwSXTL//x2AIYE8HHbnwLhNOR539HJQyAaKcqPtQcwCVj94YAFoaXVQUzrsCRB/9/AAuDkyBD/rKBQuBgU/dzrBAEZpphAvPAATjMf9dAh8ixQOxhpLUMArHgIH/vHYADQK0aHw4EAACAXBfepAdQAXECCkdJJgsmJpZAALgACATcAjNpgAaoQfw0RyDIACHEXOVAALgACATclQAC4AAgE3A4AQ4i5hwAhxFzHYACAMBDQCUih84AUyPVKvkE44cACXIlmH2MhSgAmhOL88YwskgR0Ag0TPvANTbaJLsZ+cD+JbfaIzlkCygK0c0/73QAGSw1BulYBuWZgRLcAAICP4GC1SAiBDKIFC8EHLUFQ4unmAPwBAQApVh7gt9NLRK0nnlCdgi2nDBGTfEAAFABAOl19AzyMAMr9zyoABgAAQAFidgO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rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrw=',\n waking: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJydddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcd/oH/47AAQBAGUBlZdJh5G9sxrA8HYAFbAAfbqQAFAm44CYXpxTUasssNGECENJJxx+tDI/9Ry8wBAOpYOO/f+DrFIcycwAAgIr3685zLaHgXN8IKM/o2feDBzo0Q4rHPBfP8AgCJWBh+7x2AAmAARwOv3gOBaB+1Tygfhc4CPXtgAOYUMFKd57jwafCAADYAcBMuQG07ivlZJgD8arZJMnA+ncYBgI5oNv/QcO3CKiGgDV78HQZJtQYpggXN/A67gggv/Cx97Fb6IAwEawG3/h2ADhnHDfssxKgIDDnB5vz8AE5hocrnuDCcsiQdcAAI0SyeaAI/r3/LDKKw0hoqq94YAWDbTjCEekj3v47AARgDA2nC+phF+FKPPlmgcoAHJCwOib+QsZpcAFQynCiPbJ1XgwJmExQr5/xbrIweDoOyFYDIYAQfe8NgAGmwTlgCXl4Z9/R2DQADTcH9YBry9ay7IAgHawHZ/qSoJjCQhxzw47AARgACAQFBfephQaQk9Bxxz+ErwAJTrkOCI6TmhoyQAAbADgJl0jlorTTSR594D0BpKKxXazT0AAy7yynAxBIE3/oSgKWDFpJANX9sGD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AAmkO4APxvh2AAiAAEA9IAARQDmQGaxcQU2GTKnhAMD1edgIvAo/E5vnsGpeH1KyYZ5QKaFqv/GAAF7QABAWXIkAAgPCSwDkEOQGTscjUFkQoWA0+AHmzIgOml+Awh37sN111111111111111111111111111111111111111111111111111111111111111133++/f77/ffvvrvDff7/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXg=',\n },\n h265: {\n reconnecting: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLjAFwEgEQEADwDgDgDgDQDJdd4QDwCwCwCgCgCQCQCQCQCYNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAV0oBlPeTVLjjiybt/79hr8egLGkfgRr4BMQkiA4Kvw8jeWLF+WRCVyWx9qYu5G9T5CK0jQtgcXtpIjS8fOF3Fs7TooZzWpZ8ReTWIvMHVoz1Khj/MspUENld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',\n sleeping: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLjAFwEgEQEADwDgDgDgDQDFjo4wDgCwCwCgCgCQCQCQCQCYNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAnJVW/5yV+komUy56aphjzFvh52pfB2PTfCwCh7ggQKBSBYFGbEzvodhyojZdGeFaSykK9Ax2TQRMp4vmNtSZ21tm+X8AAJbdd/W7GfmdQAeYoPMrNuiQvN/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',\n offline: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLiwLgSAiBAB4DgHAOAaAyPGfQGALAWAoBQCQEgJASAmNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAnIRJY7mt6jxDg1ZAEbP7/LhpeVRCRSHYPE9hJm0I9bQrAjX0yu6L7lrKwJuj+U+EieuvMXkRVod2huH5MMqkgjezPIXhMMMbzPxp9/3NjL4R6GTuXhhGpNVRUatKyGtGQprE67mXeVeyt8q8XH55OXhBv4tDkapTnVztyP476G0CbTtwCKWm1coovmbCwsGoeuxe1KH1XPfGSpVvJgH0eKir9DUKGvaq38A+K4vCdrPnW9h2AIgZQrFMG5esP5M5Vj+RViJ4rcaYrxWVJKxyOBSOjLp6asU5sEWJAjP0CgnmWFIIYk5t2zZ2KhVPRpZS2kZwJK+3NeZ9kVSFWLkmyzQtyHQl8Ucj2Qow+3sBQdhZKdqVYVJVVwTt/s/IObzDjXLhsl9qkhIpu1hikB7vq9GZzw/NJumDCpYEInYS9ZjrHx2jmMk0QXSKkQwL2RsB9I2l7bz6z5L1NT98KZZF4sTRSGmMlivbyZ6QH2AARLMbZsL9jTxFXmaSSc5pg1FlgnRRlrEqSt3S44/VRE6LcU54L45c/EJERdNI0lAT5iAga8pllYNnCugL/iwnq0lrXn+6HpHCRx2ajLvmql6dkwKGaZn1HSeHAUhn8rCMlgMxBIR5a4Qgehe/I8rWdm8cuZE+WzQHumkeUjfvLw5E31mquqpttU5jt1ADcJI3p//dEp0KJHGJ2wDkMeCzn9I+pOF8+hZI/LRnP8YiGf6oZuABjwZnG6hnjWnoq1wIB+/tQ7c5K6JESHleEBruHbZrCsW876YwkveQTUPVpWe/XuzgiZaJWDrWd6edJ/6Z4mcIVSTeqAV0gPOeeFlgEYI6cAB78bESFgmNZTukgcfdEw5rxFwECmrf98AabHx28Daoh6o1eP/iU0MEx+0lfwtn62vvLrkJr6LdqppJ/V4uthU+zSDDntLN3wOfBbSIBAXheiqHz9TB0PLj0IMQAcJFKvsGo1WIw9QO7tp/Fd6bENcv9tPTqJI08/spfWrW/0TE/MjfEN5FI7WN6jTWU7YiDaEJfBKSBQw2yQobgCB9hA2Hl+qG88UZhAg5oq1DfkvQyqGvDUjKT4RZim5X96qCV7rA+cHQbHM/dXNaWbv7Lncbm0+4wJ9E/VuB161yenwEq6JVlJpXEpZ4/D+cqmG9jf3ZPBBXvpzjoJR/As59Ju+izJ7rHmT2Gzwj6RsgBtYN6ljCTSxeTv6+RkWH8gjUq1BZPKbZQuSwrMpiVzy869A4FtxbTHDYig1FRfZtufurQyp7EVkIv1YRrQt9CQvdQnehenXY84QdXD3VA0X3P08Wj0oMK450ODnAIaGKgHM3z7zFBzJmZ4D+XC1owLRQ0Zj4AjPztk0HGezZczdyHnkQto7mkLcoGTDK3CATzvt8GNXOptD4/nV4M9E+jbvwWpOFViuWr+AqRwZZ8uC+1or4+3EZ2Vrvwg4AKB5PWsR+0lkUFWiLnkMyagxv8aU2+b/Cti0KlOUys0h1KactAoSxwN/JX0D7HnwTV96KAAAAwCmgAAAAwAD/BQmkkSAIeH2QL4F/A6HctVCKGX8yMdMaxWTbi3QOpN4Alk9HT68HOZZgaitJoPjH+HZc6411cnVrqfm9Iaio91UDA0UGzBk66tFpYSgBjNxkHN4cYfiwGvdz7XviLZ8O/doO+VIfXSRLBzypg46GnIJ/iGll8gtiV6ax3vU1yktNzcqMKzD9V8I1N+2yedgAM5M5BmZLSdVI9Tqn1CtDLReN6032lEHUdP0bWOOFwsuqiuBKESunznZIwiFJtuajlou+N4mSpHy0SlnJRRZTmHPdlJO3laXfzLuSiqAU4OZiJbOFPhg2DwnP+vKIHwIPGZSWW9NqqH3ZdEAaMjYbIa1vEJ0DJV+NuCr5xAmV3tILbuFFEwUCcJdbiRRAMtR1JxQ2JUlEN9L/RuBBx5bQZdd7QJhiYKNw/ssyKrDPAkyoSfGy7YR6ki6Nj3FCVNeyL3FDjvZ52487UGX5U8gP4uBp5E2r/hZyh0++PQtzgL2XbaUEw65Ztry7AQmeZo43NHtNX0juv0qLk90iAkKLgebAQS0RTWb4epVN47jXXMGWoDvTnQchHQsgXD67fj85ol2cvZm9dIv5cuX8nG3+3eRV4otZUTNr0JdaQO8+KCLlO5RM8hSavTSZ39LvLj8WdK96/B1D9RU7zaEDEamMceo9pgX44L+dwwjprLTVbTae5zSe8d3ielSzh77uhuzkqm42ZcPAOOVAPR5xjN6Ha4PPr4u5me1ioZS5iIw+uFOBFUKbex+iI/AK1q3vQFSIZ0g0HIQWXLFqInWc6bqE3lzCw4Cddnri6Znt8AdZScPqX7e/F2mUhMnCtJaOWgITt1f9bkUFnkiW+E1ZAAc+0wCmDP7I6+6nmgPjZXHOJq+Gfo5yHyOBFMoNjXJN0fZ7p4tQYIR3UgocweENGz1gLn1Pk4HodRvgNXy5YdxRIRyhqa5OL+BTCoML8oDXy9wed42esjF56MDhO2Kf0wpYoT4bNpbFoYsY7qxtF+omIMhXqS8I+PSsMh0Woo13avSa7OCo3WkTrspLvnuH7rhbVDgXS6nQPJodXz/7GA0xr9Ub1NZkJSNxKQQMi/e+Bj6XuDVH2lXa9h9TtRAlh9srl3rVItUnP227ZOHq6trhyUlLnTFlnroyDEZWYJd95sGQQAsD0kmHlfqXPR7PvtKoH1iuHKaSTLMmBE26TsrmB5ZBhc10GEH8at9RuaYxB1F//vNyhZQL5BHENtNa5akA9Wz6MxFAOs1Ab5suoLUxAvxIkXEmrrNEKlsg+jIfWLqFQM6PBMRyvmhH0vvkOtxlV9WlaqPvMhzJoXQY9K2xSxsYXkDbsugNXg8Ll7zDPW31e2gQUZimo4tFsJHa1MocIt5q1COaYN5wz9pKsE4aMBy0JsqLvr7/C/8JRPwhnh2YsatfbQkhW0XYrG6XdUN5+cARZeE3htgV7M4ZZvHH9Uq7JYrJmdJmspKxEBWAjtHg0HgME5a0nult4pUYId35Huitxo/JektWcz63gJzSu1h7opGDVUwhm0fp8nFoFgprwp1OKjTBzo/j2+Rr8dWKQNovcrNLJ8f+nkM/UTpejJwirJckIyOKzhtmhhug1wU2G/2mNaF30ZdTBp/H9p1+d8hASZfNeto3JmNZSrsoPFFZlfYNN9+KHxNSyNMlDxABkFzl7wN1rDrnt9n+A+raSJqVZT/xeFpief4TbTJr42q/5X/SpHcZrf5YQs0jR0Mvua8BvheJzjTdDQuuwJzuySbnJ8cyafEJNf9xKvhgoAzohE7DxXlC4vlUFsVOvYqh0aBwJXHAQSQHDTMA/4LIle0cJ59iYwGPbR7c+oi3T58YJ1VyBpwJ2gekbLGFnUHA8cVAUB8I4wcDHV/jdjErT/aSrIefqCKMJmEqazX5Z0PecB+sM0C7gBzsN86VU0IyILMTSEJuWgQFQuIfrCIdzApSF5hGM511/bNlNYlyiXAyBJDB+P2ogCucX2SfBR8moHx0EB5YumzDw54TwjUMvgSX+LyK0HWETf5Wrl1y7QS3WakcKshxUBsTx5KP//iLpWgj2tdTTZFgDDGahR/I1lCA8JqSzWmObHQlm3mSm2dDtXvzXo6rA8wCW6w7UiMx20nx/bxXbKoRu+ecHCQ+T4chv2Ko+Se4QCceUigLhDty8jdZsD8Z8u31mSP6wjLjwdIeymOSqmjk9QrkiybypmIrm7bMUA/oAAAAwAvnIinFF4ABwwAAAMAAAMAAAMAK2AAAAMAAAMAAAMAf4AAAAMAAAMAAAMBYwAAAwAAAwAAAwOyAAADAAADAAAG5AAAAwAAAwAADhgAAAMAAAMAABjQAAADAAADAAApIAAAAwAAAwAAPOCHAAADAAADAAADAAADAAADAAADAWk=',\n disabled: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLjAFwEgEQEADwDgDgDgDQDFlYmgDACwCwCgCgCQCQCQCQCYNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAnJXh0f6dh91ZYSfYA2WZGi8L4B4Km93Ubr5/EFPTj6FOBGEL/s1k6y5ZvJx9Rlg/VUGc79Ok5oWDJetrtsLEUH4QjulhGTw2WWHi1fKx08W4ltGWipQDk4q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',\n waking: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLiwLgSAiBAB4DgHAOAaAyfX/AGgLAWAoBQCQEgJASAmNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAMsbsDkcoZgDIVg0QtuHSsRxkPs93x2tkl6E1gXx0vsrK+j+vQJ0cELr/DHTvsNk9haL5Nc9sUfbRjjmhBTJTjTszXpCeksgb575CVY08mk/BPfZmrL7JLcxTIpD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',\n },\n}\n\nconst cache: Partial<Record<PlaceholderCodec, Partial<Record<PlaceholderKind, Buffer>>>> = {}\n\n/**\n * Get the pre-encoded placeholder frame for `(kind, codec)`. Buffer\n * is created once on first use per (codec, kind) pair and cached\n * permanently — safe to call from hot paths (~5-9KB per pair, ~60KB\n * worst-case across both codecs and all five states).\n */\nexport function getPlaceholderFrame(kind: PlaceholderKind, codec: PlaceholderCodec): Buffer {\n const codecCache = cache[codec] ?? (cache[codec] = {})\n const cached = codecCache[kind]\n if (cached) return cached\n const buf = Buffer.from(FRAME_B64[codec][kind], 'base64')\n codecCache[kind] = buf\n return buf\n}\n\n/**\n * Backwards-compatible alias for the legacy single-frame API. Returns\n * the H.264 `reconnecting` placeholder (the original \"black screen\n * during reconnect\" frame).\n *\n * @deprecated prefer `getPlaceholderFrame(kind, codec)` so the\n * consumer surfaces both the actual broker state and the matching\n * codec to the operator.\n */\nexport function getBlackPlaceholderFrame(): Buffer {\n return getPlaceholderFrame('reconnecting', 'h264')\n}\n","import type {\n IStreamBroker,\n StreamSource,\n StreamSourceType,\n EncodedPacket,\n DecodedFrame,\n DecodedAudioChunk,\n DecodeOptions,\n BrokerStatus,\n BrokerStats,\n Unsubscribe,\n IRestreamer,\n IScopedLogger,\n FrameFormat,\n} from '@camstack/types'\n\nimport { DecoderSessionProxy, type DecoderCapApi } from './decoder-session-proxy.js'\nimport type { AudioCodecCapApi } from './audio-codec-proxy.js'\nimport { AudioCodecSession } from './audio-codec-session.js'\nimport { parseAudioSpecificConfig } from '../rtsp/audio/aac-config.js'\nimport { FrameDropper } from './frame-dropper'\nimport { StreamPipeServer } from './stream-pipe-server'\nimport { EncodedRingBuffer } from './encoded-ring-buffer'\nimport { RtspRestreamer } from '../rtsp/rtsp-restreamer.js'\nimport { NativeRtspClient } from '../rtsp/rtsp-client.js'\nimport { Rfc4571Reader } from '../rtsp/rfc4571-reader.js'\nimport { RtmpReader } from '../rtmp/rtmp-reader.js'\nimport { RtpDepacketizer } from '../rtsp/rtp-depacketizer.js'\nimport type { DepacketizedNal } from '../rtsp/rtp-depacketizer.js'\nimport { AudioRtpDecoder } from '../rtsp/audio-rtp-decoder.js'\nimport type { AudioCodec } from '../rtsp/audio-rtp-decoder.js'\nimport { pcmToMulaw, downsampleUlaw } from './g711-mulaw.js'\nimport { getPlaceholderFrame, type PlaceholderKind, type PlaceholderCodec } from './placeholder-frames.js'\nimport { errMsg, maskUrlCredentials } from '@camstack/types'\nimport type { Sharp } from 'sharp'\n\ntype SharpFn = (input: Buffer | Uint8Array, opts?: Record<string, unknown>) => Sharp\n\nlet cachedSharp: SharpFn | null = null\nasync function getSharp(): Promise<SharpFn> {\n if (cachedSharp) return cachedSharp\n const mod = await import('sharp')\n cachedSharp = mod.default as unknown as SharpFn\n return cachedSharp\n}\n\n/**\n * In-place R↔B swap on a packed RGB24 buffer. Used by the broker's\n * per-frame conversion cache to satisfy `bgr` subscribers when the\n * decoder's canonical output is `rgb`. Returns a new Buffer so the\n * source frame stays untouched (other subscribers may still need it).\n */\nfunction swapRedBlue(rgb: Buffer): Buffer {\n const out = Buffer.allocUnsafe(rgb.length)\n for (let i = 0; i + 2 < rgb.length; i += 3) {\n out[i] = rgb[i + 2]!\n out[i + 1] = rgb[i + 1]!\n out[i + 2] = rgb[i]!\n }\n return out\n}\n\nconst DEFAULT_MAX_FPS = 5\nconst DEFAULT_SCALE = 1\n\nconst INITIAL_RECONNECT_DELAY_MS = 1_000\nconst MAX_RECONNECT_DELAY_MS = 30_000\nconst DECODER_TEARDOWN_GRACE_MS = 2_000\n/** Grace period before suspending after last consumer leaves. */\nconst SUSPEND_GRACE_MS = 5_000\n/** Timeout for push-mode sources: if no packet arrives within this window, emit error placeholder. */\nconst PUSH_STALL_TIMEOUT_MS = 15_000\n\n\ninterface DecodedSubscriber {\n readonly callback: (frame: DecodedFrame) => void\n readonly frameDropper: FrameDropper\n /** Caller-supplied identity — used by `listClients` for diagnostics. */\n readonly tag: string\n readonly maxFps: number\n /** Requested output format. Gray consumers don't need JPEG encoding. */\n readonly format: FrameFormat\n readonly subscribedAt: number\n framesDelivered: number\n framesDropped: number\n}\n\ninterface AudioSubscriber {\n readonly callback: (chunk: DecodedAudioChunk) => void\n readonly tag: string\n readonly subscribedAt: number\n chunksDelivered: number\n}\n\nexport class StreamBroker implements IStreamBroker {\n readonly deviceId: string\n\n private _status: BrokerStatus = 'idle'\n private source: StreamSource | undefined\n /**\n * Manager-supplied resolver that derives the current `StreamSource`\n * from the live cam-stream registry. When set, every dial attempt\n * goes through it so a re-publish (e.g. Reolink updating an\n * `rfc4571` URL after the lib's TCP server idle-tore-down and\n * rebound) is observed transparently — mirrors Scrypted's\n * `ensureRfcServer`-on-each-`getVideoStream` pattern. `null` for\n * tests and stand-alone callers; falls back to the source passed\n * to `start()`.\n */\n private sourceProvider: (() => StreamSource | null) | null = null\n /**\n * Manager-supplied notifier that asks the publishing addon to\n * refresh its loopback transport (e.g. re-run\n * `ensureRfc4571Server`). Called when a managed-loopback dial fails\n * — the publisher republishes with a fresh URL and the next\n * reconnect resolves it via `sourceProvider`. `null` when the\n * broker has no manager attached (tests).\n */\n private requestSourceRefresh: (() => void) | null = null\n /**\n * Last time we asked the publisher to refresh the source — used to\n * dedup the refresh notify. A single connect failure fans out into\n * two events (`socket.on('error')` → onError callback AND the\n * rejected `reader.connect()` promise → .catch); without dedup the\n * publisher's `publishToBroker` runs twice in the same tick.\n * Idempotent on the publisher side (the lib's shared pool catches\n * the second one as `awaiting in-flight create`), but the noise\n * isn't useful. 500 ms is wider than the worst-case fan-out delay.\n */\n private lastRefreshRequestAt = 0\n /** Detected or configured codec — persists across reconnects */\n private detectedCodec: string | undefined\n private decoderProxy: DecoderSessionProxy | null = null\n /** Current output format of the shared decoder session. */\n private decoderOutputFormat: FrameFormat | null = null\n /**\n * Moleculer nodeID of the decoder provider currently hosting\n * `decoderProxy`. Set from the `{ nodeId }` returned by\n * `decoderApi.createSession`; used by the broker-manager's\n * hwaccel-change subscription to decide which brokers need a forced\n * decoder rotation. Null when no shared decoder session exists.\n */\n private decoderNodeId: string | null = null\n private readonly decoderApi: DecoderCapApi | null\n private readonly audioCodecApi: AudioCodecCapApi | null\n private readonly logger: IScopedLogger | undefined\n private readonly startedAt: number = Date.now()\n\n private nativeClient: NativeRtspClient | null = null\n private rfc4571Reader: Rfc4571Reader | null = null\n private rtmpReader: RtmpReader | null = null\n private rtpDepacketizer: RtpDepacketizer | null = null\n private audioRtpDecoder: AudioRtpDecoder | null = null\n private audioCodecSession: AudioCodecSession | null = null\n private audioRtpSeen = 0\n /** Detected audio codec from RTSP DESCRIBE (e.g. 'PCMU', 'PCMA'). */\n private audioCodec: string | null = null\n /**\n * Full audio track snapshot from the SDP — surfaced in `BrokerStats.audio`\n * so the UI overview / audio-analyzer model picker can pre-select the\n * right model based on per-camera codec + sample rate.\n */\n private audioTrackInfo: { codec: string; sampleRate: number; channels: number; supported: boolean } | null = null\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined\n private placeholderTimer: ReturnType<typeof setInterval> | undefined\n private reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n /**\n * \"First video\" watchdog. Battery-cam rfc4571 sources sometimes\n * complete TCP handshake on the loopback but the lib's dedicated\n * session never delivers video bytes (camera fails to start the\n * upstream Baichuan stream after wake, or stream subscription gets\n * stuck). The user's manual workaround was to click restart on the\n * WebRTC viewer — that closed the broker, tore the lib's session\n * down, and on reopen the dedicated session attached cleanly.\n *\n * This watchdog automates that: it arms when the rfc4571 reader's\n * `onVideoTrack` fires (TCP up, SDP parsed) and cancels at the first\n * `onVideoRtp` callback. If it elapses without a single video RTP\n * packet, it destroys the reader, asks the publisher for a fresh\n * source, and re-dials — exactly what the manual restart did.\n */\n private firstVideoWatchdog: ReturnType<typeof setTimeout> | undefined\n /**\n * Wall-clock timeout for the first-video watchdog. Long enough that\n * a healthy live cam comfortably emits its first IDR within the\n * window (typical: ~1-2s after rfc4571 TCP connect on PoE cams,\n * ~2-5s on battery cams that need to align stream subscription with\n * the video pipeline post-wake), but short enough that a stuck\n * dedicated session is detected before the WebRTC client gives up\n * on the offer (Chrome's default ICE-disconnect timeout is ~15s).\n */\n private static readonly FIRST_VIDEO_TIMEOUT_MS = 8_000\n private manualStop = false\n private stopping = false\n private decoderTeardownTimer: ReturnType<typeof setTimeout> | undefined\n\n private restreamers: readonly IRestreamer[] = []\n private readonly pipeServer: StreamPipeServer\n private readonly rtspRestreamer: import('../rtsp/rtsp-restreamer.js').RtspRestreamer\n private readonly preBuffer: EncodedRingBuffer\n\n private readonly encodedCallbacks = new Set<(packet: EncodedPacket) => void>()\n /**\n * Source-RTP video subscribers — receive raw on-wire RTP bytes\n * (no depacketization). Used by the WebRTC server's H.265 path to\n * feed a packet-level repacketizer. See `onVideoRtp`.\n */\n private readonly rtpVideoCallbacks = new Set<(rtpData: Buffer) => void>()\n /**\n * Codec parameter sets harvested from the camera's SDP (a=fmtp\n * sprop-vps / sprop-sps / sprop-pps). Cameras that ONLY put their\n * VPS/SPS/PPS in the SDP (Reolink high-profile streams, IP-cam\n * vendors that strip param sets from the wire) leave WebRTC sessions\n * without an HEVC decoder configuration — Chrome receives every IDR\n * but VideoToolbox never initialises.\n *\n * We capture them in `onVideoTrack` and replay them as a synthetic\n * EncodedPacket the moment a new encoded-data subscriber registers\n * AND on every keyframe whose NAL stream is missing them. This way\n * `session.ts` populates `lastVps/Sps/Pps` regardless of how the\n * camera ships its parameter sets.\n */\n private sdpParameterSets: ReadonlyArray<Buffer> | null = null\n /**\n * Per-device streaming debug flag. Gates verbose info-level logs\n * around RTP/encoded subscriber lifecycle, SDP param set replay,\n * and codec parameter handling. Toggled via the device override\n * `streamingDebug` field. Off by default — production logs stay\n * quiet unless an operator explicitly enables debug for one device.\n */\n private streamingDebug = false\n private readonly decodedSubscribers = new Map<symbol, DecodedSubscriber>()\n private readonly audioSubscribers = new Map<symbol, AudioSubscriber>()\n /** Stored options from first onDecodedFrame call when codec was not yet known */\n private pendingDecodeOptions: DecodeOptions | undefined\n /** Pull mode decoder needs to wait for the first keyframe before opening */\n private pendingPullModeOpen: (() => void) | null = null\n private firstKeyframeReceived = false\n /** Retry handle for \"no decoder provider yet\" — addons register in\n * arbitrary order, the broker must wait rather than fail. Cancelled\n * on decoder attach or broker destroy. */\n private decoderResolveRetryTimer: ReturnType<typeof setTimeout> | undefined\n /** Attempt counter for the retry loop above — feeds bounded backoff. */\n private decoderResolveAttempts = 0\n\n /** Timer for push-mode stall detection. */\n private pushStallTimer: ReturnType<typeof setTimeout> | undefined\n /** Whether pre-buffer counts as demand (keeps the stream alive even with 0 subscribers). */\n private _preBufferEnabled = true\n /** Timer for delayed suspend after last consumer leaves. */\n private suspendTimer: ReturnType<typeof setTimeout> | undefined\n /** True while the stream is suspended (ffmpeg killed, no RTSP connection). */\n private _suspended = false\n\n /** Debug counters for decoder troubleshooting */\n private decoderPushCount = 0\n private decoderFrameCount = 0\n /** Counter for video RTP packets — drives the milestone diagnostic\n * log alongside `audioRtpSeen`, so a wake-up cycle that brings audio\n * back but never video is observable from the structured logs. */\n private videoRtpSeen = 0\n /** Cached decoder stats — updated by the polling loop, read synchronously by getStats(). */\n private cachedDecoderStats: import('@camstack/types').DecoderStats | undefined\n\n /** Tracking flags set synchronously by the RTP depacketizer callback. */\n private _lastNalKeyframe = false\n private _lastNalParamSet = false\n\n /** Stream stats tracking */\n private totalBytes = 0\n private bytesInWindow = 0\n private packetsInWindow = 0\n private windowStartMs = Date.now()\n private lastKeyframeMs = 0\n private idrIntervalMs = 0\n private bitrateKbps = 0\n private encodedInputFps = 0\n private packetCount = 0\n /**\n * Timestamp of the most recent video packet observed on this broker.\n * Used by the manager-level watchdog to drive `stream.online` /\n * `stream.offline` transitions. `0` until the first packet arrives.\n */\n private lastPacketAt = 0\n\n /** Default pre-buffer duration in seconds */\n static readonly DEFAULT_PRE_BUFFER_SEC = 10\n /** Maximum allowed pre-buffer duration in seconds */\n static readonly MAX_PRE_BUFFER_SEC = 30\n\n constructor(\n deviceId: string,\n decoderApi: DecoderCapApi | null,\n logger?: IScopedLogger,\n audioCodecApi?: AudioCodecCapApi | null,\n ) {\n this.deviceId = deviceId\n this.decoderApi = decoderApi\n this.audioCodecApi = audioCodecApi ?? null\n this.logger = logger\n this.pipeServer = new StreamPipeServer(logger?.child('pipe-server'))\n this.rtspRestreamer = new RtspRestreamer(deviceId)\n if (logger) this.rtspRestreamer.setLogger(logger.child('rtsp'))\n this.preBuffer = new EncodedRingBuffer(StreamBroker.DEFAULT_PRE_BUFFER_SEC)\n\n // Wire client-count callbacks so demand tracking reacts to RTSP/pipe changes\n this.rtspRestreamer.onSessionCountChanged(() => this.checkDemand())\n this.pipeServer.onClientCountChanged(() => this.checkDemand())\n }\n\n get status(): BrokerStatus {\n return this._status\n }\n\n /**\n * Ms epoch of the most recent video packet observed by the broker.\n * `0` if none yet. Read by the manager-level health watchdog.\n */\n getLastPacketAt(): number {\n return this.lastPacketAt\n }\n\n /** Source type of the active stream (rtsp/push/rtmp/placeholder/error). */\n getActiveSourceType(): string {\n return this.source?.type ?? 'unknown'\n }\n\n /**\n * Diagnostic snapshot of the active source for log-meta enrichment.\n * Always returns a stable shape so log readers can rely on the keys\n * being present whenever a connect / read error is reported. URL is\n * masked so credentials never reach disk.\n */\n private sourceMetaForLog(): {\n brokerId: string\n sourceType: string\n url: string\n } {\n const src = this.source\n const url = src && src.type !== 'placeholder' && src.type !== 'push' && src.type !== 'push-rtp'\n ? maskUrlCredentials(src.url ?? '')\n : ''\n return {\n brokerId: this.deviceId,\n sourceType: src?.type ?? 'unknown',\n url,\n }\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n }\n\n /**\n * Inject a manager-owned closure that derives the current\n * `StreamSource` from the live cam-stream registry. Every dial\n * attempt (initial start, scheduled reconnect, demand-driven resume)\n * routes through this resolver so URL changes from a re-publish are\n * picked up transparently. Calling without a provider keeps the\n * legacy \"use the source from start()\" behaviour for tests.\n */\n setSourceProvider(provider: () => StreamSource | null): void {\n this.sourceProvider = provider\n }\n\n /**\n * Inject a manager-owned notifier the broker fires when a\n * managed-loopback source (`rfc4571`) dial fails. The notifier\n * asks the publishing addon to republish with a fresh transport;\n * the next reconnect picks it up via `sourceProvider`.\n */\n setRequestSourceRefresh(notify: () => void): void {\n this.requestSourceRefresh = notify\n }\n\n /**\n * Fire `requestSourceRefresh` at most once per ~500 ms. The\n * dial-failure path fans into onError + reader.connect().catch; we\n * only want one round-trip to the publisher per real failure.\n */\n private notifySourceRefresh(): void {\n if (!this.requestSourceRefresh) return\n const now = Date.now()\n if (now - this.lastRefreshRequestAt < 500) return\n this.lastRefreshRequestAt = now\n this.requestSourceRefresh()\n }\n\n /**\n * Manager-side kick: the publisher just re-published with a fresh\n * URL after a connect-fail / refresh round-trip (e.g. Reolink\n * battery cam finished waking and bound a new rfc4571 port). Cancel\n * any pending exponential-backoff retry, reset the backoff so the\n * next failure starts from `INITIAL_RECONNECT_DELAY_MS` again, and\n * arm a fast retry. No-op when the broker isn't currently waiting\n * for a reconnect (already dialing, idle, or stopped).\n *\n * Without this, on a battery-cam wake-up the broker stays on its\n * exponential schedule (1s → 2s → 4s …) even though the publisher\n * has already published a working URL — wasting up to a full\n * `MAX_RECONNECT_DELAY_MS` window dialing stale loopback ports.\n */\n kickReconnect(): void {\n if (this.manualStop || this.stopping || !this.source) return\n if (!this.hasDemand()) return\n if (!this.reconnectTimer) return\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.lastRefreshRequestAt = 0\n this.logger?.info('reconnect kicked — publisher refreshed source')\n this.scheduleReconnect()\n }\n\n /**\n * Resolve the source to use for the next dial attempt. Prefers the\n * manager-supplied resolver — re-derives from the cam-stream\n * registry, so a re-publish that updated the URL is honoured on the\n * very next dial. Falls back to the cached `this.source` (set at\n * `start()` time) when no resolver is wired.\n */\n private resolveCurrentSource(): StreamSource | null {\n if (this.sourceProvider) {\n const fresh = this.sourceProvider()\n if (fresh) {\n this.source = fresh\n return fresh\n }\n }\n return this.source ?? null\n }\n\n\n\n async start(source: StreamSource): Promise<void> {\n this.source = source\n this.manualStop = false\n this.stopping = false\n\n // Seed `detectedCodec` from the source's declared `videoCodec`\n // so consumers (notably `BrokerWebrtcServer.createSession`) see\n // the right codec immediately — without waiting for the reader to\n // parse the SDP track. The race used to manifest on `adaptive`\n // selection: a fresh session created against a still-suspended /\n // connecting rfc4571 broker fell back to `'h264'` from\n // `getStats().codec === undefined`, negotiated an H.264 SDP, then\n // received H.265 packets once the reader got going → black\n // viewport. The SDP `onVideoTrack` callback overwrites this later\n // if it disagrees with the publisher's declaration.\n if (source.videoCodec) {\n this.detectedCodec = source.videoCodec\n }\n\n this.logger?.info('Broker starting', {\n meta: {\n sourceType: source.type,\n ...(source.type !== 'placeholder' ? { url: maskUrlCredentials(source.url) } : {}),\n ...(this.detectedCodec ? { codec: this.detectedCodec } : {}),\n },\n })\n\n await this.pipeServer.start()\n this.logger?.debug(\n 'Pipe server started',\n { meta: { url: this.pipeServer.getUrl() } },\n )\n\n // Strict on-demand semantics for every pull source:\n //\n // - `rtsp` / `rtmp` are dialed lazily because hammering a remote\n // camera with reconnect storms when nobody's watching is\n // useless and bandwidth-expensive.\n // - `rfc4571` is ALSO dialed lazily — same Scrypted prebuffer-\n // mixin contract. Holding the loopback open keeps the lib's\n // upstream Baichuan socket alive (good for reaction time) but\n // violates the user-visible rule \"stream open only when\n // someone's watching\". The lib's `ensureRfc4571Server` self-\n // heals if its TCP server idle-tore-down between dials, and\n // the manager's `sourceProvider` re-resolves a fresh URL on\n // every dial — a re-publish after recreate is observed\n // transparently.\n //\n // `checkDemand()` resumes the source the moment a subscriber\n // arrives or the pre-buffer flips on (both feed `hasDemand`).\n const isLazyDialSource =\n source.type === 'rtsp'\n || source.type === 'rtmp'\n || source.type === 'rfc4571'\n if (isLazyDialSource && !this.hasDemand()) {\n this._suspended = true\n this._status = 'idle'\n this.logger?.info('Broker registered idle — no demand, source dial deferred')\n return\n }\n\n this._status = 'connecting'\n\n if (source.type === 'rtsp') {\n this.startRtspReader(source)\n } else if (source.type === 'rfc4571') {\n this.startRfc4571Reader(source)\n } else if (source.type === 'placeholder') {\n this.startPlaceholderReader(source)\n } else if (source.type === 'push' || source.type === 'push-rtp') {\n // Push modes: the camera provider addon delivers data via\n // pushEncodedPacket() (and pushVideoRtp() for `push-rtp`).\n // Codec already seeded from `source.videoCodec` at the top of\n // `start()` — the push branch used to repeat that assignment;\n // it's redundant now and removed for clarity.\n this._status = 'streaming'\n this.logger?.info('Broker in push mode — waiting for data', {\n meta: { sourceType: source.type, codec: this.detectedCodec ?? null },\n })\n this.resetPushStallTimer()\n } else if (source.type === 'rtmp') {\n this.startRtmpReader(source)\n } else {\n // Unknown source type — assume external push.\n this._status = 'streaming'\n }\n }\n\n async stop(): Promise<void> {\n this.logger?.info('Broker stopping')\n this.stopping = true\n this.manualStop = true\n this._status = 'stopped'\n\n this.destroyNativeClient()\n this.destroyRtmpReader()\n this.stopErrorPlaceholder()\n this.cancelFirstVideoWatchdog()\n\n if (this.placeholderTimer) {\n clearInterval(this.placeholderTimer)\n this.placeholderTimer = undefined\n }\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n if (this.suspendTimer) {\n clearTimeout(this.suspendTimer)\n this.suspendTimer = undefined\n }\n\n if (this.pushStallTimer) {\n clearTimeout(this.pushStallTimer)\n this.pushStallTimer = undefined\n }\n\n if (this.decoderProxy) {\n await this.destroyDecoder()\n }\n\n await this.pipeServer.stop()\n this.rtspRestreamer.destroy()\n this.preBuffer.clear()\n\n this.pendingParamNals = []\n this.pendingPullModeOpen = null\n this.firstKeyframeReceived = false\n this.encodedCallbacks.clear()\n this.decodedSubscribers.clear()\n this.audioSubscribers.clear()\n }\n\n // ── Auto-suspend / resume ─────────────────────────────────────────────\n\n /** Whether the broker is currently suspended (idle, no RTSP connection). */\n get suspended(): boolean { return this._suspended }\n\n /** Set whether pre-buffer counts as demand. When false and no other consumers, the broker suspends. */\n setPreBufferEnabled(enabled: boolean): void {\n this._preBufferEnabled = enabled\n this.checkDemand()\n }\n\n /** True when at least one consumer needs the stream alive. */\n private hasDemand(): boolean {\n if (this.encodedCallbacks.size > 0) return true\n if (this.rtpVideoCallbacks.size > 0) return true\n if (this.decodedSubscribers.size > 0) return true\n // Audio analysis subscribers count as demand. Without this, a\n // camera with audio classification enabled but no decoded-video\n // consumer (motion-wasm off + motion phase idle) suspends the\n // broker between motion bursts; audio chunks stop flowing and\n // `device.state.audioMetrics` stays frozen — the operator sees a\n // stale \"X seconds ago\" chip in the UI even though the camera is\n // online and audible. Mirrors video demand semantics.\n if (this.audioSubscribers.size > 0) return true\n if (this.rtspRestreamer.getSessionCount() > 0) return true\n if (this.pipeServer.getClientCount() > 0) return true\n if (this._preBufferEnabled && this.preBuffer.getDuration() > 0) return true\n return false\n }\n\n /**\n * Check demand and schedule suspend/resume. Called after every subscriber\n * add/remove and after pre-buffer enable/disable.\n */\n /**\n * Pick the decoder's native output format given the current subscriber\n * mix. Phase 3 collapsed three concerns into this one decision:\n *\n * 1. Avoid wasted CPU when subscribers are homogeneous — `gray`-\n * only stays `gray` (motion-only camera), `jpeg`-only stays\n * `jpeg` (no detection consumer joined yet, broker doesn't have\n * to encode either).\n * 2. Pick the cheapest *canonical* mode for mixed subscribers —\n * `rgb` is the universal source for the broker-side conversion\n * cache: gray is one luminance pass, jpeg is one sharp encode,\n * bgr is a channel swap. Producing JPEG decoder-side and then\n * *decoding back* to feed a raw consumer would cost more than\n * both consumers combined.\n * 3. Stay stable when there are no subscribers — defaults to\n * `jpeg` so the legacy \"no subscriber yet\" code path keeps\n * behaving as it did pre-refactor.\n */\n private resolveDecoderFormat(): FrameFormat {\n if (this.decodedSubscribers.size === 0) return 'jpeg'\n let allGray = true\n let allJpeg = true\n for (const sub of this.decodedSubscribers.values()) {\n if (sub.format !== 'gray') allGray = false\n if (sub.format !== 'jpeg') allJpeg = false\n }\n if (allGray) return 'gray'\n if (allJpeg) return 'jpeg'\n return 'rgb'\n }\n\n /**\n * Check if the decoder's output format needs upgrading because a new\n * subscriber requires a richer format (e.g. gray → jpeg when detection\n * joins after motion). Calls updateConfig on the decoder proxy to\n * reinitialize the scaler without recreating the session.\n */\n private maybeUpgradeDecoderFormat(): void {\n if (!this.decoderProxy || !this.decoderOutputFormat) return\n const needed = this.resolveDecoderFormat()\n if (needed === this.decoderOutputFormat) return\n this.logger?.info('upgrading decoder output format', {\n meta: { from: this.decoderOutputFormat, to: needed },\n })\n this.decoderOutputFormat = needed\n this.decoderProxy.updateConfig({ outputFormat: needed }).catch((err: unknown) => {\n this.logger?.warn('decoder format upgrade failed', { meta: { error: errMsg(err) } })\n })\n }\n\n /**\n * Fan a single decoded frame out to every active subscriber. Each\n * subscriber may have requested a different `format` than the one the\n * decoder is currently producing — the per-frame `convertedCache` map\n * memoises any format the decoder didn't already supply, so multiple\n * subscribers asking for the same target share the conversion.\n *\n * Conversion paths covered today:\n * - rgb → jpeg (sharp encode)\n * - rgb → bgr (R/B channel swap, in-process)\n * - rgb → gray (BT.601 luminance)\n * - rgb → yuv420 (sharp toFormat)\n * - gray → jpeg (sharp encode 1ch)\n * - jpeg → rgb / bgr / gray / yuv420 — not supported in Phase 3.\n * Decoder picks `jpeg` as canonical only when ALL subscribers\n * want jpeg, so this branch should never trigger. Such a request\n * is logged and the subscriber receives the JPEG passthrough as a\n * graceful fallback (caller is expected to re-decode).\n */\n private fanoutDecodedFrame(frame: DecodedFrame): void {\n const convertedCache = new Map<FrameFormat, DecodedFrame | Promise<DecodedFrame>>()\n convertedCache.set(frame.format, frame)\n for (const subscriber of this.decodedSubscribers.values()) {\n if (!subscriber.frameDropper.shouldKeep()) {\n subscriber.framesDropped++\n continue\n }\n this.deliverConvertedFrame(subscriber, frame, convertedCache)\n }\n }\n\n private deliverConvertedFrame(\n subscriber: DecodedSubscriber,\n frame: DecodedFrame,\n cache: Map<FrameFormat, DecodedFrame | Promise<DecodedFrame>>,\n ): void {\n if (subscriber.format === frame.format) {\n subscriber.callback(frame)\n subscriber.framesDelivered++\n return\n }\n const cached = cache.get(subscriber.format)\n if (cached && !(cached instanceof Promise)) {\n subscriber.callback(cached)\n subscriber.framesDelivered++\n return\n }\n if (cached instanceof Promise) {\n cached.then((converted) => {\n subscriber.callback(converted)\n subscriber.framesDelivered++\n }).catch((err: unknown) => {\n subscriber.framesDropped++\n this.logger?.warn('frame conversion failed', {\n meta: { tag: subscriber.tag, target: subscriber.format, error: errMsg(err) },\n })\n })\n return\n }\n const conversion = this.convertFrame(frame, subscriber.format)\n cache.set(subscriber.format, conversion)\n conversion.then((converted) => {\n cache.set(subscriber.format, converted)\n subscriber.callback(converted)\n subscriber.framesDelivered++\n }).catch((err: unknown) => {\n subscriber.framesDropped++\n this.logger?.warn('frame conversion failed', {\n meta: { tag: subscriber.tag, target: subscriber.format, error: errMsg(err) },\n })\n })\n }\n\n private async convertFrame(source: DecodedFrame, target: FrameFormat): Promise<DecodedFrame> {\n if (source.format === target) return source\n const sharp = await getSharp()\n if (source.format === 'rgb' || source.format === 'gray') {\n const channels = source.format === 'gray' ? 1 : 3\n const raw = { width: source.width, height: source.height, channels: channels as 1 | 3 }\n const pipeline = sharp(source.data, { raw })\n if (target === 'jpeg') {\n const data = await pipeline.jpeg({ quality: 80, mozjpeg: false }).toBuffer()\n return { ...source, data, format: 'jpeg' }\n }\n if (target === 'bgr') {\n // Channel swap stays in-process — sharp's extractChannel + join\n // would require an extra encode/decode, so handle it directly.\n if (source.format !== 'rgb') {\n throw new Error(`bgr conversion requires rgb source, got ${source.format}`)\n }\n const data = swapRedBlue(source.data)\n return { ...source, data, format: 'bgr' }\n }\n if (target === 'gray') {\n const data = await pipeline.toColorspace('b-w').raw().toBuffer()\n return { ...source, data, format: 'gray' }\n }\n if (target === 'rgb') {\n // gray → rgb: replicate the single luminance channel into three\n // identical planes so consumers expecting RGB24 don't crash.\n const data = await pipeline.toColorspace('srgb').raw().toBuffer()\n return { ...source, data, format: 'rgb' }\n }\n if (target === 'yuv420') {\n const data = await pipeline.toColorspace('yuv').raw().toBuffer()\n return { ...source, data, format: 'yuv420' }\n }\n }\n if (source.format === 'jpeg') {\n // Decoder only picks `jpeg` when every subscriber wants jpeg,\n // so a non-jpeg request here is a misconfiguration. Log and pass\n // the JPEG through so the caller can decide what to do.\n this.logger?.warn('jpeg-source frame requested as non-jpeg — passthrough', {\n meta: { target },\n })\n return source\n }\n throw new Error(`unsupported conversion ${source.format} → ${target}`)\n }\n\n private checkDemand(): void {\n if (this.manualStop || this.stopping) return\n\n if (this.hasDemand()) {\n // Cancel pending suspend\n if (this.suspendTimer) {\n clearTimeout(this.suspendTimer)\n this.suspendTimer = undefined\n }\n // Resume if suspended — re-resolve the source from the manager\n // so a re-publish that landed during the idle window (e.g.\n // Reolink rebound the rfc4571 server on a different port) is\n // honoured on the very first dial of the resume.\n //\n // No proactive `notifySourceRefresh` here: forcing a publisher\n // re-publish on every resume would call `ensureApi` ->\n // `buildVideoStreamOptions` -> `ensureRfc4571Server` on the\n // provider side, which wakes battery cameras even when the\n // existing URL is still valid. The dial-failure path\n // (`startRfc4571Reader`'s `.catch`) already triggers a refresh\n // when the cached URL goes stale; that's the only case where\n // we actually need the publisher to wake the cam.\n if (this._suspended) {\n const fresh = this.resolveCurrentSource()\n if (fresh) {\n this._suspended = false\n this.logger?.info('demand detected — resuming stream')\n this.start(fresh).catch((err) => {\n this.logger?.error('resume failed', { meta: { error: errMsg(err) } })\n this._suspended = true\n this._status = 'idle'\n })\n }\n }\n } else if (!this._suspended) {\n // Schedule suspend after grace period\n if (!this.suspendTimer) {\n this.suspendTimer = setTimeout(() => {\n this.suspendTimer = undefined\n if (!this.hasDemand() && !this.manualStop && !this.stopping) {\n this.suspend()\n }\n }, SUSPEND_GRACE_MS)\n }\n }\n }\n\n /** Suspend the stream: kill ffmpeg/native client, clear buffers, go idle. Source is preserved for resume. */\n private suspend(): void {\n if (this._suspended) return\n this._suspended = true\n this._status = 'idle'\n\n this.logger?.info('no demand — suspending stream')\n\n this.destroyNativeClient()\n this.destroyRtmpReader()\n // Strict on-demand: drop the rfc4571 reader too. Holding the\n // loopback TCP connection keeps the lib's upstream Baichuan\n // socket pinned even with zero subscribers — exactly what the\n // user-visible \"stream open only on demand\" rule rejects. The\n // next demand triggers `start()` → `resolveCurrentSource()` →\n // dial against the (potentially refreshed) loopback URL.\n this.destroyRfc4571Reader()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n if (this.placeholderTimer) {\n clearInterval(this.placeholderTimer)\n this.placeholderTimer = undefined\n }\n\n this.preBuffer.clear()\n this.firstKeyframeReceived = false\n this.pendingParamNals = []\n this.rtspRestreamer.resetStreamState()\n\n // Reset stats\n this.totalBytes = 0\n this.bytesInWindow = 0\n this.packetsInWindow = 0\n this.packetCount = 0\n this.bitrateKbps = 0\n this.encodedInputFps = 0\n }\n\n /**\n * Declare push-source audio metadata. Push providers (Reolink Baichuan,\n * Frigate) call this BEFORE pushing the first audio EncodedPacket so\n * the broker can wire the audio-codec capability and emit decoded PCM\n * to audio subscribers + transcode to G.711 µ-law for WebRTC.\n *\n * Pass `null` to retract — broker tears down the audio decode session.\n */\n pushAudioInfo(info: import('@camstack/types').PushAudioInfo | null): void {\n if (this.stopping) return\n\n if (info === null) {\n if (this.audioCodecSession) {\n const session = this.audioCodecSession\n this.audioCodecSession = null\n void session.close().catch((err) => {\n this.logger?.warn('audio-codec: push session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n this.audioTrackInfo = null\n return\n }\n\n const codec = info.codec.toLowerCase()\n this.audioCodec = codec.toUpperCase()\n this.audioTrackInfo = {\n codec: this.audioCodec,\n sampleRate: info.sampleRate,\n channels: info.channels,\n supported: true,\n }\n\n // Native G.711 — no codec cap needed; pushEncodedPacket fans out\n // directly to encodedCallbacks.\n if (codec === 'pcmu' || codec === 'pcma') {\n this.logger?.info('push-audio: native codec — no decoder cap required', {\n meta: { codec, sampleRate: info.sampleRate, channels: info.channels },\n })\n return\n }\n\n if (!this.audioCodecApi) {\n this.logger?.warn('push-audio: codec needs audio-codec cap but cap is unavailable', {\n meta: { codec, sampleRate: info.sampleRate, channels: info.channels },\n })\n return\n }\n\n const cfg = this.buildAudioCodecSessionConfigFromPush(info)\n if (!cfg) {\n this.logger?.info('push-audio: codec not handled by cap — audio skipped', {\n meta: { codec, sampleRate: info.sampleRate, channels: info.channels },\n })\n return\n }\n\n if (this.audioCodecSession) {\n const prior = this.audioCodecSession\n this.audioCodecSession = null\n void prior.close().catch((err) => {\n this.logger?.debug('audio-codec: prior session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n\n this.audioCodecSession = new AudioCodecSession(\n this.audioCodecApi,\n cfg,\n this.buildAudioCodecEmitCallback(),\n this.logger?.child('audio-codec'),\n )\n this.logger?.info('push-audio: decode session armed', {\n meta: {\n codec: cfg.codec,\n sourceSampleRate: cfg.sourceSampleRate,\n sourceChannels: cfg.sourceChannels,\n },\n })\n }\n\n pushEncodedPacket(packet: EncodedPacket): void {\n if (this.stopping) return\n\n // Reset push-mode stall timer on every packet\n if (this.source?.type === 'push') {\n this.stopErrorPlaceholder()\n if (this._status === 'error') this._status = 'streaming'\n // Clear suspension if we were waiting on a battery camera to wake.\n if (this._suspended && this.source.allowStall) {\n this._suspended = false\n this.logger?.info('push source resumed after stall — first packet received')\n }\n this.resetPushStallTimer()\n }\n\n // Push audio: route non-native codecs (e.g. AAC) through the\n // audio-codec cap session if one was armed via pushAudioInfo. The\n // session emits decoded PCM to audioSubscribers and transcodes to\n // G.711 µ-law for WebRTC via the shared emit callback. Native\n // codecs (PCMU/PCMA) fall through to the encodedCallbacks fanout\n // below — no transcode required.\n if (packet.type === 'audio' && this.source?.type === 'push' && this.audioCodecSession) {\n const codec = packet.codec.toLowerCase()\n if (codec !== 'pcmu' && codec !== 'pcma') {\n this.audioCodecSession.pushRawFrame(packet.data)\n return\n }\n }\n\n // Track bitrate + IDR interval\n if (packet.type === 'video') {\n this.packetCount++\n this.totalBytes += packet.data.length\n this.bytesInWindow += packet.data.length\n this.packetsInWindow++\n this.lastPacketAt = Date.now()\n\n const now = Date.now()\n const windowMs = now - this.windowStartMs\n if (windowMs >= 2000) {\n this.bitrateKbps = Math.round((this.bytesInWindow * 8) / windowMs)\n this.encodedInputFps = (this.packetsInWindow / windowMs) * 1000\n this.bytesInWindow = 0\n this.packetsInWindow = 0\n this.windowStartMs = now\n }\n\n if (packet.keyframe) {\n if (this.lastKeyframeMs > 0) {\n this.idrIntervalMs = now - this.lastKeyframeMs\n }\n this.lastKeyframeMs = now\n }\n }\n\n // Buffer encoded video packets for pre-buffer (clip creation, late-join)\n if (packet.type === 'video') {\n this.preBuffer.push(packet)\n }\n\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n\n // Broadcast raw video bytes to TCP pipe clients (restreamers)\n if (packet.type === 'video') {\n this.pipeServer.broadcast(packet.data)\n\n // Feed RTSP restreamer (Annex-B mode only — in passthrough mode, raw RTP\n // is forwarded directly via pushRtpPassthrough in the onVideoRtp callback)\n if (!this.rtspRestreamer.rtpPassthroughMode) {\n this.rtspRestreamer.pushPacket(packet)\n }\n\n // Gate: wait for the first keyframe before sending data to decoder or opening pull mode.\n // H264 decoders need SPS/PPS (present in keyframes) to initialize.\n if (!this.firstKeyframeReceived && packet.keyframe) {\n this.firstKeyframeReceived = true\n this.logger?.info('First keyframe received — decoder can start')\n if (this.pendingPullModeOpen) {\n this.pendingPullModeOpen()\n this.pendingPullModeOpen = null\n }\n }\n }\n\n for (const restreamer of this.restreamers) {\n const streamId = `${this.deviceId}/video`\n restreamer.pushPacket(streamId, packet)\n }\n\n // Only push to decoder after the first keyframe — the decoder needs SPS/PPS to start.\n // Without this, streams that start mid-GOP silently produce 0 decoded frames.\n if (packet.type === 'video' && this.firstKeyframeReceived) {\n if (this.decoderProxy) {\n this.decoderPushCount++\n if (this.decoderPushCount === 1 || this.decoderPushCount % 500 === 0) {\n this.logger?.info('decoder push', {\n meta: {\n pushCount: this.decoderPushCount,\n size: packet.data.length,\n keyframe: packet.keyframe,\n decodedCount: this.decoderFrameCount,\n },\n })\n }\n this.decoderProxy.pushPacket(packet).catch((err: unknown) => {\n this.logger?.warn('decoder push error', { meta: { error: errMsg(err) } })\n })\n }\n }\n }\n\n onEncodedData(callback: (packet: EncodedPacket) => void): Unsubscribe {\n this.encodedCallbacks.add(callback)\n if (this.streamingDebug) {\n this.logger?.info('encoded subscriber added', { meta: { total: this.encodedCallbacks.size } })\n }\n\n // Seed the new subscriber with the SDP-derived param sets so\n // WebRTC sessions get VPS/SPS/PPS even when the camera doesn't\n // emit them in-band (Reolink high-profile streams).\n this.emitSdpParamSetsTo(callback)\n this.checkDemand()\n\n return () => {\n this.encodedCallbacks.delete(callback)\n if (this.streamingDebug) {\n this.logger?.info('encoded subscriber removed', { meta: { total: this.encodedCallbacks.size } })\n }\n this.checkDemand()\n }\n }\n\n /**\n * Emit the cached SDP param sets as a synthetic Annex-B EncodedPacket\n * to a single subscriber. No-op if no param sets are cached or no\n * codec is detected yet.\n */\n private emitSdpParamSetsTo(callback: (packet: EncodedPacket) => void): void {\n if (!this.sdpParameterSets || this.sdpParameterSets.length === 0) return\n const codec = this.detectedCodec\n if (!codec) return\n const startCode = Buffer.from([0x00, 0x00, 0x00, 0x01])\n const annexB = Buffer.concat(\n this.sdpParameterSets.flatMap((ps) => [startCode, ps]),\n )\n if (this.streamingDebug) {\n this.logger?.info('Emitting SDP param sets to encoded subscriber', {\n meta: {\n codec,\n psCount: this.sdpParameterSets.length,\n psSizes: this.sdpParameterSets.map((p) => p.length).join(','),\n psFirstBytesHex: this.sdpParameterSets.map((p) => p.subarray(0, 4).toString('hex')).join('|'),\n annexBLength: annexB.length,\n },\n })\n }\n callback({\n type: 'video',\n data: annexB,\n pts: 0,\n dts: 0,\n keyframe: false,\n codec,\n })\n }\n\n /** Replay SDP param sets to every existing encoded subscriber. */\n private replaySdpParamSetsToEncodedSubscribers(): void {\n if (!this.sdpParameterSets || this.encodedCallbacks.size === 0) return\n for (const cb of this.encodedCallbacks) this.emitSdpParamSetsTo(cb)\n }\n\n /**\n * Subscribers waiting on the SDP-derived VPS/SPS/PPS to land. The\n * H.265 source-RTP path needs to seed its `H265Repacketizer` with\n * these param sets so the AP codec packet can fire ahead of the\n * first IDR — without it Chrome's HEVC decoder never initialises on\n * cameras that only ship parameter sets in the SDP (Reolink high-\n * profile streams). Sessions created while the broker is still\n * dialing rfc4571 (battery-cam wake-up) have null `sdpParameterSets`\n * at create time; this subscription delivers them as soon as\n * `onVideoTrack` fires. Mirrors `replaySdpParamSetsToEncodedSubscribers`\n * for the source-RTP path.\n */\n private readonly sdpParameterSetsCallbacks = new Set<(ps: ReadonlyArray<Buffer>) => void>()\n\n /**\n * Subscribe to SDP-derived VPS/SPS/PPS deliveries. The callback\n * fires once immediately if the broker already has them (synchronous\n * delivery for late subscribers), and again every time\n * `onVideoTrack` repopulates them on a reader rebuild.\n */\n onSdpParameterSets(callback: (ps: ReadonlyArray<Buffer>) => void): Unsubscribe {\n this.sdpParameterSetsCallbacks.add(callback)\n if (this.sdpParameterSets && this.sdpParameterSets.length > 0) {\n try {\n callback(this.sdpParameterSets)\n } catch (err) {\n this.logger?.warn('sdp param-set subscriber threw on initial delivery', {\n meta: { error: errMsg(err) },\n })\n }\n }\n return () => {\n this.sdpParameterSetsCallbacks.delete(callback)\n }\n }\n\n /** Fan out the current SDP param sets to every waiting subscriber. */\n private replaySdpParamSetsToRtpSubscribers(): void {\n if (!this.sdpParameterSets || this.sdpParameterSetsCallbacks.size === 0) return\n const ps = this.sdpParameterSets\n for (const cb of this.sdpParameterSetsCallbacks) {\n try {\n cb(ps)\n } catch (err) {\n this.logger?.warn('sdp param-set subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n\n onVideoRtp(callback: (rtpData: Buffer) => void): Unsubscribe {\n this.rtpVideoCallbacks.add(callback)\n if (this.streamingDebug) {\n this.logger?.info('rtp video subscriber added', {\n meta: { total: this.rtpVideoCallbacks.size },\n })\n }\n this.checkDemand()\n return () => {\n this.rtpVideoCallbacks.delete(callback)\n if (this.streamingDebug) {\n this.logger?.info('rtp video subscriber removed', {\n meta: { total: this.rtpVideoCallbacks.size },\n })\n }\n this.checkDemand()\n }\n }\n\n getSdpParameterSets(): ReadonlyArray<Buffer> | null {\n return this.sdpParameterSets\n }\n\n getSourceType(): StreamSourceType | null {\n return this.source?.type ?? null\n }\n\n /**\n * True when the broker emits source RTP packets via `onVideoRtp`.\n * RTSP demuxes incoming RTP directly; rfc4571 (RTP-over-TCP framed\n * per RFC 4571) deserializes into the same wire shape and runs\n * through the same `buildRtpStreamCallbacks.onVideoRtp` path;\n * `push-rtp` sources synthesize it via `pushVideoRtp()`. Bare\n * `push` (AnnexB-only) and `rtmp` (no RTP wire) return false.\n *\n * Crucial for the WebRTC dispatch decision: H.265 sources marked\n * as RTP route through `H265Repacketizer` for source-RTP forwarding\n * (Chrome's HEVC depacketizer requires the original RTP header\n * layout); non-RTP sources fall back to AnnexB → `writeVideoNals`\n * with cached-keyframe-on-PLI. Marking rfc4571 as non-RTP put the\n * Reolink Baichuan H.265 native:main stream on the wrong path —\n * Chrome got stuck in a PLI loop because each cached re-send\n * collided with the live decode state.\n */\n isRtpSource(): boolean {\n const t = this.source?.type\n return t === 'rtsp' || t === 'rfc4571' || t === 'push-rtp'\n }\n\n /**\n * Push a raw RTP packet from an external `push-rtp` source. Fans\n * out to every `onVideoRtp` subscriber. Mirrors the dispatch the\n * native RTSP reader performs in its `onVideoRtp` callback. No-ops\n * when no subscribers are attached so providers can fire-and-forget.\n */\n pushVideoRtp(rtpData: Buffer): void {\n if (this.stopping) return\n if (this.rtpVideoCallbacks.size === 0) return\n for (const cb of this.rtpVideoCallbacks) {\n try {\n cb(rtpData)\n } catch (err) {\n this.logger?.warn('rtp video subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n\n /** Toggle the per-device streaming debug flag. */\n setStreamingDebug(enabled: boolean): void {\n this.streamingDebug = enabled\n }\n\n onDecodedFrame(callback: (frame: DecodedFrame) => void, options?: DecodeOptions): Unsubscribe {\n const maxFps = options?.maxFps ?? DEFAULT_MAX_FPS\n const subscriberId = Symbol('decoded-subscriber')\n const frameDropper = new FrameDropper(maxFps)\n const tag = options?.tag\n if (!tag) {\n this.logger?.warn(`onDecodedFrame called without tag — listClients will show 'unknown' (Phase 10: every caller must pass options.tag)`)\n }\n\n const subscriber: DecodedSubscriber = {\n callback,\n frameDropper,\n tag: tag ?? 'unknown',\n maxFps,\n format: options?.format ?? 'jpeg',\n subscribedAt: Date.now(),\n framesDelivered: 0,\n framesDropped: 0,\n }\n this.decodedSubscribers.set(subscriberId, subscriber)\n this.logger?.info('decoded subscriber added', {\n meta: { tag: subscriber.tag, total: this.decodedSubscribers.size, maxFps, format: subscriber.format },\n })\n this.checkDemand()\n\n // Cancel pending teardown if a new subscriber joins while grace period is active\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n // Create shared decoder on first subscriber (or reuse existing).\n // If codec is not yet known (probe in progress), defer creation until codec is detected.\n if (!this.decoderProxy && this.source) {\n const codec = this.detectedCodec ?? this.source.videoCodec\n if (codec) {\n this.createSharedDecoderSession(options)\n } else {\n this.pendingDecodeOptions = options\n this.logger?.info('Decoder creation deferred — waiting for codec probe')\n }\n } else if (this.decoderProxy) {\n // Decoder already running — check if new subscriber requires a format upgrade\n this.maybeUpgradeDecoderFormat()\n }\n\n return () => {\n this.decodedSubscribers.delete(subscriberId)\n this.logger?.info('decoded subscriber removed', { meta: { total: this.decodedSubscribers.size } })\n this.checkDemand()\n\n if (this.decodedSubscribers.size === 0 && this.decoderProxy) {\n this.logger?.info('last decoded subscriber left — scheduling decoder teardown', {\n meta: { graceMs: DECODER_TEARDOWN_GRACE_MS },\n })\n this.decoderTeardownTimer = setTimeout(() => {\n this.decoderTeardownTimer = undefined\n if (this.decodedSubscribers.size === 0 && this.decoderProxy) {\n this.logger?.info('decoder teardown — no subscribers reconnected')\n this.destroyDecoder()\n }\n }, DECODER_TEARDOWN_GRACE_MS)\n }\n }\n }\n\n onDecodedAudioChunk(\n callback: (chunk: DecodedAudioChunk) => void,\n options?: { readonly tag?: string },\n ): Unsubscribe {\n const tag = options?.tag\n if (!tag) {\n this.logger?.warn(`onDecodedAudioChunk called without tag — listClients will show 'unknown' (Phase 10)`)\n }\n const subscriberId = Symbol('audio-subscriber')\n const subscriber: AudioSubscriber = {\n callback,\n tag: tag ?? 'unknown',\n subscribedAt: Date.now(),\n chunksDelivered: 0,\n }\n this.audioSubscribers.set(subscriberId, subscriber)\n // Audio counts as demand (see hasDemand). Trigger the same\n // suspend/resume reconcile that video subscribers do — without\n // this, attaching to a suspended broker leaves it suspended and\n // no chunks ever arrive.\n this.checkDemand()\n\n return () => {\n this.audioSubscribers.delete(subscriberId)\n this.checkDemand()\n }\n }\n\n getStats(): BrokerStats {\n const decoderStats = this._suspended ? undefined : this.cachedDecoderStats\n\n return {\n status: this._suspended ? 'idle' : this._status,\n inputFps: decoderStats?.inputFps ?? this.encodedInputFps,\n decodeFps: decoderStats?.outputFps ?? 0,\n encodedSubscribers: this.encodedCallbacks.size,\n decodedSubscribers: this.decodedSubscribers.size,\n uptimeMs: Date.now() - this.startedAt,\n bitrateKbps: this.bitrateKbps,\n idrIntervalMs: this.idrIntervalMs,\n codec: this.detectedCodec ?? this.source?.videoCodec,\n totalBytes: this.totalBytes,\n packetCount: this.packetCount,\n rtspClients: this.rtspRestreamer.getSessionCount(),\n pipeClients: this.pipeServer.getClientCount(),\n preBufferSec: this.preBuffer.getDuration(),\n preBufferMs: this.preBuffer.getBufferedDurationMs(),\n preBufferPackets: this.preBuffer.getPacketCount(),\n decoderNodeId: this.decoderNodeId,\n audio: this.audioTrackInfo ?? null,\n }\n }\n\n /**\n * Per-broker consumer inventory (Phase 10). Returns the live subscriber\n * list grouped by transport — every RTSP client, decoded-frame\n * subscriber, and audio-chunk subscriber. Used by the admin UI's\n * Streams tab and by operators diagnosing \"phantom\" holders keeping a\n * broker open.\n */\n listClients(): {\n readonly rtsp: readonly {\n readonly sessionId: string\n readonly remoteAddr: string\n readonly playing: boolean\n readonly muted: boolean\n readonly connectedAt: number\n readonly lastRtpAt: number\n readonly bytesSent: number\n }[]\n readonly decoded: readonly {\n readonly tag: string\n readonly subscribedAt: number\n readonly maxFps: number\n readonly framesDelivered: number\n readonly framesDropped: number\n }[]\n readonly audio: readonly {\n readonly tag: string\n readonly subscribedAt: number\n readonly chunksDelivered: number\n }[]\n readonly pipeClients: number\n readonly encodedSubscribers: number\n } {\n return {\n rtsp: this.rtspRestreamer.listSessionInfos(),\n decoded: [...this.decodedSubscribers.values()].map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n maxFps: s.maxFps,\n framesDelivered: s.framesDelivered,\n framesDropped: s.framesDropped,\n })),\n audio: [...this.audioSubscribers.values()].map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n chunksDelivered: s.chunksDelivered,\n })),\n pipeClients: this.pipeServer.getClientCount(),\n encodedSubscribers: this.encodedCallbacks.size,\n }\n }\n\n /**\n * Force-disconnect a single consumer. Matches by channel:\n * - `rtsp`: by `sessionId` (calls `RtspRestreamer.killSession`).\n * - `decoded` / `audio`: by `tag` — drops every subscriber whose tag\n * matches, then re-evaluates demand so the decoder can wind down\n * if nothing else is holding it.\n * Returns `true` when at least one consumer was dropped.\n */\n killClient(channel: 'rtsp' | 'decoded' | 'audio', handle: string): boolean {\n if (channel === 'rtsp') {\n return this.rtspRestreamer.killSession(handle)\n }\n const subscribers = channel === 'decoded' ? this.decodedSubscribers : this.audioSubscribers\n const victims: symbol[] = []\n for (const [key, sub] of subscribers) {\n if (sub.tag === handle) victims.push(key)\n }\n if (victims.length === 0) return false\n for (const key of victims) subscribers.delete(key)\n this.logger?.info('subscribers force-disconnected', {\n meta: { channel, handle, killed: victims.length, remaining: subscribers.size },\n })\n this.checkDemand()\n return true\n }\n\n /**\n * Returns the local TCP URL that restreamers should connect to\n * instead of the camera's RTSP URL. This ensures the broker is\n * the sole reader from the camera.\n */\n getLocalStreamUrl(): string {\n return this.pipeServer.getUrl()\n }\n\n /** Get the RTSP restreamer instance (for RtspListenServer registration) */\n getRtspRestreamer(): RtspRestreamer {\n return this.rtspRestreamer\n }\n\n /** Returns the number of TCP pipe clients currently connected */\n getPipeClientCount(): number {\n return this.pipeServer.getClientCount()\n }\n\n /**\n * Get the pre-buffer: the most recent N seconds of encoded video packets,\n * starting from the last keyframe. Returns packets in chronological order.\n * Useful for clip creation (get the last N seconds on demand) and\n * late-joining consumers that need to start from a keyframe.\n */\n getPreBuffer(): readonly EncodedPacket[] {\n return this.preBuffer.getPackets()\n }\n\n /** Set the pre-buffer duration in seconds (clamped to 0..MAX_PRE_BUFFER_SEC). */\n setPreBufferDuration(seconds: number): void {\n const clamped = Math.max(0, Math.min(seconds, StreamBroker.MAX_PRE_BUFFER_SEC))\n this.preBuffer.setDuration(clamped)\n this.logger?.info('Pre-buffer duration set', { meta: { seconds: clamped } })\n }\n\n /** Get the current pre-buffer duration in seconds. */\n getPreBufferDuration(): number {\n return this.preBuffer.getDuration()\n }\n\n private startRtspReader(source: StreamSource): void {\n this.startNativeRtspReader(source)\n }\n\n /**\n * Native RTMP reader — pulls media from an RTMP URL, demuxes FLV tags\n * into AnnexB access units, and routes them through `pushEncodedPacket`\n * (same fan-out as Reolink Baichuan / Frigate push sources).\n *\n * Codec support: H.264 standard FLV + H.265 Enhanced FLV (FOURCC\n * 'hvc1' / 'hev1'). Audio: AAC only — non-AAC tracks are skipped.\n *\n * On terminal error: schedules a reconnect with the same exponential\n * backoff used by the RTSP path.\n */\n private startRtmpReader(source: StreamSource): void {\n this.logger?.info('starting RTMP reader', { meta: { url: maskUrlCredentials(source.url) } })\n\n this.destroyRtmpReader()\n\n const reader = new RtmpReader(source.url, this.logger?.child('rtmp'), {\n onVideoCodec: (codec) => {\n this.detectedCodec = codec\n this.logger?.info('rtmp: video codec detected', { meta: { codec } })\n },\n onEncodedPacket: (packet) => {\n if (this.stopping) return\n this.pushEncodedPacket(packet)\n },\n onAudioInfo: (info) => {\n if (this.stopping) return\n this.pushAudioInfo(info)\n },\n onPlaying: () => {\n this.logger?.info('rtmp: streaming')\n this._status = 'streaming'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.stopErrorPlaceholder()\n },\n onError: (error) => {\n if (this.manualStop) return\n this.logger?.warn('rtmp error', { meta: { error: error.message } })\n this.destroyRtmpReader()\n this._status = 'error'\n this.scheduleReconnect()\n },\n onTeardown: () => {\n this.rtmpReader = null\n },\n })\n\n this.rtmpReader = reader\n\n reader.connect().catch((err) => {\n if (this.manualStop || this._suspended) return\n this.logger?.warn('rtmp connect failed', {\n meta: { error: errMsg(err), ...this.sourceMetaForLog() },\n })\n this.destroyRtmpReader()\n this._status = 'error'\n this.scheduleReconnect()\n })\n }\n\n private destroyRtmpReader(): void {\n if (this.rtmpReader) {\n this.rtmpReader.destroy()\n this.rtmpReader = null\n }\n }\n\n /**\n * Native RTSP reader: connect directly to the camera, receive raw RTP.\n *\n * - Preserves RTP framing (no Annex-B corruption on low-bitrate streams)\n * - RTP passthrough to RTSP restreamer (zero re-packetization)\n * - No ffprobe needed (codec detected from SDP DESCRIBE)\n * - Lower latency (no ffmpeg buffering/probing)\n * - Native G.711 audio decode (PCMU/PCMA → PCM f32le 16kHz)\n *\n * On error: reconnects with exponential backoff, emits placeholder frames.\n */\n /**\n * Shared RTP-stream callback factory. Used by both the native RTSP\n * reader and the RFC 4571 reader — they ingest RTP from different\n * transports but the broker's downstream pipeline (depacketize →\n * AnnexB → prebuffer / pipe / decoder / restream) is identical.\n *\n * `label` is the source-tag we attach to log messages so it's clear\n * which transport surfaced the event. `onTerminate` is the\n * transport-specific cleanup invoked from `onError` (the native client\n * runs `destroyNativeClient`; the RFC 4571 reader closes the TCP\n * socket and clears its handle).\n */\n private buildRtpStreamCallbacks(\n label: string,\n onTerminate: () => void,\n ): import('../rtsp/rtsp-client.js').RtspClientCallbacks {\n return {\n onVideoTrack: (track, sdpText) => {\n this.detectedCodec = track.codec\n this.logger?.info('native: video track detected', {\n meta: {\n codec: track.codec,\n payloadType: track.payloadType,\n hasSdpParamSets: Boolean(track.codecParams?.parameterSets?.length),\n },\n })\n\n // Capture codec parameter sets from SDP. Replay these to any\n // already-registered encoded subscribers so WebRTC sessions\n // that connected before the SDP arrived still get the VPS/\n // SPS/PPS they need to initialise the HEVC decoder.\n if (track.codecParams?.parameterSets?.length) {\n this.sdpParameterSets = track.codecParams.parameterSets.map((p) => Buffer.from(p))\n this.replaySdpParamSetsToEncodedSubscribers()\n this.replaySdpParamSetsToRtpSubscribers()\n }\n\n // Set up RTP depacketizer for Annex-B output (prebuffer, pipe, decoder)\n this.rtpDepacketizer = new RtpDepacketizer(track.codec, (depacketized) => {\n this.handleDepacketizedNal(depacketized)\n })\n\n // Arm the watchdog: TCP is up + SDP parsed, so we now expect\n // RTP. If nothing arrives, force a reconnect from scratch.\n this.armFirstVideoWatchdog()\n\n // Switch restreamer to RTP passthrough mode\n this.rtspRestreamer.setCameraSdp(sdpText, track.codec, track.codecParams)\n\n // Create deferred decoder session if subscribers were waiting\n if (!this.decoderProxy && this.decodedSubscribers.size > 0 && this.pendingDecodeOptions !== undefined) {\n this.logger?.info('native: codec detected — creating deferred decoder', {\n meta: { codec: track.codec },\n })\n this.createSharedDecoderSession(this.pendingDecodeOptions)\n this.pendingDecodeOptions = undefined\n }\n },\n\n onVideoRtp: (rtpData) => {\n if (this.stopping) return\n\n this._status = 'streaming'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.stopErrorPlaceholder()\n // First RTP packet of this connection cycle — the watchdog\n // can stand down. Cheap to call when already cancelled.\n this.cancelFirstVideoWatchdog()\n\n // Diagnostic milestones: same cadence as `audio: rtp packet\n // received` so a wake-up cycle that brings audio back but\n // never video is visible side-by-side in the log. Without\n // this, on a battery cam wake-up there's no signal between\n // \"rfc4571: streaming\" and \"decoder push\" — we can't tell\n // if the camera is silent or only the consumer pipeline is\n // misconfigured.\n this.videoRtpSeen++\n if (this.videoRtpSeen === 1 || this.videoRtpSeen === 50) {\n this.logger?.info('video: rtp packet received', {\n meta: { seq: this.videoRtpSeen, bytes: rtpData.length },\n })\n }\n\n // Depacketize RTP → NALs for Annex-B consumers (prebuffer, pipe, decoder)\n // The depacketizer callback (handleDepacketizedNal) runs synchronously\n // within processPacket, so _lastNalKeyframe/_lastNalParamSet are set\n // before we reach the pushRtpPassthrough call below.\n this._lastNalKeyframe = false\n this._lastNalParamSet = false\n this.rtpDepacketizer?.processPacket(rtpData)\n\n // Forward raw RTP to RTSP restreamer (zero re-packetization)\n this.rtspRestreamer.pushRtpPassthrough(\n rtpData,\n this._lastNalKeyframe,\n this._lastNalParamSet,\n )\n\n // Fan out source RTP to direct-RTP subscribers (WebRTC\n // H.265 path uses this to feed a packet-level repacketizer).\n if (this.rtpVideoCallbacks.size > 0) {\n for (const cb of this.rtpVideoCallbacks) {\n try {\n cb(rtpData)\n } catch (err) {\n this.logger?.warn('rtp video subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n },\n\n onAudioTrack: (audioTrack) => {\n const codecUpper = audioTrack.codec.toUpperCase()\n this.audioCodec = codecUpper\n const supportedNative = codecUpper === 'PCMU' || codecUpper === 'PCMA'\n\n // Diagnostic: surface SDP fmtp keys + audio-codec cap availability\n // so we can tell at a glance why the broker did or didn't pick the\n // cap path for this camera.\n this.logger?.info('audio: track detected', {\n meta: {\n codec: audioTrack.codec,\n codecUpper,\n clockRate: audioTrack.clockRate,\n channels: audioTrack.channels,\n fmtpKeys: Object.keys(audioTrack.fmtp),\n hasConfigHex: 'config' in audioTrack.fmtp || 'CONFIG' in audioTrack.fmtp,\n audioCodecApiAvailable: this.audioCodecApi !== null,\n supportedNative,\n },\n })\n\n let supported = supportedNative\n let effectiveSampleRate = audioTrack.clockRate\n let effectiveChannels = audioTrack.channels\n\n if (supportedNative) {\n this.audioRtpDecoder = new AudioRtpDecoder(codecUpper as AudioCodec, (chunk) => {\n for (const sub of this.audioSubscribers.values()) {\n sub.callback(chunk)\n sub.chunksDelivered++\n }\n })\n this.logger?.info('native: audio decoder initialized', {\n meta: { codec: codecUpper, sampleRate: audioTrack.clockRate, channels: audioTrack.channels },\n })\n } else if (this.audioCodecApi) {\n const capCfg = this.buildAudioCodecSessionConfig(audioTrack)\n if (capCfg) {\n effectiveSampleRate = capCfg.sourceSampleRate\n effectiveChannels = capCfg.sourceChannels\n supported = true\n // Tear down any prior session before replacing — without\n // this an RTSP reconnect leaks the previous AudioCodecSession\n // (its poll timer keeps ticking and its upstream cap session\n // lingers until idle reap).\n if (this.audioCodecSession) {\n const prior = this.audioCodecSession\n this.audioCodecSession = null\n void prior.close().catch((err) => {\n this.logger?.debug('audio-codec: prior RTSP session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n this.audioCodecSession = new AudioCodecSession(\n this.audioCodecApi,\n capCfg,\n this.buildAudioCodecEmitCallback(),\n this.logger?.child('audio-codec'),\n )\n this.logger?.info('audio-codec: decode session armed', {\n meta: {\n codec: capCfg.codec,\n sourceSampleRate: capCfg.sourceSampleRate,\n sourceChannels: capCfg.sourceChannels,\n rfc3640: capCfg.rfc3640 ? 'aac-hbr' : 'one-frame',\n },\n })\n } else {\n this.logger?.info('audio-codec: codec not handled by cap — audio skipped', {\n meta: { codec: audioTrack.codec, sampleRate: audioTrack.clockRate, channels: audioTrack.channels },\n })\n }\n } else {\n this.logger?.info('native: audio codec not supported natively — audio skipped', {\n meta: { codec: audioTrack.codec, sampleRate: audioTrack.clockRate, channels: audioTrack.channels },\n })\n }\n\n this.audioTrackInfo = {\n codec: codecUpper,\n sampleRate: effectiveSampleRate,\n channels: effectiveChannels,\n supported,\n }\n },\n\n onAudioRtp: (rtpData) => {\n if (this.stopping) return\n this.audioRtpSeen++\n if (this.audioRtpSeen === 1 || this.audioRtpSeen === 50) {\n this.logger?.info('audio: rtp packet received', {\n meta: {\n seq: this.audioRtpSeen,\n bytes: rtpData.length,\n hasNativeDecoder: this.audioRtpDecoder !== null,\n hasCodecSession: this.audioCodecSession !== null,\n },\n })\n }\n this.audioRtpDecoder?.processPacket(rtpData)\n this.audioCodecSession?.processPacket(rtpData)\n\n // Emit raw G.711 audio payload as EncodedPacket so WebRTC and\n // other onEncodedData consumers can forward it without transcoding.\n // ONLY for native codecs (PCMU/PCMA) — AAC/Opus raw RTP payloads\n // are NOT G.711 and must go through audio-codec decode first.\n const isNativeAudioCodec = this.audioCodec === 'PCMU' || this.audioCodec === 'PCMA'\n if (isNativeAudioCodec && this.encodedCallbacks.size > 0 && rtpData.length > 12) {\n const payload = rtpData.subarray(12)\n if (payload.length > 0) {\n // Extract RTP timestamp (bytes 4-7, big-endian) for PTS\n const rtpTimestamp = rtpData.readUInt32BE(4)\n const pts = Math.floor(rtpTimestamp / 8) // 8kHz → ms\n const packet: EncodedPacket = {\n type: 'audio',\n data: payload,\n pts,\n dts: pts,\n keyframe: false,\n codec: this.audioCodec?.toLowerCase() ?? 'pcmu',\n }\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n }\n }\n\n // Forward raw audio RTP to RTSP restreamer for audio passthrough\n this.rtspRestreamer.pushAudioRtpPassthrough(rtpData)\n },\n\n onPlaying: () => {\n this.logger?.info(`${label}: streaming`)\n this._status = 'streaming'\n },\n\n onError: (error) => {\n if (this.manualStop) return\n this.logger?.warn(`${label} error`, {\n meta: {\n error: error.message,\n ...this.sourceMetaForLog(),\n },\n })\n onTerminate()\n this._status = 'error'\n // rfc4571 sources are loopback to a publisher-owned TCP\n // server. A read error after the lib's idle teardown means\n // the cached URL is stale — ask the publisher to republish\n // before the next reconnect tick.\n if (this.source?.type === 'rfc4571') {\n this.notifySourceRefresh()\n }\n this.scheduleReconnect()\n },\n\n onTeardown: () => {\n onTerminate()\n },\n }\n }\n\n private startNativeRtspReader(source: StreamSource): void {\n this.logger?.info('starting native RTSP reader')\n\n const client = new NativeRtspClient(\n {\n url: source.url,\n logger: this.logger?.child('rtsp-native'),\n },\n this.buildRtpStreamCallbacks('native RTSP', () => this.destroyNativeClient()),\n )\n\n this.nativeClient = client\n\n client.connect().catch((err) => {\n if (this.manualStop || this._suspended) return\n this.logger?.warn('native RTSP connect failed', {\n meta: { error: errMsg(err), ...this.sourceMetaForLog() },\n })\n this.destroyNativeClient()\n this._status = 'error'\n this.scheduleReconnect()\n })\n }\n\n /**\n * Handle a complete NAL from the RTP depacketizer.\n * Converts to EncodedPacket and feeds all Annex-B consumers\n * (prebuffer, pipe server, decoder, restreamers).\n *\n * Called synchronously from processPacket — sets _lastNalKeyframe and\n * _lastNalParamSet so the onVideoRtp callback can forward them to the\n * RTSP restreamer's pushRtpPassthrough.\n */\n /**\n * Pending parameter-set NALs (SPS/PPS for H.264, VPS/SPS/PPS for H.265).\n * Cameras emit these as separate RTP packets before the IDR/IRAP frame.\n * We buffer them here and prepend to the next VCL NAL so downstream\n * consumers (WebRTC, prebuffer, decoder) receive complete access units\n * with a single `EncodedPacket` that contains SPS+PPS+IDR together.\n */\n private pendingParamNals: Buffer[] = []\n\n /**\n * handleDepacketizedNal receives individual NALs from the RTP depacketizer\n * and emits them as EncodedPackets.\n *\n * Parameter-set NALs (SPS/PPS type 7/8 for H.264, VPS/SPS/PPS type 32/33/34\n * for H.265) are buffered and prepended to the next VCL NAL (slice/IDR).\n * This guarantees every keyframe EncodedPacket is a self-contained access\n * unit that a decoder can initialise from — critical for WebRTC (Chrome\n * can't decode an IDR without preceding SPS/PPS).\n *\n * The RTSP restreamer handles its own RTP passthrough separately and is\n * not affected by this merging.\n */\n private handleDepacketizedNal(depacketized: DepacketizedNal): void {\n const { nal, keyframe, parameterSet, timestamp } = depacketized\n const codec = this.detectedCodec ?? 'h264'\n\n // Track flags for the parent onVideoRtp call\n if (keyframe) this._lastNalKeyframe = true\n if (parameterSet) this._lastNalParamSet = true\n\n // Detect NAL type\n const isH264 = codec === 'h264'\n const nalType = isH264 ? (nal[0]! & 0x1f) : ((nal[0]! & 0x7e) >> 1)\n\n // H.264 parameter sets: SPS=7, PPS=8\n // H.265 parameter sets: VPS=32, SPS=33, PPS=34\n const isParamSet = isH264\n ? (nalType === 7 || nalType === 8)\n : (nalType === 32 || nalType === 33 || nalType === 34)\n\n if (isParamSet) {\n // Buffer parameter set — don't emit yet\n const startCode = Buffer.from([0x00, 0x00, 0x00, 0x01])\n this.pendingParamNals.push(Buffer.concat([startCode, nal]))\n return\n }\n\n // VCL NAL (slice/IDR) — prepend any buffered parameter sets\n const startCode = Buffer.from([0x00, 0x00, 0x00, 0x01])\n const hadParamSets = this.pendingParamNals.length > 0\n let annexBData: Buffer\n if (hadParamSets) {\n this.pendingParamNals.push(Buffer.concat([startCode, nal]))\n annexBData = Buffer.concat(this.pendingParamNals)\n this.pendingParamNals = []\n } else {\n annexBData = Buffer.concat([startCode, nal])\n }\n\n const pts = Math.floor(timestamp / 90) // 90kHz → ms\n\n const packet: EncodedPacket = {\n type: 'video',\n data: annexBData,\n pts,\n dts: pts,\n keyframe: keyframe || hadParamSets,\n codec,\n }\n\n // Feed all Annex-B consumers (prebuffer, pipe, decoder).\n // RTSP restream is handled separately via RTP passthrough in onVideoRtp.\n this.pushEncodedPacket(packet)\n }\n\n /**\n * Start emitting a pre-encoded H.264 keyframe at 1fps for an\n * explicit `placeholder`-typed source (i.e. disabled / not-yet-\n * configured cameras). The default reason is `'disabled'` because\n * the only path that reaches this is \"operator turned the device\n * off\" or \"device was created without credentials\"; callers who\n * arrive here via a different upstream state can adjust reason via\n * `setPlaceholderReason()` before calling.\n *\n * Frame is re-resolved on every tick from `placeholderReason` so\n * upstream state transitions (`disabled` → `offline`, etc.) propagate\n * within one second.\n */\n private startPlaceholderReader(_source: StreamSource): void {\n this.logger?.debug('Starting placeholder')\n\n if (this.placeholderReason === 'reconnecting') {\n // Default the reason to `'disabled'` for the explicit placeholder\n // source, but only if the caller hasn't already set something\n // more specific.\n this.placeholderReason = 'disabled'\n }\n this._status = 'streaming'\n\n // Emit immediately\n this.emitPlaceholderFrame()\n\n // Re-emit at 1fps so consumers see an active stream\n this.placeholderTimer = setInterval(() => {\n if (this.manualStop || this.stopping) {\n if (this.placeholderTimer) {\n clearInterval(this.placeholderTimer)\n this.placeholderTimer = undefined\n }\n return\n }\n this.emitPlaceholderFrame()\n }, 1000)\n }\n\n /**\n * Resolve the codec the placeholder frame should be encoded as.\n * Picks the broker's currently-detected codec when a reader has\n * already parsed the camera's SDP, otherwise falls back to the\n * source's published codec, otherwise H.264. The choice MUST match\n * the negotiated WebRTC session codec — sending an H.264 placeholder\n * over an H.265 RTP track corrupts the decoder state on Chrome and\n * leaves the viewer frozen on the last placeholder until the\n * session is recreated. Mirroring the broker codec keeps the\n * placeholder feed bit-stream-compatible across the same path the\n * live camera uses.\n */\n private placeholderCodec(): PlaceholderCodec {\n const codec = (this.detectedCodec ?? this.source?.videoCodec ?? '').toLowerCase()\n if (codec === 'h265' || codec === 'hevc') return 'h265'\n return 'h264'\n }\n\n /**\n * Push the current placeholder frame (selected by `placeholderReason`\n * + `placeholderCodec()`) to all encoded subscribers. No-op-safe to\n * call from any timer.\n */\n private emitPlaceholderFrame(): void {\n const codec: PlaceholderCodec = this.placeholderCodec()\n const packet: EncodedPacket = {\n type: 'video',\n data: getPlaceholderFrame(this.placeholderReason, codec),\n pts: Date.now(),\n dts: Date.now(),\n keyframe: true, // single-frame encode is always a keyframe\n codec,\n // Tag so consumers (notably the WebRTC encoded-data subscriber,\n // which keeps a sticky SPS/PPS to prepend to keyframes lacking\n // inline param sets) can drop the packet's parameter sets from\n // any per-stream cache they maintain. Without this tag, a\n // 1280×720 placeholder SPS gets prepended to the camera's real\n // (different resolution) IDR and Chrome's decoder freezes on\n // the last placeholder frame until the user manually restarts.\n isPlaceholder: true,\n }\n this.pushEncodedPacket(packet)\n }\n\n // ── First-video watchdog ──────────────────────────────────────────\n\n /**\n * Arm the first-video watchdog. Called when the rfc4571 reader\n * reports a working SDP (`onVideoTrack`), at which point the broker\n * has a live TCP connection to the lib's loopback server and is\n * waiting for the upstream Baichuan dedicated session to start\n * pushing RTP. No-op when already armed (so duplicate `onVideoTrack`\n * fires don't reset the timer).\n */\n private armFirstVideoWatchdog(): void {\n if (this.firstVideoWatchdog) return\n if (this.manualStop || this.stopping) return\n this.firstVideoWatchdog = setTimeout(() => {\n this.firstVideoWatchdog = undefined\n if (this.manualStop || this.stopping) return\n // Race-safe check: a packet may have arrived between the timer\n // firing and this callback running. Be permissive.\n if (this.videoRtpSeen > 0) return\n this.logger?.warn(\n 'first-video watchdog: no video RTP after rfc4571 connect — forcing reconnect',\n { meta: { timeoutMs: StreamBroker.FIRST_VIDEO_TIMEOUT_MS } },\n )\n // Reset videoRtpSeen so the next watchdog cycle gets a clean\n // measurement, and `video: rtp packet received` re-fires for\n // the fresh connection.\n this.videoRtpSeen = 0\n // Tear down the rfc4571 reader → lib detects TCP close →\n // releases its dedicated session → next dial triggers a fresh\n // session. This mirrors the user's \"click restart\" workaround.\n this.destroyRfc4571Reader()\n this._status = 'error'\n // Treat this as a fresh recovery cycle rather than a continuation\n // of an exponential-backoff sequence — the user noticed the\n // stuck state after wake-up, the manual restart resets backoff,\n // and we're emulating that.\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.lastRefreshRequestAt = 0\n // Provider needs to know its source might be stale (the lib's\n // server may have idle-torn-down between dial attempts).\n this.notifySourceRefresh()\n this.scheduleReconnect()\n }, StreamBroker.FIRST_VIDEO_TIMEOUT_MS)\n }\n\n /** Cancel the first-video watchdog. Idempotent. */\n private cancelFirstVideoWatchdog(): void {\n if (!this.firstVideoWatchdog) return\n clearTimeout(this.firstVideoWatchdog)\n this.firstVideoWatchdog = undefined\n }\n\n // ── Error/idle placeholder ────────────────────────────────────────\n\n /** Timer emitting error placeholder at 1fps while reconnecting. */\n private errorPlaceholderTimer: ReturnType<typeof setInterval> | undefined\n\n /**\n * Reason currently displayed by the placeholder image. Drives\n * `getPlaceholderFrame()` lookups so a sleeping cam shows\n * \"SLEEPING\", an offline cam shows \"OFFLINE\", and so on. Default\n * `'reconnecting'` matches the legacy single-frame behaviour.\n *\n * Mutated via `setPlaceholderReason()` from upstream surfaces (the\n * Reolink provider transitions on sleep events; the broker manager\n * may surface explicit disabled/offline states); the running\n * `errorPlaceholderTimer` re-reads it on every tick so transitions\n * appear within ~1 frame of the upstream signal.\n */\n private placeholderReason: PlaceholderKind = 'reconnecting'\n\n /**\n * Update the reason shown by the placeholder image. Idempotent —\n * a no-op when the reason hasn't changed. Safe to call from\n * upstream callers regardless of whether the placeholder timer is\n * currently active; the next tick (or next `startErrorPlaceholder`\n * invocation) picks up the new reason.\n */\n setPlaceholderReason(reason: PlaceholderKind): void {\n if (this.placeholderReason === reason) return\n this.placeholderReason = reason\n // If the placeholder is actively emitting, push a fresh frame so\n // the operator sees the new label immediately rather than waiting\n // for the next 1Hz tick. Cheap (~5-9KB per emit) and matches user\n // expectation when toggling state from the admin UI.\n if (this.errorPlaceholderTimer) {\n this.emitPlaceholderFrame()\n }\n }\n\n private stopErrorPlaceholder(): void {\n if (this.errorPlaceholderTimer) {\n clearInterval(this.errorPlaceholderTimer)\n this.errorPlaceholderTimer = undefined\n }\n }\n\n private scheduleReconnect(): void {\n if (this.manualStop || !this.source) {\n return\n }\n // Hard rule: a broker never holds an open dial loop when no client\n // is consuming the stream. Without demand, suspend instead of\n // re-arming the reconnect timer; the next subscriber triggers\n // `checkDemand()` → `start(this.source)` to dial again.\n if (!this.hasDemand()) {\n this._suspended = true\n this._status = 'idle'\n this.stopErrorPlaceholder()\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n this.logger?.info('Reconnect skipped — no demand, broker suspended')\n return\n }\n\n // Dedup: a single connect failure fans out into two notifications\n // (`socket.on('error')` → `onError` callback AND the rejected\n // `client.connect()` promise → `.catch`). Both end up here. When a\n // reconnect timer is already armed for the same failure window, do\n // nothing — otherwise every doubled call doubles the live timers,\n // and a few generations later one connect failure expands into\n // hundreds of scheduled retries firing in the same tick.\n if (this.reconnectTimer) {\n return\n }\n\n // Placeholder pump is intentionally NOT started during reconnect.\n // We tried both H.264 and H.265 cross-codec injection paths and\n // they confused Chrome's HEVC decoder on the H.265 RTP path\n // (placeholder SPS conflicting with the repacketizer's seeded\n // camera SPS). Reconnect/wake-up windows now leave the existing\n // last-frame on screen — the WebRTC viewer's UI overlay is the\n // surface for reconnect feedback. Placeholders remain only for\n // the explicit `placeholder` source type (disabled cameras),\n // handled by `startPlaceholderReader`.\n\n this.logger?.info(\n 'Reconnecting RTSP reader',\n { meta: { delayMs: this.reconnectDelayMs } },\n )\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined\n if (this.manualStop) return\n // Re-resolve the source on every reconnect so an URL change\n // landed while we were waiting (e.g. Reolink republished after\n // an `ensureRfc4571Server` recreate) is observed.\n const src = this.resolveCurrentSource()\n if (!src) return\n this._status = 'connecting'\n if (src.type === 'rtmp') {\n this.startRtmpReader(src)\n } else if (src.type === 'rfc4571') {\n this.startRfc4571Reader(src)\n } else {\n this.startRtspReader(src)\n }\n }, this.reconnectDelayMs)\n\n this.reconnectDelayMs = Math.min(\n this.reconnectDelayMs * 2,\n MAX_RECONNECT_DELAY_MS,\n )\n }\n\n /**\n * Reset the push-mode stall detection timer.\n * If no packet arrives within PUSH_STALL_TIMEOUT_MS, switch to error\n * state and emit placeholder frames so RTSP clients don't freeze.\n *\n * Sources flagged `allowStall: true` (battery cameras that\n * intentionally go silent between motion events) bypass the error\n * placeholder — the broker logs at debug, marks the broker\n * `suspended`, and waits silently for the next packet. The next\n * `pushEncodedPacket` clears the suspension and resumes streaming.\n */\n private resetPushStallTimer(): void {\n if (this.pushStallTimer) {\n clearTimeout(this.pushStallTimer)\n }\n this.pushStallTimer = setTimeout(() => {\n this.pushStallTimer = undefined\n if (this.stopping || this.manualStop) return\n if (this.source?.type !== 'push') return\n\n if (this.source?.allowStall) {\n this.logger?.debug('push source stall (allowStall=true) — suspending until next packet', {\n meta: { timeoutMs: PUSH_STALL_TIMEOUT_MS },\n })\n this._suspended = true\n return\n }\n\n this.logger?.warn('push source stalled — no data received', {\n meta: { timeoutMs: PUSH_STALL_TIMEOUT_MS },\n })\n this._status = 'error'\n // Placeholder intentionally not emitted — see scheduleReconnect\n // for the rationale (cross-codec issues on H.265 RTP path).\n // `_status='error'` is enough for stats consumers; UI overlay\n // handles operator-facing feedback.\n }, PUSH_STALL_TIMEOUT_MS)\n }\n\n private destroyDecoder(): Promise<void> {\n if (this.decoderResolveRetryTimer) {\n clearTimeout(this.decoderResolveRetryTimer)\n this.decoderResolveRetryTimer = undefined\n }\n this.decoderResolveAttempts = 0\n const proxy = this.decoderProxy\n this.decoderProxy = null\n this.decoderNodeId = null\n this.cachedDecoderStats = undefined\n return proxy?.destroy() ?? Promise.resolve()\n }\n\n /**\n * Moleculer nodeID of the decoder provider currently owning this broker's\n * shared session. `null` when no session is active. Used by the\n * broker-manager's `rotateDecodersOnNode` method to drop only the\n * sessions affected by a per-agent event (e.g. hwaccel override change).\n */\n getDecoderNodeId(): string | null {\n return this.decoderNodeId\n }\n\n /**\n * Force the current shared decoder session to tear down. The next\n * subscriber will trigger a fresh `createSharedDecoderSession` which\n * re-pulls the latest per-agent preferences (hwaccel backend, …). Safe\n * to call when there's no active session — resolves to a no-op.\n */\n async rotateDecoderSession(reason: string): Promise<void> {\n if (!this.decoderProxy) return\n this.logger?.info('rotating decoder session', { meta: { reason, decoderNodeId: this.decoderNodeId } })\n await this.destroyDecoder()\n // If subscribers are still attached, rebuild immediately so streaming\n // resumes without waiting for a new subscribe event.\n if (this.decodedSubscribers.size > 0 && this.source) {\n const codec = this.detectedCodec ?? this.source.videoCodec\n if (codec) {\n this.createSharedDecoderSession(this.pendingDecodeOptions).catch((err: unknown) => {\n this.logger?.warn('decoder session rebuild after rotation failed', { meta: { error: errMsg(err) } })\n })\n }\n }\n }\n\n /**\n * Poll the decoder resolver until a provider for `codec` shows up, then\n * build the session. Addons register asynchronously and out-of-order:\n * the broker must NOT fail when the decoder addon hasn't yet finished\n * spawning its isolated process — it must wait.\n *\n * Bounded backoff: 500ms → 1s → 2s → 4s → 8s (capped), with a hard\n * ceiling of ~60s total wait. If the decoder still isn't registered\n * after that, log once at error level and stop retrying — at that\n * point something is genuinely wrong with the addon config.\n *\n * The first failure is logged at debug so a healthy boot (decoder\n * arriving within the first few hundred ms) doesn't pollute the log\n * with noisy errors.\n */\n private scheduleDecoderResolveRetry(codec: string): void {\n if (this.decoderResolveRetryTimer) return\n const attempt = this.decoderResolveAttempts++\n const backoffMs = Math.min(500 * Math.pow(2, attempt), 8000)\n const cumulativeMs = (Math.pow(2, this.decoderResolveAttempts) - 1) * 500\n if (cumulativeMs > 60000) {\n this.logger?.error(\n 'No decoder provider — giving up. Check that addon-decoder-nodeav or addon-decoder-ffmpeg is installed and enabled.',\n { meta: { codec, attempts: this.decoderResolveAttempts } },\n )\n this.decoderResolveAttempts = 0\n return\n }\n if (attempt === 0) {\n this.logger?.debug(\n 'Decoder not yet registered — will retry (addon boot race)',\n { meta: { codec } },\n )\n }\n this.decoderResolveRetryTimer = setTimeout(() => {\n this.decoderResolveRetryTimer = undefined\n // Only retry if we still have subscribers and no session yet — a\n // teardown between schedule and fire is valid and should no-op.\n if (this.decoderProxy) return\n if (this.decodedSubscribers.size === 0) {\n this.decoderResolveAttempts = 0\n this.pendingDecodeOptions = undefined\n return\n }\n this.createSharedDecoderSession(this.pendingDecodeOptions)\n }, backoffMs)\n }\n\n\n private destroyNativeClient(): void {\n if (this.nativeClient) {\n this.nativeClient.destroy()\n this.nativeClient = null\n }\n this.resetRtpPipeline()\n }\n\n /**\n * RFC 4571 reader — pulls framed RTP from a local TCP server (e.g. the\n * Reolink lib's `createRfc4571TcpServer`). Same downstream pipeline as\n * the native RTSP reader: SDP arrives out-of-band on the StreamSource,\n * RTP packets feed the same depacketizer, AnnexB output goes through\n * the same prebuffer / pipe / decoder paths.\n *\n * `source.metadata.sdp` is required and must describe the video\n * (and optionally audio) track the publisher is multiplexing on the\n * single TCP connection.\n */\n private startRfc4571Reader(source: StreamSource): void {\n this.logger?.info('starting RFC 4571 reader')\n\n // Lazy publish — when the URL carries the `lazy:rfc4571:` sentinel\n // the publisher hasn't materialized the upstream socket yet (it's\n // deferred to first-consumer demand). Trigger a refresh; the\n // publisher's `StreamBrokerOnRequestStreamSourceRefresh` listener\n // is responsible for opening the real rfc4571 server and\n // re-publishing with a real `tcp://...` URL — the reconnect tick\n // resolves the fresh source via `sourceProvider`.\n if (typeof source.url === 'string' && source.url.startsWith('lazy:rfc4571:')) {\n this.logger?.info('rfc4571: lazy URL — requesting publisher to materialize upstream', {\n meta: { url: source.url },\n })\n this._status = 'error'\n this.notifySourceRefresh()\n this.scheduleReconnect()\n return\n }\n\n const sdp = typeof source.metadata?.['sdp'] === 'string' ? source.metadata['sdp'] as string : null\n if (!sdp) {\n // No SDP is the same situation as a stale lazy URL — the\n // publisher needs to repopulate. Mirror the lazy-URL path: emit\n // `notifySourceRefresh` so the publisher gets a chance to\n // materialize, instead of failing silently.\n this.logger?.warn('rfc4571: source missing metadata.sdp — requesting publisher refresh')\n this._status = 'error'\n this.notifySourceRefresh()\n this.scheduleReconnect()\n return\n }\n\n const reader = new Rfc4571Reader(\n {\n url: source.url,\n sdp,\n ...(this.logger ? { logger: this.logger.child('rfc4571') } : {}),\n },\n this.buildRtpStreamCallbacks('rfc4571', () => this.destroyRfc4571Reader()),\n )\n\n this.rfc4571Reader = reader\n\n reader.connect().catch((err) => {\n if (this.manualStop || this._suspended) return\n this.logger?.warn('rfc4571 connect failed', {\n meta: { error: errMsg(err), ...this.sourceMetaForLog() },\n })\n this.destroyRfc4571Reader()\n this._status = 'error'\n // Ask the publisher to refresh its loopback transport. The lib's\n // RFC 4571 TCP server idle-tears-down after ~15s with no\n // clients and may rebind to a different port on recreate, so a\n // cached URL goes stale. The publisher's response (re-running\n // `ensureRfc4571Server` + `publishCameraStream`) updates the\n // cam-stream registry; the next reconnect picks the fresh URL\n // up via `resolveCurrentSource()`. Deduped against the parallel\n // onError emit by `notifySourceRefresh`.\n this.notifySourceRefresh()\n this.scheduleReconnect()\n })\n }\n\n private destroyRfc4571Reader(): void {\n if (this.rfc4571Reader) {\n this.rfc4571Reader.destroy()\n this.rfc4571Reader = null\n }\n this.resetRtpPipeline()\n // The watchdog only makes sense while a reader is alive — drop it\n // here so a teardown initiated outside `armFirstVideoWatchdog`'s\n // own fire path doesn't leave a stale timer that would force a\n // useless reconnect later.\n this.cancelFirstVideoWatchdog()\n }\n\n /** Tear down the shared RTP/audio pipeline state used by both readers. */\n private resetRtpPipeline(): void {\n if (this.rtpDepacketizer) {\n this.rtpDepacketizer.reset()\n this.rtpDepacketizer = null\n }\n if (this.audioRtpDecoder) {\n this.audioRtpDecoder.flush()\n this.audioRtpDecoder = null\n }\n if (this.audioCodecSession) {\n const session = this.audioCodecSession\n this.audioCodecSession = null\n void session.close().catch((err) => {\n this.logger?.warn('audio-codec: session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n }\n\n private async createSharedDecoderSession(options?: DecodeOptions): Promise<void> {\n const codec = this.detectedCodec ?? this.source?.videoCodec ?? 'h264'\n\n if (!this.decoderApi) {\n this.logger?.warn('no decoder API available — decoded frames will not be produced')\n return\n }\n\n // Check if the decoder supports this codec before creating a session.\n const supported = await this.decoderApi.supportsCodec({ codec })\n if (!supported) {\n // Decoder addon may not be registered yet (boot race).\n this.pendingDecodeOptions = options\n this.scheduleDecoderResolveRetry(codec)\n return\n }\n\n // A provider is now available — clear any pending retry so we don't\n // double-create the session on the next tick.\n if (this.decoderResolveRetryTimer) {\n clearTimeout(this.decoderResolveRetryTimer)\n this.decoderResolveRetryTimer = undefined\n }\n this.decoderResolveAttempts = 0\n\n // Shared decoder runs at unlimited FPS — each subscriber throttles locally.\n // Use resolveDecoderFormat() to start with the cheapest format that\n // satisfies all current subscribers (e.g. gray when only motion is active).\n const resolvedFormat = this.resolveDecoderFormat()\n // Stream-broker `deviceId` is actually the brokerId (`<numeric>/<profile>`).\n // Parse out the numeric for the cap config so decoder logs land with\n // a clean integer `deviceId` tag — the full brokerId goes through `tag`.\n const slash = this.deviceId.indexOf('/')\n const numericDeviceId = slash >= 0\n ? Number.parseInt(this.deviceId.slice(0, slash), 10)\n : Number.parseInt(this.deviceId, 10)\n const config = {\n codec,\n maxFps: 0,\n outputFormat: resolvedFormat,\n scale: options?.scale ?? DEFAULT_SCALE,\n ...(Number.isFinite(numericDeviceId) ? { deviceId: numericDeviceId } : {}),\n tag: `broker:${this.deviceId}`,\n }\n this.decoderOutputFormat = resolvedFormat\n\n const { sessionId, nodeId } = await this.decoderApi.createSession(config)\n const proxy = new DecoderSessionProxy(this.decoderApi, sessionId)\n this.decoderProxy = proxy\n this.decoderNodeId = nodeId\n\n // Fan-out decoded frames to all subscribers with per-subscriber\n // throttling. In Phase 3 the decoder produces one *canonical*\n // output (gray / rgb / jpeg per `resolveDecoderFormat`) and the\n // broker derives any other format on demand via a per-frame\n // conversion cache, so that an `rgb`-source frame fanned out to\n // five `jpeg` subscribers performs exactly one sharp encode and\n // pushes the same buffer to each. Subscribers that match the\n // canonical format short-circuit the cache entirely.\n const onFrame = (frame: DecodedFrame) => {\n this.decoderFrameCount++\n if (this.decoderFrameCount === 1) {\n this.logger?.info('first decoded frame', {\n meta: { width: frame.width, height: frame.height, format: frame.format },\n })\n }\n // Update cached stats periodically (every 30 frames)\n if (this.decoderFrameCount % 30 === 0) {\n proxy.getStats().then((stats) => {\n this.cachedDecoderStats = stats\n }).catch(() => { /* stats refresh failure is non-fatal */ })\n }\n this.fanoutDecodedFrame(frame)\n }\n\n // Start the polling loop in the background — it runs until stopPolling or destroy\n proxy.startPolling(onFrame).catch((err: unknown) => {\n this.logger?.warn('decoder polling error', { meta: { error: errMsg(err) } })\n })\n\n // Pull mode: decoder reads from the broker's local pipe server (no duplicate RTSP connection)\n // Must wait for the first keyframe so the pipe contains SPS/PPS for the decoder.\n const info = await this.decoderApi.getInfo()\n if (info.isPullMode) {\n const localUrl = this.pipeServer.getUrl()\n const isHevc = codec === 'h265' || codec === 'hevc'\n const inputUrl = `${localUrl}?format=${isHevc ? 'hevc' : 'h264'}`\n\n const doOpen = () => {\n this.logger?.info('Pull mode decoder: opening local pipe', {\n meta: { localUrl, codec },\n })\n proxy.openStream(inputUrl).catch((err: unknown) => {\n this.logger?.error('Pull mode decoder failed', { meta: { error: errMsg(err) } })\n })\n }\n\n if (this.firstKeyframeReceived) {\n doOpen()\n } else {\n this.logger?.info('Pull mode decoder: waiting for first keyframe before opening')\n this.pendingPullModeOpen = doOpen\n }\n }\n }\n\n /**\n * Map an SDP audio track to an `AudioCodecSession` config the\n * audio-codec cap can consume. Returns `null` when the codec is\n * outside the cap's catalogue (broker logs and skips audio in that\n * case). Pure — relies only on parsed SDP fields.\n */\n /**\n * Shared emit callback for `AudioCodecSession`. Decoded PCM is fanned\n * out to audio subscribers and re-encoded as G.711 µ-law for WebRTC\n * consumers. Used by both the RTSP onAudioTrack path and the push\n * `pushAudioInfo` path.\n */\n private buildAudioCodecEmitCallback(): (chunk: import('@camstack/types').DecodedAudioChunk) => void {\n return (chunk) => {\n // The analyzer pipeline relies on the AudioCodecSession's ~500ms\n // window to give the sound classifier a stable input length, so\n // we hand the big chunk over to `audioSubscribers` unchanged.\n for (const sub of this.audioSubscribers.values()) {\n sub.callback(chunk)\n sub.chunksDelivered++\n }\n if (this.encodedCallbacks.size > 0) {\n const f32 = new Float32Array(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength / 4)\n const pcm16 = new Int16Array(f32.length)\n for (let i = 0; i < f32.length; i++) {\n const s = Math.max(-1, Math.min(1, f32[i]!))\n pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF\n }\n const ulaw = pcmToMulaw(pcm16)\n const ratio = Math.max(1, Math.round(chunk.sampleRate / 8000))\n const resampled = ratio > 1 ? downsampleUlaw(ulaw, ratio) : ulaw\n\n // Split the 500ms-window mu-law buffer into RFC 3551-canonical\n // 20ms RTP packets (160 bytes @ 8kHz µ-law). The analyzer\n // pipeline can stomach the long window because it integrates\n // over half-second classifier frames, but WebRTC senders\n // can't: a single 4000-byte payload exceeds typical UDP MTU\n // (frags drop on any single-fragment loss) AND arrives at the\n // browser in 500ms-bursts the audio jitter buffer can't\n // smooth out — net effect is \"no audio output on the player\".\n // 20ms per packet is what every Reolink-style RTSP/rfc4571\n // restream produces on the wire, so the WebRTC sender now\n // looks identical to a passthrough G.711 source.\n const PCMU_BYTES_PER_PACKET = 160 // 8kHz × 20ms × 1 byte/sample\n const baseTs = chunk.timestamp\n let offsetBytes = 0\n // Stamp each sub-packet's `pts` with a monotonically advancing\n // ms timestamp (20ms per slice). The WebRTC session uses its\n // own RTP timestamp counter (`audioTimestampBase` in\n // `session.ts:1032`), so this value mostly drives metadata —\n // but keeping it monotonic lets any future RTP-aware consumer\n // reconstruct the original timing without surprises.\n let chunkOffsetMs = 0\n while (offsetBytes < resampled.length) {\n const end = Math.min(offsetBytes + PCMU_BYTES_PER_PACKET, resampled.length)\n const slice = resampled.subarray(offsetBytes, end)\n const packet: EncodedPacket = {\n type: 'audio',\n data: Buffer.from(slice),\n pts: baseTs + chunkOffsetMs,\n dts: baseTs + chunkOffsetMs,\n keyframe: false,\n codec: 'pcmu',\n }\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n offsetBytes = end\n chunkOffsetMs += 20\n }\n }\n }\n }\n\n /**\n * Build an `AudioCodecSessionConfig` from `PushAudioInfo` declared by\n * a push-source provider. Mirrors `buildAudioCodecSessionConfig`\n * (which builds from an SDP audio track) for sources that don't\n * carry SDP — Reolink Baichuan, Frigate, etc.\n *\n * Push sources don't carry rfc3640 framing — they emit one complete\n * codec frame per packet — so the session is configured without\n * `rfc3640` and the broker calls `pushRawFrame` instead of\n * `processPacket`.\n */\n private buildAudioCodecSessionConfigFromPush(\n info: import('@camstack/types').PushAudioInfo,\n ): import('./audio-codec-session.js').AudioCodecSessionConfig | null {\n const codec = info.codec.toLowerCase()\n const tag = `broker:${this.deviceId}`\n if (codec === 'aac') {\n return {\n codec: 'aac',\n sourceSampleRate: info.sampleRate,\n sourceChannels: info.channels,\n ...(info.extraData ? { extraData: info.extraData } : {}),\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n }\n }\n if (codec === 'opus') {\n return {\n codec: 'opus',\n sourceSampleRate: info.sampleRate,\n sourceChannels: info.channels,\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n }\n }\n return null\n }\n\n private buildAudioCodecSessionConfig(\n audioTrack: import('../rtsp/sdp-parser.js').SdpAudioTrack,\n ): import('./audio-codec-session.js').AudioCodecSessionConfig | null {\n const codecUpper = audioTrack.codec.toUpperCase()\n const fmtp = audioTrack.fmtp\n const tag = `broker:${this.deviceId}`\n\n if (codecUpper === 'MPEG4-GENERIC' || codecUpper === 'AAC') {\n let extraData: Uint8Array | undefined\n let sourceSampleRate = audioTrack.clockRate\n let sourceChannels = audioTrack.channels\n const configHex = fmtp['config'] ?? fmtp['CONFIG']\n if (configHex) {\n try {\n const asc = parseAudioSpecificConfig(configHex)\n sourceSampleRate = asc.sampleRate\n sourceChannels = asc.channelConfig > 0 ? asc.channelConfig : sourceChannels\n extraData = hexToBytes(configHex)\n } catch (err) {\n this.logger?.warn('audio-codec: AudioSpecificConfig parse failed', {\n meta: { configHex, error: errMsg(err) },\n })\n }\n }\n const sizeLength = parseFmtpInt(fmtp, 'sizelength', 13)\n const indexLength = parseFmtpInt(fmtp, 'indexlength', 3)\n const indexDeltaLength = parseFmtpInt(fmtp, 'indexdeltalength', indexLength)\n return {\n codec: 'aac',\n sourceSampleRate,\n sourceChannels,\n ...(extraData ? { extraData } : {}),\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n rfc3640: { sizeLength, indexLength, indexDeltaLength },\n }\n }\n\n if (codecUpper === 'OPUS') {\n return {\n codec: 'opus',\n sourceSampleRate: audioTrack.clockRate,\n sourceChannels: audioTrack.channels,\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n }\n }\n\n return null\n }\n}\n\nfunction parseFmtpInt(\n fmtp: Readonly<Record<string, string>>,\n key: string,\n fallback: number,\n): number {\n const raw = fmtp[key] ?? fmtp[key.toLowerCase()] ?? fmtp[key.toUpperCase()]\n if (raw === undefined) return fallback\n const n = Number(raw)\n return Number.isFinite(n) && n > 0 ? n : fallback\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const trimmed = hex.replace(/\\s+/g, '')\n if (trimmed.length % 2 !== 0) {\n throw new Error(`audio-codec: hex string has odd length (${trimmed.length})`)\n }\n const out = new Uint8Array(trimmed.length / 2)\n for (let i = 0; i < out.length; i++) {\n const byte = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16)\n if (Number.isNaN(byte)) {\n throw new Error(`audio-codec: invalid hex at offset ${i * 2}`)\n }\n out[i] = byte\n }\n return out\n}\n","/**\n * Thin tRPC-backed proxy for the `audio-codec` capability — the broker\n * uses this to decode AAC / opus / pcm_alaw / pcm_mulaw audio into PCM\n * for downstream subscribers (audio-analyzer, WebRTC audio leg, …).\n *\n * The cap is a system singleton; one provider serves all brokers on the\n * cluster. Plumbing here is a pass-through to `ctx.api.audioCodec.*` —\n * the broker doesn't know whether the audio decode runs locally or on a\n * remote node, the cap router resolves it.\n */\nimport type {\n AudioDecodeSessionConfig,\n AudioPcmChunk,\n AudioEncodeSessionConfig,\n AudioEncodedChunk,\n AudioCodecInfo,\n} from '@camstack/types'\n\n// Session-bound methods accept an optional `nodeId` so callers can pin\n// every call to the node that owns the session. The generated cap router\n// auto-routes any input field literally named `nodeId` to that node's\n// provider — sessions are process-local, so without sticky routing the\n// round-robin across nodes serves \"session not found\" on every other call.\n\nexport interface AudioCodecCapApi {\n listSupportedCodecs(): Promise<readonly AudioCodecInfo[]>\n canHandle(input: { codec: string; kind: 'decode' | 'encode' }): Promise<boolean>\n createDecodeSession(input: AudioDecodeSessionConfig): Promise<{ sessionId: string; nodeId: string }>\n createEncodeSession(input: AudioEncodeSessionConfig): Promise<{ sessionId: string; nodeId: string }>\n closeSession(input: { sessionId: string; nodeId?: string }): Promise<void>\n pushEncodedFrame(input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }): Promise<void>\n pullPcm(input: { sessionId: string; nodeId?: string; maxCount: number }): Promise<readonly AudioPcmChunk[]>\n pushPcm(input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }): Promise<void>\n pullEncoded(input: { sessionId: string; nodeId?: string; maxCount: number }): Promise<readonly AudioEncodedChunk[]>\n flushEncode(input: { sessionId: string; nodeId?: string }): Promise<readonly AudioEncodedChunk[]>\n}\n\ninterface AudioCodecRouter {\n listSupportedCodecs: { query: (input?: void) => Promise<readonly AudioCodecInfo[]> }\n canHandle: { query: (input: { codec: string; kind: 'decode' | 'encode' }) => Promise<boolean> }\n createDecodeSession: { mutate: (input: AudioDecodeSessionConfig) => Promise<{ sessionId: string; nodeId: string }> }\n createEncodeSession: { mutate: (input: AudioEncodeSessionConfig) => Promise<{ sessionId: string; nodeId: string }> }\n closeSession: { mutate: (input: { sessionId: string; nodeId?: string }) => Promise<void> }\n pushEncodedFrame: { mutate: (input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }) => Promise<void> }\n pullPcm: { query: (input: { sessionId: string; nodeId?: string; maxCount: number }) => Promise<readonly AudioPcmChunk[]> }\n pushPcm: { mutate: (input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }) => Promise<void> }\n pullEncoded: { query: (input: { sessionId: string; nodeId?: string; maxCount: number }) => Promise<readonly AudioEncodedChunk[]> }\n flushEncode: { mutate: (input: { sessionId: string; nodeId?: string }) => Promise<readonly AudioEncodedChunk[]> }\n}\n\n/**\n * Build a tRPC-backed `AudioCodecCapApi`. Returns `null` when the cap\n * isn't mounted (no audio-codec addon installed) so callers can fall\n * back to native PCMU/PCMA path or skip audio entirely.\n */\nexport function buildAudioCodecCapApi(api: unknown | null): AudioCodecCapApi | null {\n if (!api) return null\n const router = (api as Record<string, unknown>)['audioCodec'] as AudioCodecRouter | undefined\n if (!router) return null\n\n return {\n listSupportedCodecs: () => router.listSupportedCodecs.query(),\n canHandle: (input) => router.canHandle.query(input),\n createDecodeSession: (input) => router.createDecodeSession.mutate(input),\n createEncodeSession: (input) => router.createEncodeSession.mutate(input),\n closeSession: (input) => router.closeSession.mutate(input),\n pushEncodedFrame: (input) => router.pushEncodedFrame.mutate(input),\n pullPcm: (input) => router.pullPcm.query(input),\n pushPcm: (input) => router.pushPcm.mutate(input),\n pullEncoded: (input) => router.pullEncoded.query(input),\n flushEncode: (input) => router.flushEncode.mutate(input),\n }\n}\n","import type { Socket } from 'node:net'\nimport { RTSP_METHODS, INTERLEAVED_MAGIC, MAX_WRITE_BUFFER, type RtpPacket, type RtspRequest, type TrackSetup } from './rtsp-types.js'\nimport { randomUUID } from 'node:crypto'\n\n/**\n * Handles one RTSP client connection.\n * Parses requests, responds to OPTIONS/DESCRIBE/SETUP/PLAY/TEARDOWN,\n * and sends RTP data via TCP interleaved framing.\n */\nexport class RtspSession {\n private readonly sessionId: string\n private readonly tracks: TrackSetup[] = []\n private playing = false\n private buffer = ''\n private closed = false\n private readonly sdp: string\n private readonly onClose?: () => void\n /** Epoch ms when the TCP connection was accepted. Used by listClients. */\n private readonly connectedAt: number = Date.now()\n /** Epoch ms of the last RTP packet sent successfully. 0 if none yet. */\n private lastRtpAt = 0\n /** Total video RTP bytes written to the socket. */\n private bytesSent = 0\n\n /** When true, this session receives video-only (no audio RTP). */\n private readonly muted: boolean\n\n constructor(\n private readonly socket: Socket,\n sdp: string,\n onClose?: () => void,\n muted?: boolean,\n ) {\n this.sdp = sdp\n this.onClose = onClose\n this.muted = muted ?? false\n this.sessionId = randomUUID().replace(/-/g, '').slice(0, 16)\n\n socket.on('data', (chunk: Buffer) => {\n if (this.closed) return\n this.buffer += chunk.toString('ascii')\n this.processBuffer()\n })\n\n socket.on('close', () => { this.closed = true; this.onClose?.() })\n socket.on('error', () => { this.closed = true; this.onClose?.() })\n }\n\n getSessionId(): string { return this.sessionId }\n isPlaying(): boolean { return this.playing && !this.closed }\n isMuted(): boolean { return this.muted }\n\n /**\n * Diagnostic snapshot consumed by `StreamBroker.listClients` (Phase 10).\n * Gives operators the information needed to identify a specific RTSP\n * session: where it's coming from, whether it's active, when it\n * connected, and the last RTP activity timestamp.\n */\n getInfo(): {\n readonly sessionId: string\n readonly remoteAddr: string\n readonly playing: boolean\n readonly muted: boolean\n readonly connectedAt: number\n readonly lastRtpAt: number\n readonly bytesSent: number\n } {\n const remoteIp = this.socket.remoteAddress ?? 'unknown'\n const remotePort = this.socket.remotePort ?? 0\n return {\n sessionId: this.sessionId,\n remoteAddr: `${remoteIp}:${remotePort}`,\n playing: this.isPlaying(),\n muted: this.muted,\n connectedAt: this.connectedAt,\n lastRtpAt: this.lastRtpAt,\n bytesSent: this.bytesSent,\n }\n }\n\n /** Send a video RTP packet via TCP interleaved framing. */\n sendRtp(packet: RtpPacket): void {\n if (!this.playing || this.closed) return\n if (this.tracks.length === 0) return\n\n // Drop slow clients\n if (this.socket.writableLength > MAX_WRITE_BUFFER) {\n this.close()\n return\n }\n\n const channel = this.tracks[0]!.rtpChannel\n const frame = Buffer.allocUnsafe(4 + packet.data.length)\n frame[0] = INTERLEAVED_MAGIC\n frame[1] = channel\n frame.writeUInt16BE(packet.data.length, 2)\n packet.data.copy(frame, 4)\n\n this.socket.write(frame)\n this.lastRtpAt = Date.now()\n this.bytesSent += frame.length\n }\n\n /** Send an audio RTP packet via TCP interleaved framing on the audio track channel. */\n sendAudioRtp(packet: RtpPacket): void {\n if (!this.playing || this.closed || this.muted) return\n if (this.tracks.length < 2) return // no audio track set up\n\n if (this.socket.writableLength > MAX_WRITE_BUFFER) {\n this.close()\n return\n }\n\n const channel = this.tracks[1]!.rtpChannel\n const frame = Buffer.allocUnsafe(4 + packet.data.length)\n frame[0] = INTERLEAVED_MAGIC\n frame[1] = channel\n frame.writeUInt16BE(packet.data.length, 2)\n packet.data.copy(frame, 4)\n\n this.socket.write(frame)\n this.lastRtpAt = Date.now()\n this.bytesSent += frame.length\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.playing = false\n try { this.socket.destroy() } catch { /* already closed */ }\n this.onClose?.()\n }\n\n private processBuffer(): void {\n while (true) {\n // Skip any interleaved data from client (starts with $)\n if (this.buffer.length > 0 && this.buffer.charCodeAt(0) === INTERLEAVED_MAGIC) {\n if (this.buffer.length < 4) return\n const len = (this.buffer.charCodeAt(2) << 8) | this.buffer.charCodeAt(3)\n if (this.buffer.length < 4 + len) return\n this.buffer = this.buffer.slice(4 + len)\n continue\n }\n\n const endIdx = this.buffer.indexOf('\\r\\n\\r\\n')\n if (endIdx < 0) return\n\n const requestText = this.buffer.slice(0, endIdx)\n this.buffer = this.buffer.slice(endIdx + 4)\n\n const request = this.parseRequest(requestText)\n if (request) this.handleRequest(request)\n }\n }\n\n private parseRequest(text: string): RtspRequest | null {\n const lines = text.split('\\r\\n')\n const firstLine = lines[0]\n if (!firstLine) return null\n\n const parts = firstLine.split(' ')\n if (parts.length < 3) return null\n\n const headers = new Map<string, string>()\n for (let i = 1; i < lines.length; i++) {\n const colonIdx = lines[i]!.indexOf(':')\n if (colonIdx < 0) continue\n const key = lines[i]!.slice(0, colonIdx).trim().toLowerCase()\n const value = lines[i]!.slice(colonIdx + 1).trim()\n headers.set(key, value)\n }\n\n return {\n method: parts[0]!,\n uri: parts[1]!,\n cseq: parseInt(headers.get('cseq') ?? '0', 10),\n headers,\n }\n }\n\n private handleRequest(req: RtspRequest): void {\n switch (req.method) {\n case 'OPTIONS': return this.handleOptions(req)\n case 'DESCRIBE': return this.handleDescribe(req)\n case 'SETUP': return this.handleSetup(req)\n case 'PLAY': return this.handlePlay(req)\n case 'TEARDOWN': return this.handleTeardown(req)\n case 'GET_PARAMETER': return this.respond(req, 200, 'OK')\n default: return this.respond(req, 405, 'Method Not Allowed')\n }\n }\n\n private handleOptions(req: RtspRequest): void {\n this.respond(req, 200, 'OK', {\n 'Public': RTSP_METHODS.join(', '),\n })\n }\n\n private handleDescribe(req: RtspRequest): void {\n this.respond(req, 200, 'OK', {\n 'Content-Type': 'application/sdp',\n 'Content-Length': String(Buffer.byteLength(this.sdp)),\n }, this.sdp)\n }\n\n private handleSetup(req: RtspRequest): void {\n const transport = req.headers.get('transport') ?? ''\n const interleavedMatch = transport.match(/interleaved=(\\d+)-(\\d+)/)\n const rtpChannel = interleavedMatch ? parseInt(interleavedMatch[1]!, 10) : 0\n const rtcpChannel = interleavedMatch ? parseInt(interleavedMatch[2]!, 10) : 1\n\n this.tracks.push({ trackId: this.tracks.length, rtpChannel, rtcpChannel })\n\n this.respond(req, 200, 'OK', {\n 'Transport': `RTP/AVP/TCP;unicast;interleaved=${rtpChannel}-${rtcpChannel}`,\n 'Session': this.sessionId,\n })\n }\n\n private handlePlay(req: RtspRequest): void {\n this.playing = true\n this.respond(req, 200, 'OK', {\n 'Session': this.sessionId,\n 'Range': 'npt=0.000-',\n })\n }\n\n private handleTeardown(req: RtspRequest): void {\n this.respond(req, 200, 'OK')\n this.close()\n }\n\n private respond(req: RtspRequest, code: number, reason: string, headers?: Record<string, string>, body?: string): void {\n if (this.closed) return\n\n let response = `RTSP/1.0 ${code} ${reason}\\r\\nCSeq: ${req.cseq}\\r\\n`\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n response += `${k}: ${v}\\r\\n`\n }\n }\n response += '\\r\\n'\n if (body) response += body\n\n this.socket.write(response)\n }\n}\n","import * as net from 'node:net'\nimport { randomBytes } from 'node:crypto'\nimport { RtspSession } from './rtsp-session.js'\nimport { RtspRestreamer } from './rtsp-restreamer.js'\n\n/**\n * Single TCP server for all RTSP restream connections.\n * Routes clients to the correct RtspRestreamer by a random token path\n * (not the predictable brokerId) for security.\n */\nexport class RtspListenServer {\n private server: net.Server | null = null\n private port = 0\n /** token → restreamer */\n private readonly restreamers = new Map<string, RtspRestreamer>()\n /** brokerId → token (for lookup/regeneration) */\n private readonly brokerTokens = new Map<string, string>()\n /** token → brokerId (reverse map) */\n private readonly tokenBrokers = new Map<string, string>()\n /** brokerId → enabled (default true) */\n private readonly brokerEnabled = new Map<string, boolean>()\n\n /** Generate a random token for a stream path */\n static generateToken(): string {\n return randomBytes(16).toString('hex')\n }\n\n /**\n * Register a restreamer with a token.\n * If token is provided, use it (restored from persistence).\n * If not, generate a new one.\n * Returns the token used.\n */\n registerRestreamer(brokerId: string, restreamer: RtspRestreamer, existingToken?: string): string {\n // Unregister old token if broker is being re-registered\n const oldToken = this.brokerTokens.get(brokerId)\n if (oldToken) {\n this.restreamers.delete(oldToken)\n this.tokenBrokers.delete(oldToken)\n }\n\n const token = existingToken ?? RtspListenServer.generateToken()\n this.restreamers.set(token, restreamer)\n this.brokerTokens.set(brokerId, token)\n this.tokenBrokers.set(token, brokerId)\n return token\n }\n\n unregisterRestreamer(brokerId: string): void {\n const token = this.brokerTokens.get(brokerId)\n if (token) {\n this.restreamers.delete(token)\n this.tokenBrokers.delete(token)\n }\n this.brokerTokens.delete(brokerId)\n }\n\n /** Regenerate a random token for a broker. Returns the new token. */\n regenerateToken(brokerId: string): string | null {\n const oldToken = this.brokerTokens.get(brokerId)\n if (!oldToken) return null\n\n const restreamer = this.restreamers.get(oldToken)\n if (!restreamer) return null\n\n // Remove old\n this.restreamers.delete(oldToken)\n this.tokenBrokers.delete(oldToken)\n\n // Create new\n const newToken = RtspListenServer.generateToken()\n this.restreamers.set(newToken, restreamer)\n this.brokerTokens.set(brokerId, newToken)\n this.tokenBrokers.set(newToken, brokerId)\n return newToken\n }\n\n /** Get the current token for a broker. */\n getToken(brokerId: string): string | undefined {\n return this.brokerTokens.get(brokerId)\n }\n\n /** Get all broker → token mappings. */\n getAllTokens(): ReadonlyMap<string, string> {\n return this.brokerTokens\n }\n\n /** Set the enabled state for a broker's restream. Default is true. */\n setEnabled(brokerId: string, enabled: boolean): void {\n this.brokerEnabled.set(brokerId, enabled)\n }\n\n /** Check if a broker's restream is enabled. Default true. */\n isEnabled(brokerId: string): boolean {\n return this.brokerEnabled.get(brokerId) ?? true\n }\n\n /** Get all broker → enabled mappings (only stores explicit overrides). */\n getAllEnabled(): ReadonlyMap<string, boolean> {\n return this.brokerEnabled\n }\n\n /** Load persisted enabled states (called at boot). */\n loadEnabled(states: ReadonlyMap<string, boolean>): void {\n for (const [k, v] of states) {\n this.brokerEnabled.set(k, v)\n }\n }\n\n getPort(): number { return this.port }\n\n async start(port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n this.server = net.createServer((socket) => this.handleConnection(socket))\n this.server.on('error', reject)\n this.server.listen(port, () => {\n const addr = this.server!.address()\n this.port = typeof addr === 'object' && addr ? addr.port : port\n resolve()\n })\n })\n }\n\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (!this.server) { resolve(); return }\n this.server.close(() => resolve())\n this.server = null\n })\n }\n\n private handleConnection(socket: net.Socket): void {\n let requestBuffer = ''\n\n const onData = (chunk: Buffer) => {\n requestBuffer += chunk.toString('ascii')\n\n // Wait for first complete RTSP request to determine the stream\n const endIdx = requestBuffer.indexOf('\\r\\n\\r\\n')\n if (endIdx < 0) return\n\n socket.removeListener('data', onData)\n\n // Parse the URI from the first line to determine stream path\n const firstLine = requestBuffer.split('\\r\\n')[0] ?? ''\n const uriMatch = firstLine.match(/\\s(rtsp:\\/\\/[^\\s]+)\\s/)\n const uri = uriMatch ? uriMatch[1]! : ''\n\n // Extract token path: rtsp://host:port/{token} → {token}\n const pathMatch = uri.match(/rtsp:\\/\\/[^/]+(\\/.*?)(?:\\s|$)/)\n const streamPath = pathMatch ? pathMatch[1]!.replace(/^\\//, '').replace(/\\/trackID=\\d+$/, '') : ''\n\n // Check for /muted suffix — same token, audio-free variant\n const isMuted = streamPath.endsWith('/muted')\n const lookupPath = isMuted ? streamPath.slice(0, -'/muted'.length) : streamPath\n\n const restreamer = this.restreamers.get(lookupPath)\n if (!restreamer) {\n socket.write(`RTSP/1.0 404 Not Found\\r\\nCSeq: 1\\r\\n\\r\\n`)\n socket.destroy()\n return\n }\n\n // Check if restream is enabled for this broker\n const brokerId = this.tokenBrokers.get(lookupPath)\n if (brokerId && !this.isEnabled(brokerId)) {\n socket.write(`RTSP/1.0 403 Forbidden\\r\\nCSeq: 1\\r\\n\\r\\n`)\n socket.destroy()\n return\n }\n\n const sdp = isMuted\n ? (restreamer.getMutedSdp() ?? restreamer.getSdp() ?? '')\n : (restreamer.getSdp() ?? '')\n const session = new RtspSession(socket, sdp, () => {\n restreamer.removeSession(session.getSessionId())\n }, isMuted)\n restreamer.addSession(session)\n\n // Re-inject the buffered data so the session processes the first request\n socket.unshift(Buffer.from(requestBuffer, 'ascii'))\n }\n\n socket.on('data', onData)\n socket.on('error', () => {})\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\nimport type { RtspListenServer } from './rtsp-listen-server.js'\n\nexport type EnabledPersister = (states: ReadonlyMap<string, boolean>) => void\nexport type TokenPersister = (tokens: ReadonlyMap<string, string>) => void\n\n/**\n * Per-broker RTSP restream entry.\n *\n * Kept local to `addon-stream-broker` after the `rtsp-restream` capability\n * was absorbed into `stream-broker`. The shape is still validated at the\n * `stream-broker.cap.ts` boundary via `RtspRestreamEntrySchema`.\n */\nexport interface RtspRestreamEntry {\n readonly brokerId: string\n readonly url: string\n readonly mutedUrl: string\n readonly enabled: boolean\n}\n\n/**\n * Internal sub-object of `StreamBrokerManager` that implements the RTSP\n * restream bookkeeping. No longer registered as a separate capability —\n * accessed via passthrough methods on `StreamBrokerManager`.\n */\nexport class RtspRestreamProviderImpl {\n private tokenPersister: TokenPersister | null = null\n private enabledPersister: EnabledPersister | null = null\n\n constructor(\n private readonly server: RtspListenServer,\n private readonly logger: IScopedLogger,\n ) {}\n\n // ── Persistence ────────────────────────────────────────────────────────\n\n setTokenPersister(persister: TokenPersister): void {\n this.tokenPersister = persister\n }\n\n setEnabledPersister(persister: EnabledPersister): void {\n this.enabledPersister = persister\n }\n\n loadPersistedEnabled(states: ReadonlyMap<string, boolean>): void {\n this.server.loadEnabled(states)\n }\n\n // ── IRtspRestreamProvider ──────────────────────────────────────────────\n\n getRtspPort(): number {\n return this.server.getPort()\n }\n\n getAllEntries(hostname?: string): readonly RtspRestreamEntry[] {\n const port = this.server.getPort()\n if (port === 0) return []\n const host = hostname ?? '127.0.0.1'\n const result: RtspRestreamEntry[] = []\n for (const [brokerId, token] of this.server.getAllTokens()) {\n result.push({\n brokerId,\n url: `rtsp://${host}:${port}/${token}`,\n mutedUrl: `rtsp://${host}:${port}/${token}/muted`,\n enabled: this.server.isEnabled(brokerId),\n })\n }\n return result\n }\n\n /**\n * Server-side filter by device — returns only entries whose\n * `brokerId` is keyed `${deviceId}/${camStreamId}`. Mirrors\n * `getAllEntries` but lets device-scoped consumers skip the\n * cluster-wide scan + client-side filter that was the cost of\n * routing through `getAllEntries`.\n */\n getEntriesForDevice(deviceId: number, hostname?: string): readonly RtspRestreamEntry[] {\n const port = this.server.getPort()\n if (port === 0) return []\n const host = hostname ?? '127.0.0.1'\n const prefix = `${deviceId}/`\n const result: RtspRestreamEntry[] = []\n for (const [brokerId, token] of this.server.getAllTokens()) {\n if (!brokerId.startsWith(prefix)) continue\n result.push({\n brokerId,\n url: `rtsp://${host}:${port}/${token}`,\n mutedUrl: `rtsp://${host}:${port}/${token}/muted`,\n enabled: this.server.isEnabled(brokerId),\n })\n }\n return result\n }\n\n getEntry(brokerId: string, hostname?: string): RtspRestreamEntry | null {\n const token = this.server.getToken(brokerId)\n if (!token) return null\n const port = this.server.getPort()\n const host = hostname ?? '127.0.0.1'\n return {\n brokerId,\n url: `rtsp://${host}:${port}/${token}`,\n mutedUrl: `rtsp://${host}:${port}/${token}/muted`,\n enabled: this.server.isEnabled(brokerId),\n }\n }\n\n regenerateToken(brokerId: string): string | null {\n const token = this.server.regenerateToken(brokerId)\n if (token) {\n this.persistTokens()\n this.logger.info('RTSP token regenerated', {\n meta: { brokerId, tokenPrefix: token.slice(0, 8) },\n })\n }\n return token\n }\n\n setEnabled(brokerId: string, enabled: boolean): void {\n this.server.setEnabled(brokerId, enabled)\n this.persistEnabled()\n this.logger.info('RTSP restream state changed', { meta: { brokerId, enabled } })\n }\n\n isEnabled(brokerId: string): boolean {\n return this.server.isEnabled(brokerId)\n }\n\n // ── Internal (called by StreamBrokerManager) ──────────────────────────\n\n /** Persist tokens after registration/regeneration triggered by broker manager */\n persistTokens(): void {\n if (this.tokenPersister) {\n this.tokenPersister(this.server.getAllTokens())\n }\n }\n\n private persistEnabled(): void {\n if (this.enabledPersister) {\n this.enabledPersister(this.server.getAllEnabled())\n }\n }\n}\n","import type {\n StreamSource, IRestreamer, IDecoderProvider, IScopedLogger,\n StreamFormat, ConfigUISchemaWithValues, ConfigFieldWithValue,\n ConfigSectionWithValues, ConfigSubTabDefinitionWithValue,\n CameraStream, CamProfile, CamStreamKind, ProfileSlot, ProfileSlotStatus,\n IEventBus, SystemEvent, IStreamBroker, BrokerStats, AddonApi,\n PlaceholderReason,\n} from '@camstack/types'\nimport { StreamBroker } from './stream-broker'\nimport type { DecoderCapApi } from './decoder-session-proxy.js'\nimport { buildAudioCodecCapApi } from './audio-codec-proxy.js'\nimport { RtspListenServer } from '../rtsp/rtsp-listen-server.js'\nimport { RtspRestreamProviderImpl, type RtspRestreamEntry } from '../rtsp/rtsp-restream-provider.js'\nimport { CAM_PROFILE_ORDER, DeviceFeature, EventCategory, errMsg, RingBuffer } from '@camstack/types'\n\n/**\n * Runtime capability accessor injected by the addon on initialization.\n * Mirrors the pattern used by other addons — the server extends\n * AddonContext at runtime with this shape so the broker can consume\n * other capabilities (decoder, restreamer, webrtc) without a backend\n * dependency.\n */\nexport interface CapabilitiesAccess {\n getCollection?<T = unknown>(name: string): readonly T[] | undefined\n getCollectionEntries?<T = unknown>(name: string): readonly (readonly [string, T])[] | undefined\n get?<T = unknown>(name: string): T | undefined\n}\n\ninterface StreamPreBuffer {\n readonly enabled: boolean\n readonly seconds: number\n}\n\ninterface DeviceOverride {\n /** Legacy single-value override — kept for migration compat. */\n preBufferSecOverride?: number\n /**\n * Per-stream pre-buffer settings keyed by `camStreamId` (e.g.\n * `native:main`, `rtsp:sub`). Each stream sourceconfigures its own\n * buffer independently; profile assignment is decoupled — a profile\n * always inherits the pre-buffer of the cam stream currently mapped\n * to it via `assignments[deviceId].map[profile]`.\n */\n preBuffer?: Record<string, StreamPreBuffer>\n /**\n * Per-device debug flag for streaming subsystem (broker + WebRTC\n * session + repacketizer). When true, verbose info-level logs about\n * SDP param sets, RTP forwarding counts, repacketizer codecInfo\n * seeding, and source-RTP subscriber lifecycle become enabled.\n * Off by default to keep production logs quiet.\n */\n streamingDebug?: boolean\n}\n\n/**\n * Per-device assignment state persisted via `setProfileMapPersister`.\n * `auto` is true until the operator makes a manual assignment — used\n * so providers can keep publishing streams and have the broker\n * re-run `computeInitialAssignment` without overwriting a user's\n * chosen mapping.\n */\ninterface DeviceAssignment {\n readonly map: Partial<Record<CamProfile, string>>\n readonly auto: boolean\n}\n\nexport type ProfileMapPersister = (\n assignments: ReadonlyMap<number, DeviceAssignment>,\n) => void\n\ntype CameraStreamInternal = CameraStream & {\n /** Insertion order within (deviceId) — drives UI dropdown ordering. */\n readonly order: number\n readonly deviceFeatures: readonly string[]\n /**\n * Whether this stream is eligible for the broker's automatic profile\n * assignment. `false` streams stay published (visible + manually\n * assignable), but `computeInitialAssignment` skips them. Defaults\n * `true` for back-compat with publishers that don't set the flag.\n */\n readonly autoEligible: boolean\n}\n\nconst PUSH_KINDS: ReadonlySet<CamStreamKind> = new Set(['push-annexb'])\n\n/** Walk a profile map and return the slot that points at `camStreamId`, or null. */\nfunction findProfileForStream(\n map: Partial<Record<CamProfile, string>>,\n camStreamId: string,\n): CamProfile | null {\n for (const profile of CAM_PROFILE_ORDER) {\n if (map[profile] === camStreamId) return profile\n }\n return null\n}\n\n\n/**\n * Watchdog parameters for the stream-health emitter. Brokers that have\n * received zero video packets for STREAM_STALE_TIMEOUT_MS are reported\n * as `stream.offline`; the next packet flips them back to `stream.online`.\n */\nconst STREAM_STALE_TIMEOUT_MS = 120_000\nconst STREAM_HEALTH_POLL_MS = 15_000\n\n/** Compose a broker id from a `(deviceId, camStreamId)` tuple. */\nfunction brokerIdFor(deviceId: number, camStreamId: string): string {\n return `${deviceId}/${camStreamId}`\n}\n\nexport class StreamBrokerManager {\n /**\n * brokers keyed by brokerId = `${deviceId}/${camStreamId}`.\n *\n * Single source of truth: one StreamBroker per cam stream source.\n * Profiles are an alias layer over this map — the profile slot\n * `(deviceId, profile)` resolves to the broker via\n * `assignments[deviceId].map[profile] -> camStreamId`.\n *\n * Lifecycle is tied 1:1 to publish/retract: a broker exists for as\n * long as the cam stream is published. Pre-buffer defaults OFF —\n * idle cam streams cost roughly nothing (the upstream readers stay\n * dormant until something subscribes), but we keep the broker\n * registered so callers (WebRTC, RTSP restream) can latch on at any\n * time without an activation handshake.\n */\n private readonly brokers = new Map<string, StreamBroker>()\n /** Per-device published cam-stream registry. */\n private readonly cameraStreams = new Map<number, Map<string, CameraStreamInternal>>()\n /** Monotonic insertion counter per device — feeds `CameraStreamInternal.order`. */\n private readonly streamOrderSeq = new Map<number, number>()\n /** Persisted operator intent: which cam stream serves which profile. */\n private readonly assignments = new Map<number, DeviceAssignment>()\n /** Per-device overrides (pre-buffer + legacy). */\n private readonly deviceOverrides = new Map<number, DeviceOverride>()\n private deviceOverridePersister: ((overrides: ReadonlyMap<number, DeviceOverride>) => void) | null = null\n private profileMapPersister: ProfileMapPersister | null = null\n private eventBus: IEventBus | null = null\n /**\n * Per-broker stream health flag set by the watchdog. `undefined`\n * before the first transition — emit `stream.online` on first packet\n * regardless of prior state.\n */\n private readonly streamHealthByBroker = new Map<string, boolean>()\n private streamHealthTimer: ReturnType<typeof setInterval> | undefined\n // Legacy static restreamers list — production reads via capabilities.\n private restreamers: readonly IRestreamer[] = []\n private readonly staticDecoders: readonly IDecoderProvider[]\n private readonly logger: IScopedLogger\n private readonly rtspServer = new RtspListenServer()\n private readonly rtspProvider: RtspRestreamProviderImpl\n private persistedTokens = new Map<string, string>()\n private capabilities: CapabilitiesAccess | null = null\n /**\n * tRPC api proxy injected from the host addon. Used to resolve the\n * `decoder` capability (and any future cross-cap consumers) via the\n * canonical API surface — `localProviderLink` short-circuits when\n * the provider lives in the same broker / group, otherwise the call\n * goes over `brokerTransportLink`. Replaces the previous direct\n * `CapabilityRegistry` lookup, which only worked on the hub.\n */\n private api: AddonApi | null = null\n private defaultPreBufferSec = 10\n private webrtcServer: import('../webrtc/broker-webrtc-server.js').BrokerWebrtcServer | null = null\n\n constructor(\n staticDecoders: readonly IDecoderProvider[] | undefined,\n logger: IScopedLogger,\n ) {\n this.staticDecoders = staticDecoders ?? []\n this.logger = logger\n this.rtspProvider = new RtspRestreamProviderImpl(this.rtspServer, logger.child('rtsp-restream'))\n }\n\n // ── Decoder resolution ───────────────────────────────────────────────\n //\n // Routes every decoder call through `ctx.api.decoder.*` (tRPC). The\n // proxy is wired by the host addon via `setApiAccess(ctx.api)`. When\n // the decoder addon lives in the same broker (e.g. inside the\n // `pipeline` group on the hub) the call is short-circuited by\n // `localProviderLink` with no serialization. When the decoder lives\n // on a different node, the call falls through to\n // `brokerTransportLink` which dispatches the matching Moleculer\n // action. Either way, the broker stays unaware of the addon's\n // physical placement.\n\n private buildDecoderCapApi(_deviceId: number | null): DecoderCapApi | null {\n const localApi = this.buildLocalDecoderCapApi()\n const apiDecoder = (): {\n supportsCodec: { query: (input: { codec: string }) => Promise<boolean> }\n getInfo: { query: (input?: void) => Promise<{ id: string; name: string; isPullMode?: boolean; priority?: number }> }\n createSession: { query: (input: import('@camstack/types').DecoderSessionConfig) => Promise<{ sessionId: string; nodeId: string }> }\n pushPacket: { query: (input: { sessionId: string; packet: import('@camstack/types').EncodedPacket }) => Promise<void> }\n pullFrames: { query: (input: { sessionId: string; maxCount: number }) => Promise<readonly import('@camstack/types').DecodedFrame[]> }\n destroySession: { query: (input: { sessionId: string }) => Promise<void> }\n openStream: { query: (input: { sessionId: string; url: string }) => Promise<void> }\n updateConfig: { query: (input: { sessionId: string; config: Partial<import('@camstack/types').DecoderSessionConfig> }) => Promise<void> }\n getStats: { query: (input: { sessionId: string }) => Promise<import('@camstack/types').DecoderStats> }\n } | null => {\n if (!this.api) return null\n const decoder = (this.api as Record<string, unknown>)['decoder']\n return decoder ? (decoder as ReturnType<typeof apiDecoder> extends infer R ? R extends null ? never : R : never) : null\n }\n\n return {\n supportsCodec: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.supportsCodec.query(input)\n if (!localApi) return false\n return localApi.supportsCodec(input)\n },\n getInfo: async () => {\n const proxy = apiDecoder()\n if (proxy) return proxy.getInfo.query()\n if (!localApi) return { id: 'none', name: 'No decoder' }\n return localApi.getInfo()\n },\n createSession: async (config) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.createSession.query(config)\n if (!localApi) throw new Error('No decoder provider available')\n return localApi.createSession(config)\n },\n pushPacket: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.pushPacket.query(input)\n if (!localApi) return\n return localApi.pushPacket(input)\n },\n pullFrames: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.pullFrames.query(input)\n if (!localApi) return []\n return localApi.pullFrames(input)\n },\n destroySession: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.destroySession.query(input)\n if (!localApi) return\n return localApi.destroySession(input)\n },\n openStream: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.openStream.query(input)\n if (!localApi) return\n return localApi.openStream(input)\n },\n updateConfig: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.updateConfig.query(input)\n if (!localApi) return\n return localApi.updateConfig(input)\n },\n getStats: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.getStats.query(input)\n if (!localApi) return { inputFps: 0, outputFps: 0, avgDecodeTimeMs: 0, droppedFrames: 0 }\n return localApi.getStats(input)\n },\n }\n }\n\n private buildLocalDecoderCapApi(): DecoderCapApi | null {\n if (this.staticDecoders.length === 0) return null\n // Bounded frame buffer per session — when `pullFrames` falls behind the\n // decoder's frame rate, old frames get evicted rather than accumulating\n // unboundedly. Mirrors the capacity used by the nodeav addon.\n const FRAME_BUFFER_CAPACITY = 32\n type DecodedFrame = import('@camstack/types').DecodedFrame\n const frameBuffers = new Map<string, RingBuffer<DecodedFrame>>()\n const sessions = new Map<string, import('@camstack/types').IDecoderSession>()\n const unsubscribers = new Map<string, () => void>()\n let nextId = 0\n\n const resolveDecoder = async (codec: string): Promise<IDecoderProvider | null> => {\n for (const p of this.staticDecoders) {\n if (await p.supportsCodec({ codec })) return p\n }\n return null\n }\n\n return {\n supportsCodec: async (input) => (await resolveDecoder(input.codec)) !== null,\n getInfo: async () => {\n const provider = this.staticDecoders[0]\n if (!provider) return { id: 'none', name: 'No decoder' }\n return { id: provider.id, name: provider.name, isPullMode: provider.isPullMode, priority: provider.priority }\n },\n createSession: async (config) => {\n const provider = await resolveDecoder(config.codec)\n if (!provider) throw new Error(`No decoder provider for codec \"${config.codec}\"`)\n const session = await provider.createSession(config)\n const sessionId = `dec-${++nextId}`\n sessions.set(sessionId, session)\n const buffer = new RingBuffer<DecodedFrame>(FRAME_BUFFER_CAPACITY)\n frameBuffers.set(sessionId, buffer)\n const unsub = session.onFrame((frame) => { buffer.push(frame) })\n unsubscribers.set(sessionId, unsub)\n return { sessionId, nodeId: 'local' }\n },\n pushPacket: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session) session.pushPacket(input.packet)\n },\n pullFrames: async (input) => {\n const buffer = frameBuffers.get(input.sessionId)\n if (!buffer) return []\n return buffer.drain(input.maxCount)\n },\n destroySession: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session) {\n const unsub = unsubscribers.get(input.sessionId)\n if (unsub) unsub()\n sessions.delete(input.sessionId)\n frameBuffers.delete(input.sessionId)\n unsubscribers.delete(input.sessionId)\n await session.destroy()\n }\n },\n openStream: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session?.openStream) await session.openStream(input.url)\n },\n updateConfig: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session) session.updateConfig(input.config)\n },\n getStats: async (input) => {\n const session = sessions.get(input.sessionId)\n if (!session) return { inputFps: 0, outputFps: 0, avgDecodeTimeMs: 0, droppedFrames: 0 }\n return session.getStats()\n },\n }\n }\n\n // ── Setters (wiring from the addon) ──────────────────────────────────\n\n setCapabilitiesAccess(access: CapabilitiesAccess): void {\n this.capabilities = access\n }\n\n /**\n * Wire the addon's `ctx.api` proxy. Required for the broker to\n * resolve the `decoder` capability via tRPC — see\n * `buildDecoderCapApi` for the full rationale.\n */\n setApiAccess(api: AddonApi): void {\n this.api = api\n }\n\n setDefaultPreBufferSec(sec: number): void {\n this.defaultPreBufferSec = sec\n }\n\n setEventBus(bus: IEventBus): void {\n this.eventBus = bus\n this.startStreamHealthWatchdog()\n this.subscribePlaceholderStateSources(bus)\n }\n\n /**\n * Subscribe to the cap-scoped bus events that drive the per-broker\n * placeholder image. Lives in the manager (not the broker) because\n * a single device can host multiple brokers (`high`/`mid`/`low`)\n * and one listener fans out to all of them. Lives at the broker\n * layer (not the provider) because providers must stay oblivious\n * to the placeholder pipeline — they only mutate their runtime-\n * state slices.\n *\n * Each cap defines its own state-change event (`BatteryStatus`\n * surfaces `battery.onStatusChanged`), wired automatically by the\n * `subscribeCap('battery')` bridge in the provider's\n * `registerBatteryIfSupported`. We subscribe to the cap event\n * directly rather than the catch-all `device.state-changed`\n * firehose so we don't filter the entire device-state stream\n * client-side and we get a typed payload.\n */\n private subscribePlaceholderStateSources(bus: IEventBus): void {\n bus.subscribe({ category: EventCategory.BatteryOnStatusChanged }, (event: SystemEvent) => {\n const data = event.data as {\n deviceId?: unknown\n status?: { sleeping?: unknown } | null\n }\n const deviceId = typeof data.deviceId === 'number' ? data.deviceId : null\n if (deviceId === null) return\n // Slice may be `undefined` (cap slice cleared) or `null` (slice\n // dropped); both map to \"no longer sleeping\" — fall back to the\n // generic reconnecting placeholder.\n const sleeping = data.status?.sleeping === true\n this.applyPlaceholderReasonToDevice(deviceId, sleeping ? 'sleeping' : 'reconnecting')\n })\n\n const handlerForReason = (reason: PlaceholderReason) =>\n (event: SystemEvent) => {\n const data = event.data as { deviceId?: unknown }\n const deviceId = typeof data.deviceId === 'number' ? data.deviceId : null\n if (deviceId === null) return\n this.applyPlaceholderReasonToDevice(deviceId, reason)\n }\n bus.subscribe({ category: EventCategory.DeviceOffline }, handlerForReason('offline'))\n bus.subscribe({ category: EventCategory.DeviceDisabled }, handlerForReason('disabled'))\n }\n\n setWebrtcServer(server: import('../webrtc/broker-webrtc-server.js').BrokerWebrtcServer): void {\n this.webrtcServer = server\n // Inject the assignments-aware tier resolver so the WebRTC adaptive\n // bitrate logic can map cam-stream-keyed brokers back to their\n // current profile tier ('high'/'mid'/'low'). Without this, all\n // candidates appear \"untiered\" and `prefersTier` hints fall through\n // to the order-based pick.\n server.setProfileTierResolver((brokerId) => {\n const parsed = this.parseBrokerId(brokerId)\n if (!parsed) return null\n return findProfileForStream(this.assignments.get(parsed.deviceId)?.map ?? {}, parsed.camStreamId)\n })\n }\n\n setProfileMapPersister(persister: ProfileMapPersister): void {\n this.profileMapPersister = persister\n }\n\n loadPersistedProfileMap(entries: ReadonlyMap<number, DeviceAssignment>): void {\n this.assignments.clear()\n for (const [k, v] of entries) this.assignments.set(k, v)\n }\n\n // ── Legacy restreamer accessors (kept for tests) ────────────────────\n\n private getLiveRestreamers(): readonly IRestreamer[] {\n const live = this.capabilities?.getCollection?.<IRestreamer>('restreamer')\n if (live && live.length > 0) return live\n return this.restreamers\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n for (const broker of this.brokers.values()) broker.setRestreamers(restreamers)\n this.logger.info('Updated restreamers', { meta: { count: restreamers.length } })\n }\n\n getRestreamers(): readonly IRestreamer[] {\n return this.restreamers\n }\n\n getRtspRestreamProvider(): RtspRestreamProviderImpl {\n return this.rtspProvider\n }\n\n loadPersistedTokens(tokens: ReadonlyMap<string, string>): void {\n this.persistedTokens = new Map(tokens)\n }\n\n // ── Cap methods: cam stream lifecycle ────────────────────────────────\n\n async publishCameraStream(input: {\n deviceId: number\n camStreamId: string\n kind: CamStreamKind\n url?: string\n codec?: string\n resolution?: { width: number; height: number }\n fps?: number\n label?: string\n /**\n * Device-level features the publisher advertised — single source of\n * truth for downstream policy (battery → relaxed stall watchdog,\n * prebuffer off, snapshot rate-limit longer). Re-publishes overwrite\n * the prior list so device feature changes (e.g. live battery\n * detection) propagate.\n */\n deviceFeatures?: readonly string[]\n /**\n * Whether this stream is eligible for automatic profile assignment.\n * Re-publishes overwrite — flipping a stream from auto-eligible to\n * not (or vice versa) takes effect on the next assignment recompute.\n * Default `true`.\n */\n autoEligible?: boolean\n /**\n * Transport-specific opaque metadata. Stored alongside the stream\n * record and forwarded to the source reader via\n * `StreamSource.metadata`. `pull-rfc4571` publishers put the SDP\n * here.\n */\n metadata?: Readonly<Record<string, unknown>>\n }): Promise<{ success: true }> {\n const { deviceId, camStreamId, kind, url, codec, resolution, fps, label, metadata } = input\n\n let streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) {\n streamMap = new Map()\n this.cameraStreams.set(deviceId, streamMap)\n }\n const existing = streamMap.get(camStreamId)\n const order = existing?.order ?? this.nextOrder(deviceId)\n // Detect a refreshed loopback transport: the publisher just re-ran\n // its publish pipeline (typically in response to an\n // `OnRequestStreamSourceRefresh` round-trip after a wake-up or an\n // idle-tear-down) and bound a new URL. Capture it now so we can\n // kick the broker's reconnect timer once the registry has\n // absorbed the new entry — without this nudge the broker stays\n // on its exponential-backoff schedule and may dial the stale URL\n // for several more seconds before observing the change.\n const urlRefreshed = existing !== undefined\n && url !== undefined\n && existing.url !== url\n // Re-publish updates the feature snapshot — battery detection can\n // happen post-creation (live probe), so keeping the latest signal\n // matters more than the first.\n const deviceFeatures: string[] = input.deviceFeatures\n ? [...input.deviceFeatures]\n : (existing?.deviceFeatures ? [...existing.deviceFeatures] : [])\n const autoEligible = input.autoEligible ?? existing?.autoEligible ?? true\n const cam: CameraStreamInternal = {\n camStreamId,\n deviceId,\n kind,\n ...(url !== undefined ? { url } : {}),\n ...(codec !== undefined ? { codec } : {}),\n ...(resolution !== undefined ? { resolution } : {}),\n ...(fps !== undefined ? { fps } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(metadata !== undefined ? { metadata } : (existing?.metadata !== undefined ? { metadata: existing.metadata } : {})),\n order,\n deviceFeatures,\n autoEligible,\n }\n streamMap.set(camStreamId, cam)\n\n // Re-run auto-assignment when the device has never had a manual\n // assignment AND (a) profileMap is empty, or (b) auto flag is true.\n // Re-publish of a known camStreamId with the SAME id keeps map stable.\n const current = this.assignments.get(deviceId)\n const streams = [...streamMap.values()].sort((a, b) => a.order - b.order)\n const isAuto = current?.auto ?? true\n let newMap: Partial<Record<CamProfile, string>>\n if (!current || (isAuto && this.wouldChangeAutoMap(current.map, streams))) {\n newMap = this.computeInitialAssignment(streams)\n } else {\n newMap = current.map\n }\n\n // Snapshot whether the just-published stream is already in a profile\n // slot before applyAssignmentUpdate runs. If it is — and the slot\n // still points at it after the update (i.e. no transition will fire\n // demand naturally) — we need to re-emit demand explicitly. A\n // re-publish from a freshly-restarted provider wipes its in-memory\n // demand state, so the broker must re-tell it which streams are\n // wanted. Demand handlers are idempotent on the provider side\n // (`if (active.has(id)) return`), so re-emitting is always safe.\n const prePublishProfile = PUSH_KINDS.has(kind)\n ? findProfileForStream(current?.map ?? {}, camStreamId)\n : null\n const wasAlreadyAssigned = prePublishProfile !== null && existing !== undefined\n\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: isAuto, reason: 'publish' })\n\n if (wasAlreadyAssigned) {\n const finalProfile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId)\n if (finalProfile !== null) {\n this.emitCamStreamDemand(deviceId, camStreamId, finalProfile)\n }\n }\n\n // Every published cam stream gets a live broker. Pre-buffer is\n // OFF by default so idle streams don't pin RAM or wake battery\n // cameras; the broker just sits ready for the first subscriber\n // (WebRTC viewer, RTSP restream client, detection pipeline) to\n // attach.\n await this.ensureBroker(deviceId, camStreamId)\n\n // The broker that survived this re-publish may have been waiting\n // on its exponential-backoff timer — kick it so the next dial\n // uses the fresh URL right away instead of after several more\n // wasted retries against the stale loopback port.\n if (urlRefreshed) {\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n broker?.kickReconnect()\n }\n\n this.emitCamStreamsChanged(deviceId)\n return { success: true as const }\n }\n\n async retractCameraStream(input: {\n deviceId: number\n camStreamId: string\n }): Promise<{ success: true }> {\n const { deviceId, camStreamId } = input\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap?.has(camStreamId)) return { success: true as const }\n\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n let newMap = { ...current.map }\n for (const profile of CAM_PROFILE_ORDER) {\n if (newMap[profile] === camStreamId) delete newMap[profile]\n }\n if (current.auto) {\n const remaining = [...streamMap.values()]\n .filter((s) => s.camStreamId !== camStreamId)\n .sort((a, b) => a.order - b.order)\n newMap = this.computeInitialAssignment(remaining)\n }\n\n // Apply the assignment BEFORE removing from streamMap so\n // `countPushConsumers` can still identify the retracted stream as\n // push-kind and emit the N→0 idle event.\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: current.auto, reason: 'retract' })\n\n // Stream lifecycle is tied 1:1 to publish/retract — destroy the\n // broker now that its source is gone.\n await this.releaseBroker(deviceId, camStreamId)\n\n streamMap.delete(camStreamId)\n if (streamMap.size === 0) this.cameraStreams.delete(deviceId)\n\n this.emitCamStreamsChanged(deviceId)\n return { success: true as const }\n }\n\n // ── Cap methods: profile assignment ─────────────────────────────────\n\n async assignProfile(input: {\n deviceId: number\n profile: CamProfile\n camStreamId: string\n }): Promise<{ success: true }> {\n const { deviceId, profile, camStreamId } = input\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap || !streamMap.has(camStreamId)) {\n throw new Error(\n `assignProfile: camStreamId \"${camStreamId}\" is not published for device ${deviceId}`,\n )\n }\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const newMap = { ...current.map, [profile]: camStreamId }\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: false, reason: 'assign' })\n return { success: true as const }\n }\n\n async unassignProfile(input: {\n deviceId: number\n profile: CamProfile\n }): Promise<{ success: true }> {\n const { deviceId, profile } = input\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n if (current.map[profile] === undefined) {\n // Still mark manual so subsequent publishes don't auto-reassign.\n if (current.auto) {\n await this.applyAssignmentUpdate(deviceId, current.map, { auto: false, reason: 'unassign' })\n }\n return { success: true as const }\n }\n const newMap = { ...current.map }\n delete newMap[profile]\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: false, reason: 'unassign' })\n return { success: true as const }\n }\n\n async restartProfile(input: {\n deviceId: number\n profile: CamProfile\n }): Promise<{ success: boolean }> {\n const { deviceId, profile } = input\n const camStreamId = this.assignments.get(deviceId)?.map[profile]\n if (!camStreamId) return { success: false }\n await this.restartBroker(deviceId, camStreamId)\n this.emitProfileSlotsChanged(deviceId)\n return { success: true }\n }\n\n /**\n * Recreate the broker for a cam stream — destroy the existing\n * instance then re-create it from the same source. Used by\n * `restartProfile`; lifecycle stays tied to publish/retract so the\n * broker comes back unconditionally as long as the source is still\n * published.\n */\n private async restartBroker(deviceId: number, camStreamId: string): Promise<void> {\n if (!this.cameraStreams.get(deviceId)?.has(camStreamId)) return\n await this.releaseBroker(deviceId, camStreamId)\n await this.ensureBroker(deviceId, camStreamId)\n }\n\n // ── Cap methods: system-wide views ──────────────────────────────────\n\n async listAllCameraStreams(): Promise<readonly CameraStream[]> {\n const out: CameraStream[] = []\n for (const streamMap of this.cameraStreams.values()) {\n const sorted = [...streamMap.values()].sort((a, b) => a.order - b.order)\n for (const s of sorted) out.push(this.toCameraStream(s))\n }\n return out\n }\n\n async listAllProfileSlots(): Promise<readonly ProfileSlot[]> {\n const out: ProfileSlot[] = []\n for (const deviceId of this.assignments.keys()) {\n for (const slot of this.snapshotProfileSlots(deviceId)) out.push(slot)\n }\n return out\n }\n\n // ── Device-scoped facades (for camera-streams cap) ───────────────────\n\n getCameraStreamsForDevice(deviceId: number): readonly CameraStream[] {\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) return []\n return [...streamMap.values()]\n .sort((a, b) => a.order - b.order)\n .map((s) => this.toCameraStream(s))\n }\n\n getProfileSlotsForDevice(deviceId: number): readonly ProfileSlot[] {\n // Return empty for devices the broker has never touched — avoids\n // phantom `unassigned` slots for every numeric id in the universe.\n if (!this.cameraStreams.has(deviceId) && !this.assignments.has(deviceId)) return []\n return this.snapshotProfileSlots(deviceId)\n }\n\n // ── Cap methods: broker runtime (stats + client inventory) ──────────\n\n async getBroker(input: { brokerId: string }): Promise<IStreamBroker | null> {\n return this.brokers.get(input.brokerId) ?? null\n }\n\n async getBrokerStats(input: { brokerId: string }): Promise<BrokerStats> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) {\n return {\n status: 'stopped',\n inputFps: 0,\n decodeFps: 0,\n encodedSubscribers: 0,\n decodedSubscribers: 0,\n uptimeMs: 0,\n bitrateKbps: 0,\n idrIntervalMs: 0,\n totalBytes: 0,\n packetCount: 0,\n rtspClients: 0,\n pipeClients: 0,\n preBufferSec: 0,\n preBufferMs: 0,\n preBufferPackets: 0,\n decoderNodeId: null,\n audio: null,\n }\n }\n return broker.getStats()\n }\n\n async listClients(input: { brokerId: string }): Promise<import('@camstack/types').BrokerClients> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) {\n return { rtsp: [], decoded: [], audio: [], pipeClients: 0, encodedSubscribers: 0 }\n }\n return broker.listClients()\n }\n\n async killClient(input: {\n brokerId: string\n channel: 'rtsp' | 'decoded' | 'audio'\n handle: string\n }): Promise<{ killed: boolean }> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) return { killed: false }\n return { killed: broker.killClient(input.channel, input.handle) }\n }\n\n async rotateDecodersOnNode(agentNodeId: string, reason: string): Promise<number> {\n let rotated = 0\n for (const broker of this.brokers.values()) {\n const nodeId = broker.getDecoderNodeId()\n if (!nodeId) continue\n const brokerAgent = nodeId.includes('/') ? nodeId.split('/')[0]! : nodeId\n if (brokerAgent !== agentNodeId) continue\n try {\n await broker.rotateDecoderSession(reason)\n rotated++\n } catch (err) {\n this.logger.warn('decoder rotation failed', {\n meta: { brokerId: broker.deviceId, agentNodeId, reason, error: errMsg(err) },\n })\n }\n }\n return rotated\n }\n\n /** Raw broker instances — internal use only. */\n getBrokerInstances(): readonly StreamBroker[] {\n return [...this.brokers.values()]\n }\n\n async destroyAll(): Promise<void> {\n if (this.streamHealthTimer) {\n clearInterval(this.streamHealthTimer)\n this.streamHealthTimer = undefined\n }\n const stopPromises = [...this.brokers.values()].map((broker) => broker.stop())\n await Promise.all(stopPromises)\n this.brokers.clear()\n this.streamHealthByBroker.clear()\n await this.rtspServer.stop()\n }\n\n // ── Stream health watchdog ──────────────────────────────────────────\n //\n // Polls every active broker every STREAM_HEALTH_POLL_MS. A broker is\n // considered stale when its last video packet is older than\n // STREAM_STALE_TIMEOUT_MS. State transitions emit `stream.online` /\n // `stream.offline` on the event bus; payload includes the camStreamId\n // currently bound to the profile slot (the operator-visible \"key\").\n //\n // Brokers whose slot is unassigned (no camStreamId) skip emission —\n // there is no published stream to report on.\n\n private startStreamHealthWatchdog(): void {\n if (this.streamHealthTimer) return\n this.streamHealthTimer = setInterval(() => this.evaluateStreamHealth(), STREAM_HEALTH_POLL_MS)\n }\n\n private evaluateStreamHealth(): void {\n if (!this.eventBus) return\n const now = Date.now()\n for (const [brokerId, broker] of this.brokers) {\n const parsed = this.parseBrokerId(brokerId)\n if (!parsed) continue\n const { deviceId, camStreamId } = parsed\n const lastPacketAt = broker.getLastPacketAt()\n const wasHealthy = this.streamHealthByBroker.get(brokerId)\n const isHealthy = lastPacketAt > 0 && now - lastPacketAt <= STREAM_STALE_TIMEOUT_MS\n if (wasHealthy === isHealthy) continue\n this.streamHealthByBroker.set(brokerId, isHealthy)\n this.emitStreamHealth(isHealthy, {\n deviceId,\n camStreamId,\n profile: findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId),\n brokerId,\n sourceType: broker.getActiveSourceType(),\n lastPacketAt,\n reason: isHealthy ? (wasHealthy === undefined ? 'first-packet' : 'recovered') : 'stale-timeout',\n })\n }\n }\n\n private emitStreamHealth(\n online: boolean,\n payload: {\n deviceId: number\n camStreamId: string\n profile: CamProfile | null\n brokerId: string\n sourceType: string\n lastPacketAt: number\n reason: string\n },\n ): void {\n const bus = this.eventBus\n if (!bus) return\n const category = online ? 'stream.online' : 'stream.offline'\n bus.emit({\n id: `${category}-${payload.brokerId}-${Date.now()}`,\n timestamp: new Date(),\n category,\n source: { type: 'stream-broker', id: payload.brokerId },\n data: payload,\n })\n // Slot status flipped — re-snapshot and propagate to the device's\n // `camera-streams` runtime-state slice so consumers (RTSP autonomous\n // online tracking, UI dashboards) get push semantics without having\n // to subscribe to the per-stream events themselves. SLICE-ONLY:\n // we deliberately do NOT fire `onProfileSlotsChanged` here — the\n // orchestrator listens to that event to restart detection, but a\n // health flip (lazy-dial suspend / on-demand resume) is not a slot\n // mutation and must not tear down the runner attachment. Without\n // this guard each `suspend()`/`start()` cycle would race a\n // `stopDetection`+`startDetection` pair, surfacing as\n // `PipelineCameraUnassigned`+`PipelineCameraAssigned` pairs in the\n // UI events panel.\n void this.writeCameraStreamsSlice(payload.deviceId, this.snapshotProfileSlots(payload.deviceId))\n }\n\n /**\n * Decompose a brokerId `${deviceId}/${camStreamId}` back into its\n * components. `camStreamId` may contain `:` (e.g. `native:main`), so\n * we split on the FIRST `/` only — everything after is the cam stream\n * id verbatim.\n */\n private parseBrokerId(brokerId: string): { deviceId: number; camStreamId: string } | null {\n const idx = brokerId.indexOf('/')\n if (idx <= 0) return null\n const deviceId = Number(brokerId.slice(0, idx))\n const camStreamId = brokerId.slice(idx + 1)\n if (!Number.isFinite(deviceId) || camStreamId.length === 0) return null\n return { deviceId, camStreamId }\n }\n\n async startRtspServer(port: number = 8554): Promise<void> {\n await this.rtspServer.start(port)\n this.logger.info('RTSP restream server listening', { meta: { port: this.rtspServer.getPort() } })\n }\n\n // ── Cap methods: pre-buffer ─────────────────────────────────────────\n\n async setPreBufferDuration(input: { brokerId: string; seconds: number }): Promise<void> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) return\n broker.setPreBufferDuration(input.seconds)\n }\n\n async getPreBufferInfo(input: {\n brokerId: string\n }): Promise<{ configuredSec: number; bufferedMs: number; packetCount: number }> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) return { configuredSec: 0, bufferedMs: 0, packetCount: 0 }\n const stats = broker.getStats()\n return {\n configuredSec: stats.preBufferSec,\n bufferedMs: stats.preBufferMs,\n packetCount: stats.preBufferPackets,\n }\n }\n\n // ── Cap methods: stream URLs ────────────────────────────────────────\n\n async getStreamUrl(input: { streamId: string; format: StreamFormat }): Promise<{ url: string }> {\n const prefix = input.streamId.split('/')[0]\n if (!prefix) return { url: '' }\n const deviceId = Number(prefix)\n if (!Number.isFinite(deviceId)) return { url: '' }\n for (const restreamer of this.getLiveRestreamers()) {\n const resources = restreamer.getExposedResources(deviceId)\n const match = resources.find((r) => r.format === input.format)\n if (match) return { url: match.value }\n }\n return { url: '' }\n }\n\n // ── Cap methods: RTSP restream ──────────────────────────────────────\n\n async getRtspPort(): Promise<number> {\n return this.rtspProvider.getRtspPort()\n }\n\n async getAllRtspEntries(input?: { hostname?: string }): Promise<readonly RtspRestreamEntry[]> {\n return this.rtspProvider.getAllEntries(input?.hostname)\n }\n\n /** Device-scoped RTSP entry fetch — backs `cameraStreams.getRtspEntries`. */\n getRtspEntriesForDevice(deviceId: number, hostname?: string): readonly RtspRestreamEntry[] {\n return this.rtspProvider.getEntriesForDevice(deviceId, hostname)\n }\n\n async getRtspEntry(input: { brokerId: string; hostname?: string }): Promise<RtspRestreamEntry | null> {\n return this.rtspProvider.getEntry(input.brokerId, input.hostname)\n }\n\n async regenerateRtspToken(input: { brokerId: string }): Promise<string | null> {\n this.logger.info('regenerateRtspToken called', { meta: { brokerId: input.brokerId } })\n const result = this.rtspProvider.regenerateToken(input.brokerId)\n if (result) {\n this.logger.info('regenerateRtspToken succeeded', {\n meta: { brokerId: input.brokerId, newTokenPrefix: result.slice(0, 8) },\n })\n } else {\n this.logger.warn('regenerateRtspToken failed — broker not found', {\n meta: { brokerId: input.brokerId },\n })\n }\n return result\n }\n\n async setRtspEnabled(input: { brokerId: string; enabled: boolean }): Promise<void> {\n this.rtspProvider.setEnabled(input.brokerId, input.enabled)\n }\n\n async isRtspEnabled(input: { brokerId: string }): Promise<boolean> {\n return this.rtspProvider.isEnabled(input.brokerId)\n }\n\n /**\n * Fan-out a placeholder reason to every broker registered for a\n * given device. Internal — drives the runtime-state-driven\n * placeholder behaviour: the broker manager subscribes to events\n * that reflect device state (battery sleep, online/offline,\n * disabled), translates them to a `PlaceholderReason`, and pushes\n * to the per-device broker set. Providers stay oblivious — they\n * only mutate their own runtime state.\n */\n private applyPlaceholderReasonToDevice(deviceId: number, reason: PlaceholderReason): void {\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) return\n for (const camStreamId of streamMap.keys()) {\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n broker?.setPlaceholderReason(reason)\n }\n }\n\n // ── Device overrides (kept for per-profile pre-buffer tuning) ──────\n\n setDeviceOverridePersister(cb: (overrides: ReadonlyMap<number, DeviceOverride>) => void): void {\n this.deviceOverridePersister = cb\n }\n\n loadPersistedDeviceOverrides(overrides: ReadonlyMap<number, DeviceOverride>): void {\n this.deviceOverrides.clear()\n for (const [k, v] of overrides) this.deviceOverrides.set(k, v)\n }\n\n /**\n * Effective pre-buffer duration for a brokerId's profile.\n *\n * Priority: per-profile override → legacy single-value override →\n * battery-aware default (0 when the device has BatteryOperated and\n * no override) → addon default.\n */\n getEffectivePreBufferSec(\n deviceId: number,\n camStreamId: string,\n addonDefault: number,\n forceForDetection = false,\n ): number {\n const override = this.deviceOverrides.get(deviceId)\n const perStream = override?.preBuffer?.[camStreamId]\n if (perStream) {\n const enabled = perStream.enabled || forceForDetection\n return enabled ? perStream.seconds : 0\n }\n if (override?.preBufferSecOverride !== undefined) return override.preBufferSecOverride\n if (this.deviceHasFeature(deviceId, DeviceFeature.BatteryOperated)) return 0\n return addonDefault\n }\n\n /**\n * Per-device streaming debug flag — controls verbose info-level\n * logging across the broker, WebRTC server, and session for one\n * device. Off by default.\n */\n isStreamingDebug(deviceId: number): boolean {\n return this.deviceOverrides.get(deviceId)?.streamingDebug === true\n }\n\n // ── Device-details aggregator contribution ─────────────────────────\n\n async getDeviceSettingsContribution(input: {\n deviceId: number\n }): Promise<ConfigUISchemaWithValues | null> {\n const deviceId = input.deviceId\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap || streamMap.size === 0) return null\n const assignment = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const streams = [...streamMap.values()].sort((a, b) => a.order - b.order)\n const override = this.deviceOverrides.get(deviceId)\n\n const streamOptions = [\n { value: '', label: 'Not assigned' },\n ...streams.map((s) => ({\n value: s.camStreamId,\n label: this.labelForDropdown(s),\n })),\n ]\n\n const PROFILES: ReadonlyArray<{ key: CamProfile; label: string }> = [\n { key: 'high', label: 'High' },\n { key: 'mid', label: 'Mid' },\n { key: 'low', label: 'Low' },\n ]\n\n // ── Sub-tab \"Assignment\" — three profile→stream selectors ──────\n const assignmentTab: ConfigSubTabDefinitionWithValue = {\n id: 'assignment',\n label: 'Assignment',\n fields: PROFILES.map(({ key: profile, label }) => ({\n type: 'select' as const,\n key: `streamProfile:${profile}`,\n label: `${label} Quality`,\n description: `Which camera stream serves the \"${label}\" profile slot`,\n options: streamOptions,\n span: 2 as const,\n value: assignment.map[profile] ?? '',\n })),\n }\n\n // ── One sub-tab per stream source with its broker settings ─────\n // Every published cam stream owns a broker for as long as the\n // source is published — there is no \"Active\" toggle. Pre-buffer\n // is OFF by default and opt-in per stream.\n const streamTabs: ConfigSubTabDefinitionWithValue[] = streams.map((stream) => {\n const camStreamId = stream.camStreamId\n const brokerId = brokerIdFor(deviceId, camStreamId)\n const profile = findProfileForStream(assignment.map, camStreamId)\n const rtspEnabled = this.rtspProvider.isEnabled(brokerId)\n const rtspEntry = this.rtspProvider.getEntry(brokerId)\n const perStream = override?.preBuffer?.[camStreamId]\n const preBufferEnabled = perStream?.enabled ?? false\n const preBufferSec = perStream?.seconds ?? this.defaultPreBufferSec\n\n const fields: ConfigFieldWithValue[] = [\n {\n type: 'boolean' as const,\n style: 'switch' as const,\n key: `rtspEnabled:${camStreamId}`,\n label: 'RTSP Restream',\n description: 'Expose this stream via the RTSP restream server',\n default: true,\n value: rtspEnabled,\n },\n {\n type: 'text' as const,\n key: `rtspUrl:${camStreamId}`,\n label: 'RTSP URL',\n readonlyField: true,\n span: 2 as const,\n value: rtspEntry?.url ?? 'inactive',\n actions: [\n { action: 'copy-value', icon: 'copy', tooltip: 'Copy URL to clipboard' },\n {\n action: 'regenerate-rtsp-token',\n icon: 'refresh-cw',\n tooltip: 'Regenerate RTSP token',\n variant: 'danger' as const,\n confirmMessage: `Rotate the RTSP token for ${stream.label ?? camStreamId}? Current URL will stop working immediately.`,\n },\n ],\n },\n {\n type: 'text' as const,\n key: `rtspMutedUrl:${camStreamId}`,\n label: 'RTSP URL (muted)',\n description: 'Video-only stream — no audio track',\n readonlyField: true,\n span: 2 as const,\n value: rtspEntry?.url ? `${rtspEntry.url}/muted` : 'inactive',\n actions: [\n { action: 'copy-value', icon: 'copy', tooltip: 'Copy muted URL to clipboard' },\n ],\n },\n {\n type: 'boolean' as const,\n style: 'switch' as const,\n key: `preBufferEnabled:${camStreamId}`,\n label: 'Pre-buffer',\n description:\n 'Keep a rolling buffer for instant playback on events. Forced on when detection uses this stream.',\n default: false,\n value: preBufferEnabled,\n },\n {\n type: 'slider' as const,\n key: `preBufferSec:${camStreamId}`,\n label: 'Pre-buffer Duration',\n min: 0,\n max: 30,\n step: 1,\n unit: 's',\n default: this.defaultPreBufferSec,\n showValue: true,\n value: preBufferSec,\n },\n ]\n\n const tabBadge = profile\n ? PROFILES.find((p) => p.key === profile)?.label\n : undefined\n const tabLabel = stream.label ?? camStreamId\n const def: ConfigSubTabDefinitionWithValue = {\n id: `stream:${camStreamId}`,\n label: tabLabel,\n fields,\n ...(tabBadge ? { badge: tabBadge } : {}),\n }\n return def\n })\n\n const streamBrokerSection: ConfigSectionWithValues = {\n id: 'stream-broker-streams',\n title: '',\n tab: 'stream-broker',\n order: 10,\n fields: [\n {\n type: 'sub-tabs' as const,\n key: 'streamBroker:streams',\n label: '',\n span: 2 as const,\n tabs: [assignmentTab, ...streamTabs],\n },\n ],\n }\n\n const diagnosticsSection: ConfigSectionWithValues = {\n id: 'stream-broker-diagnostics',\n title: 'Diagnostics',\n tab: 'stream-broker',\n order: 100,\n fields: [\n {\n type: 'boolean' as const,\n style: 'switch' as const,\n key: 'streamingDebug',\n label: 'Streaming debug logs',\n description:\n 'Enable verbose info-level logs for the broker, WebRTC server, and H.265 repacketizer for this device. Off by default; turn on while diagnosing playback issues.',\n default: false,\n value: override?.streamingDebug === true,\n },\n ],\n }\n\n return { sections: [streamBrokerSection, diagnosticsSection] }\n }\n\n async getDeviceLiveContribution(_input: {\n deviceId: number\n }): Promise<ConfigUISchemaWithValues | null> {\n return null\n }\n\n async applyDeviceSettingsPatch(input: {\n deviceId: number\n patch: Record<string, unknown>\n }): Promise<{ success: true }> {\n const deviceId = input.deviceId\n if (!this.cameraStreams.has(deviceId)) {\n this.logger.debug('applyDeviceSettingsPatch: unknown deviceId — no-op', {\n tags: { deviceId },\n })\n return { success: true as const }\n }\n\n let overrideDirty = false\n const nextOverride: DeviceOverride = { ...(this.deviceOverrides.get(deviceId) ?? {}) }\n\n for (const [fieldKey, value] of Object.entries(input.patch)) {\n // Profile → camStreamId selectors live in the Assignment sub-tab\n // (`streamProfile:<profile>`).\n if (fieldKey.startsWith('streamProfile:')) {\n const profile = fieldKey.slice('streamProfile:'.length) as CamProfile\n if (!CAM_PROFILE_ORDER.includes(profile)) continue\n const newCamId = typeof value === 'string' && value !== '' ? value : null\n if (newCamId === null) {\n await this.unassignProfile({ deviceId, profile })\n } else {\n await this.assignProfile({ deviceId, profile, camStreamId: newCamId })\n }\n continue\n }\n if (fieldKey.startsWith('rtspEnabled:')) {\n const camStreamId = fieldKey.slice('rtspEnabled:'.length)\n const brokerId = brokerIdFor(deviceId, camStreamId)\n this.rtspProvider.setEnabled(brokerId, Boolean(value))\n continue\n }\n if (fieldKey === 'preBufferSecOverride') {\n overrideDirty = true\n if (value === null || value === undefined) {\n delete nextOverride.preBufferSecOverride\n } else if (typeof value === 'number' && Number.isFinite(value)) {\n nextOverride.preBufferSecOverride = value\n }\n continue\n }\n if (fieldKey.startsWith('preBufferEnabled:')) {\n const camStreamId = fieldKey.slice('preBufferEnabled:'.length)\n overrideDirty = true\n const pb = { ...(nextOverride.preBuffer ?? {}) }\n const current = pb[camStreamId] ?? this.defaultStreamPreBuffer(deviceId, camStreamId)\n const enabled = Boolean(value)\n pb[camStreamId] = { ...current, enabled }\n nextOverride.preBuffer = pb\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n if (broker) {\n broker.setPreBufferDuration(enabled ? pb[camStreamId]?.seconds ?? this.defaultPreBufferSec : 0)\n broker.setPreBufferEnabled(enabled)\n }\n continue\n }\n if (fieldKey.startsWith('preBufferSec:')) {\n const camStreamId = fieldKey.slice('preBufferSec:'.length)\n const seconds =\n typeof value === 'number' && Number.isFinite(value) ? value : this.defaultPreBufferSec\n overrideDirty = true\n const pb = { ...(nextOverride.preBuffer ?? {}) }\n const current = pb[camStreamId] ?? this.defaultStreamPreBuffer(deviceId, camStreamId)\n pb[camStreamId] = { ...current, seconds }\n nextOverride.preBuffer = pb\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n if (broker && pb[camStreamId]?.enabled !== false) {\n broker.setPreBufferDuration(seconds)\n }\n continue\n }\n if (fieldKey === 'streamingDebug') {\n overrideDirty = true\n const next = value === null || value === undefined ? undefined : Boolean(value)\n if (next === undefined) {\n delete nextOverride.streamingDebug\n } else {\n nextOverride.streamingDebug = next\n }\n // Push to every active broker for this device immediately so\n // the toggle takes effect without restarting the stream.\n for (const [bid, broker] of this.brokers) {\n if (bid.startsWith(`${deviceId}/`)) {\n broker.setStreamingDebug(next === true)\n }\n }\n continue\n }\n }\n\n if (overrideDirty) {\n if (\n nextOverride.preBufferSecOverride === undefined &&\n (!nextOverride.preBuffer || Object.keys(nextOverride.preBuffer).length === 0) &&\n nextOverride.streamingDebug === undefined\n ) {\n this.deviceOverrides.delete(deviceId)\n } else {\n this.deviceOverrides.set(deviceId, nextOverride)\n }\n this.deviceOverridePersister?.(this.deviceOverrides)\n }\n return { success: true as const }\n }\n\n // ── Private helpers ─────────────────────────────────────────────────\n\n private nextOrder(deviceId: number): number {\n const next = (this.streamOrderSeq.get(deviceId) ?? 0) + 1\n this.streamOrderSeq.set(deviceId, next)\n return next\n }\n\n private toCameraStream(s: CameraStreamInternal): CameraStream {\n const { order: _order, deviceFeatures: _df, ...rest } = s\n return rest\n }\n\n private deviceHasFeature(deviceId: number, feature: string): boolean {\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) return false\n for (const s of streamMap.values()) {\n if (s.deviceFeatures.includes(feature)) return true\n }\n return false\n }\n\n private labelForDropdown(s: CameraStreamInternal): string {\n const base = s.label ?? s.camStreamId\n if (s.resolution) return `${base} (${s.resolution.width}×${s.resolution.height})`\n return base\n }\n\n /**\n * Compute the canonical profile map for a given ordered stream list.\n *\n * 1 stream → { mid }\n * 2 streams → { high, low } (higher resolution → high)\n * 3+ streams → { high, mid, low }\n *\n * Ties on resolution fall back to publish order (stream.order asc).\n * Streams with no resolution sort below those with one — publish\n * order breaks ties within each rank.\n *\n * Streams marked `autoEligible: false` are skipped — they remain\n * published (visible + manually assignable via `assignProfile`) but\n * never become the default. If every stream is non-eligible the\n * algorithm degrades gracefully and considers them all (so a device\n * with only ineligible streams still gets some default assignment).\n */\n private computeInitialAssignment(\n streams: readonly CameraStreamInternal[],\n ): Partial<Record<CamProfile, string>> {\n if (streams.length === 0) return {}\n const eligible = streams.filter((s) => s.autoEligible)\n const pool = eligible.length > 0 ? eligible : streams\n const ranked = [...pool].sort((a, b) => {\n const aPx = a.resolution ? a.resolution.width * a.resolution.height : -1\n const bPx = b.resolution ? b.resolution.width * b.resolution.height : -1\n if (aPx !== bPx) return bPx - aPx\n return a.order - b.order\n })\n if (ranked.length === 1) return { mid: ranked[0]!.camStreamId }\n if (ranked.length === 2) {\n return { high: ranked[0]!.camStreamId, low: ranked[1]!.camStreamId }\n }\n return {\n high: ranked[0]!.camStreamId,\n mid: ranked[1]!.camStreamId,\n low: ranked[2]!.camStreamId,\n }\n }\n\n private wouldChangeAutoMap(\n current: Partial<Record<CamProfile, string>>,\n streams: readonly CameraStreamInternal[],\n ): boolean {\n const computed = this.computeInitialAssignment(streams)\n for (const p of CAM_PROFILE_ORDER) {\n if (current[p] !== computed[p]) return true\n }\n return false\n }\n\n /**\n * Core transition: diff `current.map` vs `newMap`, then translate the\n * profile-level diff to demand/idle events for push-kind streams.\n * Brokers are NOT touched here — every published cam stream owns\n * its broker for as long as the source exists, regardless of which\n * profile slot points at it. Profile reassignments are pure\n * metadata changes from the broker's point of view.\n */\n private async applyAssignmentUpdate(\n deviceId: number,\n newMap: Partial<Record<CamProfile, string>>,\n opts: { auto: boolean; reason: 'publish' | 'retract' | 'assign' | 'unassign' },\n ): Promise<void> {\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const nextAssignment: DeviceAssignment = { map: newMap, auto: opts.auto }\n\n // Pre-change demand view: which push cam streams have active consumers?\n const oldPushConsumers = this.countPushConsumers(current.map, deviceId)\n const newPushConsumers = this.countPushConsumers(newMap, deviceId)\n\n let anyChange = false\n for (const profile of CAM_PROFILE_ORDER) {\n if (current.map[profile] !== newMap[profile]) {\n anyChange = true\n break\n }\n }\n\n this.assignments.set(deviceId, nextAssignment)\n\n // Demand transitions for push-kind streams.\n for (const [camId, prevCount] of oldPushConsumers) {\n if ((newPushConsumers.get(camId) ?? 0) === 0 && prevCount > 0) {\n this.emitCamStreamIdle(deviceId, camId)\n }\n }\n for (const [camId, nextCount] of newPushConsumers) {\n if ((oldPushConsumers.get(camId) ?? 0) === 0 && nextCount > 0) {\n // Figure out which profile triggered the demand (first one in canonical order).\n let triggerProfile: CamProfile = 'mid'\n for (const p of CAM_PROFILE_ORDER) {\n if (newMap[p] === camId) { triggerProfile = p; break }\n }\n this.emitCamStreamDemand(deviceId, camId, triggerProfile)\n }\n }\n\n if (anyChange || current.auto !== nextAssignment.auto) {\n this.persistProfileMap()\n this.emitProfileSlotsChanged(deviceId)\n } else if (opts.reason === 'publish') {\n // Re-publish with unchanged assignment (e.g. provider restart): still\n // emit so the orchestrator can re-dispatch detection on provider reconnect.\n this.emitProfileSlotsChanged(deviceId)\n }\n }\n\n private countPushConsumers(\n map: Partial<Record<CamProfile, string>>,\n deviceId: number,\n ): Map<string, number> {\n const out = new Map<string, number>()\n const streamMap = this.cameraStreams.get(deviceId)\n for (const profile of CAM_PROFILE_ORDER) {\n const camId = map[profile]\n if (!camId) continue\n const cam = streamMap?.get(camId)\n if (!cam || !PUSH_KINDS.has(cam.kind)) continue\n out.set(camId, (out.get(camId) ?? 0) + 1)\n }\n return out\n }\n\n private persistProfileMap(): void {\n this.profileMapPersister?.(this.assignments)\n }\n\n private buildSourceFromCamStream(cam: CameraStreamInternal): StreamSource {\n const label = cam.label ?? cam.camStreamId\n const codec = cam.codec !== undefined ? { videoCodec: cam.codec } : {}\n // Battery-flagged devices want the stall watchdog relaxed (Reolink\n // Argus and friends intentionally go silent between motion events).\n // Derive the per-source flag from the device-level feature list so\n // publishers don't have to carry their own per-stream flag.\n const stall = cam.deviceFeatures.includes(DeviceFeature.BatteryOperated)\n ? { allowStall: true as const }\n : {}\n // Pre-compose pass-through metadata so the publisher's opaque payload\n // (e.g. `pull-rfc4571` SDP) reaches the broker reader unchanged.\n const passthroughMeta = cam.metadata ?? {}\n switch (cam.kind) {\n case 'push-annexb':\n return { type: 'push', url: '', ...codec, ...stall, metadata: { kind: 'push-annexb', label, ...passthroughMeta } }\n case 'pull-rtsp':\n return { type: 'rtsp', url: cam.url ?? '', ...codec, metadata: { kind: 'pull-rtsp', label, ...passthroughMeta } }\n case 'pull-rtmp':\n return { type: 'rtmp', url: cam.url ?? '', ...codec, metadata: { kind: 'pull-rtmp', label, ...passthroughMeta } }\n case 'pull-http':\n return { type: 'http-mjpeg', url: cam.url ?? '', ...codec, metadata: { kind: 'pull-http', label, ...passthroughMeta } }\n case 'pull-rfc4571':\n return { type: 'rfc4571', url: cam.url ?? '', ...codec, ...stall, metadata: { kind: 'pull-rfc4571', label, ...passthroughMeta } }\n default: {\n const _exhaustive: never = cam.kind\n throw new Error(`Unknown cam stream kind: ${String(_exhaustive)}`)\n }\n }\n }\n\n /**\n * Acquire (or create) the broker for a (deviceId, camStreamId) pair.\n * Idempotent — returns the existing broker when one is already\n * registered. Tied to publish lifecycle: `publishCameraStream` calls\n * this once per stream; `retractCameraStream` releases it.\n */\n private async ensureBroker(\n deviceId: number,\n camStreamId: string,\n ): Promise<void> {\n const cam = this.cameraStreams.get(deviceId)?.get(camStreamId)\n if (!cam) return\n const brokerId = brokerIdFor(deviceId, camStreamId)\n if (this.brokers.has(brokerId)) return\n\n const brokerLogger = this.logger\n .child(camStreamId)\n .withTags({ deviceId, camStreamId })\n const source = this.buildSourceFromCamStream(cam)\n const broker = new StreamBroker(\n brokerId,\n this.buildDecoderCapApi(deviceId),\n brokerLogger,\n buildAudioCodecCapApi(this.api),\n )\n broker.setRestreamers(this.restreamers)\n this.applyPreBufferConfig(broker, deviceId, camStreamId, cam)\n const override = this.deviceOverrides.get(deviceId)\n if (override?.streamingDebug) broker.setStreamingDebug(true)\n\n // Hook the broker into the cam-stream registry: every dial\n // attempt re-derives the source so a re-publish (e.g. Reolink\n // refreshing its rfc4571 URL) is observed transparently. Mirrors\n // Scrypted's per-`getVideoStream()` `ensureRfcServer` resolution.\n broker.setSourceProvider(() => {\n const latest = this.cameraStreams.get(deviceId)?.get(camStreamId)\n return latest ? this.buildSourceFromCamStream(latest) : null\n })\n broker.setRequestSourceRefresh(() => {\n this.emitRequestStreamSourceRefresh(deviceId, camStreamId, brokerId)\n })\n\n await broker.start(source)\n this.brokers.set(brokerId, broker)\n\n const existingToken = this.persistedTokens.get(brokerId)\n this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), existingToken)\n if (!existingToken) this.rtspProvider.persistTokens()\n\n await this.fanOutRegistration(deviceId, brokerId, cam, broker)\n }\n\n /**\n * Tear down the broker for a `(deviceId, camStreamId)` pair. No-op\n * when no broker exists. Called by `retractCameraStream` and by the\n * teardown half of `restartBroker`.\n */\n private async releaseBroker(\n deviceId: number,\n camStreamId: string,\n ): Promise<void> {\n const brokerId = brokerIdFor(deviceId, camStreamId)\n const broker = this.brokers.get(brokerId)\n if (!broker) return\n\n await this.fanOutUnregistration(deviceId, brokerId)\n this.rtspServer.unregisterRestreamer(brokerId)\n // Emit terminal stream.offline so subscribers can clear their\n // aggregate before the broker disappears. Skip if the broker never\n // became healthy (no transition to report).\n const wasHealthy = this.streamHealthByBroker.get(brokerId)\n if (wasHealthy && this.eventBus) {\n this.emitStreamHealth(false, {\n deviceId,\n camStreamId,\n profile: findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId),\n brokerId,\n sourceType: broker.getActiveSourceType(),\n lastPacketAt: broker.getLastPacketAt(),\n reason: 'broker-destroyed',\n })\n }\n this.streamHealthByBroker.delete(brokerId)\n await broker.stop()\n this.brokers.delete(brokerId)\n this.rtspProvider.persistTokens()\n }\n\n /**\n * Default `StreamPreBuffer` for a brand-new override entry — mirrors\n * the UI-side default in `getDeviceSettingsContribution`. Pre-buffer\n * is OFF by default for every stream. Operators opt in per-stream\n * via the device-settings sub-tabs; detection / recording paths\n * pass `forceForDetection` to `getEffectivePreBufferSec` when they\n * need a buffered window irrespective of the operator toggle.\n */\n private defaultStreamPreBuffer(_deviceId: number, _camStreamId: string): StreamPreBuffer {\n return { enabled: false, seconds: this.defaultPreBufferSec }\n }\n\n /**\n * Apply the effective pre-buffer config to a fresh broker instance.\n * Default behaviour is OFF for every cam stream — keeps RAM and the\n * upstream reader idle until the operator opts in or a detection /\n * recording subscriber attaches.\n */\n private applyPreBufferConfig(\n broker: StreamBroker,\n deviceId: number,\n camStreamId: string,\n _cam: CameraStreamInternal,\n ): void {\n const override = this.deviceOverrides.get(deviceId)\n const perStream = override?.preBuffer?.[camStreamId]\n if (perStream) {\n broker.setPreBufferEnabled(perStream.enabled)\n broker.setPreBufferDuration(perStream.enabled ? perStream.seconds : 0)\n return\n }\n broker.setPreBufferEnabled(false)\n broker.setPreBufferDuration(0)\n }\n\n private async fanOutRegistration(\n deviceId: number,\n brokerId: string,\n cam: CameraStreamInternal,\n broker: StreamBroker,\n ): Promise<void> {\n const log = this.logger.withTags({ deviceId, brokerId, camStreamId: cam.camStreamId })\n const localStreamUrl = broker.getLocalStreamUrl()\n const codec = 'h264'\n const label = cam.label ?? brokerId\n\n for (const restreamer of this.getLiveRestreamers()) {\n try {\n await restreamer.registerDevice(deviceId, [\n { streamId: brokerId, label, codec, type: 'video' as const, sourceUrl: localStreamUrl },\n ])\n } catch (err) {\n log.error('Failed to register with restreamer', {\n meta: { restreamerId: restreamer.id, error: errMsg(err) },\n })\n }\n }\n\n // Register broker with the built-in WebRTC server (direct encoded\n // packet subscription — no ffmpeg, no RTSP double-hop).\n if (this.webrtcServer) {\n this.webrtcServer.registerBroker(brokerId, broker)\n }\n }\n\n private async fanOutUnregistration(deviceId: number, brokerId: string): Promise<void> {\n const log = this.logger.withTags({ deviceId, brokerId })\n for (const restreamer of this.getLiveRestreamers()) {\n try {\n await restreamer.unregisterDevice(deviceId)\n } catch (err) {\n log.error('Failed to unregister from restreamer', {\n meta: { restreamerId: restreamer.id, error: errMsg(err) },\n })\n }\n }\n // Unregister from built-in WebRTC server.\n if (this.webrtcServer) {\n this.webrtcServer.unregisterBroker(brokerId)\n }\n }\n\n private brokerStatusToSlotStatus(status: BrokerStats['status']): ProfileSlotStatus {\n // BrokerStatus has 'stopped' (broker tore down cleanly) which the\n // ProfileSlotStatus schema does not model — collapse to 'idle' since\n // a stopped-but-assigned slot from the consumer's POV is equivalent\n // to idle awaiting restart.\n if (status === 'stopped') return 'idle'\n return status\n }\n\n private snapshotProfileSlots(deviceId: number): ProfileSlot[] {\n const assignment = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const streamMap = this.cameraStreams.get(deviceId)\n const slots: ProfileSlot[] = []\n for (const profile of CAM_PROFILE_ORDER) {\n const camId = assignment.map[profile] ?? null\n // brokerId is keyed by camStreamId (the canonical broker identity);\n // for unassigned slots there's no live broker, so we surface a\n // synthetic profile-shaped key purely so consumers that ignore\n // the field for unassigned slots keep working unchanged.\n const brokerId = camId ? brokerIdFor(deviceId, camId) : `${deviceId}/${profile}`\n const broker = camId ? this.brokers.get(brokerId) : undefined\n const stats = broker?.getStats()\n const cam = camId ? streamMap?.get(camId) : undefined\n const status: ProfileSlotStatus = camId\n ? stats ? this.brokerStatusToSlotStatus(stats.status) : 'idle'\n : 'unassigned'\n // Codec resolution: prefer the live broker's detected codec\n // (`stats.codec` — populated after first frame), fall back to the\n // provider-declared `cam.codec`. Reolink cameras don't always\n // populate cam.codec at registration, so without this fallback\n // every slot reported codec=undefined and the UI rendered \"—\".\n const resolvedCodec = stats?.codec ?? cam?.codec\n const slot: ProfileSlot = {\n deviceId,\n profile,\n brokerId,\n sourceCamStreamId: camId,\n status,\n ...(cam?.resolution !== undefined ? { resolution: cam.resolution } : {}),\n ...(resolvedCodec !== undefined ? { codec: resolvedCodec } : {}),\n ...(stats !== undefined ? { preBufferSec: stats.preBufferSec } : {}),\n }\n slots.push(slot)\n }\n return slots\n }\n\n // ── Event emission (via IEventBus) ──────────────────────────────────\n\n private emitCapEvent(category: string, deviceId: number, data: Record<string, unknown>): void {\n const bus = this.eventBus\n if (!bus) return\n bus.emit({\n id: `cap-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date(),\n source: { type: 'device', id: deviceId, addonId: 'stream-broker', deviceId },\n category,\n data,\n })\n }\n\n private emitCamStreamDemand(deviceId: number, camStreamId: string, profile: CamProfile): void {\n this.emitCapEvent(EventCategory.StreamBrokerOnCamStreamDemand, deviceId, { deviceId, camStreamId, profile })\n }\n\n private emitCamStreamIdle(deviceId: number, camStreamId: string): void {\n this.emitCapEvent(EventCategory.StreamBrokerOnCamStreamIdle, deviceId, { deviceId, camStreamId })\n }\n\n private emitRequestStreamSourceRefresh(deviceId: number, camStreamId: string, brokerId: string): void {\n this.emitCapEvent(EventCategory.StreamBrokerOnRequestStreamSourceRefresh, deviceId, {\n deviceId,\n camStreamId,\n brokerId,\n })\n }\n\n private emitCamStreamsChanged(deviceId: number): void {\n const camStreams = this.getCameraStreamsForDevice(deviceId)\n this.emitCapEvent('camera-streams.onCamStreamsChanged', deviceId, { deviceId, camStreams })\n }\n\n private emitProfileSlotsChanged(deviceId: number): void {\n const profileSlots = this.snapshotProfileSlots(deviceId)\n this.emitCapEvent('camera-streams.onProfileSlotsChanged', deviceId, { deviceId, profileSlots })\n void this.writeCameraStreamsSlice(deviceId, profileSlots)\n }\n\n /**\n * Mirror per-device runtime state into the `camera-streams` slice.\n * Fire-and-forget — failures land on the debug log and don't impact\n * broker behavior (the slice is observability + RTSP autonomous-online\n * source, both fault-tolerant). Mirrors the same `setCapSlice` pattern\n * pipeline-runner uses for the `motion` slice.\n */\n private async writeCameraStreamsSlice(deviceId: number, slots: readonly ProfileSlot[]): Promise<void> {\n const api = this.api\n if (!api) return\n const slotStatuses: { high?: ProfileSlotStatus; mid?: ProfileSlotStatus; low?: ProfileSlotStatus } = {}\n const slotErrors: { high?: string; mid?: string; low?: string } = {}\n let online = false\n for (const slot of slots) {\n if (slot.status !== 'unassigned') {\n slotStatuses[slot.profile] = slot.status\n if (slot.errorMessage) slotErrors[slot.profile] = slot.errorMessage\n if (slot.status === 'streaming') online = true\n }\n }\n try {\n await api.deviceState.setCapSlice.mutate({\n deviceId,\n capName: 'camera-streams',\n slice: { online, slotStatuses, slotErrors, lastChangedAt: Date.now() },\n })\n } catch (err) {\n this.logger.debug('writeCameraStreamsSlice failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n}\n","/**\n * NAL unit parsing utilities.\n * Protocol-agnostic functions for working with Annex-B NAL units.\n */\n\nimport type { VideoCodec } from \"./types.js\";\n\nconst NAL_START_CODE_4B = Buffer.from([0x00, 0x00, 0x00, 0x01]);\nconst NAL_START_CODE_3B = Buffer.from([0x00, 0x00, 0x01]);\n\n/**\n * Returns true if the buffer starts with an Annex-B start code (0x00000001 or 0x000001).\n */\nexport function hasStartCodes(data: Buffer): boolean {\n if (data.length < 4) return false;\n if (data.subarray(0, 4).equals(NAL_START_CODE_4B)) return true;\n if (data.subarray(0, 3).equals(NAL_START_CODE_3B)) return true;\n return false;\n}\n\n/**\n * Split Annex-B data into individual NAL unit payloads (without start codes).\n * Works for both H.264 and H.265.\n */\nexport function splitAnnexBToNals(annexB: Buffer): Buffer[] {\n const nals: Buffer[] = [];\n const len = annexB.length;\n\n const isStartCodeAt = (i: number): number => {\n if (i + 3 <= len && annexB[i] === 0x00 && annexB[i + 1] === 0x00) {\n if (annexB[i + 2] === 0x01) return 3;\n if (i + 4 <= len && annexB[i + 2] === 0x00 && annexB[i + 3] === 0x01)\n return 4;\n }\n return 0;\n };\n\n let i = 0;\n // Find first start code\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (sc) break;\n i++;\n }\n\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (!sc) {\n i++;\n continue;\n }\n const nalStart = i + sc;\n let j = nalStart;\n while (j < len) {\n const sc2 = isStartCodeAt(j);\n if (sc2) break;\n j++;\n }\n if (nalStart < j) {\n const nal = annexB.subarray(nalStart, j);\n if (nal.length > 0) nals.push(nal);\n }\n i = j;\n }\n\n return nals;\n}\n\n/**\n * Prepend a 4-byte Annex-B start code to a NAL payload.\n */\nexport function prependStartCode(nal: Buffer): Buffer {\n return Buffer.concat([NAL_START_CODE_4B, nal]);\n}\n\n/**\n * Join multiple NAL payloads (without start codes) into an Annex-B access unit.\n */\nexport function joinNalsToAnnexB(\n ...nals: Array<Buffer | null | undefined>\n): Buffer | undefined {\n const present = nals.filter((n): n is Buffer => !!n && n.length > 0);\n if (!present.length) return;\n const parts: Buffer[] = [];\n for (const nal of present) {\n parts.push(NAL_START_CODE_4B, nal);\n }\n return Buffer.concat(parts);\n}\n\n/**\n * Detect the actual video codec from raw NAL data.\n * Some cameras report wrong codec (e.g. \"H264\" but send H.265 data).\n * Analyzes the NAL header to determine the real codec.\n *\n * @param data - Raw video data (either Annex-B or length-prefixed)\n * @returns Detected codec type or null if detection fails\n */\nexport function detectVideoCodecFromNal(data: Buffer): VideoCodec | null {\n if (!data || data.length < 5) return null;\n\n // Find start code (00 00 00 01 or 00 00 01)\n let nalStart = -1;\n for (let i = 0; i < Math.min(data.length - 4, 100); i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4;\n break;\n }\n if (data[i + 2] === 1) {\n nalStart = i + 3;\n break;\n }\n }\n }\n\n // If no start code, try length-prefixed (AVCC/HVCC)\n if (nalStart < 0 && data.length >= 5) {\n const len = data.readUInt32BE(0);\n if (len > 0 && len <= data.length - 4) {\n nalStart = 4;\n }\n }\n\n if (nalStart < 0 || nalStart >= data.length) return null;\n\n const nalByte = data[nalStart];\n if (nalByte === undefined) return null;\n\n // Check H.264 FIRST because H.264 NAL bytes can be misinterpreted as H.265.\n const forbiddenBit264 = (nalByte >> 7) & 1;\n const h264Type = nalByte & 0x1f;\n\n if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {\n if (h264Type === 7 || h264Type === 8) return \"H264\"; // SPS, PPS\n if (h264Type === 5) return \"H264\"; // IDR\n if (h264Type === 1) {\n const nalRefIdc = (nalByte >> 5) & 0x03;\n if (nalRefIdc >= 1) return \"H264\";\n }\n }\n\n // H.265/HEVC detection\n if (nalStart + 1 < data.length) {\n const nalByte2 = data[nalStart + 1];\n if (nalByte2 !== undefined) {\n const forbiddenBit = (nalByte >> 7) & 1;\n const hevcType = (nalByte >> 1) & 0x3f;\n const temporalId = nalByte2 & 0x07;\n\n if (forbiddenBit === 0 && temporalId > 0 && hevcType <= 40) {\n if (hevcType === 32 || hevcType === 33 || hevcType === 34)\n return \"H265\"; // VPS, SPS, PPS\n if (hevcType === 19 || hevcType === 20 || hevcType === 21)\n return \"H265\"; // IDR, CRA\n if (hevcType <= 1 && nalByte <= 0x03) return \"H265\"; // TRAIL\n }\n }\n }\n\n return null;\n}\n\nexport { NAL_START_CODE_4B, NAL_START_CODE_3B };\n","/**\n * H.264/AVC utilities.\n * AVCC to Annex-B conversion, SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- AVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH264Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 1) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = b0 & 0x1f;\n return nalType >= 1 && nalType <= 23;\n}\n\nfunction depacketizeRtpAggregationToAnnexB(payload: Buffer): Buffer | null {\n if (payload.length < 1) return null;\n const nalHeader = payload[0]!;\n const nalType = nalHeader & 0x1f;\n const out: Buffer[] = [];\n const pushNal = (nal: Buffer) => {\n if (nal.length === 0) return;\n out.push(NAL_START_CODE_4B, nal);\n };\n\n // STAP-A (24)\n if (nalType === 24) {\n let off = 1;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // STAP-B (25)\n if (nalType === 25) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP16 (26)\n if (nalType === 26) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 2 > payload.length) return null;\n off += 1 + 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP24 (27)\n if (nalType === 27) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 3 > payload.length) return null;\n off += 1 + 3;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n return null;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.264 data from length-prefixed (AVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH264ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n // Some models prepend a small header before an Annex-B access unit.\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try AVCC → AnnexB conversion (BE then LE)\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n // 3-byte lengths\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n // 2-byte lengths\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n // RTP aggregation payload\n const agg = depacketizeRtpAggregationToAnnexB(data);\n if (agg) return agg;\n\n // Single NAL without start code\n if (looksLikeSingleH264Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n\n return data;\n}\n\n/**\n * Check if an H.264 Annex-B access unit is a keyframe (IDR + SPS + PPS).\n */\nexport function isH264KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasSps = false;\n let hasPps = false;\n let hasIdr = false;\n for (const nal of nals) {\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 7) hasSps = true;\n if (t === 8) hasPps = true;\n if (t === 5) hasIdr = true;\n }\n return hasIdr && hasSps && hasPps;\n}\n\n/**\n * Check if an H.264 Annex-B access unit contains an IDR slice.\n */\n/**\n * Detect H.264 keyframe (IDR or I-frame with SPS).\n *\n * Some cameras emit non-IDR I-frames (NAL type 1 with slice_type=I)\n * instead of IDR frames (NAL type 5). Both are intra-coded and valid\n * as decoder entry points, but only IDR carries NAL type 5.\n *\n * We detect keyframes by:\n * 1. NAL type 5 (IDR) — canonical\n * 2. SPS (type 7) present — parameter sets are always sent with keyframes\n */\nexport function isH264IdrAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasSps = false;\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 5) return true; // IDR slice\n if (t === 7) hasSps = true; // SPS — indicates keyframe boundary\n }\n return hasSps;\n}\n\n/**\n * Extract SPS and PPS from an H.264 Annex-B access unit.\n */\nexport function extractH264ParamSets(annexB: Buffer): {\n sps?: Buffer;\n pps?: Buffer;\n profileLevelId?: string;\n} {\n const nals = splitAnnexBToNals(annexB);\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n let profileLevelId: string | undefined;\n\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const nalType = nal[0]! & 0x1f;\n if (nalType === 7) {\n sps = nal;\n if (nal.length >= 4) {\n profileLevelId = Buffer.from([nal[1]!, nal[2]!, nal[3]!]).toString(\n \"hex\",\n );\n }\n } else if (nalType === 8) {\n pps = nal;\n }\n }\n\n const out: { sps?: Buffer; pps?: Buffer; profileLevelId?: string } = {};\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n if (profileLevelId) out.profileLevelId = profileLevelId;\n return out;\n}\n\n/**\n * H.264 RTP depacketizer (RFC 6184).\n * Handles single NAL units, STAP-A aggregation, and FU-A/FU-B fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H264RtpDepacketizer {\n private fuNalHeader: number | null = null;\n private fuParts: Buffer[] = [];\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuNalHeader = null;\n this.fuParts = [];\n }\n\n push(payload: Buffer): Buffer[] {\n if (payload.length === 0) return [];\n\n const rtpPayload = H264RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n if (hasStartCodes(payload)) return [payload];\n\n const b0 = payload[0]!;\n if ((b0 & 0x80) !== 0) return [];\n const nalType = b0 & 0x1f;\n\n // Single NAL unit\n if (nalType >= 1 && nalType <= 23) {\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n\n // STAP-A (24)\n if (nalType === 24) {\n if (payload.length < 1 + 2) return [];\n let off = 1;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length < 1) return [];\n if ((nal[0]! & 0x80) !== 0) return [];\n const t = nal[0]! & 0x1f;\n if (t === 0 || t >= 24) return [];\n out.push(Buffer.concat([NAL_START_CODE_4B, nal]));\n }\n return out;\n }\n\n // FU-A (28) / FU-B (29)\n if (nalType === 28 || nalType === 29) {\n if (payload.length < 2) return [];\n const fuIndicator = payload[0]!;\n const fuHeader = payload[1]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x1f;\n const reconstructedHeader = (fuIndicator & 0xe0) | origType;\n\n let off = 2;\n if (nalType === 29) {\n if (payload.length < off + 2) return [];\n off += 2;\n }\n const frag = payload.subarray(off);\n\n if (start) {\n this.fuNalHeader = reconstructedHeader;\n this.fuParts = [frag];\n } else if (this.fuNalHeader != null) {\n this.fuParts.push(frag);\n } else {\n return [];\n }\n\n if (end && this.fuNalHeader != null) {\n const nal = Buffer.concat([\n Buffer.from([this.fuNalHeader]),\n ...this.fuParts,\n ]);\n this.reset();\n return [Buffer.concat([NAL_START_CODE_4B, nal])];\n }\n return [];\n }\n\n return [];\n }\n}\n","/**\n * H.265/HEVC utilities.\n * HVCC to Annex-B conversion, VPS/SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- HVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH265Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 2) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = (b0 >> 1) & 0x3f;\n return nalType <= 40;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.265 data from length-prefixed (HVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH265ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try HVCC → AnnexB conversion\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n if (looksLikeSingleH265Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n return data;\n}\n\n/**\n * Get H.265 NAL unit type from a NAL payload (without start code).\n */\nexport function getH265NalType(nalPayload: Buffer): number | null {\n if (nalPayload.length < 1) return null;\n const b0 = nalPayload[0];\n if (b0 === undefined) return null;\n if ((b0 & 0x80) !== 0) return null;\n return (b0 >> 1) & 0x3f;\n}\n\n/**\n * Check if an H.265 NAL unit type is an IRAP (Intra Random Access Point) picture.\n * IRAP types: BLA (16-18), IDR (19-20), CRA (21).\n */\nexport function isH265Irap(nalType: number): boolean {\n return nalType >= 16 && nalType <= 23;\n}\n\n/**\n * Check if an H.265 Annex-B access unit is a keyframe (IRAP + VPS + SPS + PPS).\n */\nexport function isH265KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasVps = false;\n let hasSps = false;\n let hasPps = false;\n let hasIrap = false;\n\n for (const nal of nals) {\n const nalType = getH265NalType(nal);\n if (nalType === null) continue;\n if (nalType === 32) hasVps = true;\n if (nalType === 33) hasSps = true;\n if (nalType === 34) hasPps = true;\n if (isH265Irap(nalType)) hasIrap = true;\n }\n\n return hasIrap && hasVps && hasSps && hasPps;\n}\n\n/**\n * Check if an H.265 Annex-B access unit contains an IRAP picture.\n */\nexport function isH265IrapAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const b0 = nal[0];\n if (b0 === undefined) continue;\n if ((b0 & 0x80) !== 0) continue;\n const nalType = (b0 >> 1) & 0x3f;\n if (isH265Irap(nalType)) return true;\n }\n return false;\n}\n\n/**\n * Extract VPS, SPS, and PPS from an H.265 Annex-B access unit.\n */\nexport function extractH265ParamSets(annexB: Buffer): {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n} {\n const nals = splitAnnexBToNals(annexB);\n let vps: Buffer | undefined;\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const nalType = (nal[0]! >> 1) & 0x3f;\n if (nalType === 32) vps = nal;\n else if (nalType === 33) sps = nal;\n else if (nalType === 34) pps = nal;\n }\n\n const out: { vps?: Buffer; sps?: Buffer; pps?: Buffer } = {};\n if (vps) out.vps = vps;\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n return out;\n}\n\n/**\n * H.265 RTP depacketizer (RFC 7798).\n * Handles single NAL units, AP aggregation, and FU fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H265RtpDepacketizer {\n private fuParts: Buffer[] | null = null;\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuParts = null;\n }\n\n push(payload: Buffer): Buffer[] {\n if (!payload || payload.length < 2) return [];\n\n const rtpPayload = H265RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n const h0 = payload[0]!;\n const h1 = payload[1]!;\n if ((h0 & 0x80) !== 0) return [];\n const nalType = (h0 >> 1) & 0x3f;\n\n // AP (48): aggregation packet\n if (nalType === 48) {\n let off = 2;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length) out.push(NAL_START_CODE_4B, nal);\n }\n return out.length ? [Buffer.concat(out)] : [];\n }\n\n // FU (49): fragmentation unit\n if (nalType === 49) {\n if (payload.length < 3) return [];\n const fuHeader = payload[2]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x3f;\n const orig0 = (h0 & 0x81) | ((origType & 0x3f) << 1);\n const orig1 = h1;\n const frag = payload.subarray(3);\n\n if (start) {\n this.fuParts = [NAL_START_CODE_4B, Buffer.from([orig0, orig1]), frag];\n } else {\n if (!this.fuParts) return [];\n this.fuParts.push(frag);\n }\n\n if (end) {\n if (!this.fuParts) return [];\n const out = Buffer.concat(this.fuParts);\n this.fuParts = null;\n return [out];\n }\n\n return [];\n }\n\n // Single NAL unit\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n}\n","import { lookup as dnsLookup } from 'node:dns/promises'\nimport { errMsg } from '@camstack/types'\nimport type { Logger } from './types.js'\n\n/**\n * Rewrite mDNS (`*.local`) candidates in an SDP by resolving each\n * hostname to an IP via the OS resolver.\n *\n * Chrome emits anonymised `<uuid>.local` host candidates by default\n * for privacy; werift's ICE agent has no mDNS resolver, so those\n * candidates never pair and the session sits at \"checking\" forever.\n *\n * On macOS the OS resolver delegates `.local` to Bonjour (built in); on\n * Linux it usually requires Avahi's NSS module. We parallelise the\n * lookups and silently drop candidates that don't resolve — a failed\n * lookup is the same as \"werift didn't use it\", so we're not making\n * the worst case worse. The original line is replaced in place, keeping\n * the rest of the candidate fields (priority, ufrag, type, …) intact.\n *\n * Used by both `AdaptiveSession` and `SharedSession` — pulled out into\n * its own module so the two paths stay identical.\n */\nexport async function resolveMdnsCandidatesInSdp(\n sdp: string,\n logger: Logger,\n sessionTag: string,\n): Promise<string> {\n const mdnsHostRe = /([0-9a-fA-F-]+\\.local)/g\n const hosts = new Set<string>()\n for (const match of sdp.matchAll(mdnsHostRe)) hosts.add(match[1]!)\n if (hosts.size === 0) return sdp\n\n const replacements = await Promise.all(\n [...hosts].map(async (host) => {\n try {\n const { address } = await dnsLookup(host, { family: 4 })\n logger.info('mDNS resolve succeeded', { meta: { sessionTag, host, address } })\n return [host, address] as const\n } catch (err) {\n logger.warn('mDNS resolve failed', { meta: { sessionTag, host, error: errMsg(err) } })\n return [host, null] as const\n }\n }),\n )\n\n let rewritten = sdp\n for (const [host, address] of replacements) {\n if (!address) continue\n const escaped = host.replace(/\\./g, '\\\\.')\n rewritten = rewritten.replace(new RegExp(escaped, 'g'), address)\n }\n return rewritten\n}\n","/**\n * Ported from Scrypted (BSD-2-Clause):\n * https://github.com/koush/scrypted/blob/main/plugins/homekit/src/types/camera/jitter-buffer.ts\n *\n * Used by H265Repacketizer to reorder out-of-order RTP packets within a\n * small jitter window before re-fragmenting. Reordering matters for FU\n * fragments arriving across an unreliable transport — over UDP, a late\n * FU_middle could otherwise break the running re-fragmentation state.\n *\n * No semantic changes — only types narrowed for our codebase.\n */\n\n/**\n * This is a subset of Werift's RtpPacket.\n */\nexport interface RtpPacket {\n payload: Buffer;\n header: {\n padding: boolean;\n marker: boolean;\n timestamp: number;\n sequenceNumber: number;\n };\n clone(): RtpPacket;\n serialize(): Buffer;\n}\n\nexport function sequenceNumberDistance(s1: number, s2: number): number {\n if (s2 === s1)\n return 0;\n const distance = s2 - s1;\n let rolloverDistance: number;\n if (s2 > s1)\n rolloverDistance = s1 + 0x10000 - s2;\n else\n rolloverDistance = s2 + 0x10000 - s1;\n\n if (Math.abs(distance) < Math.abs(rolloverDistance))\n return distance;\n return rolloverDistance;\n}\n\nexport function nextSequenceNumber(current: number, increment = 1) {\n return (current + increment + 0x10000) % 0x10000;\n}\n\nconst maxRtpTimestamp = BigInt(0xFFFFFFFF);\nexport function addRtpTimestamp(current: number, adjust: number) {\n return Number(maxRtpTimestamp & (BigInt(current) + BigInt(adjust)));\n}\n\nexport function isNextSequenceNumber(current: number, next: number) {\n return nextSequenceNumber(current) === next;\n}\n\nexport class JitterBuffer {\n lastSequenceNumber: number | undefined;\n pending: (RtpPacket | undefined)[] = [];\n\n constructor(public console: Console, public jitterSize: number,) {\n }\n\n flushPending(afterSequenceNumber: number, ret: RtpPacket[]): RtpPacket[] {\n if (!this.pending)\n return ret;\n\n const start = nextSequenceNumber(afterSequenceNumber);\n\n for (let i = 0; i < this.jitterSize; i++) {\n const index = (start + i) % this.jitterSize;\n const packet = this.pending[index];\n if (!packet)\n continue;\n const { sequenceNumber } = packet.header;\n const sd = sequenceNumberDistance(this.lastSequenceNumber ?? sequenceNumber, sequenceNumber);\n // packet needs to be purged from the the buffer for being too old.\n if (sd <= 0) {\n this.console.log('jitter buffer purged packet:', sequenceNumber);\n this.pending[index] = undefined;\n ret.push(packet);\n }\n else if (sd === 1) {\n this.pending[index] = undefined;\n this.lastSequenceNumber = sequenceNumber;\n ret.push(packet);\n }\n else {\n // can't do anything with this packet yet.\n }\n }\n return ret;\n }\n\n queue(packet: RtpPacket): RtpPacket[] {\n if (this.lastSequenceNumber === undefined || isNextSequenceNumber(this.lastSequenceNumber, packet.header.sequenceNumber)) {\n this.lastSequenceNumber = packet.header.sequenceNumber;\n return this.flushPending(this.lastSequenceNumber, [packet]);\n }\n\n const { sequenceNumber } = packet.header;\n const packetDistance = sequenceNumberDistance(this.lastSequenceNumber, sequenceNumber);\n // late/duplicate packet\n if (packetDistance <= 0)\n return [];\n\n const ret: RtpPacket[] = [];\n\n // missed/late bunch of packets\n if (packetDistance > this.jitterSize) {\n // this.console.log('jitter buffer skipped packets:', packetDistance);\n const { lastSequenceNumber } = this;\n this.lastSequenceNumber = sequenceNumber - this.jitterSize;\n // use the previous sequence number to flush any packets that are too old compared\n // to the new sequence number.\n this.flushPending(lastSequenceNumber, ret);\n }\n\n this.pending[packet.header.sequenceNumber % this.jitterSize] = packet;\n return this.flushPending(this.lastSequenceNumber, ret);\n }\n}\n","/**\n * Ported from Scrypted (BSD-2-Clause):\n * https://github.com/koush/scrypted/blob/main/plugins/webrtc/src/h265-packetizer.ts\n *\n * Re-fragments RTP packets coming straight off the camera so they fit\n * the WebRTC peer's MTU (~1200 bytes) WITHOUT re-encoding or rebuilding\n * the RTP header from scratch. Source SSRC/seq/timestamp are preserved\n * (only adjusted by werift's `replaceRTP` offset machinery on send).\n *\n * Forwarding source RTP through this repacketizer fixes the\n * `framesAssembledFromMultiplePackets = 0` issue our prior\n * \"depacketize → AnnexB → re-packetize from scratch\" path produced\n * for H.265 over WebRTC. Original RTP header layout is what Chrome's\n * HEVC depacketizer expects.\n */\nimport { isNextSequenceNumber, JitterBuffer, RtpPacket } from \"./jitter-buffer.js\";\n\n// H.265 NAL unit types\n// https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/hevc/hevc.h\nconst NAL_TYPE_TRAIL_N = 0;\nconst _UNUSED_NAL_TYPE_TRAIL_R = 1;\nconst _UNUSED_NAL_TYPE_TSA_N = 2;\nconst _UNUSED_NAL_TYPE_TSA_R = 3;\nconst _UNUSED_NAL_TYPE_STSA_N = 4;\nconst _UNUSED_NAL_TYPE_STSA_R = 5;\nconst _UNUSED_NAL_TYPE_RADL_N = 6;\nconst _UNUSED_NAL_TYPE_RADL_R = 7;\nconst _UNUSED_NAL_TYPE_RASL_N = 8;\nconst _UNUSED_NAL_TYPE_RASL_R = 9;\nconst _UNUSED_NAL_TYPE_VCL_N10 = 10;\nconst _UNUSED_NAL_TYPE_VCL_R11 = 11;\nconst _UNUSED_NAL_TYPE_VCL_N12 = 12;\nconst _UNUSED_NAL_TYPE_VCL_R13 = 13;\nconst _UNUSED_NAL_TYPE_VCL_N14 = 14;\nconst _UNUSED_NAL_TYPE_VCL_R15 = 15;\nconst NAL_TYPE_BLA_W_LP = 16;\nconst NAL_TYPE_BLA_W_RADL = 17;\nconst NAL_TYPE_BLA_N_LP = 18;\nconst NAL_TYPE_IDR_W_RADL = 19;\nconst NAL_TYPE_IDR_N_LP = 20;\nconst NAL_TYPE_CRA_NUT = 21;\nconst _UNUSED_NAL_TYPE_RSV_IRAP_VCL22 = 22;\nconst NAL_TYPE_RSV_IRAP_VCL23 = 23;\nconst _UNUSED_NAL_TYPE_RSV_VCL24 = 24;\nconst _UNUSED_NAL_TYPE_RSV_VCL25 = 25;\nconst _UNUSED_NAL_TYPE_RSV_VCL26 = 26;\nconst _UNUSED_NAL_TYPE_RSV_VCL27 = 27;\nconst _UNUSED_NAL_TYPE_RSV_VCL28 = 28;\nconst _UNUSED_NAL_TYPE_RSV_VCL29 = 29;\nconst _UNUSED_NAL_TYPE_RSV_VCL30 = 30;\nconst _UNUSED_NAL_TYPE_RSV_VCL31 = 31;\nconst NAL_TYPE_VPS = 32;\nconst NAL_TYPE_SPS = 33;\nconst NAL_TYPE_PPS = 34;\nconst NAL_TYPE_AUD = 35;\nconst _UNUSED_NAL_TYPE_EOS_NUT = 36;\nconst _UNUSED_NAL_TYPE_EOB_NUT = 37;\nconst _UNUSED_NAL_TYPE_FD_NUT = 38;\nconst NAL_TYPE_SEI_PREFIX = 39;\nconst NAL_TYPE_SEI_SUFFIX = 40;\nconst _UNUSED_NAL_TYPE_RSV_NVCL41 = 41;\nconst _UNUSED_NAL_TYPE_RSV_NVCL42 = 42;\nconst _UNUSED_NAL_TYPE_RSV_NVCL43 = 43;\nconst _UNUSED_NAL_TYPE_RSV_NVCL44 = 44;\nconst _UNUSED_NAL_TYPE_RSV_NVCL45 = 45;\nconst _UNUSED_NAL_TYPE_RSV_NVCL46 = 46;\nconst _UNUSED_NAL_TYPE_RSV_NVCL47 = 47;\n// RTP payload format for H.265 defines these special types\nconst NAL_TYPE_AP = 48; // Aggregation Packet\nconst NAL_TYPE_FU = 49; // Fragmentation Unit\nconst _UNUSED_NAL_TYPE_UNSPEC50 = 50;\nconst _UNUSED_NAL_TYPE_UNSPEC51 = 51;\nconst _UNUSED_NAL_TYPE_UNSPEC52 = 52;\nconst _UNUSED_NAL_TYPE_UNSPEC53 = 53;\nconst _UNUSED_NAL_TYPE_UNSPEC54 = 54;\nconst _UNUSED_NAL_TYPE_UNSPEC55 = 55;\nconst _UNUSED_NAL_TYPE_UNSPEC56 = 56;\nconst _UNUSED_NAL_TYPE_UNSPEC57 = 57;\nconst _UNUSED_NAL_TYPE_UNSPEC58 = 58;\nconst _UNUSED_NAL_TYPE_UNSPEC59 = 59;\nconst _UNUSED_NAL_TYPE_UNSPEC60 = 60;\nconst _UNUSED_NAL_TYPE_UNSPEC61 = 61;\nconst _UNUSED_NAL_TYPE_UNSPEC62 = 62;\nconst _UNUSED_NAL_TYPE_UNSPEC63 = 63;\n\n\nconst NAL_HEADER_SIZE = 2; // H265 has 2-byte NAL header\nconst FU_HEADER_SIZE = 3; // 2-byte NAL header + 1-byte FU header\nconst LENGTH_FIELD_SIZE = 2;\nconst AP_HEADER_SIZE = NAL_HEADER_SIZE + LENGTH_FIELD_SIZE;\n\n// Function to extract NAL unit type from H.265 NAL header\nfunction getNalType(data: Buffer): number {\n return (data[0]! & 0x7E) >> 1; // 6 bits starting from bit 1\n}\n\nfunction isKeyFrame(nalType: number): boolean {\n // For IDR frames, send codec info first\n if (nalType === NAL_TYPE_IDR_W_RADL || nalType === NAL_TYPE_IDR_N_LP ||\n nalType === NAL_TYPE_BLA_W_LP || nalType === NAL_TYPE_BLA_W_RADL ||\n nalType === NAL_TYPE_BLA_N_LP || nalType === NAL_TYPE_CRA_NUT) {\n return true;\n }\n return false;\n}\n\n// Function to depacketize Aggregation Packets (similar to STAP-A in H.264)\nexport function depacketizeAP(data: Buffer): Buffer[] {\n const ret: Buffer[] = [];\n let lastPos: number | undefined;\n let pos = NAL_HEADER_SIZE;\n while (pos < data.length) {\n if (lastPos !== undefined)\n ret.push(data.subarray(lastPos, pos));\n const naluSize = data.readUInt16BE(pos);\n pos += LENGTH_FIELD_SIZE;\n lastPos = pos;\n pos += naluSize;\n }\n if (lastPos !== undefined) ret.push(data.subarray(lastPos));\n return ret;\n}\n\nexport function splitH265NaluStartCode(data: Buffer) {\n const ret: Buffer[] = [];\n let previous = 0;\n let offset = 0;\n const maybeAddSlice = () => {\n const slice = data.subarray(previous, offset);\n if (slice.length)\n ret.push(slice);\n offset += 4;\n previous = offset;\n }\n\n while (offset < data.length - 4) {\n const startCode = data.readUInt32BE(offset);\n if (startCode === 1) {\n maybeAddSlice();\n }\n else {\n offset++;\n }\n }\n offset = data.length;\n maybeAddSlice();\n\n return ret;\n}\n\nexport interface H265CodecInfo {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n seiPrefix?: Buffer;\n seiSuffix?: Buffer;\n}\n\nexport class H265Repacketizer {\n extraPackets = 0;\n fuMax!: number;\n pendingFU: RtpPacket[] | undefined;\n // the AP packet that will be sent before an IDR frame.\n ap: RtpPacket | undefined;\n fuMin!: number;\n // h265 can send multiple IDR and TRAIL_R frames per RTP timestamp.\n // the repurposed h264-packetizer code sends codec information before every\n // IDR frame.\n // the IDR burst frames are interleaved with codec information which causes decode failure.\n // The interleaved codec issues does not cause issues with Safari, but does cause\n // Chrome to send repeated Picture Loss Indication (PLI) requests.\n // the fix is to send the codec information before the first IDR frame,\n // then wait for the marker bit, which denotes the last packet for a given rtp\n // timestamp, to reset the flag.\n // expected result:\n // AP codec info -> IDR frame 1 -> IDR frame 2 -> ... -> IDR frame N with marker bit set\n sentMarker = true;\n\n constructor(public console: Console, private maxPacketSize: number, public codecInfo?: H265CodecInfo, public jitterBuffer = new JitterBuffer(console, 4)) {\n this.setMaxPacketSize(maxPacketSize);\n }\n\n setMaxPacketSize(maxPacketSize: number) {\n this.maxPacketSize = maxPacketSize;\n // 12 is the rtp/srtp header size.\n this.fuMax = maxPacketSize - FU_HEADER_SIZE;\n this.fuMin = Math.round(maxPacketSize * .8);\n }\n\n ensureCodecInfo(): H265CodecInfo {\n if (!this.codecInfo) {\n this.codecInfo = {};\n }\n return this.codecInfo;\n }\n\n updateVps(vps: Buffer): void {\n this.ensureCodecInfo().vps = vps;\n }\n\n updateSps(sps: Buffer): void {\n this.ensureCodecInfo().sps = sps;\n }\n\n updatePps(pps: Buffer): void {\n this.ensureCodecInfo().pps = pps;\n }\n\n updateSeiPrefix(sei: Buffer): void {\n this.ensureCodecInfo().seiPrefix = sei;\n }\n\n updateSeiSuffix(sei: Buffer): void {\n this.ensureCodecInfo().seiSuffix = sei;\n }\n\n shouldFilter(_nalType: number): boolean {\n // Currently nothing is filtered, but this could be customized\n return false;\n }\n\n // Fragmentation Unit (FU) for H.265\n // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3\n packetizeFU(data: Buffer, noStart?: boolean, noEnd?: boolean): Buffer[] {\n // Handle both normal packets and FU packets.\n const initialNalType = getNalType(data);\n\n // Check if the data is already a fragmentation unit\n if (initialNalType === NAL_TYPE_FU) {\n // Extract original NAL header information\n const originalNalType = data[2]! & 0x3F; // 6 bits\n const isFuStart = !!(data[2]! & 0x80);\n const isFuEnd = !!(data[2]! & 0x40);\n const isFuMiddle = !isFuStart && !isFuEnd;\n\n // Reconstruct the original NAL header\n const layerId = ((data[0]! & 0x01) << 5) | ((data[1]! & 0xF8) >> 3);\n const tid = data[1]! & 0x07;\n\n const originalNalHeader = Buffer.alloc(2);\n originalNalHeader[0] = (originalNalType << 1) | (layerId >> 5);\n originalNalHeader[1] = ((layerId & 0x1F) << 3) | tid;\n\n data = Buffer.concat([originalNalHeader, data.subarray(FU_HEADER_SIZE)]);\n\n if (isFuStart) {\n noEnd = true;\n }\n else if (isFuEnd) {\n noStart = true;\n }\n else if (isFuMiddle) {\n noStart = true;\n noEnd = true;\n }\n }\n\n // Extract information from the NAL header\n const nalType = getNalType(data);\n const layerId = ((data[0]! & 0x01) << 5) | ((data[1]! & 0xF8) >> 3);\n const tid = data[1]! & 0x07;\n\n // Construct the FU NAL header\n const fuNalHeader = Buffer.alloc(2);\n fuNalHeader[0] = (NAL_TYPE_FU << 1) | (layerId >> 5);\n fuNalHeader[1] = ((layerId & 0x1F) << 3) | tid;\n\n // Construct FU headers for different positions\n const fuHeaderMiddle = Buffer.from([...fuNalHeader, nalType]);\n const fuHeaderStart = noStart ? fuHeaderMiddle : Buffer.from([...fuNalHeader, nalType | 0x80]);\n const fuHeaderEnd = noEnd ? fuHeaderMiddle : Buffer.from([...fuNalHeader, nalType | 0x40]);\n let fuHeader = fuHeaderStart;\n\n const packages: Buffer[] = [];\n let offset = NAL_HEADER_SIZE;\n\n while (offset < data.length) {\n let payload: Buffer;\n const packageSize = Math.min(this.fuMax, data.length - offset);\n payload = data.subarray(offset, offset + packageSize);\n offset += packageSize;\n\n if (offset === data.length) {\n fuHeader = fuHeaderEnd;\n }\n\n packages.push(Buffer.concat([fuHeader, payload]));\n\n fuHeader = fuHeaderMiddle;\n }\n\n return packages;\n }\n\n // Aggregation Packet (AP) for H.265 (similar to STAP-A in H.264)\n // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2\n packetizeOneAP(datas: Buffer[]): Buffer {\n if (!datas.length)\n throw new Error('packetizeOneAP requires at least one NAL');\n\n let counter = 0;\n let availableSize = this.maxPacketSize - AP_HEADER_SIZE;\n\n // In H.265, AP uses a fixed header with NAL type 48\n const apHeader = Buffer.alloc(2);\n apHeader[0] = NAL_TYPE_AP << 1; // Type 48, no layer ID in first byte\n apHeader[1] = 0x01; // Default temporal ID = 1, no layer ID\n\n const payload: Buffer[] = [apHeader];\n\n while (datas.length && datas[0]!.length + LENGTH_FIELD_SIZE <= availableSize && counter < 9) {\n const nalu = datas.shift()!;\n availableSize -= LENGTH_FIELD_SIZE + nalu.length;\n counter += 1;\n const lengthField = Buffer.alloc(2);\n lengthField.writeUInt16BE(nalu.length, 0);\n payload.push(lengthField, nalu);\n }\n\n // If no NALUs fit, return the first one for FU packetization\n if (counter === 0)\n return datas.shift()!;\n\n // A single NALU AP is unnecessary, return the NALU itself\n if (counter === 1) {\n return payload[2]!; // Skip header and length field\n }\n\n return Buffer.concat(payload);\n }\n\n packetizeAP(datas: Buffer[]) {\n const ret: Buffer[] = [];\n while (datas.length) {\n const nalu = this.packetizeOneAP(datas);\n if (nalu.length < this.maxPacketSize) {\n ret.push(nalu);\n continue;\n }\n const fus = this.packetizeFU(nalu);\n ret.push(...fus);\n }\n return ret;\n }\n\n createPacket(rtp: RtpPacket, data: Buffer, marker: boolean) {\n const ret = rtp.clone();\n ret.header.sequenceNumber = (rtp.header.sequenceNumber + this.extraPackets + 0x10000) % 0x10000;\n ret.header.marker = marker;\n ret.header.padding = false;\n ret.payload = data;\n if (data.length > this.maxPacketSize)\n this.console.warn('packet exceeded max packet size. this may be a bug.');\n this.sentMarker = ret.header.marker;\n return ret;\n }\n\n flushPendingFU(ret: RtpPacket[]) {\n const pending = this.pendingFU;\n if (!pending || pending.length === 0)\n return;\n\n // Defragmenting assumes packets are sorted by sequence number,\n // and are all available, which is guaranteed over rtsp/tcp, but not over rtp/udp.\n const first = pending[0]!;\n const last = pending[pending.length - 1]!;\n const originalNalType = first.payload[2]! & 0x3F;\n const hasFuStart = !!(first.payload[2]! & 0x80);\n const hasFuEnd = !!(last.payload[2]! & 0x40);\n\n // Extract layerId and tid from FU header\n const layerId = ((first.payload[0]! & 0x01) << 5) | ((first.payload[1]! & 0xF8) >> 3);\n const tid = first.payload[1]! & 0x07;\n\n // Reconstruct original NAL header\n const originalNalHeader = Buffer.alloc(2);\n originalNalHeader[0] = (originalNalType << 1) | (layerId >> 5);\n originalNalHeader[1] = ((layerId & 0x1F) << 3) | tid;\n\n const getDefragmentedPendingFu = (): Buffer => {\n const originalFragments = pending.map(packet => packet.payload.subarray(FU_HEADER_SIZE));\n originalFragments.unshift(originalNalHeader);\n return Buffer.concat(originalFragments);\n }\n\n // Handle special case for VPS/SPS/PPS in FU (not standard but seen in some implementations)\n if (originalNalType === NAL_TYPE_VPS || originalNalType === NAL_TYPE_SPS) {\n const defragmented = getDefragmentedPendingFu();\n const splits = splitH265NaluStartCode(defragmented);\n\n while (splits.length) {\n const split = splits.shift()!;\n const splitNaluType = getNalType(split);\n\n if (splitNaluType === NAL_TYPE_VPS) {\n this.updateVps(split);\n }\n else if (splitNaluType === NAL_TYPE_SPS) {\n this.updateSps(split);\n }\n else if (splitNaluType === NAL_TYPE_PPS) {\n this.updatePps(split);\n }\n else {\n // For IDR frames, send codec info first\n if (isKeyFrame(splitNaluType)) {\n this.maybeSendAPCodecInfo(first, ret);\n }\n\n this.fragment(first, ret, {\n payload: split,\n noStart: !hasFuStart,\n noEnd: !hasFuEnd,\n marker: last.header.marker,\n });\n }\n }\n }\n else {\n // Process regular fragmentation units\n while (pending.length) {\n const fu = pending[0]!;\n if (fu.payload.length > this.maxPacketSize || fu.payload.length < this.fuMin)\n break;\n pending.shift();\n ret.push(this.createPacket(fu, fu.payload, fu.header.marker));\n }\n\n if (!pending.length) {\n this.pendingFU = undefined;\n return;\n }\n\n // Re-fragment remaining FU packets\n const refragFirst = pending[0]!;\n const refragLast = pending[pending.length - 1]!;\n const refragHasFuStart = !!(refragFirst.payload[2]! & 0x80);\n const refragHasFuEnd = !!(refragLast.payload[2]! & 0x40);\n\n const defragmented = getDefragmentedPendingFu();\n\n this.fragment(refragFirst, ret, {\n payload: defragmented,\n noStart: !refragHasFuStart,\n noEnd: !refragHasFuEnd,\n marker: refragLast.header.marker\n });\n }\n\n this.extraPackets -= pending.length - 1;\n this.pendingFU = undefined;\n }\n\n createRtpPackets(packet: RtpPacket, nalus: Buffer[], ret: RtpPacket[], hadMarker = packet.header.marker) {\n nalus.forEach((packetized, index) => {\n if (index !== 0)\n this.extraPackets++;\n const marker = hadMarker && index === nalus.length - 1;\n ret.push(this.createPacket(packet, packetized, marker));\n });\n }\n\n maybeSendAPCodecInfo(packet: RtpPacket, ret: RtpPacket[]) {\n if (this.ap) {\n // AP with codec information was sent recently, no need to send codec info.\n this.ap = undefined;\n return;\n }\n\n // can not send codec info if in the middle of sending packets for a specific rtp timestamp.\n if (!this.sentMarker)\n return;\n\n // vps is not required.\n if (!this.codecInfo?.sps || !this.codecInfo?.pps)\n return;\n\n const agg = [this.codecInfo.sps, this.codecInfo.pps];\n if (this.codecInfo.vps)\n agg.unshift(this.codecInfo.vps);\n if (this.codecInfo?.seiPrefix)\n agg.push(this.codecInfo.seiPrefix);\n if (this.codecInfo?.seiSuffix)\n agg.push(this.codecInfo.seiSuffix);\n\n const aggregates = this.packetizeAP(agg);\n if (aggregates.length !== 1) {\n this.console.error('expected only 1 packet for vps/sps/pps AP');\n return;\n }\n // This AP only contains codec info (and no frame data), thus the marker bit should not be set.\n this.createRtpPackets(packet, aggregates, ret, false);\n this.extraPackets++;\n }\n\n // Fragment payload into multiple packets as needed\n fragment(packet: RtpPacket, ret: RtpPacket[], fuOptions: {\n payload: Buffer;\n noStart: boolean;\n noEnd: boolean;\n marker: boolean;\n } = {\n payload: packet.payload,\n noStart: false,\n noEnd: false,\n marker: packet.header.marker\n }) {\n const { payload, noStart, noEnd, marker } = fuOptions;\n if (payload.length > this.maxPacketSize || noStart || noEnd) {\n const fragments = this.packetizeFU(payload, noStart, noEnd);\n this.createRtpPackets(packet, fragments, ret, marker);\n }\n else {\n // Can send this packet as is\n ret.push(this.createPacket(packet, payload, marker));\n }\n }\n\n repacketize<T extends RtpPacket>(packet: T): T[] {\n const ret: T[] = [];\n for (const dejittered of this.jitterBuffer.queue(packet)) {\n this.repacketizeOne(dejittered, ret);\n }\n return ret;\n }\n\n repacketizeOne(packet: RtpPacket, ret: RtpPacket[]) {\n // Filter empty packets\n if (!packet.payload.length) {\n this.flushPendingFU(ret);\n this.extraPackets--;\n return;\n }\n\n const nalType = getNalType(packet.payload);\n\n // Fragmented packets must share a timestamp\n if (this.pendingFU && this.pendingFU[0]!.header.timestamp !== packet.header.timestamp) {\n this.flushPendingFU(ret);\n }\n\n if (nalType === NAL_TYPE_FU) {\n // Handle Fragmentation Units\n const data = packet.payload;\n const originalNalType = data[2]! & 0x3F;\n\n if (this.shouldFilter(originalNalType)) {\n this.extraPackets--;\n return;\n }\n\n const isFuStart = !!(data[2]! & 0x80);\n const isFuEnd = !!(data[2]! & 0x40);\n\n if (isFuStart) {\n if (this.pendingFU)\n this.console.error('FU restarted. skipping refragmentation of previous FU.', originalNalType);\n\n this.pendingFU = undefined;\n\n // If this is an IDR frame, but no codec info has been sent via an AP, send it\n if (originalNalType === NAL_TYPE_IDR_W_RADL || originalNalType === NAL_TYPE_IDR_N_LP) {\n this.maybeSendAPCodecInfo(packet, ret);\n }\n }\n else {\n if (this.pendingFU) {\n // Check if packets were missing from the previously queued FU packets\n const last = this.pendingFU[this.pendingFU.length - 1]!;\n if (!isNextSequenceNumber(last.header.sequenceNumber, packet.header.sequenceNumber)) {\n this.console.error('FU packet missing. skipping refragmentation.', originalNalType);\n return;\n }\n }\n }\n\n if (!this.pendingFU)\n this.pendingFU = [];\n\n this.pendingFU.push(packet);\n\n if (isFuEnd) {\n this.flushPendingFU(ret);\n }\n else if (this.pendingFU.reduce((p, c) => p + c.payload.length - FU_HEADER_SIZE, NAL_HEADER_SIZE) > this.maxPacketSize) {\n // Refragment FU packets as they are received\n const last = this.pendingFU[this.pendingFU.length - 1]!.clone();\n const partial: RtpPacket[] = [];\n this.flushPendingFU(partial);\n // Retain a FU packet to validate subsequent FU packets\n const retain = partial.pop()!;\n last.payload = retain.payload;\n this.pendingFU = [last];\n ret.push(...partial);\n this.sentMarker = false;\n }\n }\n else if (nalType === NAL_TYPE_AP) {\n this.flushPendingFU(ret);\n\n let hasVps = false;\n let hasSps = false;\n let hasPps = false;\n\n // Process Aggregation Packets\n const depacketized = depacketizeAP(packet.payload);\n depacketized.forEach(payload => {\n const nalType = getNalType(payload);\n if (nalType === NAL_TYPE_VPS) {\n hasVps = true;\n this.updateVps(payload);\n }\n else if (nalType === NAL_TYPE_SPS) {\n hasSps = true;\n this.updateSps(payload);\n }\n else if (nalType === NAL_TYPE_PPS) {\n hasPps = true;\n this.updatePps(payload);\n }\n else if (nalType === NAL_TYPE_SEI_PREFIX) {\n this.updateSeiPrefix(payload);\n }\n else if (nalType === NAL_TYPE_SEI_SUFFIX) {\n this.updateSeiSuffix(payload);\n }\n else if (nalType === NAL_TYPE_AUD) {\n // Access Unit Delimiter - typically a no-op\n }\n else if (nalType >= NAL_TYPE_TRAIL_N && nalType <= 9 /* NAL_TYPE_RASL_R */) {\n // Various slice types - typically VCL NAL units\n }\n else if (nalType === NAL_TYPE_IDR_W_RADL) {\n // IDR\n }\n else if (nalType === 0) {\n // NAL delimiter or something. usually empty.\n }\n else {\n this.console.warn('Skipped an AP type.', nalType);\n }\n });\n\n // Log that an AP with codec info was sent\n if (hasVps && hasSps && hasPps && packet.payload.length <= this.maxPacketSize) {\n this.ap = packet;\n\n const ap = this.packetizeAP(depacketized);\n this.createRtpPackets(packet, ap, ret);\n }\n else {\n // h265 can send multiple IDR or TRAIL_R per rtp timestamp\n // so this can result in a large aggregation packet\n // if the MTU is large (like 64k on localhost udp).\n // the aggregation packet must be depacketized and potentially\n // fragmented.\n const fus: Buffer[] = [];\n while (depacketized.length) {\n const next = depacketized.shift()!;\n if (next.length <= this.maxPacketSize) {\n fus.push(next);\n continue;\n }\n fus.push(...this.packetizeFU(next));\n }\n this.createRtpPackets(packet, fus, ret);\n }\n }\n else if (nalType <= NAL_TYPE_RSV_IRAP_VCL23 || (nalType >= NAL_TYPE_VPS && nalType <= NAL_TYPE_SEI_SUFFIX)) {\n this.flushPendingFU(ret);\n\n if (this.shouldFilter(nalType)) {\n this.extraPackets--;\n return;\n }\n\n // Handle codec information\n if (nalType === NAL_TYPE_VPS) {\n this.extraPackets--;\n this.updateVps(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_SPS) {\n this.extraPackets--;\n this.updateSps(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_PPS) {\n this.extraPackets--;\n this.updatePps(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_SEI_PREFIX) {\n this.extraPackets--;\n this.updateSeiPrefix(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_SEI_SUFFIX) {\n this.extraPackets--;\n this.updateSeiSuffix(packet.payload);\n return;\n }\n\n if (this.shouldFilter(nalType)) {\n this.extraPackets--;\n return;\n }\n\n // For IDR frames, send codec info first\n if (isKeyFrame(nalType)) {\n this.maybeSendAPCodecInfo(packet, ret);\n }\n\n this.fragment(packet, ret);\n }\n else {\n this.console.error('unknown NAL unit type ' + nalType);\n this.extraPackets--;\n }\n\n return;\n }\n}\n// Keep unused NAL_TYPE_ constants alive — they document the full RFC\n// 7798 NAL-type table even when only a subset is referenced by the\n// packetizer logic.\nvoid [_UNUSED_NAL_TYPE_TRAIL_R,_UNUSED_NAL_TYPE_TSA_N,_UNUSED_NAL_TYPE_TSA_R,_UNUSED_NAL_TYPE_STSA_N,_UNUSED_NAL_TYPE_STSA_R,_UNUSED_NAL_TYPE_RADL_N,_UNUSED_NAL_TYPE_RADL_R,_UNUSED_NAL_TYPE_RASL_N,_UNUSED_NAL_TYPE_RASL_R,_UNUSED_NAL_TYPE_VCL_N10,_UNUSED_NAL_TYPE_VCL_R11,_UNUSED_NAL_TYPE_VCL_N12,_UNUSED_NAL_TYPE_VCL_R13,_UNUSED_NAL_TYPE_VCL_N14,_UNUSED_NAL_TYPE_VCL_R15,_UNUSED_NAL_TYPE_RSV_IRAP_VCL22,_UNUSED_NAL_TYPE_RSV_VCL24,_UNUSED_NAL_TYPE_RSV_VCL25,_UNUSED_NAL_TYPE_RSV_VCL26,_UNUSED_NAL_TYPE_RSV_VCL27,_UNUSED_NAL_TYPE_RSV_VCL28,_UNUSED_NAL_TYPE_RSV_VCL29,_UNUSED_NAL_TYPE_RSV_VCL30,_UNUSED_NAL_TYPE_RSV_VCL31,_UNUSED_NAL_TYPE_EOS_NUT,_UNUSED_NAL_TYPE_EOB_NUT,_UNUSED_NAL_TYPE_FD_NUT,_UNUSED_NAL_TYPE_RSV_NVCL41,_UNUSED_NAL_TYPE_RSV_NVCL42,_UNUSED_NAL_TYPE_RSV_NVCL43,_UNUSED_NAL_TYPE_RSV_NVCL44,_UNUSED_NAL_TYPE_RSV_NVCL45,_UNUSED_NAL_TYPE_RSV_NVCL46,_UNUSED_NAL_TYPE_RSV_NVCL47,_UNUSED_NAL_TYPE_UNSPEC50,_UNUSED_NAL_TYPE_UNSPEC51,_UNUSED_NAL_TYPE_UNSPEC52,_UNUSED_NAL_TYPE_UNSPEC53,_UNUSED_NAL_TYPE_UNSPEC54,_UNUSED_NAL_TYPE_UNSPEC55,_UNUSED_NAL_TYPE_UNSPEC56,_UNUSED_NAL_TYPE_UNSPEC57,_UNUSED_NAL_TYPE_UNSPEC58,_UNUSED_NAL_TYPE_UNSPEC59,_UNUSED_NAL_TYPE_UNSPEC60,_UNUSED_NAL_TYPE_UNSPEC61,_UNUSED_NAL_TYPE_UNSPEC62,_UNUSED_NAL_TYPE_UNSPEC63]\n","/**\n * Adaptive WebRTC session — extends the base session pattern with:\n * - RTCP Receiver Report monitoring (packet loss, jitter, RTT)\n * - Source replacement (replaceTrack for seamless quality switching)\n * - Stats emission for the AdaptiveController\n *\n * Uses werift (optional peer dependency) for server-side WebRTC.\n */\n\nimport type {\n FrameSource,\n Logger,\n VideoCodec,\n AudioCodec,\n} from \"./types.js\";\nimport { splitAnnexBToNals } from \"./nal-utils.js\";\nimport { convertH264ToAnnexB, isH264IdrAccessUnit } from \"./h264-utils.js\";\nimport { convertH265ToAnnexB, isH265IrapAccessUnit } from \"./h265-utils.js\";\nimport { resolveMdnsCandidatesInSdp } from \"./mdns-resolve.js\";\nimport { H265Repacketizer, type H265CodecInfo } from \"./h265-repacketizer.js\";\nimport type {\n WeriftModule,\n WeriftPeerConnection,\n WeriftMediaStreamTrack,\n WeriftRtpSender,\n WeriftTransceiver,\n WeriftPcOptions,\n WeriftIceServer,\n} from \"./werift-types.js\";\nimport { errMsg } from '@camstack/types'\n\n// ---------------------------------------------------------------------------\n// Werift type aliases (lazy-loaded optional peer dep)\n// ---------------------------------------------------------------------------\n\nlet _werift: WeriftModule | undefined;\n\nasync function loadWerift(): Promise<WeriftModule> {\n if (_werift) return _werift;\n try {\n const moduleName = \"werift\";\n // Dynamic import via Function to avoid bundler static analysis\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- dynamic import via Function to avoid bundler static analysis\n _werift = await (Function(\"m\", \"return import(m)\")(moduleName) as Promise<WeriftModule>);\n return _werift!;\n } catch {\n throw new Error(\n \"The 'werift' package is required for WebRTC support but is not installed. \" +\n \"Install it with: npm install werift\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Unified ICE server entry — RTCIceServer-compatible. Covers both STUN\n * (no credentials) and TURN (username + credential). We keep a single\n * list instead of splitting stun/turn because the downstream werift API\n * takes a flat `iceServers` array and the split-only added ceremony.\n */\nexport interface IceServerEntry {\n urls: string | string[];\n username?: string;\n credential?: string;\n}\n\nexport interface AdaptiveSessionOptions {\n sessionId: string;\n source: FrameSource;\n intercom?: {\n onAudioReceived: (data: Buffer, format: AudioCodec) => void | Promise<void>;\n };\n iceConfig?: {\n /** Flat RTCIceServer-compatible list (STUN + TURN merged). */\n iceServers?: readonly IceServerEntry[];\n portRange?: [number, number];\n additionalHostAddresses?: readonly string[];\n };\n /** Callback for RTCP stats (called every ~3s). */\n onStats?: (stats: SessionStats) => void;\n /** Enable verbose frame/RTP logging. */\n debug?: boolean;\n /** Source video codec. Determines SDP negotiation + RTP packetization. */\n sourceCodec?: 'H264' | 'H265';\n logger: Logger;\n}\n\nexport interface SessionStats {\n sessionId: string;\n /** Fraction of packets lost (0.0–1.0). */\n packetLoss: number;\n /** Interarrival jitter in ms. */\n jitterMs: number;\n /** Round-trip time in ms (from RTCP SR/RR). */\n rttMs: number;\n /** Total packets received. */\n packetsReceived: number;\n /** Total packets lost. */\n packetsLost: number;\n /** Timestamp. */\n timestamp: number;\n}\n\nexport interface SessionInfo {\n sessionId: string;\n state: \"new\" | \"connecting\" | \"connected\" | \"disconnected\" | \"closed\";\n createdAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveSession\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveSession {\n private readonly sessionId: string;\n private source: FrameSource;\n private readonly logger: Logger;\n private readonly intercom: AdaptiveSessionOptions[\"intercom\"];\n private readonly iceConfig: AdaptiveSessionOptions[\"iceConfig\"];\n private readonly onStats: AdaptiveSessionOptions[\"onStats\"];\n debug: boolean;\n private readonly sourceCodec: 'H264' | 'H265';\n /** Codec actually negotiated with the browser after SDP answer. */\n private negotiatedCodec: 'H264' | 'H265' = 'H264';\n /** True when source is H.265 but browser negotiated H.264 — needs transcode. */\n get needsTranscode(): boolean { return this.sourceCodec === 'H265' && this.negotiatedCodec === 'H264' }\n private _firstKeyFrame: number | undefined;\n /**\n * Last seen SPS and PPS NALs. Many cameras send SPS/PPS only once\n * at stream start (not inline with every IDR). We cache them so\n * PLI-triggered keyframe re-sends include the parameter sets the\n * decoder needs to re-initialise.\n */\n private lastSps: Buffer | null = null;\n private lastPps: Buffer | null = null;\n /** H.265 VPS (Video Parameter Set) — required before every IRAP for decoder init. */\n private lastVps: Buffer | null = null;\n /** Whether replaceRTP has been called on the video sender to sync SSRC/seq. */\n private videoRtpSynced = false;\n private readonly createdAt: number;\n\n private state: SessionInfo[\"state\"] = \"new\";\n private pc: WeriftPeerConnection | null = null;\n private videoTrack: WeriftMediaStreamTrack | null = null;\n private audioTrack: WeriftMediaStreamTrack | null = null;\n /** Transceiver senders for direct sendRtp (more reliable than track.writeRtp) */\n private videoSender: WeriftRtpSender | null = null;\n private audioSender: WeriftRtpSender | null = null;\n private feedAbort: AbortController | null = null;\n private closed = false;\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n\n /**\n * Notification hook invoked exactly once when `close()` finishes. The\n * adaptive server wires this up after `createSession()` so the ICE\n * disconnect/failed path (see the `iceConnectionStateChange` handler\n * that calls `this.close()` on its own) reaches `cam.sessions.delete`\n * + `scheduleCameraAutoStop`. Without this callback the server kept\n * stale entries in `cam.sessions`, the auto-stop guard (`size > 0`)\n * never fired, ffmpeg stayed up, and the RTSP client toward the\n * broker leaked forever (`rtspClients: 2` on idle brokers).\n */\n onClosed: (() => void) | null = null;\n\n /** RTP sequence number counter (must increment per packet). */\n private videoSeqNum = 0;\n private audioSeqNum = 0;\n /**\n * Cached last keyframe NALs (SPS + PPS + IDR slices) in Annex-B format,\n * split and ready for RTP packetization. When the browser sends a PLI\n * (Picture Loss Indication) because it lost reference frames, we\n * immediately re-send this stored keyframe so the decoder can recover\n * without waiting for the next natural keyframe from the encoder.\n */\n private lastKeyframeNals: Buffer[] | null = null;\n private lastKeyframeRtpTs = 0;\n /** Throttle: minimum interval between PLI-triggered keyframe re-sends (ms). */\n private static readonly PLI_RESEND_COOLDOWN_MS = 500;\n private lastPliResendAt = 0;\n\n /**\n * Per-session H.265 RTP repacketizer (lazy-init). The H.265 path\n * forwards source RTP from the broker through this repacketizer so\n * Chrome's HEVC depacketizer sees an RTP shape it actually accepts —\n * the prior depacketize → AnnexB → re-packetize-from-scratch path\n * produced `framesAssembledFromMultiplePackets = 0` regardless of\n * codec metadata being correct. See `forwardSourceRtpVideo`.\n */\n private h265Repacketizer: H265Repacketizer | null = null;\n /** RTP MTU for the repacketizer — leave headroom for SRTP auth tag. */\n private static readonly H265_REPACKETIZER_MTU = 1180;\n /** Source SSRC — captured on first source RTP, used to populate the\n * outbound packets so werift's send pipeline doesn't reject them. */\n private sourceVideoSsrc: number | null = null;\n\n constructor(options: AdaptiveSessionOptions) {\n this.sessionId = options.sessionId;\n this.source = options.source;\n this.logger = options.logger;\n this.intercom = options.intercom;\n this.iceConfig = options.iceConfig;\n this.onStats = options.onStats;\n this.debug = options.debug ?? false;\n this.sourceCodec = options.sourceCodec ?? 'H264';\n this.createdAt = Date.now();\n }\n\n /** Build PeerConnection options including H.264 codec config. */\n private async buildPcOptions(): Promise<{ werift: WeriftModule; pcOptions: WeriftPcOptions }> {\n const werift = await loadWerift();\n\n // See `shared-session.ts` — werift's WeriftIceServer carries one URL\n // per entry, so array-shaped `entry.urls` expands into N entries that\n // share the same credentials.\n const iceServers: WeriftIceServer[] = [];\n for (const entry of this.iceConfig?.iceServers ?? []) {\n const urlList = Array.isArray(entry.urls) ? entry.urls : [entry.urls];\n for (const url of urlList) {\n iceServers.push({\n urls: url,\n ...(entry.username !== undefined ? { username: entry.username } : {}),\n ...(entry.credential !== undefined ? { credential: entry.credential } : {}),\n });\n }\n }\n\n const rtcpFeedback = [\n { type: \"transport-cc\" },\n { type: \"ccm\", parameter: \"fir\" },\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ];\n\n // Offer both codecs when source is H.265: browser picks H.265 if\n // supported, falls back to H.264. When source is H.264, offer only H.264.\n // After SDP negotiation, `negotiatedCodec` tells us which one won.\n const h264Codec = new werift.RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n payloadType: 96,\n parameters: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\",\n rtcpFeedback,\n });\n // H.265 fmtp — Scrypted ships the H.264-shaped fmtp string for H.265\n // and it works. Chrome rewrites the fmtp to proper H.265 syntax in\n // the answer regardless. Switching the offer to RFC 7798 syntax did\n // NOT change `framesDecoded=0` so the offer fmtp isn't the dominant\n // factor — keep the value Scrypted uses for parity with a known-\n // good implementation.\n const h265Codec = new werift.RTCRtpCodecParameters({\n mimeType: \"video/H265\",\n clockRate: 90000,\n payloadType: 97,\n parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',\n rtcpFeedback,\n });\n\n const videoCodecs = this.sourceCodec === 'H265'\n ? [h265Codec, h264Codec] // prefer H.265, fallback H.264\n : [h264Codec]; // H.264 only\n\n const pcOptions: WeriftPcOptions = {\n codecs: {\n video: videoCodecs,\n audio: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"audio/PCMU\",\n clockRate: 8000,\n payloadType: 0,\n channels: 1,\n parameters: \"\",\n }),\n ],\n },\n // RTP header extensions required for BUNDLE demuxing and congestion control.\n // Without sdes:mid, browsers cannot demux incoming RTP on a BUNDLE'd connection.\n headerExtensions: {\n video: [\n { uri: \"urn:ietf:params:rtp-hdrext:sdes:mid\" },\n { uri: \"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\" },\n { uri: \"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\" },\n ],\n audio: [\n { uri: \"urn:ietf:params:rtp-hdrext:sdes:mid\" },\n ],\n },\n };\n if (iceServers.length > 0) pcOptions.iceServers = iceServers;\n if (this.iceConfig?.portRange) pcOptions.icePortRange = this.iceConfig.portRange;\n if (this.iceConfig?.additionalHostAddresses?.length) {\n pcOptions.iceAdditionalHostAddresses = [...this.iceConfig.additionalHostAddresses];\n }\n\n return { werift, pcOptions };\n }\n\n /** Create offer SDP (server → client). */\n async createOffer(): Promise<{ sdp: string; type: \"offer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n // ICE state monitoring — promoted to info so the full lifecycle\n // (new → checking → connected/failed) is visible without flipping a\n // debug flag. Sessions stuck at \"checking\" are the canonical symptom\n // of ICE candidate mismatch; hiding the transitions behind `debug`\n // meant we couldn't tell \"stuck checking\" from \"never even started\".\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.info('ICE state changed', { meta: { phase: 'session', sessionId: this.sessionId, state } });\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Gathering state — surfaces whether the server is actually producing\n // ICE candidates and how long it takes. \"gathering\" without a\n // subsequent \"complete\" points at a local-network / binding issue.\n this.pc.iceGatheringStateChange.subscribe((state: string) => {\n this.logger.info('ICE gathering state changed', { meta: { phase: 'session', sessionId: this.sessionId, state } });\n });\n\n // Video track (sendonly) — save transceiver sender for sendRtp\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n const videoTransceiver = this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n this.videoSender = videoTransceiver.sender;\n\n // PLI/FIR listener — when the browser reports picture loss (decoder\n // can't decode P-frames because it missed the reference), re-send\n // the last cached keyframe immediately. This is the fix for the\n // \"stale frame with diffs\" symptom: without it, the viewer is stuck\n // on an old I-frame until the encoder naturally emits the next one.\n this.setupPliListener();\n\n // Audio track (sendrecv if intercom, sendonly otherwise)\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n const audioDir = this.intercom ? \"sendrecv\" : \"sendonly\";\n const audioTransceiver = this.pc.addTransceiver(this.audioTrack, { direction: audioDir });\n this.audioSender = audioTransceiver.sender;\n\n // Intercom: listen for incoming audio\n if (this.intercom) {\n const cb = this.intercom.onAudioReceived;\n audioTransceiver.onTrack.subscribe((track) => {\n track.onReceiveRtp.subscribe((pkt) => {\n try {\n const payload = pkt.payload;\n if (payload?.length > 0) void cb(payload, \"Opus\");\n } catch (err) {\n this.logger.error('Intercom error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n });\n });\n }\n\n const offer = await this.pc.createOffer();\n await this.pc.setLocalDescription(offer);\n\n // Wait for ICE gathering to complete so the offer includes candidates.\n // Without this, the offer has no candidates and the client can't connect.\n await new Promise<void>((resolve) => {\n if (this.pc?.iceGatheringState === \"complete\") { resolve(); return; }\n this.pc?.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") resolve();\n });\n // Timeout: don't wait forever if STUN is unreachable\n setTimeout(resolve, 5000);\n });\n\n // Use the local description which now includes gathered ICE candidates.\n // Force a=setup:actpass (let browser choose role) — werift defaults may cause DTLS issues.\n let finalSdp = this.pc.localDescription?.sdp ?? offer.sdp;\n // Ensure actpass so browser can be either active or passive\n finalSdp = finalSdp.replace(/a=setup:active\\r?\\n/g, \"a=setup:actpass\\r\\n\");\n this.state = \"connecting\";\n\n // Surface the ICE candidates the server ended up advertising. Printed\n // at info level because this is the first thing to inspect when a\n // session gets stuck at \"checking\" — if we only see a single\n // `127.0.0.1 host` line, the browser may well be unable to pair from\n // anywhere except a same-machine loopback, and anonymised mDNS\n // candidates from Chrome won't bind.\n const gatheredCandidates = finalSdp\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.startsWith('a=candidate:'))\n this.logger.info('Offer gathered ICE candidates', {\n meta: { phase: 'session', sessionId: this.sessionId, count: gatheredCandidates.length, candidates: gatheredCandidates },\n })\n\n // Log SDP video codec lines for debugging\n const videoCodecLines = finalSdp.split('\\n').filter((l: string) => l.includes('H264') || l.includes('rtpmap:96') || l.includes('fmtp:96') || l.includes('profile-level-id')).map((l: string) => l.trim());\n if (this.debug) this.logger.info('Offer created — video SDP', {\n meta: { phase: 'session', sessionId: this.sessionId, videoCodecLines },\n });\n return { sdp: finalSdp, type: \"offer\" };\n }\n\n /** Handle WHEP answer: client sends SDP answer, we set remote description and start feeding. */\n async handleAnswer(answer: { sdp: string; type: \"answer\" }): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n\n // Surface the browser's ICE candidates BEFORE setRemoteDescription so\n // we can see them even if werift rejects one of them. Chrome's mDNS\n // privacy layer emits candidates like `abcdef.local` on loopback; a\n // Node.js ICE agent without mDNS resolution can't pair them, and the\n // session stays at \"checking\" forever. If the only candidates shown\n // here are `*.local`, that's the smoking gun.\n const remoteCandidates = answer.sdp\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.startsWith('a=candidate:'))\n this.logger.info('Answer received ICE candidates', {\n meta: { phase: 'session', sessionId: this.sessionId, count: remoteCandidates.length, candidates: remoteCandidates },\n })\n\n // Rewrite Chrome-anonymised `*.local` candidates into their resolved IPs\n // before werift sees them; werift's ICE agent doesn't run mDNS itself\n // and would otherwise stall at \"checking\" forever.\n const resolvedSdp = await resolveMdnsCandidatesInSdp(answer.sdp, this.logger, `session:${this.sessionId}`)\n const desc = new werift.RTCSessionDescription(resolvedSdp, answer.type);\n await this.pc.setRemoteDescription(desc);\n\n // Detect which video codec the browser selected from the answer SDP.\n // If source is H.265 but browser only supports H.264, needsTranscode\n // will be true and the feed loop will spawn ffmpeg.\n const answerVideoCodec = resolvedSdp.match(/a=rtpmap:\\d+ (H264|H265)\\/90000/i)?.[1]?.toUpperCase()\n if (answerVideoCodec === 'H264' || answerVideoCodec === 'H265') {\n this.negotiatedCodec = answerVideoCodec as 'H264' | 'H265'\n }\n this.logger.info('Codec negotiated', {\n meta: { phase: 'session', sessionId: this.sessionId, source: this.sourceCodec, negotiated: this.negotiatedCodec, needsTranscode: this.needsTranscode },\n })\n\n // Wait for DTLS to connect before feeding — sendRtp silently drops if DTLS not ready\n const dtlsTransport = this.videoSender?.dtlsTransport;\n if (dtlsTransport && dtlsTransport.state !== \"connected\") {\n this.logger.debug('Waiting for DTLS...', {\n meta: { phase: 'session', sessionId: this.sessionId, current: dtlsTransport.state },\n });\n const deadline = Date.now() + 10_000;\n while (dtlsTransport.state !== \"connected\" && Date.now() < deadline) {\n await new Promise<void>((r) => setTimeout(r, 100));\n }\n this.logger.debug('DTLS wait complete', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n state: dtlsTransport.state,\n waitedSec: (10000 - (deadline - Date.now())) / 1000,\n },\n });\n }\n\n this.logger.debug('Answer set, feeding started', {\n meta: { phase: 'session', sessionId: this.sessionId, dtls: dtlsTransport?.state ?? 'unknown' },\n });\n this.startFeedingFrames();\n }\n\n /**\n * Handle WHEP offer: client sends SDP offer, we create answer.\n *\n * Uses the server-creates-offer pattern internally: we create our own offer\n * with sendonly tracks, then use the client's offer codecs to build a\n * compatible answer. This avoids werift transceiver direction issues.\n */\n async handleOffer(clientOffer: { sdp: string; type: \"offer\" }): Promise<{ sdp: string; type: \"answer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug('ICE state', { meta: { phase: 'session', sessionId: this.sessionId, state } });\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Set the client's offer as remote description. mDNS-rewriting the\n // offer for the same reason as `handleAnswer` — Chrome sends\n // anonymised `*.local` host candidates even in the offer path when\n // issuing WHEP-style requests.\n const resolvedOfferSdp = await resolveMdnsCandidatesInSdp(clientOffer.sdp, this.logger, `session:${this.sessionId}`)\n const remoteDesc = new werift.RTCSessionDescription(resolvedOfferSdp, clientOffer.type);\n await this.pc.setRemoteDescription(remoteDesc);\n\n // Find the transceivers werift created from the offer and attach our tracks.\n const transceivers: WeriftTransceiver[] = this.pc.getTransceivers();\n for (const t of transceivers) {\n const kind = t.receiver?.track?.kind ?? t.kind;\n if (kind === \"video\" && !this.videoTrack) {\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n await t.sender.replaceTrack(this.videoTrack);\n } else if (kind === \"audio\" && !this.audioTrack) {\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n await t.sender.replaceTrack(this.audioTrack);\n }\n }\n\n // Fallback: if no transceivers matched (shouldn't happen with valid offer)\n if (!this.videoTrack) {\n this.logger.warn('No video transceiver found in offer, adding one', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n }\n if (!this.audioTrack) {\n this.logger.warn('No audio transceiver found in offer, adding one', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n this.pc.addTransceiver(this.audioTrack, { direction: \"sendonly\" });\n }\n\n const answerDesc = await this.pc.createAnswer();\n await this.pc.setLocalDescription(answerDesc);\n this.state = \"connecting\";\n this.logger.info('WHEP answer created', { meta: { phase: 'session', sessionId: this.sessionId } });\n\n this.startFeedingFrames();\n\n return { sdp: answerDesc.sdp, type: \"answer\" };\n }\n\n /** Add ICE candidate. */\n async addIceCandidate(candidate: unknown): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n await this.pc.addIceCandidate(new werift.RTCIceCandidate(candidate));\n }\n\n /**\n * Detach the frame source (for connection pooling).\n * The session stays alive (ICE/DTLS connected) but stops feeding frames.\n * Call replaceSource() later to reattach a camera.\n */\n detachSource(): void {\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.logger.debug('Source detached (idle)', { meta: { phase: 'session', sessionId: this.sessionId } });\n }\n\n /** Whether the session has an active feed (vs idle/pooled). */\n get isFeeding(): boolean {\n return this.feedAbort !== null && !this.feedAbort.signal.aborted;\n }\n\n /**\n * Replace the frame source (for seamless source switching).\n * The new source will take effect at the next keyframe.\n */\n replaceSource(newSource: FrameSource): void {\n this.source = newSource;\n // Abort old feed — the feeding loop will restart with new source\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.startFeedingFrames();\n }\n\n getInfo(): SessionInfo {\n return { sessionId: this.sessionId, state: this.state, createdAt: this.createdAt };\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.state = \"closed\";\n this.logger.info('Closing', { meta: { phase: 'session', sessionId: this.sessionId } });\n\n if (this.statsTimer) {\n clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n try { await this.source.return(undefined); } catch { /* */ }\n if (this.pc) {\n try { await this.pc.close(); } catch { /* */ }\n this.pc = null;\n }\n this.videoTrack = null;\n this.audioTrack = null;\n\n // Notify the adaptive server so it can drop us from cam.sessions and\n // trigger scheduleCameraAutoStop. Guarded null-call so the server is\n // free to not wire it (tests, pooled sessions that manage cleanup\n // themselves).\n const hook = this.onClosed;\n this.onClosed = null;\n try { hook?.(); } catch { /* server-side bookkeeping shouldn't break close */ }\n }\n\n // -----------------------------------------------------------------------\n // H.265 source-RTP forwarding (packet-level repacketization)\n // -----------------------------------------------------------------------\n\n /**\n * Seed the H.265 repacketizer's codec info with VPS/SPS/PPS harvested\n * from the camera SDP. Cameras that publish parameter sets only via\n * SDP (e.g. Reolink high-profile streams) leave the wire bare of\n * VPS/SPS/PPS — without this seed the repacketizer would never\n * generate the AP codec packet that initialises the browser decoder.\n */\n seedH265CodecInfoFromSdp(parameterSets: ReadonlyArray<Buffer>): void {\n if (this.negotiatedCodec !== 'H265') return;\n if (!parameterSets.length) return;\n this.ensureH265Repacketizer();\n const rep = this.h265Repacketizer!;\n for (const ps of parameterSets) {\n const nalType = (ps[0]! & 0x7e) >> 1;\n if (nalType === 32) rep.updateVps(Buffer.from(ps));\n else if (nalType === 33) rep.updateSps(Buffer.from(ps));\n else if (nalType === 34) rep.updatePps(Buffer.from(ps));\n }\n if (this.debug) {\n this.logger.info('seeded H.265 repacketizer codecInfo from SDP', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n psCount: parameterSets.length,\n hasVps: !!rep.codecInfo?.vps,\n hasSps: !!rep.codecInfo?.sps,\n hasPps: !!rep.codecInfo?.pps,\n },\n });\n }\n }\n\n private ensureH265Repacketizer(): void {\n if (this.h265Repacketizer) return;\n const codecInfo: H265CodecInfo | undefined = undefined;\n this.h265Repacketizer = new H265Repacketizer(\n console,\n AdaptiveSession.H265_REPACKETIZER_MTU,\n codecInfo,\n );\n }\n\n /**\n * Forward a source RTP video packet (raw on-wire bytes) through the\n * H.265 repacketizer to the browser. Used by the broker's H.265\n * direct-RTP subscription — bypasses the AnnexB→writeVideoNals\n * path entirely.\n *\n * Drops everything until the SDP answer has been negotiated\n * (`negotiatedCodec`/`videoSender` populated) and silently no-ops on\n * non-H.265 sessions.\n */\n forwardSourceRtpVideo(rtpData: Buffer): void {\n if (this.closed) return;\n if (this.negotiatedCodec !== 'H265') return;\n if (!this.videoSender || !_werift) return;\n const werift = _werift;\n this.ensureH265Repacketizer();\n const rep = this.h265Repacketizer!;\n\n let srcPkt;\n try {\n srcPkt = werift.RtpPacket.deSerialize(rtpData);\n } catch (err) {\n this.logger.warn('H265 RTP deserialize failed', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err), len: rtpData.length },\n });\n return;\n }\n\n if (this.sourceVideoSsrc === null) {\n this.sourceVideoSsrc = srcPkt.header.ssrc;\n }\n\n const senderCodec = this.videoSender.codec;\n const pt = senderCodec?.payloadType ?? 97;\n\n let outPkts;\n try {\n outPkts = rep.repacketize(srcPkt);\n } catch (err) {\n this.logger.warn('H265 repacketize failed', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) },\n });\n return;\n }\n\n for (const pkt of outPkts) {\n pkt.header.payloadType = pt;\n // Sync werift's sender SSRC/seq to our outbound stream on first\n // packet — same fix the writeVideoNals path uses.\n if (!this.videoRtpSynced) {\n this.videoRtpSynced = true;\n try {\n this.videoSender.replaceRTP(\n { sequenceNumber: pkt.header.sequenceNumber, timestamp: pkt.header.timestamp },\n true,\n );\n } catch (e) {\n this.logger.warn('replaceRTP failed (non-fatal)', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(e) },\n });\n }\n }\n try {\n this.videoSender.sendRtp(pkt);\n this.rtpPacketsSent++;\n if (this.debug && (this.rtpPacketsSent === 1 || this.rtpPacketsSent % 500 === 0)) {\n this.logger.info('H265 source-RTP forwarded', {\n meta: { phase: 'session', sessionId: this.sessionId, count: this.rtpPacketsSent },\n });\n }\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error('sendRtp (h265 forward) error', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) },\n });\n }\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // PLI handling — resend cached keyframe on picture loss\n // -----------------------------------------------------------------------\n\n private setupPliListener(): void {\n const sender = this.videoSender;\n if (!sender) return;\n\n // werift exposes PLI via onPictureLossIndication (preferred) or the\n // generic onRtcpFeedback. Not all builds surface these — degrade\n // gracefully if neither is available.\n const onPli = () => this.handlePli();\n\n if (sender.onPictureLossIndication) {\n sender.onPictureLossIndication.subscribe(onPli);\n this.logger.debug('PLI listener attached (onPictureLossIndication)', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n } else if (sender.onRtcpFeedback) {\n sender.onRtcpFeedback.subscribe((fb) => {\n if (fb.type === 'nack' && fb.parameter === 'pli') onPli();\n if (fb.type === 'ccm' && fb.parameter === 'fir') onPli();\n });\n this.logger.debug('PLI listener attached (onRtcpFeedback)', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n } else {\n this.logger.debug('PLI listener not available — werift version may not expose RTCP feedback', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n }\n }\n\n private handlePli(): void {\n // Sessions running the H.265 repacketizer forward source RTP\n // through it — the upstream camera will respond to PLI by emitting\n // a fresh IDR access unit on its own. Re-sending a cached one\n // through writeVideoNals would collide with the repacketizer's\n // outbound RTP stream. Push-mode H.265 (no repacketizer) keeps the\n // cached-keyframe re-send path.\n if (this.h265Repacketizer) return;\n\n const now = Date.now();\n if (now - this.lastPliResendAt < AdaptiveSession.PLI_RESEND_COOLDOWN_MS) return;\n this.lastPliResendAt = now;\n\n if (!this.lastKeyframeNals || this.lastKeyframeNals.length === 0) {\n this.logger.debug('PLI received but no cached keyframe yet', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n return;\n }\n\n this.logger.info('PLI received — re-sending cached keyframe', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n nals: this.lastKeyframeNals.length,\n totalBytes: this.lastKeyframeNals.reduce((s, n) => s + n.length, 0),\n },\n });\n // Re-send all NALs via writeVideoNals — same path the feed loop\n // uses for keyframes (single-NAL packets for VPS/SPS/PPS, FU\n // fragmentation for IDR slice). All share the same RTP timestamp;\n // marker bit set only on the last FU of the last NAL.\n this.writeVideoNals(this.lastKeyframeNals, this.lastKeyframeRtpTs, this.negotiatedCodec);\n }\n\n // -----------------------------------------------------------------------\n // Frame feeding\n // -----------------------------------------------------------------------\n\n private startFeedingFrames(): void {\n this.feedAbort = new AbortController();\n const { signal } = this.feedAbort;\n\n this.logger.info('Feed loop starting', {\n meta: { phase: 'session', sessionId: this.sessionId, dtls: this.videoSender?.dtlsTransport?.state ?? 'unknown', needsTranscode: this.needsTranscode },\n });\n\n if (this.needsTranscode) {\n this.startTranscodeFeed(signal);\n } else {\n this.startDirectFeed(signal);\n }\n }\n\n /**\n * Direct feed — source codec matches negotiated codec. Zero transcode.\n * H.264 and H.265 both go through this path with codec-specific RTP\n * packetization handled by writeVideoNals.\n */\n private startDirectFeed(signal: AbortSignal): void {\n\n void (async () => {\n let gotKeyframe = false;\n let videoTimestampBase: number | null = null;\n let audioTimestampBase: number | null = null;\n let frameCount = 0;\n\n try {\n for await (const mediaFrame of this.source) {\n if (signal.aborted || this.closed) break;\n frameCount++;\n if (frameCount <= 5 || frameCount % 100 === 0) {\n this.logger.debug('Frame received', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n frameCount,\n type: mediaFrame.type,\n size: mediaFrame.frame.data.length,\n isKeyframe: mediaFrame.type === 'video' && 'isKeyframe' in mediaFrame.frame\n ? mediaFrame.frame.isKeyframe\n : undefined,\n },\n });\n }\n\n if (mediaFrame.type === \"video\") {\n const frame = mediaFrame.frame;\n const annexB = frame.codec === \"H264\"\n ? convertH264ToAnnexB(frame.data)\n : convertH265ToAnnexB(frame.data);\n\n // Cache parameter sets BEFORE the keyframe gate. Cameras\n // that ship VPS/SPS/PPS in their own non-keyframe access\n // unit (or via the broker's synthetic SDP-replay packet —\n // see StreamBroker.emitSdpParamSetsTo) deliver them in a\n // frame whose `isKeyframe` is false. The previous code\n // skipped such frames entirely with `continue`, so\n // `lastVps/Sps/Pps` were never populated and HEVC decoding\n // failed on the first IDR.\n const __nalsForCaching = splitAnnexBToNals(annexB);\n if (frame.codec === 'H265') {\n for (const n of __nalsForCaching) {\n const nalType = (n[0]! & 0x7e) >> 1;\n if (nalType === 32) this.lastVps = Buffer.from(n);\n if (nalType === 33) this.lastSps = Buffer.from(n);\n if (nalType === 34) this.lastPps = Buffer.from(n);\n }\n } else {\n for (const n of __nalsForCaching) {\n const nalType = n[0]! & 0x1f;\n if (nalType === 7) this.lastSps = Buffer.from(n);\n if (nalType === 8) this.lastPps = Buffer.from(n);\n }\n }\n\n if (!gotKeyframe) {\n const isKey = frame.codec === \"H264\"\n ? isH264IdrAccessUnit(annexB)\n : isH265IrapAccessUnit(annexB);\n if (!isKey) {\n if (frameCount <= 3) {\n this.logger.debug('Skipping frame (waiting for keyframe)', {\n meta: { phase: 'session', sessionId: this.sessionId, frameCount, size: annexB.length },\n });\n }\n continue;\n }\n gotKeyframe = true;\n const iceState = this.pc?.iceConnectionState ?? \"unknown\";\n this.logger.info('First keyframe', {\n meta: { phase: 'session', sessionId: this.sessionId, frameCount, size: annexB.length, ice: iceState },\n });\n }\n\n if (videoTimestampBase === null) videoTimestampBase = frame.timestampMicros;\n const rtpTs = Math.floor(\n ((frame.timestampMicros - videoTimestampBase) * 90000) / 1_000_000,\n ) >>> 0;\n\n // Filter out AUD and SEI — not needed in RTP.\n // H.264: AUD=9, SEI=6 (mask 0x1f). H.265: AUD=35, SEI_PREFIX=39, SEI_SUFFIX=40 (mask (b>>1)&0x3f).\n const isH265 = frame.codec === 'H265';\n const allNals = splitAnnexBToNals(annexB);\n const nals = allNals.filter((n: Buffer) => {\n if (isH265) {\n const t = (n[0]! & 0x7e) >> 1;\n return t !== 35 && t !== 39 && t !== 40; // AUD, SEI_PREFIX, SEI_SUFFIX\n }\n const t = n[0]! & 0x1f;\n return t !== 9 && t !== 6; // AUD, SEI\n });\n\n // Debug: dump NAL info for first few frames (only when debug enabled)\n if (this.debug && (frameCount <= 5 || (gotKeyframe && frameCount <= (this._firstKeyFrame ?? 0) + 2))) {\n const nalInfo = allNals.map((n: Buffer) => {\n if (isH265) {\n const t = (n[0]! & 0x7e) >> 1;\n const names: Record<number, string> = {0:'TRAIL_N',1:'TRAIL_R',19:'IDR_W_RADL',20:'IDR_N_LP',21:'CRA',32:'VPS',33:'SPS',34:'PPS',35:'AUD',39:'SEI_P',40:'SEI_S'};\n return `${names[t] ?? `t${t}`}(${n.length})`;\n }\n const t = n[0]! & 0x1f;\n const names: Record<number, string> = {1:'SLICE',5:'IDR',6:'SEI',7:'SPS',8:'PPS',9:'AUD'};\n return `${names[t] ?? `t${t}`}(${n.length})`;\n }).join(' + ');\n this.logger.info('Frame NAL breakdown', {\n meta: { phase: 'session', sessionId: this.sessionId, frameCount, codec: frame.codec, nals: nalInfo, sending: nals.length },\n });\n if (gotKeyframe && !this._firstKeyFrame) this._firstKeyFrame = frameCount;\n }\n\n if (nals.length > 0 && this.videoTrack) {\n // For H.265 IRAP access units, send VPS+SPS+PPS as a\n // single Aggregation Packet (RFC 7798 §4.4.2) BEFORE the\n // IDR fragments. Scrypted does the same and Chrome's HEVC\n // depacketizer relies on this shape to initialise\n // VideoToolbox properly. Sending the param sets as 3\n // separate single-NAL packets caused `framesDecoded` to\n // stay at 0 with PLI flooding even with valid SDP fmtp.\n if (isH265) {\n // ── Param set tracking + AP+IDR for keyframes ──\n for (const n of nals) {\n const nalType = (n[0]! & 0x7e) >> 1;\n if (nalType === 32) this.lastVps = Buffer.from(n);\n if (nalType === 33) this.lastSps = Buffer.from(n);\n if (nalType === 34) this.lastPps = Buffer.from(n);\n }\n\n if (isH265IrapAccessUnit(annexB)) {\n // Resolve param sets — prefer inline, fall back to cache.\n const inlineVps = nals.find((n) => ((n[0]! & 0x7e) >> 1) === 32) ?? this.lastVps\n const inlineSps = nals.find((n) => ((n[0]! & 0x7e) >> 1) === 33) ?? this.lastSps\n const inlinePps = nals.find((n) => ((n[0]! & 0x7e) >> 1) === 34) ?? this.lastPps\n // VCL NALs (IRAP slices) — everything that isn't a param set.\n const vclNals = nals.filter((n) => {\n const t = (n[0]! & 0x7e) >> 1\n return t !== 32 && t !== 33 && t !== 34\n })\n if (inlineVps && inlineSps && inlinePps && vclNals.length > 0) {\n // Send VPS+SPS+PPS+IDR all via writeVideoNals — same\n // shape as the H.264 path that Chrome decodes\n // correctly. The previous Aggregation Packet (AP,\n // RFC 7798 §4.4.2) bundling produced a packet shape\n // that Chrome's HEVC depacketizer never accepted —\n // `framesAssembledFromMultiplePackets` stayed 0 and\n // `framesDecoded` stayed 0 even with valid SDP and\n // fmtp. Single-NAL packets for the param sets\n // followed by FU-fragmented IDR is what the H.264\n // path does for SPS/PPS/IDR and it works.\n const auNals = [inlineVps, inlineSps, inlinePps, ...vclNals]\n this.writeVideoNals(auNals, rtpTs, frame.codec)\n this.lastKeyframeNals = auNals.map((n) => Buffer.from(n))\n this.lastKeyframeRtpTs = rtpTs\n } else {\n // Missing param sets — fall back to \"send as-is\".\n this.writeVideoNals(nals, rtpTs, frame.codec)\n }\n } else {\n // Non-IRAP frames (TRAIL_R / RADL_R / etc.) — send as-is.\n this.writeVideoNals(nals, rtpTs, frame.codec)\n }\n } else {\n // ── H.264 param set tracking ──\n let auNals: Buffer[] = nals\n for (const n of nals) {\n const nalType = n[0]! & 0x1f;\n if (nalType === 7) this.lastSps = Buffer.from(n);\n if (nalType === 8) this.lastPps = Buffer.from(n);\n }\n if (isH264IdrAccessUnit(annexB)) {\n const hasInlineSps = nals.some((n: Buffer) => (n[0]! & 0x1f) === 7);\n if (!hasInlineSps && this.lastSps && this.lastPps) {\n auNals = [this.lastSps, this.lastPps, ...nals]\n }\n this.lastKeyframeNals = auNals.map((n) => Buffer.from(n))\n this.lastKeyframeRtpTs = rtpTs;\n }\n this.writeVideoNals(auNals, rtpTs, frame.codec);\n }\n if (this.debug && frameCount % 250 === 0) {\n this.logger.info('Feed progress', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n frames: frameCount,\n rtpPackets: this.rtpPacketsSent,\n ice: this.pc?.iceConnectionState ?? \"?\",\n conn: this.pc?.connectionState ?? \"?\",\n },\n });\n }\n }\n } else if (mediaFrame.type === \"audio\") {\n const frame = mediaFrame.frame;\n if (!this.audioSender) continue;\n\n // For G.711 (PCMU/PCMA): timestamp increments by frame size (160 samples per 20ms)\n // Using a simple counter is more reliable than wall-clock based timestamps\n if (audioTimestampBase === null) audioTimestampBase = 0;\n audioTimestampBase = ((audioTimestampBase as number) + frame.data.length) >>> 0;\n const rtpTs = audioTimestampBase as number;\n\n this.writeAudio(frame.data, rtpTs, frame.codec);\n }\n }\n } catch (err) {\n if (!signal.aborted && !this.closed) {\n this.logger.error('Feed error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n } finally {\n if (!this.closed) {\n if (this.debug) this.logger.info('Feed ended', { meta: { phase: 'session', sessionId: this.sessionId } });\n void this.close();\n }\n }\n })();\n }\n\n /** Max RTP payload size (MTU 1200 to stay under typical network MTU). */\n /**\n * Transcode feed — source is H.265 but browser only supports H.264.\n * Pipes raw H.265 Annex-B to ffmpeg stdin, reads H.264 from stdout.\n */\n private startTranscodeFeed(signal: AbortSignal): void {\n const { spawn } = require('node:child_process') as typeof import('node:child_process')\n\n this.logger.info('Starting H.265→H.264 transcode feed', {\n meta: { phase: 'session', sessionId: this.sessionId },\n })\n\n const ff = spawn('ffmpeg', [\n '-hide_banner', '-loglevel', 'error',\n '-f', 'hevc', '-i', 'pipe:0',\n '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency',\n '-profile:v', 'baseline', '-g', '30', '-bf', '0',\n '-an', '-f', 'h264', 'pipe:1',\n ], { stdio: ['pipe', 'pipe', 'pipe'] })\n\n signal.addEventListener('abort', () => {\n if (!ff.killed) ff.kill('SIGTERM')\n })\n\n ff.on('exit', (code) => {\n this.logger.info('Transcode ffmpeg exited', { meta: { phase: 'session', sessionId: this.sessionId, code } })\n })\n ff.stderr.on('data', (chunk: Buffer) => {\n const msg = chunk.toString().trim()\n if (msg.length > 0) this.logger.warn('Transcode ffmpeg stderr', { meta: { sessionId: this.sessionId, msg } })\n })\n\n // Parse H.264 output from ffmpeg and send via RTP\n let pendingBuf = Buffer.alloc(0)\n let rtpTs = 0\n const FRAME_INTERVAL_90K = 3000 // 30fps → 90kHz ticks\n\n ff.stdout.on('data', (chunk: Buffer) => {\n pendingBuf = Buffer.concat([pendingBuf, chunk])\n // Look for keyframe boundaries to emit complete access units\n const lastSc = findLastStartCode(pendingBuf)\n if (lastSc <= 0) return\n const complete = pendingBuf.subarray(0, lastSc)\n pendingBuf = Buffer.from(pendingBuf.subarray(lastSc))\n\n const completedNals = splitAnnexBToNals(complete)\n if (completedNals.length === 0) return\n\n rtpTs = (rtpTs + FRAME_INTERVAL_90K) >>> 0\n this.writeVideoNals(completedNals, rtpTs, 'H264')\n })\n\n // Feed source H.265 data to ffmpeg stdin\n void (async () => {\n try {\n for await (const mediaFrame of this.source) {\n if (signal.aborted || this.closed) break\n if (mediaFrame.type === 'video') {\n if (!ff.stdin.destroyed) {\n ff.stdin.write(mediaFrame.frame.data)\n }\n } else if (mediaFrame.type === 'audio') {\n // Audio passthrough (no transcode needed)\n const frame = mediaFrame.frame\n this.writeAudio(frame.data, frame.timestampMicros, frame.codec)\n }\n }\n } catch (err) {\n if (!signal.aborted && !this.closed) {\n this.logger.error('Transcode feed error', { meta: { sessionId: this.sessionId, error: errMsg(err) } })\n }\n } finally {\n if (!ff.stdin.destroyed) ff.stdin.end()\n if (!this.closed) void this.close()\n }\n })()\n }\n\n private static readonly MAX_RTP_PAYLOAD = 1200;\n\n private rtpPacketsSent = 0;\n private rtpLogCounter = 0;\n\n private writeVideoNals(nals: Buffer[], rtpTs: number, codec: VideoCodec): void {\n if (!this.videoSender || !_werift) {\n if (this.rtpLogCounter === 0) {\n this.logger.warn('writeVideoNals: no sender', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n hasVideoSender: !!this.videoSender,\n hasWerift: !!_werift,\n },\n });\n this.rtpLogCounter++;\n }\n return;\n }\n const werift = _werift;\n // Use the payload type from the negotiated codec on the sender,\n // not a hardcoded value. After SDP negotiation, werift assigns the\n // correct PT to the sender's codec. Using a different PT causes\n // the browser to ignore the packets.\n const senderCodec = this.videoSender.codec;\n const pt = senderCodec?.payloadType ?? (codec === \"H264\" ? 96 : 97);\n\n const sendPkt = (payload: Buffer, marker: boolean) => {\n try {\n const header = new werift.RtpHeader();\n header.payloadType = pt;\n header.timestamp = rtpTs;\n header.marker = marker;\n header.sequenceNumber = (this.videoSeqNum = (this.videoSeqNum + 1) & 0xffff);\n const pkt = new werift.RtpPacket(header, payload);\n\n // FIX: Sync SSRC/seq on first packet (like Scrypted's replaceRTP).\n // Without this, werift's internal SSRC and the RTP header SSRC\n // diverge, and the browser ignores the packets.\n if (!this.videoRtpSynced) {\n this.videoRtpSynced = true;\n try {\n this.videoSender!.replaceRTP(header, true);\n } catch (e) {\n this.logger.warn('replaceRTP failed (non-fatal)', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(e) },\n });\n }\n }\n\n // Debug: check sender state for first few packets\n if (this.debug && this.rtpPacketsSent < 3) {\n const dtls = this.videoSender?.dtlsTransport;\n const senderCodec = this.videoSender?.codec;\n const ssrc = this.videoSender?.ssrc;\n this.logger.info('sendRtp debug', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n packetIndex: this.rtpPacketsSent,\n dtls: dtls?.state ?? 'none',\n codec: senderCodec?.mimeType ?? 'NULL',\n payloadType: senderCodec?.payloadType ?? '?',\n ssrc: String(ssrc),\n payloadSize: payload.length,\n },\n });\n }\n this.videoSender!.sendRtp(pkt);\n\n this.rtpPacketsSent++;\n if (this.debug && (this.rtpPacketsSent === 1 || this.rtpPacketsSent % 500 === 0)) {\n this.logger.info('RTP packets sent', {\n meta: { phase: 'session', sessionId: this.sessionId, count: this.rtpPacketsSent, payload: payload.length },\n });\n }\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error('sendRtp error', {\n meta: { phase: 'session', sessionId: this.sessionId, packetIndex: this.rtpPacketsSent, error: errMsg(err) },\n });\n }\n }\n };\n\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n\n if (nal.length <= AdaptiveSession.MAX_RTP_PAYLOAD) {\n sendPkt(nal, isLastNal);\n } else if (codec === 'H265') {\n // H.265 FU fragmentation (RFC 7798 §4.4.3)\n // NAL header is 2 bytes: [F|type(6)|layerId_hi(1)] [layerId_lo(5)|tid(3)]\n const nalType = (nal[0]! & 0x7e) >> 1;\n const layerId = ((nal[0]! & 0x01) << 5) | ((nal[1]! & 0xf8) >> 3);\n const tid = nal[1]! & 0x07;\n // FU NAL header: type=49 (FU), keep layerId+tid\n const fuNalHeader0 = (49 << 1) | (layerId >> 5);\n const fuNalHeader1 = ((layerId & 0x1f) << 3) | tid;\n const nalBody = nal.subarray(2); // skip 2-byte NAL header\n\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + AdaptiveSession.MAX_RTP_PAYLOAD - 3, nalBody.length);\n const isLast = end >= nalBody.length;\n\n let fuHeader = nalType & 0x3f;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n\n const fragment = Buffer.alloc(3 + (end - offset));\n fragment[0] = fuNalHeader0;\n fragment[1] = fuNalHeader1;\n fragment[2] = fuHeader;\n nalBody.copy(fragment, 3, offset, end);\n\n sendPkt(fragment, isLastNal && isLast);\n offset = end;\n isFirst = false;\n }\n } else {\n // H.264 FU-A fragmentation (RFC 6184 §5.8)\n const nalHeader = nal[0]!;\n const fnri = nalHeader & 0xe0;\n const nalType = nalHeader & 0x1f;\n const fuIndicator = fnri | 28;\n const nalBody = nal.subarray(1);\n\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + AdaptiveSession.MAX_RTP_PAYLOAD - 2, nalBody.length);\n const isLast = end >= nalBody.length;\n\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n\n const fragment = Buffer.alloc(2 + (end - offset));\n fragment[0] = fuIndicator;\n fragment[1] = fuHeader;\n nalBody.copy(fragment, 2, offset, end);\n\n sendPkt(fragment, isLastNal && isLast);\n offset = end;\n isFirst = false;\n }\n }\n }\n }\n\n private writeAudio(data: Buffer, rtpTs: number, codec?: string): void {\n if (!this.audioSender || !_werift) return;\n const werift = _werift;\n // Use the payload type from the negotiated codec on the sender —\n // SAME pattern as `writeVideoNals`. werift assigns the negotiated\n // codec to the sender after SDP exchange completes; reading\n // `senderCodec.payloadType` keeps us in sync with whatever the\n // browser actually accepted in its answer. Fallback to the\n // static OFFER-side PT only if werift hasn't bound the codec yet\n // (timing race during the very first packet).\n // NOTE: NOT calling `audioSender.replaceRTP(header, true)`. Video\n // does it (and works), but audio's SSRC binding is more brittle\n // in werift — overriding the sender's SSRC with our header's\n // default (zero) leaves the SRTP context out of sync with the\n // SSRC the browser expects from the answer SDP, so the browser\n // gets RTP with the right PT but can't decrypt → silent drop.\n // werift assigns the audio sender's SSRC during transceiver\n // setup; let `sendRtp` use it directly.\n const senderCodec = this.audioSender.codec;\n const fallbackPt = codec === \"Pcmu\" || codec === \"Pcma\" ? 0 : 111;\n const pt = senderCodec?.payloadType ?? fallbackPt;\n try {\n const header = new werift.RtpHeader();\n header.payloadType = pt;\n header.timestamp = rtpTs;\n // marker=true on every audio packet — browsers tolerate it on\n // G.711 codecs, and some receive paths stall the jitter buffer\n // waiting for marker=true to start playback. Setting marker\n // on the first packet only (RFC 3551 talkspurt convention)\n // produced silent receivers in practice.\n header.marker = true;\n header.sequenceNumber = (this.audioSeqNum = (this.audioSeqNum + 1) & 0xffff);\n const pkt = new werift.RtpPacket(header, data);\n this.audioSender.sendRtp(pkt);\n } catch (err) {\n this.logger.debug('Audio write error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n }\n\n // -----------------------------------------------------------------------\n // RTCP stats collection\n // -----------------------------------------------------------------------\n\n private startStatsCollection(): void {\n if (this.statsTimer || !this.onStats) return;\n\n this.statsTimer = setInterval(() => {\n if (!this.pc || this.closed) return;\n this.collectStats();\n }, 3_000);\n }\n\n private collectStats(): void {\n if (!this.pc || !this.onStats) return;\n\n try {\n // werift exposes getReceivers() and getSenders() on RTCPeerConnection.\n // For video sender stats, we check RTCP RR feedback from the receiver.\n const senders = this.pc.getSenders?.() ?? [];\n\n for (const sender of senders) {\n const track = sender.track;\n if (!track || track.kind !== \"video\") continue;\n\n // werift tracks RTCP receiver reports via sender.rtcpReceiveReport\n // or via the transport's RTCP event. The exact API depends on werift version.\n // Fallback: use werift's built-in stats if available.\n const report = sender.lastReceiverReport ?? sender.rtcpReport;\n if (!report) continue;\n\n const fractionLost = report.fractionLost ?? 0;\n const packetsLost = report.packetsLost ?? report.cumulativeLost ?? 0;\n const jitter = report.jitter ?? 0;\n const rtt = report.roundTripTime ?? report.rtt ?? 0;\n\n const packetLoss = fractionLost / 256; // Fraction lost is 0–255\n\n this.onStats({\n sessionId: this.sessionId,\n packetLoss,\n jitterMs: jitter,\n rttMs: rtt * 1000, // seconds → ms\n packetsReceived: 0, // Not available from sender side\n packetsLost,\n timestamp: Date.now(),\n });\n return; // One video sender is enough\n }\n\n // Fallback: no RTCP report available yet — emit zeros\n // (Client-reported stats will supplement this)\n } catch {\n // RTCP not yet available — normal during early connection\n }\n }\n}\n\n\n/** Find the byte offset of the last 0x00000001 start code in a buffer. */\nfunction findLastStartCode(buf: Buffer): number {\n for (let i = buf.length - 4; i >= 0; i--) {\n if (buf[i] === 0 && buf[i + 1] === 0 && buf[i + 2] === 0 && buf[i + 3] === 1) return i\n }\n return -1\n}\n","/**\n * Broker-integrated WebRTC server.\n *\n * Unlike the old `addon-webrtc-adaptive` which spawned ffmpeg to re-encode\n * the broker's RTSP restream (Camera → Broker → RTSP → ffmpeg libx264 → RTP),\n * this server subscribes directly to `IStreamBroker.onEncodedData()` and\n * forward the raw H.264 packets to WebRTC sessions as-is — zero transcode,\n * zero extra RTSP connection, zero latency from re-encoding.\n *\n * Flow: Camera → RTSP → Broker → EncodedPacket → RTP packetization → WebRTC\n */\n\nimport crypto from 'node:crypto'\nimport type { EncodedPacket, IStreamBroker, IScopedLogger, Unsubscribe } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport { AdaptiveSession, type IceServerEntry } from './session.js'\nimport type { FrameSource, MediaFrame } from './types.js'\nimport { convertH264ToAnnexB } from './h264-utils.js'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface BrokerWebrtcServerOptions {\n readonly logger: IScopedLogger\n readonly getIceServers?: () => Promise<readonly IceServerEntry[]>\n readonly iceServers?: readonly IceServerEntry[]\n readonly icePortRange?: [number, number]\n readonly iceAdditionalHostAddresses?: readonly string[]\n}\n\ninterface SessionEntry {\n readonly session: AdaptiveSession\n readonly brokerId: string\n /** Unsubscribe from broker's onEncodedData. */\n readonly unsubBroker: Unsubscribe\n /**\n * Unsubscribe from broker's `onVideoRtp` for H.265 sessions that\n * forward source RTP through `H265Repacketizer`. `null` for H.264\n * sessions (which use the AnnexB feed-loop path).\n */\n readonly unsubRtp: Unsubscribe | null\n /**\n * Unsubscribe from broker's `onSdpParameterSets` for H.265 sessions\n * that need late delivery of VPS/SPS/PPS. Sessions created during a\n * battery-cam wake-up window register this so the repacketizer is\n * seeded as soon as the broker's first `onVideoTrack` fires —\n * without it the AP codec packet is never emitted and Chrome's HEVC\n * decoder never initialises. `null` for H.264 sessions and for H.265\n * sessions that found the SDP params already populated at create\n * time.\n */\n readonly unsubSdpParams: Unsubscribe | null\n /** Close the push source. */\n readonly closeSource: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Adaptive source selection\n// ---------------------------------------------------------------------------\n\n/** Fallback resolution/bitrate per label when the broker hasn't reported stats yet. */\nconst LABEL_DEFAULTS: Readonly<Record<string, { pixels: number; bitrateKbps: number }>> = {\n high: { pixels: 1920 * 1080, bitrateKbps: 4000 },\n mid: { pixels: 1280 * 720, bitrateKbps: 1200 },\n low: { pixels: 640 * 360, bitrateKbps: 400 },\n}\nconst TIER_PREFERENCE: readonly string[] = ['high', 'mid', 'low']\n\ninterface ClientHints {\n readonly viewportWidth?: number\n readonly viewportHeight?: number\n readonly devicePixelRatio?: number\n readonly downlinkMbps?: number\n readonly prefersTier?: string\n}\n\nfunction scoreBroker(\n tier: 'high' | 'mid' | 'low' | null,\n broker: IStreamBroker,\n hints: ClientHints,\n): number {\n const stats = broker.getStats()\n const fallback = tier ? LABEL_DEFAULTS[tier] : undefined\n const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : (fallback?.bitrateKbps ?? 2000)\n // Approximate pixel count from tier defaults (broker doesn't track resolution)\n const srcPixels = fallback?.pixels ?? 1920 * 1080\n\n const dpr = hints.devicePixelRatio ?? 1\n const targetW = (hints.viewportWidth ?? 1920) * dpr\n const targetH = (hints.viewportHeight ?? 1080) * dpr\n const targetPixels = targetW * targetH\n\n const pixelDistance = Math.abs(srcPixels - targetPixels) / 1_000_000\n\n let bandwidthPenalty = 0\n if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {\n const availableKbps = hints.downlinkMbps * 1000 * 0.6\n if (bitrateKbps > availableKbps) {\n bandwidthPenalty = ((bitrateKbps - availableKbps) / availableKbps) * 100\n }\n }\n return pixelDistance + bandwidthPenalty\n}\n\n// ---------------------------------------------------------------------------\n// BrokerWebrtcServer\n// ---------------------------------------------------------------------------\n\nexport class BrokerWebrtcServer {\n private readonly logger: IScopedLogger\n private readonly getIceServersFn: BrokerWebrtcServerOptions['getIceServers']\n private readonly staticIceServers: readonly IceServerEntry[] | undefined\n private readonly icePortRange: [number, number] | undefined\n private readonly iceAdditionalHostAddresses: readonly string[] | undefined\n\n /** brokerId → IStreamBroker reference. Set by the broker manager on registration. */\n private readonly brokers = new Map<string, IStreamBroker>()\n /**\n * Resolves a brokerId to its current profile tier (`'high' | 'mid' | 'low'`)\n * via the manager's `assignments` map, or `null` for brokers that\n * are only kept alive by a manual activation. Injected by the\n * StreamBrokerManager since this server doesn't own the assignment\n * map directly. Defaults to a no-op resolver — adaptive bitrate logic\n * still works (falls back to \"mid\" defaults) but tier-explicit\n * `prefersTier` requests always pick the first matching candidate.\n */\n private profileTierResolver: ((brokerId: string) => 'high' | 'mid' | 'low' | null) | null = null\n /** sessionId → session entry. */\n private readonly sessions = new Map<string, SessionEntry>()\n private stopped = false\n\n constructor(options: BrokerWebrtcServerOptions) {\n this.logger = options.logger\n this.getIceServersFn = options.getIceServers\n this.staticIceServers = options.iceServers\n this.icePortRange = options.icePortRange\n this.iceAdditionalHostAddresses = options.iceAdditionalHostAddresses\n }\n\n // ── Broker registration (called by StreamBrokerManager) ─────────────\n\n registerBroker(brokerId: string, broker: IStreamBroker): void {\n this.brokers.set(brokerId, broker)\n this.logger.info('WebRTC broker registered', { meta: { brokerId } })\n }\n\n /**\n * Provide a resolver that maps brokerId → current profile tier. The\n * manager injects one wired to its `assignments` map. Without it the\n * server still serves brokers but can't honour client `prefersTier`\n * hints precisely (it falls through to the order-based pick).\n */\n setProfileTierResolver(\n resolver: (brokerId: string) => 'high' | 'mid' | 'low' | null,\n ): void {\n this.profileTierResolver = resolver\n }\n\n unregisterBroker(brokerId: string): void {\n this.brokers.delete(brokerId)\n // Close all sessions on this broker\n for (const [sid, entry] of this.sessions) {\n if (entry.brokerId === brokerId) {\n this.cleanupSession(sid)\n void entry.session.close().catch(() => {})\n }\n }\n this.logger.info('WebRTC broker unregistered', { meta: { brokerId } })\n }\n\n supportsStream(streamId: string): boolean {\n // streamId is brokerId (e.g. \"6/high\") or bare deviceKey\n // Accept if any registered broker matches\n if (this.brokers.has(streamId)) return true\n // Check for adaptive alias: \"6/adaptive\" → any broker for device \"6\"\n const slash = streamId.lastIndexOf('/')\n if (slash > 0) {\n const tail = streamId.slice(slash + 1)\n if (tail === 'adaptive') {\n const deviceKey = streamId.slice(0, slash)\n for (const bid of this.brokers.keys()) {\n if (bid.startsWith(`${deviceKey}/`)) return true\n }\n }\n }\n return false\n }\n\n // ── Session lifecycle ───────────────────────────────────────────────\n\n async createSession(\n streamId: string,\n hints: ClientHints = {},\n opts: { streamingDebug?: boolean } = {},\n ): Promise<{ sessionId: string; sdpOffer: string }> {\n if (this.stopped) throw new Error('Server stopped')\n\n const brokerId = this.resolveBrokerId(streamId, hints)\n const broker = this.brokers.get(brokerId)\n if (!broker) throw new Error(`No broker for stream \"${streamId}\" (resolved: \"${brokerId}\")`)\n\n // Parse the brokerId so every session log gets `deviceId` /\n // `camStreamId` tags. Without these tags, operator log filters\n // scoped to a single device (the common debugging shape) drop\n // every WebRTC session lifecycle line — and we end up flying\n // blind on whether the session was created, on which codec path,\n // and whether the H.265 SDP seed / first-RTP forward fired.\n const slashIdx = brokerId.indexOf('/')\n const deviceTags = slashIdx > 0\n ? {\n deviceId: Number.parseInt(brokerId.slice(0, slashIdx), 10),\n camStreamId: brokerId.slice(slashIdx + 1),\n }\n : { deviceId: -1, camStreamId: brokerId }\n const sessionLogger = this.logger.withTags(deviceTags)\n\n const brokerCodec = broker.getStats().codec ?? 'h264'\n const isHevc = brokerCodec === 'h265' || brokerCodec === 'hevc'\n const sessionCodec = isHevc ? 'H265' : 'H264' as const\n // RTP-bearing brokers expose source RTP via `onVideoRtp` — that's\n // RTSP (pull) and `push-rtp` (provider-synthesised RTP). AnnexB-\n // only push brokers (Reolink Baichuan today, Frigate event streams)\n // and the future RTMP path have no source RTP and fall back to\n // AnnexB → writeVideoNals even on H.265.\n const sourceType = broker.getSourceType()\n const isRtp = broker.isRtpSource()\n const useH265Repacketizer = sessionCodec === 'H265' && isRtp\n // Surface the codec/path resolution in the message itself so it\n // shows up in operator log filters that strip the structured meta\n // — without this, \"WebRTC session created\" alone makes it\n // impossible to tell whether a session went down the H.265 RTP\n // repacketizer path (where the late-seed fix matters) or the\n // AnnexB fallback (where it doesn't), which is the first\n // discriminator any wake-up debugging needs.\n sessionLogger.info(\n `WebRTC session: codec=${sessionCodec} brokerCodec=${brokerCodec} sourceType=${sourceType ?? 'null'} isRtp=${isRtp} repacketizer=${useH265Repacketizer}`,\n { meta: { brokerId, sessionCodec, brokerCodec, sourceType, isRtp, useH265Repacketizer } },\n )\n\n // Create push-based FrameSource from broker's encoded data\n const { source, pushFrame, close: closeSource } = createPushFrameSource()\n\n // Subscribe to broker's encoded packets. Both H.264 and H.265 use\n // the same direct path — the session handles codec-specific RTP\n // packetization (FU-A for H.264, FU for H.265).\n //\n // Parameter-set NALs (SPS/PPS for H.264, VPS/SPS/PPS for H.265)\n // arrive as separate EncodedPackets from the broker. We buffer them\n // and prepend to the next VCL NAL so the decoder gets a complete\n // access unit.\n const pendingParamNals: Buffer[] = []\n /**\n * Sticky SDP-derived VPS/SPS/PPS Annex-B blob. Cameras that publish\n * parameter sets only in SDP (Reolink high-profile streams) emit\n * raw IDR access units on the wire — without this sticky prepend,\n * every keyframe after the first one would arrive at the WebRTC\n * session as a bare IDR and Chrome's HEVC decoder never advances\n * past zero.\n *\n * Captured the first time `pendingParamNals` is populated by a\n * paramSet packet (the synthetic SDP-replay emitted by\n * `StreamBroker.emitSdpParamSetsTo` when this subscriber registered).\n * After that, every keyframe missing inline VPS/SPS/PPS gets the\n * sticky blob prepended.\n */\n let stickyParamSets: Buffer | null = null\n /**\n * Whether we've seen a non-placeholder video packet on this\n * session yet. The placeholder pump emits a 1280×720 SPS/PPS+IDR\n * blob at 1Hz while the broker waits for real RTP. If those\n * param sets land in `stickyParamSets`, the camera's first real\n * keyframe (a different resolution) would get the placeholder\n * SPS prepended and Chrome's decoder would freeze on the last\n * placeholder frame until the session is recreated. Flipping\n * `seenRealPacket` on the first real video packet flushes the\n * placeholder-derived sticky state.\n */\n let seenRealPacket = false\n const unsubBroker = broker.onEncodedData((packet: EncodedPacket) => {\n if (packet.type === 'video') {\n // RTSP+H.265 sessions forward source RTP directly through the\n // repacketizer — the AnnexB → writeVideoNals path is\n // bypassed entirely. The repacketizer is seeded at\n // session-create time with the camera's VPS/SPS/PPS via\n // `seedH265CodecInfoFromSdp`. Pushing placeholder frames\n // through writeVideoNals here would re-configure Chrome's\n // HEVC decoder with the placeholder's parameter sets\n // (1280×720) and the transition back to the camera's real\n // SPS through the repacketizer's AP codec packet doesn't\n // recover cleanly — the viewer ends up frozen on the\n // placeholder. So H.265 sessions show no labelled\n // placeholder during wake-up; H.264 paths still get them.\n if (useH265Repacketizer) return\n\n // First real packet after a placeholder window — drop any\n // sticky state captured from placeholder param sets. From\n // this point forward `pendingParamNals` / `stickyParamSets`\n // track the live camera's actual SPS/PPS only.\n if (!packet.isPlaceholder && !seenRealPacket) {\n seenRealPacket = true\n pendingParamNals.length = 0\n stickyParamSets = null\n }\n\n const annexB = convertH264ToAnnexB(packet.data)\n\n // Detect first NAL type\n const nalTypeInfo = detectFirstNalType(annexB, isHevc)\n if (nalTypeInfo.isParamSet) {\n // Placeholder packets are self-contained access units —\n // VPS+SPS+PPS+IDR concatenated in a single buffer. The\n // first NAL is a parameter set, but the buffer also\n // contains the IDR slice that produces the visible frame.\n // `detectFirstNalType` only inspects the leading NAL, so\n // the live-stream branch (which assumes \"param sets arrive\n // separately, then a VCL\") would discard the IDR portion\n // and the operator never sees the labelled placeholder.\n // Push the whole bundle as a keyframe and skip the sticky-\n // param caching (corrupts the camera's real keyframe later\n // — different resolution/profile).\n if (packet.isPlaceholder) {\n pushFrame({\n type: 'video',\n frame: {\n data: annexB,\n codec: sessionCodec,\n isKeyframe: true,\n timestampMicros: packet.pts,\n },\n })\n return\n }\n pendingParamNals.push(Buffer.from(annexB))\n // Keep a sticky copy so future keyframes that ship without\n // inline param sets still get configured decoder.\n stickyParamSets = Buffer.concat(pendingParamNals)\n return\n }\n\n // VCL NAL — prepend any buffered parameter sets, OR the sticky\n // SDP-derived blob if the keyframe lacks them inline.\n let finalAnnexB = annexB\n if (pendingParamNals.length > 0) {\n pendingParamNals.push(annexB)\n finalAnnexB = Buffer.concat(pendingParamNals)\n pendingParamNals.length = 0\n } else if (nalTypeInfo.isKeyframe && stickyParamSets) {\n finalAnnexB = Buffer.concat([stickyParamSets, annexB])\n }\n\n pushFrame({\n type: 'video',\n frame: {\n data: finalAnnexB,\n codec: sessionCodec,\n isKeyframe: packet.keyframe || nalTypeInfo.isKeyframe,\n timestampMicros: packet.pts,\n },\n })\n } else if (packet.type === 'audio') {\n pushFrame({\n type: 'audio',\n frame: {\n data: packet.data,\n codec: packet.codec?.toLowerCase() === 'pcma' ? 'Pcma' : 'Pcmu',\n sampleRate: 8000,\n channels: 1,\n timestampMicros: packet.pts,\n },\n })\n }\n })\n\n // Flush pre-buffer. Seed `preParamNals` with the SDP-derived\n // VPS/SPS/PPS captured in the live callback's `stickyParamSets`\n // closure variable above. Without this seed, pre-buffer flush\n // starts with empty param sets and the first VCL flushed is a\n // bare IDR — Chrome's HEVC decoder fails on cameras that ship\n // param sets only via SDP (Reolink high-profile streams).\n const preBuffer = broker.getPreBuffer()\n const preParamNals: Buffer[] = stickyParamSets\n ? [Buffer.from(stickyParamSets)]\n : pendingParamNals.length > 0\n ? pendingParamNals.map((b) => Buffer.from(b))\n : []\n for (const pkt of preBuffer) {\n if (pkt.type === 'video') {\n // RTSP+H.265 pre-buffer holds AnnexB packets — feeding them\n // into the writeVideoNals path conflicts with the\n // repacketizer's outbound RTP stream. Skip pre-buffer for\n // video on the repacketizer path and rely on PLI to trigger\n // a fresh keyframe from the camera. Push-mode H.265 keeps\n // pre-buffer flush.\n if (useH265Repacketizer) continue\n\n const annexB = convertH264ToAnnexB(pkt.data)\n const nalTypeInfo = detectFirstNalType(annexB, isHevc)\n if (nalTypeInfo.isParamSet) { preParamNals.push(Buffer.from(annexB)); continue }\n\n let finalAnnexB = annexB\n if (preParamNals.length > 0) {\n preParamNals.push(annexB)\n finalAnnexB = Buffer.concat(preParamNals)\n preParamNals.length = 0\n } else if (nalTypeInfo.isKeyframe && stickyParamSets) {\n finalAnnexB = Buffer.concat([stickyParamSets, annexB])\n }\n pushFrame({\n type: 'video',\n frame: {\n data: finalAnnexB,\n codec: sessionCodec,\n isKeyframe: pkt.keyframe || nalTypeInfo.isKeyframe,\n timestampMicros: pkt.pts,\n },\n })\n }\n }\n\n const sessionId = crypto.randomUUID()\n const iceServers = await this.resolveIceServers()\n\n const session = new AdaptiveSession({\n sessionId,\n source,\n sourceCodec: sessionCodec,\n iceConfig: {\n iceServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n },\n logger: this.logger,\n debug: opts.streamingDebug === true,\n })\n\n // For RTSP+H.265 sessions: subscribe to source-RTP and forward\n // through the H265Repacketizer in the session. Also seed the\n // repacketizer codec info from the SDP-derived VPS/SPS/PPS so the\n // AP codec packet can be sent ahead of the first IDR even when\n // the camera doesn't emit param sets in-band (Reolink RTSP\n // high-profile streams). Push-mode H.265 (Reolink Baichuan, etc.)\n // skips this and uses the AnnexB feed path.\n let unsubRtp: Unsubscribe | null = null\n let unsubSdpParams: Unsubscribe | null = null\n if (useH265Repacketizer) {\n const sdpPs = broker.getSdpParameterSets()\n if (sdpPs && sdpPs.length > 0) {\n session.seedH265CodecInfoFromSdp(sdpPs)\n sessionLogger.info('H.265 session seeded from SDP at create-time', {\n meta: { sessionId, brokerId, psCount: sdpPs.length },\n })\n } else {\n // Battery-cam wake-up: broker is still dialing rfc4571, so\n // `getSdpParameterSets()` is null at this point. Subscribe so\n // the seed lands the moment `onVideoTrack` fires — before\n // that, the repacketizer can't emit its AP codec packet, so\n // Chrome receives RTP it can't initialise a decoder against.\n // `onSdpParameterSets` delivers synchronously when params are\n // already known (no-op for the wake-up case), and on every\n // future reader rebuild.\n sessionLogger.info('H.265 session deferred: SDP params not ready, subscribing for late delivery', {\n meta: { sessionId, brokerId },\n })\n unsubSdpParams = broker.onSdpParameterSets((ps) => {\n try {\n session.seedH265CodecInfoFromSdp(ps)\n sessionLogger.info('H.265 session seeded from SDP late delivery', {\n meta: { sessionId, brokerId, psCount: ps.length },\n })\n } catch (err) {\n sessionLogger.warn('seedH265CodecInfoFromSdp threw', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n })\n }\n let firstRtpForwarded = false\n unsubRtp = broker.onVideoRtp((rtpData) => {\n if (!firstRtpForwarded) {\n firstRtpForwarded = true\n sessionLogger.info('H.265 session: first source RTP forwarded to repacketizer', {\n meta: { sessionId, brokerId, bytes: rtpData.length },\n })\n }\n try {\n session.forwardSourceRtpVideo(rtpData)\n } catch (err) {\n sessionLogger.warn('forwardSourceRtpVideo threw', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n })\n }\n\n const entry: SessionEntry = { session, brokerId, unsubBroker, unsubRtp, unsubSdpParams, closeSource }\n this.sessions.set(sessionId, entry)\n\n session.onClosed = () => this.cleanupSession(sessionId)\n\n try {\n const offer = await session.createOffer()\n sessionLogger.info('WebRTC session created', {\n meta: { sessionId, brokerId, iceServers: iceServers.length, codec: sessionCodec, useH265Repacketizer },\n })\n return { sessionId, sdpOffer: offer.sdp }\n } catch (err) {\n this.cleanupSession(sessionId)\n await session.close().catch(() => {})\n throw err\n }\n }\n\n async handleAnswer(sessionId: string, sdpAnswer: string): Promise<void> {\n const entry = this.sessions.get(sessionId)\n if (!entry) throw new Error(`Session not found: ${sessionId}`)\n await entry.session.handleAnswer({ sdp: sdpAnswer, type: 'answer' })\n }\n\n async closeSession(sessionId: string): Promise<void> {\n const entry = this.sessions.get(sessionId)\n if (!entry) return\n this.cleanupSession(sessionId)\n await entry.session.close().catch(() => {})\n this.logger.info('WebRTC session closed', { meta: { sessionId } })\n }\n\n async stop(): Promise<void> {\n if (this.stopped) return\n this.stopped = true\n const closes: Promise<void>[] = []\n for (const [sid, entry] of this.sessions) {\n this.cleanupSession(sid)\n closes.push(entry.session.close().catch(() => {}))\n }\n await Promise.all(closes)\n this.brokers.clear()\n this.logger.info('WebRTC server stopped')\n }\n\n getSessionCount(): number {\n return this.sessions.size\n }\n\n // ── Private ─────────────────────────────────────────────────────────\n\n private cleanupSession(sessionId: string): void {\n const entry = this.sessions.get(sessionId)\n if (!entry) return\n this.sessions.delete(sessionId)\n entry.unsubBroker()\n entry.unsubRtp?.()\n entry.unsubSdpParams?.()\n entry.closeSource()\n }\n\n private resolveBrokerId(streamId: string, hints: ClientHints = {}): string {\n // Direct match — `webrtc-session.createSession` resolves the\n // discriminated `WebrtcStreamTarget` to a canonical brokerId\n // (`${deviceId}/${camStreamId}`) before invoking the server, so\n // every legitimate client request hits this branch.\n if (this.brokers.has(streamId)) return streamId\n\n // Adaptive fallthrough — `${deviceId}/adaptive` is a sentinel\n // that means \"pick the best broker for the device\". Bare\n // deviceKey (no slash) is treated the same way for legacy\n // callers that only know the device id.\n const slash = streamId.indexOf('/')\n if (slash > 0) {\n const tail = streamId.slice(slash + 1)\n if (tail === 'adaptive') {\n return this.selectBestBroker(streamId.slice(0, slash), hints)\n }\n } else {\n return this.selectBestBroker(streamId, hints)\n }\n // Unknown shape — let the lookup fail in the caller so the\n // 'No broker for stream' message points at the actual input.\n return streamId\n }\n\n /**\n * Adaptive source selection: score registered brokers for a device\n * against client hints (viewport, downlink). Returns the broker ID\n * with the best fit. Falls back to tier preference when no hints.\n */\n private selectBestBroker(deviceKey: string, hints: ClientHints): string {\n // Collect all brokers for this device, attaching the resolver-\n // derived tier so candidates can be ranked irrespective of brokerId\n // shape (post-refactor brokerId carries the camStreamId, not the\n // tier — tiers are looked up in the assignments map at request\n // time via `profileTierResolver`).\n type Candidate = {\n brokerId: string\n tier: 'high' | 'mid' | 'low' | null\n broker: IStreamBroker\n }\n const candidates: Candidate[] = []\n for (const [bid, broker] of this.brokers) {\n if (!bid.startsWith(`${deviceKey}/`)) continue\n const tier = this.profileTierResolver?.(bid) ?? null\n candidates.push({ brokerId: bid, tier, broker })\n }\n if (candidates.length === 0) return `${deviceKey}/high` // will fail at broker lookup\n if (candidates.length === 1) return candidates[0]!.brokerId\n\n // Explicit tier override from client\n if (hints.prefersTier === 'high' || hints.prefersTier === 'mid' || hints.prefersTier === 'low') {\n const explicit = candidates.find((c) => c.tier === hints.prefersTier)\n if (explicit) return explicit.brokerId\n }\n\n // No hints — use tier preference order\n const hasHints = hints.viewportWidth !== undefined\n || hints.viewportHeight !== undefined\n || hints.downlinkMbps !== undefined\n if (!hasHints) {\n for (const tier of TIER_PREFERENCE) {\n const c = candidates.find((c) => c.tier === tier)\n if (c) return c.brokerId\n }\n return candidates[0]!.brokerId\n }\n\n // Score each candidate\n let bestId = candidates[0]!.brokerId\n let bestTier: 'high' | 'mid' | 'low' | null = candidates[0]!.tier\n let bestScore = Infinity\n for (const c of candidates) {\n const score = scoreBroker(c.tier, c.broker, hints)\n const cRank = c.tier ? TIER_PREFERENCE.indexOf(c.tier) : TIER_PREFERENCE.length\n const bestRank = bestTier ? TIER_PREFERENCE.indexOf(bestTier) : TIER_PREFERENCE.length\n if (score < bestScore || (score === bestScore && cRank < bestRank)) {\n bestId = c.brokerId\n bestTier = c.tier\n bestScore = score\n }\n }\n\n this.logger.debug('Adaptive source selected', {\n meta: { deviceKey, selected: bestId, tier: bestTier, score: Math.round(bestScore * 100) / 100, hints },\n })\n return bestId\n }\n\n private async resolveIceServers(): Promise<readonly IceServerEntry[]> {\n if (this.getIceServersFn) {\n try { return await this.getIceServersFn() } catch (err) {\n this.logger.warn('getIceServers failed', { meta: { error: errMsg(err) } })\n }\n }\n return this.staticIceServers ?? []\n }\n}\n\n// ---------------------------------------------------------------------------\n// Push-based FrameSource factory\n// ---------------------------------------------------------------------------\n// NAL type detection (codec-agnostic)\n// ---------------------------------------------------------------------------\n\ninterface NalTypeInfo {\n readonly isParamSet: boolean\n readonly isKeyframe: boolean\n}\n\n/**\n * Detect the type of the first NAL in an Annex-B buffer.\n * Works for both H.264 (1-byte NAL header) and H.265 (2-byte NAL header).\n */\nfunction detectFirstNalType(annexB: Buffer, isHevc: boolean): NalTypeInfo {\n for (let i = 0; i < annexB.length - 4; i++) {\n if (annexB[i] === 0 && annexB[i + 1] === 0 && annexB[i + 2] === 0 && annexB[i + 3] === 1 && i + 4 < annexB.length) {\n if (isHevc) {\n const nalType = (annexB[i + 4]! & 0x7e) >> 1\n const isParamSet = nalType === 32 || nalType === 33 || nalType === 34\n const isKeyframe = isParamSet || (nalType >= 16 && nalType <= 21)\n return { isParamSet, isKeyframe }\n } else {\n const nalType = annexB[i + 4]! & 0x1f\n const isParamSet = nalType === 7 || nalType === 8\n const isKeyframe = nalType === 5 || isParamSet\n return { isParamSet, isKeyframe }\n }\n }\n }\n return { isParamSet: false, isKeyframe: false }\n}\n\n// ---------------------------------------------------------------------------\n// Push-based FrameSource factory\n// ---------------------------------------------------------------------------\n\nfunction createPushFrameSource(): {\n source: FrameSource\n pushFrame: (frame: MediaFrame) => void\n close: () => void\n} {\n const queue: MediaFrame[] = []\n let resolve: ((value: IteratorResult<MediaFrame>) => void) | null = null\n let done = false\n\n const pushFrame = (mf: MediaFrame): void => {\n if (done) return\n if (resolve) {\n const r = resolve\n resolve = null\n r({ value: mf, done: false })\n } else {\n queue.push(mf)\n // Back-pressure: keep last 60 frames if queue overflows\n if (queue.length > 120) queue.splice(0, queue.length - 60)\n }\n }\n\n const close = (): void => {\n done = true\n if (resolve) {\n const r = resolve\n resolve = null\n r({ value: undefined as never, done: true })\n }\n }\n\n const source: FrameSource = (async function* () {\n try {\n while (true) {\n const item = queue.shift()\n if (item) { yield item; continue }\n if (done) return\n const result = await new Promise<IteratorResult<MediaFrame>>((r) => {\n resolve = r\n })\n if (result.done) return\n yield result.value\n }\n } finally {\n done = true\n }\n })()\n\n return { source, pushFrame, close }\n}\n\n// ---------------------------------------------------------------------------\n","import type {\n CameraStream, ProfileSlot, CamProfile,\n RtspRestreamEntry,\n WebrtcClientHints, WebrtcStreamChoice, WebrtcStreamTarget,\n} from '@camstack/types'\nimport type { StreamBrokerManager } from './stream-broker/stream-broker-manager.js'\n\n/**\n * Device-scoped facade over the broker's cam-stream registry.\n * Implements `cameraStreamsCapability` (reads only — every mutation\n * lives on the system-scope `stream-broker` cap).\n */\nexport class CameraStreamsProvider {\n constructor(private readonly manager: StreamBrokerManager) {}\n\n async getCameraStreams(input: { deviceId: number }): Promise<readonly CameraStream[]> {\n return this.manager.getCameraStreamsForDevice(input.deviceId)\n }\n\n async getBrokerStreams(input: { deviceId: number }): Promise<readonly ProfileSlot[]> {\n return this.manager.getProfileSlotsForDevice(input.deviceId)\n }\n\n async getRtspEntries(\n input: { deviceId: number; hostname?: string },\n ): Promise<readonly RtspRestreamEntry[]> {\n return this.manager.getRtspEntriesForDevice(input.deviceId, input.hostname)\n }\n}\n\n/**\n * Device-scoped WebRTC session facade.\n *\n * Implements `webrtcSessionCapability`. Resolves `{deviceId, profile}` to\n * a broker-integrated WebRTC session — no external webrtc addon needed.\n * The `BrokerWebrtcServer` subscribes directly to `IStreamBroker.onEncodedData()`\n * so frames flow Camera → Broker → RTP → WebRTC with zero re-encoding.\n */\nexport { BrokerWebrtcServer } from './webrtc/broker-webrtc-server.js'\nimport type { BrokerWebrtcServer } from './webrtc/broker-webrtc-server.js'\n\nexport class WebrtcSessionProvider {\n constructor(\n private readonly webrtcServer: BrokerWebrtcServer,\n private readonly manager: StreamBrokerManager,\n ) {}\n\n private static readonly PROFILE_LABELS: Record<CamProfile, string> = {\n high: 'High',\n mid: 'Mid',\n low: 'Low',\n }\n\n /**\n * Resolve a structured `WebrtcStreamTarget` to the brokerId the\n * WebRTC server understands. `'adaptive'` yields the magic\n * `${deviceId}/adaptive` sentinel that triggers `selectBestBroker`\n * server-side; the other two kinds resolve to the canonical\n * cam-stream-keyed brokerId.\n */\n private resolveTargetToStreamId(deviceId: number, target: WebrtcStreamTarget): string {\n switch (target.kind) {\n case 'adaptive':\n return `${deviceId}/adaptive`\n case 'profile': {\n const slots = this.manager.getProfileSlotsForDevice(deviceId)\n const slot = slots.find((s) => s.profile === target.profile)\n if (!slot?.sourceCamStreamId) {\n throw new Error(`webrtc-session: profile \"${target.profile}\" has no source assigned for device ${deviceId}`)\n }\n return `${deviceId}/${slot.sourceCamStreamId}`\n }\n case 'cam-stream':\n return `${deviceId}/${target.camStreamId}`\n }\n }\n\n async listStreams(input: { deviceId: number }): Promise<readonly WebrtcStreamChoice[]> {\n const { deviceId } = input\n const slots = this.manager.getProfileSlotsForDevice(deviceId)\n const assignedSlots = slots.filter((s) => s.sourceCamStreamId !== null)\n const camStreams = this.manager.getCameraStreamsForDevice(deviceId)\n const camStreamById = new Map<string, CameraStream>()\n for (const c of camStreams) camStreamById.set(c.camStreamId, c)\n\n const choices: WebrtcStreamChoice[] = []\n\n if (assignedSlots.length > 0) {\n choices.push({\n id: 'adaptive',\n label: 'Adaptive',\n target: { kind: 'adaptive' },\n codec: null,\n resolution: null,\n status: null,\n inputFps: null,\n decodeFps: null,\n bitrateKbps: null,\n })\n }\n\n // Profile-tier choices: pin a session to high / mid / low. Source\n // can change as the operator reassigns the slot — the session\n // follows.\n for (const slot of assignedSlots) {\n const profile = slot.profile as CamProfile\n const camStreamId = slot.sourceCamStreamId\n if (!camStreamId) continue\n const broker = await this.manager.getBroker({ brokerId: `${deviceId}/${camStreamId}` })\n const stats = broker?.getStats()\n const cam = camStreamById.get(camStreamId)\n choices.push({\n id: `profile:${profile}`,\n label: WebrtcSessionProvider.PROFILE_LABELS[profile] ?? profile,\n target: { kind: 'profile', profile },\n codec: stats?.codec ?? cam?.codec ?? slot.codec ?? null,\n resolution: cam?.resolution ?? slot.resolution ?? null,\n status: stats?.status ?? null,\n inputFps: stats?.inputFps ?? null,\n decodeFps: stats?.decodeFps ?? null,\n bitrateKbps: stats?.bitrateKbps ?? null,\n })\n }\n\n // Cam-stream-direct choices: enumerate EVERY published camStream.\n // Each published stream has a live broker (broker lifecycle is 1:1\n // with `publishCameraStream` / `retractCameraStream`).\n for (const cam of camStreams) {\n const camStreamId = cam.camStreamId\n const broker = await this.manager.getBroker({ brokerId: `${deviceId}/${camStreamId}` })\n const stats = broker?.getStats()\n choices.push({\n id: `stream:${camStreamId}`,\n label: cam.label ?? camStreamId,\n target: { kind: 'cam-stream', camStreamId },\n codec: stats?.codec ?? cam.codec ?? null,\n resolution: cam.resolution ?? null,\n status: stats?.status ?? null,\n inputFps: stats?.inputFps ?? null,\n decodeFps: stats?.decodeFps ?? null,\n bitrateKbps: stats?.bitrateKbps ?? null,\n })\n }\n return choices\n }\n\n async createSession(input: {\n deviceId: number\n target: WebrtcStreamTarget\n hints?: WebrtcClientHints\n }): Promise<{ sessionId: string; sdpOffer: string }> {\n const streamId = this.resolveTargetToStreamId(input.deviceId, input.target)\n const streamingDebug = typeof this.manager.isStreamingDebug === 'function'\n ? this.manager.isStreamingDebug(input.deviceId)\n : false\n return this.webrtcServer.createSession(streamId, input.hints, { streamingDebug })\n }\n\n async handleAnswer(input: {\n deviceId: number\n sessionId: string\n sdpAnswer: string\n }): Promise<void> {\n await this.webrtcServer.handleAnswer(input.sessionId, input.sdpAnswer)\n }\n\n async closeSession(input: { deviceId: number; sessionId: string }): Promise<void> {\n await this.webrtcServer.closeSession(input.sessionId)\n }\n\n async hasAdaptiveBitrate(_input: {\n deviceId: number\n profile: CamProfile\n }): Promise<boolean> {\n // Broker-direct mode: no adaptive bitrate switching (single source per profile).\n // The broker serves the camera's native stream at its native bitrate.\n return false\n }\n}\n","import type {\n ProviderRegistration,\n} from '@camstack/types'\nimport {\n BaseAddon, asJsonObject, EventCategory, createEvent, streamBrokerCapability,\n cameraStreamsCapability, webrtcSessionCapability, errMsg,\n addonWidgetsSourceCapability,\n type IAddonWidgetsSourceProvider,\n} from '@camstack/types'\nimport { StreamBrokerManager, type ProfileMapPersister } from './stream-broker/stream-broker-manager'\nimport { CameraStreamsProvider, WebrtcSessionProvider } from './device-scoped-providers'\nimport { BrokerWebrtcServer } from './webrtc/broker-webrtc-server'\nimport type { CamProfile } from '@camstack/types'\n\ninterface StreamBrokerConfig {\n readonly defaultPreBufferSec: number\n readonly rtspPort: number\n readonly maxDecodeFps: number\n readonly initialReconnectDelayMs: number\n readonly maxReconnectDelayMs: number\n}\n\ninterface PersistedAssignment {\n readonly map: Partial<Record<CamProfile, string>>\n readonly auto: boolean\n}\n\nconst PROFILE_KEYS: readonly CamProfile[] = ['high', 'mid', 'low']\n\n/**\n * Cadence for the per-broker metrics-snapshot bus event. ~1 Hz keeps\n * UI dashboards (StreamBrokerPanel, broker stats overlays) live without\n * needing to poll `getBrokerStats` every 3s — admin-ui subscribes to\n * the snapshot category and updates state from the event payload.\n */\nconst BROKER_METRICS_SNAPSHOT_INTERVAL_MS = 1_000\n\n/**\n * Build a dedup-key for a snapshot by serialising the payload with\n * monotonic counter fields stripped. Without this, fields like\n * `uptimeMs` / `totalBytes` / `packetCount` on broker stats — which\n * tick up every second regardless of state — make every JSON-equal\n * check fail and the defer never fires. Stripping happens in the\n * key only; the full payload still rides the bus when an emit\n * fires.\n */\nfunction stableSnapshotKey<T>(payload: T, dropKeys: readonly string[]): string {\n const dropSet = new Set(dropKeys)\n const strip = (v: unknown): unknown => {\n if (Array.isArray(v)) return v.map(strip)\n if (v && typeof v === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (dropSet.has(k)) continue\n out[k] = strip(val)\n }\n return out\n }\n return v\n }\n return JSON.stringify(strip(payload))\n}\n/**\n * Force a broker-stats emit at least every 30s even when the\n * payload hasn't changed — gives consumers a heartbeat without\n * the per-tick spam an unconditional emit produces. Picks up\n * idle brokers (no encoded subscribers, status frozen at\n * `streaming` with stale fps/bitrate).\n */\nconst BROKER_METRICS_HEARTBEAT_MS = 30_000\n\nexport class StreamBrokerAddon extends BaseAddon<StreamBrokerConfig> {\n private brokerManager: StreamBrokerManager | null = null\n private metricsSnapshotTimer: ReturnType<typeof setInterval> | null = null\n /**\n * Snapshot-equality cache for the broker-metrics emit. Each\n * broker emits a stats snapshot every BROKER_METRICS_SNAPSHOT_-\n * INTERVAL_MS; for an idle broker (status=streaming, fps=0,\n * subscribers=0, bitrate frozen) the per-tick payload is\n * bytewise-identical and floods the bus to no end. We skip the\n * emit when the JSON-serialised payload matches the last one\n * and reset the heartbeat clock so a quiet broker still emits\n * every BROKER_METRICS_HEARTBEAT_MS.\n *\n * Keyed by broker.deviceId (the brokerId — `<deviceId>/<profile>`\n * string). Cleared when the broker manager is destroyed.\n */\n private readonly lastEmittedBrokerSnapshot = new Map<string, { json: string; emittedAt: number }>()\n\n constructor() {\n super({\n defaultPreBufferSec: 10,\n rtspPort: 8554,\n maxDecodeFps: 5,\n initialReconnectDelayMs: 1000,\n maxReconnectDelayMs: 30000,\n })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.brokerManager = new StreamBrokerManager(undefined, this.ctx.logger)\n this.brokerManager.setDefaultPreBufferSec(this.config.defaultPreBufferSec)\n this.brokerManager.setEventBus(this.ctx.eventBus)\n // Wire the tRPC api proxy so the broker can reach `decoder` (and\n // any future cross-cap consumer) via `localProviderLink` /\n // `brokerTransportLink` regardless of where the decoder addon\n // physically runs. Replaces the previous direct `CapabilitiesAccess`\n // lookup which only worked on the hub.\n this.brokerManager.setApiAccess(this.ctx.api)\n if (this.capabilities) {\n this.brokerManager.setCapabilitiesAccess(this.capabilities)\n }\n\n // Decoder placement resolver removed: the `decoder` cap is now a\n // singleton (phase 2g+). The capability registry resolves the one\n // active provider per node; no per-device decoder-node routing.\n const rtspProvider = this.brokerManager.getRtspRestreamProvider()\n\n // Load persisted RTSP state from addon settings store\n try {\n const addonStore = (await this.ctx.settings?.readAddonStore()) ?? {}\n const tokenData = asJsonObject(addonStore['rtspTokens'])\n if (tokenData) {\n const tokens = new Map<string, string>()\n for (const [k, v] of Object.entries(tokenData)) {\n if (typeof v === 'string') tokens.set(k, v)\n }\n this.brokerManager.loadPersistedTokens(tokens)\n this.ctx.logger.info('Loaded persisted RTSP tokens', { meta: { tokenCount: tokens.size } })\n }\n const enabledData = asJsonObject(addonStore['rtspEnabled'])\n if (enabledData) {\n const states = new Map<string, boolean>()\n for (const [k, v] of Object.entries(enabledData)) {\n states.set(k, Boolean(v))\n }\n rtspProvider.loadPersistedEnabled(states)\n this.ctx.logger.info('Loaded persisted RTSP enabled states', { meta: { stateCount: states.size } })\n }\n } catch { /* first boot — no persisted state yet */ }\n\n rtspProvider.setTokenPersister((tokens) => {\n const obj: Record<string, string> = {}\n for (const [k, v] of tokens) { obj[k] = v }\n this.ctx.settings?.writeAddonStore({ rtspTokens: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist RTSP tokens', { meta: { error: errMsg(err) } })\n })\n })\n\n rtspProvider.setEnabledPersister((states) => {\n const obj: Record<string, boolean> = {}\n for (const [k, v] of states) { obj[k] = v }\n this.ctx.settings?.writeAddonStore({ rtspEnabled: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist RTSP enabled states', { meta: { error: errMsg(err) } })\n })\n })\n\n // Device overrides (per-profile pre-buffer) persisted as numeric\n // device ids. JSON serialises numeric Map keys as strings, so on\n // read we reparse and drop anything that isn't a valid number.\n try {\n const addonStore = (await this.ctx.settings?.readAddonStore()) ?? {}\n const overrideData = asJsonObject(addonStore['deviceOverrides'])\n if (overrideData) {\n const overrides = new Map<number, {\n readonly preBufferSecOverride?: number\n readonly preBuffer?: Record<string, { enabled: boolean; seconds: number }>\n readonly streamingDebug?: boolean\n }>()\n for (const [rawKey, raw] of Object.entries(overrideData)) {\n const numericKey = Number(rawKey)\n if (!Number.isFinite(numericKey) || !Number.isInteger(numericKey)) continue\n const o = asJsonObject(raw)\n if (!o) continue\n const entry: Record<string, unknown> = {}\n if (typeof o['preBufferSecOverride'] === 'number') {\n entry['preBufferSecOverride'] = o['preBufferSecOverride']\n }\n if (typeof o['streamingDebug'] === 'boolean') {\n entry['streamingDebug'] = o['streamingDebug']\n }\n const pbRaw = asJsonObject(o['preBuffer'])\n if (pbRaw) {\n const preBuffer: Record<string, { enabled: boolean; seconds: number }> = {}\n for (const [profile, v] of Object.entries(pbRaw)) {\n const sv = asJsonObject(v)\n if (sv && typeof sv['seconds'] === 'number') {\n preBuffer[profile] = { enabled: sv['enabled'] !== false, seconds: sv['seconds'] }\n }\n }\n entry['preBuffer'] = preBuffer\n }\n overrides.set(numericKey, entry)\n }\n this.brokerManager.loadPersistedDeviceOverrides(overrides)\n this.ctx.logger.info('Loaded persisted device overrides', { meta: { overrideCount: overrides.size } })\n }\n } catch { /* first boot — no persisted state yet */ }\n\n this.brokerManager.setDeviceOverridePersister((overrides) => {\n const obj: Record<string, Record<string, unknown>> = {}\n for (const [deviceId, ov] of overrides) {\n const entry: Record<string, unknown> = {}\n if (ov.preBufferSecOverride !== undefined) entry['preBufferSecOverride'] = ov.preBufferSecOverride\n if (ov.streamingDebug !== undefined) entry['streamingDebug'] = ov.streamingDebug\n if (ov.preBuffer) entry['preBuffer'] = ov.preBuffer\n obj[`${deviceId}`] = entry\n }\n this.ctx.settings?.writeAddonStore({ deviceOverrides: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist device overrides', { meta: { error: errMsg(err) } })\n })\n })\n\n // Profile-map persistence: load on boot, persist on every mutation.\n try {\n const addonStore = (await this.ctx.settings?.readAddonStore()) ?? {}\n const raw = asJsonObject(addonStore['profileMap'])\n if (raw) {\n const loaded = new Map<number, PersistedAssignment>()\n for (const [rawKey, rawVal] of Object.entries(raw)) {\n const deviceId = Number(rawKey)\n if (!Number.isFinite(deviceId) || !Number.isInteger(deviceId)) continue\n const val = asJsonObject(rawVal)\n if (!val) continue\n const mapObj = asJsonObject(val['map']) ?? {}\n const map: Partial<Record<CamProfile, string>> = {}\n for (const profile of PROFILE_KEYS) {\n const v = mapObj[profile]\n if (typeof v === 'string' && v.length > 0) map[profile] = v\n }\n const auto = val['auto'] !== false\n loaded.set(deviceId, { map, auto })\n }\n this.brokerManager.loadPersistedProfileMap(loaded)\n this.ctx.logger.info('Loaded persisted profile map', { meta: { deviceCount: loaded.size } })\n }\n } catch { /* first boot — no persisted state yet */ }\n\n const profileMapPersister: ProfileMapPersister = (assignments) => {\n const obj: Record<string, PersistedAssignment> = {}\n for (const [deviceId, assignment] of assignments) {\n obj[`${deviceId}`] = { map: { ...assignment.map }, auto: assignment.auto }\n }\n this.ctx.settings?.writeAddonStore({ profileMap: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist profile map', { meta: { error: errMsg(err) } })\n })\n }\n this.brokerManager.setProfileMapPersister(profileMapPersister)\n\n // Start RTSP restream server\n await this.brokerManager.startRtspServer(this.config.rtspPort)\n\n // Per-agent hwaccel override changed — force-rotate any active shared\n // decoder session whose owning node matches.\n this.subscribe(\n { category: EventCategory.PipelineAgentHwaccelChanged },\n (event) => {\n const { agentNodeId, reason } = event.data\n if (typeof agentNodeId !== 'string' || agentNodeId.length === 0) return\n const manager = this.brokerManager\n if (!manager) return\n const reasonLabel = typeof reason === 'string' ? reason : 'hwaccel-changed'\n void manager.rotateDecodersOnNode(agentNodeId, reasonLabel).then((rotated) => {\n if (rotated > 0) {\n this.ctx.logger.info('decoders rotated for hwaccel change', {\n tags: { nodeId: agentNodeId },\n meta: { rotated, reason: reasonLabel },\n })\n }\n }).catch((err: unknown) => {\n this.ctx.logger.warn('hwaccel-driven decoder rotation failed', {\n tags: { nodeId: agentNodeId },\n meta: { error: errMsg(err) },\n })\n })\n },\n )\n\n // Decoder down → rotate active shared sessions to a healthy provider.\n this.watchCapability(['decoder'], {\n onDown: (nodeId, capName) => {\n if (capName !== 'decoder') return\n const manager = this.brokerManager\n if (!manager) return\n const reasonLabel = `decoder-down:${nodeId}`\n void manager.rotateDecodersOnNode(nodeId, reasonLabel).then((rotated) => {\n if (rotated > 0) {\n this.ctx.logger.info('decoders rotated after provider went down', {\n tags: { nodeId },\n meta: { rotated, reason: reasonLabel },\n })\n }\n }).catch((err: unknown) => {\n this.ctx.logger.warn('decoder-down rotation failed', {\n tags: { nodeId },\n meta: { error: errMsg(err) },\n })\n })\n },\n })\n\n const cameraStreamsProvider = new CameraStreamsProvider(this.brokerManager)\n\n // Broker-integrated WebRTC server — subscribes directly to each\n // StreamBroker's onEncodedData() callback, zero ffmpeg re-encoding.\n //\n // `getIceServers` is the single per-session entry-point into the\n // `turn-orchestrator` cap (singleton). The orchestrator aggregates\n // ICE servers from every enabled `turn-provider` (cloudflare-turn,\n // future coturn / Twilio, …) — there's NO per-device round-trip;\n // the orchestrator caches at the provider layer (cloudflare-turn\n // refreshes ICE credentials in the background ~12 h before the\n // 24 h TTL), so the per-session call costs a single cap dispatch +\n // an in-process memory read. Without TURN configured the call\n // returns `[]` and WebRTC falls back to host/srflx candidates.\n const turnApi = this.ctx.api as unknown as {\n turnOrchestrator?: {\n getAllServers: { query: (input: object) => Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]> }\n }\n }\n const webrtcServer = new BrokerWebrtcServer({\n logger: this.ctx.logger.child('webrtc'),\n getIceServers: async () => {\n if (!turnApi.turnOrchestrator) return []\n try {\n const servers = await turnApi.turnOrchestrator.getAllServers.query({})\n // The cap returns readonly entries; normalise to mutable shape\n // the werift session expects + drop entries with empty url lists.\n const out: { urls: string | string[]; username?: string; credential?: string }[] = []\n for (const s of servers) {\n if (!s.urls || (Array.isArray(s.urls) && s.urls.length === 0)) continue\n out.push({\n urls: Array.isArray(s.urls) ? [...s.urls] : s.urls,\n ...(s.username ? { username: s.username } : {}),\n ...(s.credential ? { credential: s.credential } : {}),\n })\n }\n return out\n } catch (err) {\n // Non-fatal — log + return empty so the session still gets\n // built (LAN-only / public-IP scenarios don't need TURN).\n this.ctx.logger.warn('turnOrchestrator.getAllServers failed — session will start without TURN', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return []\n }\n },\n })\n // Wire the server into the broker manager so it registers/unregisters\n // brokers as camera profiles come and go.\n this.brokerManager.setWebrtcServer(webrtcServer)\n\n const webrtcSessionProvider = new WebrtcSessionProvider(webrtcServer, this.brokerManager)\n\n // Per-broker metrics snapshot emission (~1 Hz). UI dashboards\n // subscribe to `stream-broker.metrics-snapshot` and drive their\n // overlays from event payloads instead of polling the cap.\n this.metricsSnapshotTimer = setInterval(\n () => this.emitBrokerMetricsSnapshot(),\n BROKER_METRICS_SNAPSHOT_INTERVAL_MS,\n )\n\n // Widget bundle contribution — declares the per-camera\n // stream-broker panel as an addon-shipped widget. Admin-ui's\n // <WidgetRegistryProvider> registers the Module Federation remote\n // at /api/addon-widgets/stream-broker/remoteEntry.js?v=<mtime>,\n // then loads the exposed `./widgets` module; the form-builder /\n // dashboard / device-tab dispatcher mounts each widget via\n // <WidgetSlot widgetId=\"stream-broker/stream-broker-panel\" deviceId=…/>.\n const widgetsProvider: IAddonWidgetsSourceProvider = {\n listWidgets: async () => [\n {\n stableId: 'stream-broker-panel',\n label: 'Stream Brokers',\n description: 'Per-camera stream broker panel with adaptive controls.',\n icon: 'radio',\n remoteName: 'addon_stream_broker_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'lg' as const,\n allowedSizes: ['md', 'lg', 'xl'] as const,\n defaultColumns: 8,\n defaultRows: 2,\n },\n ],\n }\n\n this.ctx.logger.info('Stream broker manager initialized')\n const registrations: ProviderRegistration[] = [\n { capability: streamBrokerCapability, provider: this.brokerManager },\n {\n capability: cameraStreamsCapability,\n provider: cameraStreamsProvider,\n kind: 'wrapper',\n defaultActive: true,\n },\n {\n capability: webrtcSessionCapability,\n provider: webrtcSessionProvider,\n kind: 'wrapper',\n defaultActive: true,\n },\n { capability: addonWidgetsSourceCapability, provider: widgetsProvider },\n ]\n return registrations\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.metricsSnapshotTimer) {\n clearInterval(this.metricsSnapshotTimer)\n this.metricsSnapshotTimer = null\n }\n await this.brokerManager?.destroyAll()\n this.brokerManager = null\n }\n\n /**\n * Emit one `stream-broker.metrics-snapshot` event per active broker.\n * BrokerStats already aggregates everything UI consumers need\n * (status, fps, bitrate, codec, subscriber count). Skipped when no\n * brokers are registered so quiet dev runs don't emit noise.\n */\n private emitBrokerMetricsSnapshot(): void {\n const manager = this.brokerManager\n const eventBus = this.ctx.eventBus\n if (!manager || !eventBus) return\n const brokers = manager.getBrokerInstances()\n if (brokers.length === 0) return\n const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id\n const nodeId = rawNodeId.includes('/') ? rawNodeId.split('/')[0]! : rawNodeId\n const timestamp = Date.now()\n const seenBrokerIds = new Set<string>()\n for (const broker of brokers) {\n const stats = broker.getStats()\n // brokerId encoded as `<deviceId>/<profile>` (StreamBroker.deviceId\n // field is overloaded to mean brokerId — see broker constructor).\n const brokerId = broker.deviceId\n seenBrokerIds.add(brokerId)\n const slash = brokerId.indexOf('/')\n const deviceId = slash > 0 ? Number(brokerId.slice(0, slash)) : Number(brokerId)\n const profile = slash > 0 ? brokerId.slice(slash + 1) : ''\n if (!Number.isFinite(deviceId)) continue\n // Snapshot-equality skip — same pattern as pipeline-runner's\n // emitMetricsSnapshot. Idle broker → bytewise-identical payload\n // → no emit. Quiet brokers still emit every\n // BROKER_METRICS_HEARTBEAT_MS so the UI's \"broker reachable\"\n // chip doesn't go stale.\n //\n // Strip monotonic counters before equality — `uptimeMs` ticks\n // every second on every broker, `totalBytes` / `packetCount`\n // grow continuously while streaming, so the raw JSON of stats\n // always differs and the defer never fires. We only want\n // observable-state changes (status, fps, subscribers, codec,\n // bitrate). The full payload still goes on the bus when an\n // emit fires; the strip only affects the dedup key.\n const json = stableSnapshotKey(stats, ['uptimeMs', 'totalBytes', 'packetCount'])\n const prev = this.lastEmittedBrokerSnapshot.get(brokerId)\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= BROKER_METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) continue\n this.lastEmittedBrokerSnapshot.set(brokerId, { json, emittedAt: timestamp })\n eventBus.emit(createEvent(\n EventCategory.StreamBrokerMetricsSnapshot,\n { type: 'device', id: deviceId, nodeId },\n { brokerId, deviceId, profile, nodeId, stats, timestamp },\n ))\n }\n // Drop cache entries for brokers that no longer exist (camera\n // detached, stream profile unassigned). Cheap because brokers\n // is the canonical truth and seenBrokerIds is a Set.\n if (this.lastEmittedBrokerSnapshot.size > seenBrokerIds.size) {\n for (const cachedId of this.lastEmittedBrokerSnapshot.keys()) {\n if (!seenBrokerIds.has(cachedId)) this.lastEmittedBrokerSnapshot.delete(cachedId)\n }\n }\n }\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'stream-broker-settings',\n title: 'Stream Broker',\n columns: 3,\n fields: [\n {\n type: 'number',\n key: 'defaultPreBufferSec',\n label: 'Pre-buffer Duration',\n description: 'Seconds of video to keep buffered before a detection event',\n min: 0,\n max: 30,\n step: 1,\n default: 10,\n unit: 's',\n },\n {\n type: 'number',\n key: 'rtspPort',\n label: 'RTSP Restream Port',\n description: 'Port for the RTSP restream server. Requires restart.',\n min: 1024,\n max: 65535,\n step: 1,\n default: 8554,\n },\n {\n type: 'number',\n key: 'maxDecodeFps',\n label: 'Max Decode FPS',\n description: 'Maximum frames per second for decoded stream subscribers',\n min: 1,\n max: 30,\n step: 1,\n default: 5,\n },\n ],\n },\n {\n id: 'stream-broker-reconnect',\n title: 'Reconnection',\n description: 'Controls how quickly the broker retries a failed RTSP connection (exponential backoff).',\n columns: 2,\n fields: [\n {\n type: 'number',\n key: 'initialReconnectDelayMs',\n label: 'Initial Reconnect Delay',\n description: 'First retry delay after a stream disconnect',\n min: 100,\n max: 10000,\n step: 100,\n default: 1000,\n unit: 'ms',\n },\n {\n type: 'number',\n key: 'maxReconnectDelayMs',\n label: 'Max Reconnect Delay',\n description: 'Upper bound for exponential backoff between retries',\n min: 1000,\n max: 120000,\n step: 1000,\n default: 30000,\n unit: 'ms',\n },\n ],\n },\n ],\n })\n }\n}\n\nexport default StreamBrokerAddon\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type { IDecoderSession, DecoderSessionConfig, DecoderStats, EncodedPacket, DecodedFrame, Unsubscribe, IScopedLogger } from '@camstack/types'\nimport { FrameDropper } from './frame-dropper'\n\n/** Minimal no-op logger for default parameter */\nconst noopLogger: IScopedLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n child() { return noopLogger },\n withTags() { return noopLogger },\n}\n\nconst SOI = Buffer.from([0xff, 0xd8])\nconst EOI = Buffer.from([0xff, 0xd9])\n\nfunction codecToInputFormat(codec: string): string {\n switch (codec) {\n case 'h265':\n case 'hevc':\n return 'hevc'\n case 'mjpeg':\n return 'mjpeg'\n case 'h264':\n default:\n return 'h264'\n }\n}\n\nfunction buildFfmpegArgs(config: DecoderSessionConfig): string[] {\n const inputFormat = codecToInputFormat(config.codec)\n const args = [\n '-hide_banner', '-loglevel', 'error',\n // Low-latency flags: skip stream probing, disable buffering, flush immediately\n '-fflags', '+nobuffer+flush_packets',\n '-flags', 'low_delay',\n '-probesize', '32',\n '-analyzeduration', '0',\n '-f', inputFormat, '-i', 'pipe:0',\n ]\n\n if (config.scale > 1) {\n args.push('-vf', `scale=iw/${config.scale}:ih/${config.scale}`)\n }\n\n args.push('-f', 'image2pipe', '-vcodec', 'mjpeg', '-q:v', '3', '-threads', '1', 'pipe:1')\n return args\n}\n\nexport class FfmpegDecoderSession implements IDecoderSession {\n private config: DecoderSessionConfig\n private frameDropper: FrameDropper\n private process: ChildProcess | null = null\n private frameCallbacks = new Set<(frame: DecodedFrame) => void>()\n private outputBuffer = Buffer.alloc(0)\n private destroyed = false\n private readonly logger: IScopedLogger\n\n // Cached dimensions — won't change between frames from the same FFmpeg session\n private cachedWidth = 0\n private cachedHeight = 0\n\n // Stats tracking\n private inputPackets = 0\n private outputFrames = 0\n private droppedFrames = 0\n private totalDecodeTimeMs = 0\n private decodeCount = 0\n private startTime = Date.now()\n\n constructor(config: DecoderSessionConfig, logger: IScopedLogger = noopLogger) {\n this.config = { ...config }\n this.logger = logger\n this.frameDropper = new FrameDropper(config.maxFps)\n this.spawnFfmpeg()\n }\n\n private spawnFfmpeg(): void {\n if (this.destroyed) return\n\n this.killFfmpeg()\n this.outputBuffer = Buffer.alloc(0)\n this.cachedWidth = 0\n this.cachedHeight = 0\n\n const args = buildFfmpegArgs(this.config)\n this.process = spawn('ffmpeg', args)\n\n // Swallow EPIPE errors on stdin during shutdown\n this.process.stdin?.on('error', () => {})\n\n this.process.stdout?.on('data', (chunk: Buffer) => {\n this.handleOutputData(chunk)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const line = data.toString().trim()\n if (line) this.logger.debug('ffmpeg stderr', { meta: { line } })\n })\n\n this.process.on('error', (err: Error) => {\n this.logger.error('FFmpeg decoder spawn error', { meta: { error: err.message } })\n })\n\n this.process.on('close', (_code, _signal) => {\n if (!this.destroyed) {\n this.process = null\n }\n })\n }\n\n private killFfmpeg(): void {\n if (this.process) {\n try {\n this.process.kill('SIGKILL')\n } catch {\n // already dead\n }\n this.process = null\n }\n }\n\n private handleOutputData(chunk: Buffer): void {\n this.outputBuffer = Buffer.concat([this.outputBuffer, chunk])\n\n // Extract complete JPEG frames from the buffer\n let searchFrom = 0\n while (true) {\n const soiIndex = this.outputBuffer.indexOf(SOI, searchFrom)\n if (soiIndex === -1) break\n\n const eoiIndex = this.outputBuffer.indexOf(EOI, soiIndex + 2)\n if (eoiIndex === -1) break\n\n const frameEnd = eoiIndex + 2\n const jpegData = this.outputBuffer.subarray(soiIndex, frameEnd)\n\n // Advance past the consumed frame\n searchFrom = frameEnd\n\n this.emitFrame(Buffer.from(jpegData))\n }\n\n // Keep only unprocessed tail\n if (searchFrom > 0) {\n this.outputBuffer = Buffer.from(this.outputBuffer.subarray(searchFrom))\n }\n }\n\n private emitFrame(data: Buffer): void {\n const decodeStart = Date.now()\n\n if (!this.frameDropper.shouldKeep()) {\n this.droppedFrames++\n return\n }\n\n const decodeTime = Date.now() - decodeStart\n this.totalDecodeTimeMs += decodeTime\n this.decodeCount++\n this.outputFrames++\n\n // Only parse dimensions on first frame or after FFmpeg restart (cached=0)\n if (this.cachedWidth === 0) {\n const dims = parseJpegDimensions(data)\n this.cachedWidth = dims.width\n this.cachedHeight = dims.height\n }\n\n const frame: DecodedFrame = {\n data,\n width: this.cachedWidth,\n height: this.cachedHeight,\n format: 'jpeg',\n timestamp: Date.now(),\n }\n\n for (const cb of this.frameCallbacks) {\n cb(frame)\n }\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (this.destroyed || !this.process?.stdin) return\n this.inputPackets++\n try {\n this.process.stdin.write(packet.data)\n } catch {\n // stdin may be closed if ffmpeg crashed\n }\n }\n\n onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe {\n this.frameCallbacks.add(callback)\n return () => {\n this.frameCallbacks.delete(callback)\n }\n }\n\n updateConfig(update: Partial<DecoderSessionConfig>): void {\n const needsRestart =\n (update.scale !== undefined && update.scale !== this.config.scale) ||\n (update.outputFormat !== undefined && update.outputFormat !== this.config.outputFormat) ||\n (update.codec !== undefined && update.codec !== this.config.codec)\n\n this.config = { ...this.config, ...update }\n\n if (update.maxFps !== undefined) {\n this.frameDropper.setMaxFps(update.maxFps)\n }\n\n if (needsRestart) {\n this.spawnFfmpeg()\n }\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n this.killFfmpeg()\n this.frameCallbacks.clear()\n }\n\n getStats(): DecoderStats {\n const uptimeSec = Math.max((Date.now() - this.startTime) / 1000, 1)\n return {\n inputFps: this.inputPackets / uptimeSec,\n outputFps: this.outputFrames / uptimeSec,\n avgDecodeTimeMs: this.decodeCount > 0 ? this.totalDecodeTimeMs / this.decodeCount : 0,\n droppedFrames: this.droppedFrames,\n }\n }\n}\n\n/**\n * Parse JPEG SOF0/SOF2 marker to extract image dimensions.\n * Scans for 0xFF 0xC0 (baseline) or 0xFF 0xC2 (progressive) markers.\n * Returns {width: 0, height: 0} if not found.\n */\nfunction parseJpegDimensions(data: Buffer): { width: number; height: number } {\n for (let i = 0; i < data.length - 8; i++) {\n if (data[i] === 0xFF && (data[i + 1] === 0xC0 || data[i + 1] === 0xC2)) {\n const height = (data[i + 5]! << 8) | data[i + 6]!\n const width = (data[i + 7]! << 8) | data[i + 8]!\n return { width, height }\n }\n }\n return { width: 0, height: 0 }\n}\n","import type { IDecoderProvider, DecoderSessionConfig, IDecoderSession } from '@camstack/types'\nimport { FfmpegDecoderSession } from './ffmpeg-decoder-session'\n\nconst SUPPORTED_CODECS = new Set(['h264', 'h265', 'hevc', 'mjpeg'])\n\nexport class FfmpegDecoderProvider implements IDecoderProvider {\n readonly id = 'ffmpeg'\n readonly name = 'FFmpeg Decoder'\n /** Software decoder — used as fallback when hardware decoders are unavailable. */\n readonly priority = 50\n\n async supportsCodec(input: { codec: string }): Promise<boolean> {\n return SUPPORTED_CODECS.has(input.codec)\n }\n\n async createSession(config: DecoderSessionConfig): Promise<IDecoderSession> {\n return new FfmpegDecoderSession(config)\n }\n}\n","/**\n * Generic length-prefix codec.\n *\n * Wire format: [4 bytes length (BE uint32)] [payload bytes]\n *\n * The length field is the size of the payload only (not including the\n * 4-byte prefix itself). Maximum payload size: 2^32 - 1 bytes. Single\n * frames larger than this will throw at encode time.\n *\n * Decode is stateful — callers feed raw bytes via `push()` and receive\n * 0+ complete payloads. Bytes straddling frame boundaries are buffered\n * internally until the next `push()` completes the frame.\n *\n * This codec is transport-agnostic. It's used on top of any stream-\n * oriented transport (TCP, UDS, in-process). It does NOT work for\n * datagram transports (UDP) — those need a different framing strategy\n * because the transport itself provides message boundaries.\n */\nimport type { FrameCodec, FrameDecoder } from '../types.js'\n\nconst LENGTH_PREFIX_BYTES = 4\n/** Hard cap — 2^31-1 to stay safely within uint32 BE range and V8 Buffer limits. */\nconst MAX_PAYLOAD_BYTES = 0x7fffffff\n\nexport interface LengthPrefixCodecOptions<TMessage> {\n /** Encode a message to its payload bytes (without any length prefix). */\n readonly serialize: (msg: TMessage) => Uint8Array\n /** Decode a single complete payload back to a message. */\n readonly deserialize: (bytes: Uint8Array) => TMessage\n /** Codec identifier for diagnostics/metrics. */\n readonly kind?: string\n}\n\nexport class LengthPrefixCodec<TMessage> implements FrameCodec<TMessage> {\n readonly kind: string\n private readonly serialize: (msg: TMessage) => Uint8Array\n private readonly deserialize: (bytes: Uint8Array) => TMessage\n\n constructor(options: LengthPrefixCodecOptions<TMessage>) {\n this.serialize = options.serialize\n this.deserialize = options.deserialize\n this.kind = options.kind ?? 'length-prefix'\n }\n\n encode(msg: TMessage): Uint8Array {\n const payload = this.serialize(msg)\n if (payload.length > MAX_PAYLOAD_BYTES) {\n throw new Error(`LengthPrefixCodec.encode: payload too large (${payload.length} bytes, max ${MAX_PAYLOAD_BYTES})`)\n }\n const buf = Buffer.allocUnsafe(LENGTH_PREFIX_BYTES + payload.length)\n buf.writeUInt32BE(payload.length, 0)\n buf.set(payload, LENGTH_PREFIX_BYTES)\n return buf\n }\n\n createDecoder(): FrameDecoder<TMessage> {\n return new LengthPrefixDecoder<TMessage>(this.deserialize)\n }\n}\n\nclass LengthPrefixDecoder<TMessage> implements FrameDecoder<TMessage> {\n /** Accumulates bytes until at least one full frame is available. */\n private buffer = Buffer.alloc(0)\n private readonly deserialize: (bytes: Uint8Array) => TMessage\n\n constructor(deserialize: (bytes: Uint8Array) => TMessage) {\n this.deserialize = deserialize\n }\n\n push(chunk: Uint8Array): readonly TMessage[] {\n // Concat the new chunk onto the pending buffer. Allocation-light for the\n // common case where chunk already contains one or more complete frames.\n this.buffer = this.buffer.length === 0\n ? Buffer.from(chunk)\n : Buffer.concat([this.buffer, Buffer.from(chunk)])\n\n const out: TMessage[] = []\n let offset = 0\n\n while (true) {\n // Need at least the length prefix.\n if (this.buffer.length - offset < LENGTH_PREFIX_BYTES) break\n\n const payloadLen = this.buffer.readUInt32BE(offset)\n if (payloadLen > MAX_PAYLOAD_BYTES) {\n // Corrupted stream — reset to avoid cascading failures.\n this.buffer = Buffer.alloc(0)\n throw new Error(`LengthPrefixDecoder: declared payload length ${payloadLen} exceeds max ${MAX_PAYLOAD_BYTES}`)\n }\n\n const totalFrameBytes = LENGTH_PREFIX_BYTES + payloadLen\n if (this.buffer.length - offset < totalFrameBytes) {\n // Frame is incomplete — wait for more bytes.\n break\n }\n\n const payloadStart = offset + LENGTH_PREFIX_BYTES\n const payloadEnd = payloadStart + payloadLen\n const payload = this.buffer.subarray(payloadStart, payloadEnd)\n out.push(this.deserialize(payload))\n offset = payloadEnd\n }\n\n // Trim the consumed prefix to free memory once per call (vs. per-frame).\n if (offset > 0) {\n this.buffer = this.buffer.subarray(offset)\n }\n\n return out\n }\n\n reset(): void {\n this.buffer = Buffer.alloc(0)\n }\n}\n","/**\n * Codec specialized for `DecodedFrame` values.\n *\n * Wraps a fixed-size binary header + raw payload inside a\n * `LengthPrefixCodec` so the transport layer doesn't need to know the\n * shape. Using a bespoke binary layout (instead of JSON via superjson)\n * keeps per-frame overhead low — critical on the hot path where a broker\n * fans out a frame to many consumers at 30+ fps.\n *\n * Wire format (after the length prefix added by LengthPrefixCodec):\n *\n * Offset Size Field\n * ────── ──── ─────────────────────────────────────────────────\n * 0 8 timestamp (BE bigint64, ms epoch)\n * 8 4 width (BE uint32)\n * 12 4 height (BE uint32)\n * 16 1 format enum (0=jpeg, 1=rgb, 2=yuv420, 3=gray, 4=bgr)\n * 17 3 reserved (zero-padded for 4-byte alignment)\n * 20 N frame data\n *\n * Fixed header: 20 bytes. All subsequent bytes are the raw frame payload.\n * The outer `LengthPrefixCodec` adds the 4-byte length prefix, so the\n * total on-wire size per frame is `4 + 20 + data.length` = 24 + data.\n *\n * Enum values are append-only: existing values must never be reassigned\n * — a worker built before a new format is added would still send the\n * old code over the wire.\n */\nimport type { DecodedFrame } from '@camstack/types'\nimport { LengthPrefixCodec } from './length-prefix-codec.js'\n\nconst HEADER_BYTES = 20\nconst FORMAT_MAP = {\n jpeg: 0,\n rgb: 1,\n yuv420: 2,\n gray: 3,\n bgr: 4,\n} as const satisfies Record<DecodedFrame['format'], number>\n\ntype FormatNumber = typeof FORMAT_MAP[keyof typeof FORMAT_MAP]\n\nconst FORMAT_REVERSE: Readonly<Record<FormatNumber, DecodedFrame['format']>> = {\n 0: 'jpeg',\n 1: 'rgb',\n 2: 'yuv420',\n 3: 'gray',\n 4: 'bgr',\n}\n\nfunction isKnownFormatNumber(n: number): n is FormatNumber {\n return n === 0 || n === 1 || n === 2 || n === 3 || n === 4\n}\n\nexport function encodeDecodedFrame(frame: DecodedFrame): Uint8Array {\n const payload = Buffer.allocUnsafe(HEADER_BYTES + frame.data.length)\n payload.writeBigInt64BE(BigInt(frame.timestamp), 0)\n payload.writeUInt32BE(frame.width, 8)\n payload.writeUInt32BE(frame.height, 12)\n payload.writeUInt8(FORMAT_MAP[frame.format], 16)\n // Zero-fill the 3 reserved bytes (17..19)\n payload.writeUInt8(0, 17)\n payload.writeUInt8(0, 18)\n payload.writeUInt8(0, 19)\n payload.set(frame.data, HEADER_BYTES)\n return payload\n}\n\nexport function decodeDecodedFrame(bytes: Uint8Array): DecodedFrame {\n if (bytes.length < HEADER_BYTES) {\n throw new Error(`decodeDecodedFrame: payload too short (${bytes.length} < ${HEADER_BYTES})`)\n }\n const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n const timestampBig = buf.readBigInt64BE(0)\n // Safe because timestamps in ms fit in Number.MAX_SAFE_INTEGER until year 287396.\n const timestamp = Number(timestampBig)\n const width = buf.readUInt32BE(8)\n const height = buf.readUInt32BE(12)\n const formatNum = buf.readUInt8(16)\n if (!isKnownFormatNumber(formatNum)) {\n throw new Error(`decodeDecodedFrame: unknown format enum ${formatNum}`)\n }\n const format = FORMAT_REVERSE[formatNum]\n const data = Buffer.from(buf.subarray(HEADER_BYTES))\n return { data, width, height, format, timestamp }\n}\n\n/**\n * Pre-built codec instance for `DecodedFrame`.\n * Composes `LengthPrefixCodec` with the DecodedFrame serializers above.\n */\nexport const decodedFrameCodec = new LengthPrefixCodec<DecodedFrame>({\n kind: 'decoded-frame',\n serialize: encodeDecodedFrame,\n deserialize: decodeDecodedFrame,\n})\n","/**\n * TCP frame transport.\n *\n * Wire:\n * - URL scheme: `tcp://host:port/`\n * - Producer calls `listen({host, port})` → server bound to a TCP port\n * - Consumer calls `connect(\"tcp://host:port/\")` → opens a socket\n * - Bidirectional byte stream via `net.Socket`\n *\n * The transport is framing-agnostic. The codec stacked on top (usually\n * `LengthPrefixCodec`) handles message boundaries.\n *\n * Backpressure: `net.Socket.write()` returns false when the internal\n * buffer is full. We expose `bufferedBytes` (from `socket.writableLength`)\n * so the FrameServer's drop policy can react. We do NOT wait for 'drain'\n * inside `send()` — the caller (FrameServer) decides whether to drop.\n *\n * Max outbound buffer is set via `socket.setDefaultEncoding`-style config;\n * Node's default high-water mark is 16 KB which is too small for video\n * frames. We bump the socket's writable high-water mark at connection time.\n */\nimport net from 'node:net'\nimport { randomUUID } from 'node:crypto'\nimport type {\n FrameTransport,\n TransportEndpoint,\n TransportConnection,\n TransportListenOptions,\n TransportConnectOptions,\n} from '../types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nconst URL_SCHEME = 'tcp://'\n\nexport class TcpTransport implements FrameTransport {\n readonly kind = 'tcp' as const\n\n async listen(options?: TransportListenOptions): Promise<TransportEndpoint> {\n const host = options?.host ?? '127.0.0.1'\n const port = options?.port ?? 0\n return new Promise<TransportEndpoint>((resolve, reject) => {\n const connectionHandlers = new Set<(conn: TransportConnection) => void>()\n const connections = new Set<TcpConnection>()\n\n const server = net.createServer((socket) => {\n socket.setNoDelay(true)\n const conn = new TcpConnection(socket, () => {\n connections.delete(conn)\n })\n connections.add(conn)\n for (const handler of connectionHandlers) handler(conn)\n })\n\n server.once('error', (err) => reject(err))\n\n server.listen(port, host, () => {\n server.removeAllListeners('error')\n const address = server.address()\n if (address === null || typeof address === 'string') {\n server.close()\n reject(new Error('TcpTransport: server.address() returned unexpected value'))\n return\n }\n // Use the actual bound address/port (host may be 0.0.0.0, port may be 0 → OS assigned).\n const boundHost = address.address === '::' ? '0.0.0.0' : address.address\n const url = `${URL_SCHEME}${boundHost}:${address.port}/`\n const namespace = options?.namespace\n const finalUrl = namespace !== undefined ? `${url}${namespace}` : url\n\n const endpoint: TransportEndpoint = {\n kind: 'tcp',\n url: finalUrl,\n get activeConnections() { return connections.size },\n onConnection(handler) {\n connectionHandlers.add(handler)\n return () => connectionHandlers.delete(handler)\n },\n async close() {\n for (const conn of [...connections]) {\n conn.close('endpoint-closed')\n }\n await new Promise<void>((res) => {\n server.close(() => res())\n })\n },\n }\n resolve(endpoint)\n })\n })\n }\n\n async connect(url: string, options?: TransportConnectOptions): Promise<TransportConnection> {\n const parsed = parseTcpUrl(url)\n if (!parsed) {\n throw new Error(`TcpTransport: invalid URL \"${url}\"`)\n }\n\n const timeoutMs = options?.timeoutMs ?? 10_000\n\n return new Promise<TransportConnection>((resolve, reject) => {\n const socket = new net.Socket()\n socket.setNoDelay(true)\n\n let settled = false\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n socket.destroy()\n reject(new Error(`TcpTransport: connect timeout after ${timeoutMs}ms (${url})`))\n }, timeoutMs)\n\n socket.once('error', (err) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(err)\n })\n\n socket.once('connect', () => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n socket.removeAllListeners('error')\n resolve(new TcpConnection(socket, () => { /* client-side dispose noop */ }))\n })\n\n socket.connect(parsed.port, parsed.host)\n })\n }\n}\n\nfunction parseTcpUrl(url: string): { host: string; port: number } | null {\n if (!url.startsWith(URL_SCHEME)) return null\n const rest = url.slice(URL_SCHEME.length)\n // Strip trailing path/namespace — only host:port matters for routing.\n const authority = rest.split('/')[0] ?? ''\n const lastColon = authority.lastIndexOf(':')\n if (lastColon === -1) return null\n const host = authority.slice(0, lastColon)\n const port = Number(authority.slice(lastColon + 1))\n if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) return null\n return { host, port }\n}\n\nclass TcpConnection implements TransportConnection {\n readonly id = randomUUID()\n readonly kind = 'tcp' as const\n readonly remoteAddress: string\n\n private readonly dataHandlers = new Set<(chunk: Uint8Array) => void>()\n private readonly closeHandlers = new Set<(reason?: string) => void>()\n private closed = false\n\n constructor(\n private readonly socket: net.Socket,\n private readonly onDispose: () => void,\n ) {\n this.remoteAddress = socket.remoteAddress !== undefined && socket.remotePort !== undefined\n ? `${socket.remoteAddress}:${socket.remotePort}`\n : 'unknown'\n\n socket.on('data', (chunk: Buffer) => {\n if (this.closed) return\n for (const handler of this.dataHandlers) handler(chunk)\n })\n\n const emitClose = (reason?: string) => {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.onDispose()\n }\n socket.on('close', () => emitClose('socket-closed'))\n socket.on('error', (err) => emitClose(err.message))\n socket.on('end', () => emitClose('peer-ended'))\n }\n\n send(chunk: Uint8Array): void {\n if (this.closed) return\n // We ignore the boolean returned by write() — the FrameServer's drop\n // policy uses `bufferedBytes` to decide when to back off.\n this.socket.write(chunk)\n }\n\n onData(handler: (chunk: Uint8Array) => void): Unsubscribe {\n this.dataHandlers.add(handler)\n return () => this.dataHandlers.delete(handler)\n }\n\n onClose(handler: (reason?: string) => void): Unsubscribe {\n this.closeHandlers.add(handler)\n return () => this.closeHandlers.delete(handler)\n }\n\n close(reason?: string): void {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.socket.destroy()\n this.onDispose()\n }\n\n get bufferedBytes(): number {\n return this.socket.writableLength\n }\n}\n","/**\n * Unix Domain Socket frame transport.\n *\n * Same-host IPC with lower overhead than TCP loopback (no TCP/IP stack\n * traversal, no three-way handshake). Best choice when producer and\n * consumer are on the same machine — roughly 2-3× the throughput of TCP\n * loopback for large binary payloads like decoded frames.\n *\n * Wire:\n * - URL scheme: `unix:///absolute/path/to/socket.sock`\n * - Producer calls `listen({socketPath})` → creates the socket file\n * - Consumer calls `connect(\"unix:///.../socket.sock\")` → opens AF_UNIX\n * - Bidirectional stream via `net.Socket` (Node treats AF_UNIX as a\n * regular socket behind the same API)\n *\n * The socket file is removed on `close()` and at server startup if stale.\n * Windows note: Node supports AF_UNIX on Windows 10+ but named pipes may\n * be preferable. This transport uses `net.createServer` which handles\n * AF_UNIX natively on all platforms Node targets.\n */\nimport net from 'node:net'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport * as os from 'node:os'\nimport { randomUUID } from 'node:crypto'\nimport type {\n FrameTransport,\n TransportEndpoint,\n TransportConnection,\n TransportListenOptions,\n TransportConnectOptions,\n} from '../types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nconst URL_SCHEME = 'unix://'\n\nexport class UdsTransport implements FrameTransport {\n readonly kind = 'uds' as const\n\n async listen(options?: TransportListenOptions): Promise<TransportEndpoint> {\n const socketPath = options?.socketPath ?? path.join(os.tmpdir(), `camstack-frame-${randomUUID()}.sock`)\n\n // Remove stale socket file if it exists (common after crash).\n try {\n fs.unlinkSync(socketPath)\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code !== 'ENOENT') throw err\n }\n\n return new Promise<TransportEndpoint>((resolve, reject) => {\n const connectionHandlers = new Set<(conn: TransportConnection) => void>()\n const connections = new Set<UdsConnection>()\n\n const server = net.createServer((socket) => {\n const conn = new UdsConnection(socket, () => {\n connections.delete(conn)\n })\n connections.add(conn)\n for (const handler of connectionHandlers) handler(conn)\n })\n\n server.once('error', (err) => reject(err))\n\n server.listen(socketPath, () => {\n server.removeAllListeners('error')\n const url = `${URL_SCHEME}${socketPath}`\n\n const endpoint: TransportEndpoint = {\n kind: 'uds',\n url,\n get activeConnections() { return connections.size },\n onConnection(handler) {\n connectionHandlers.add(handler)\n return () => connectionHandlers.delete(handler)\n },\n async close() {\n for (const conn of [...connections]) {\n conn.close('endpoint-closed')\n }\n await new Promise<void>((res) => {\n server.close(() => res())\n })\n try {\n fs.unlinkSync(socketPath)\n } catch {\n // File may already be gone — ignore.\n }\n },\n }\n resolve(endpoint)\n })\n })\n }\n\n async connect(url: string, options?: TransportConnectOptions): Promise<TransportConnection> {\n const socketPath = parseUdsUrl(url)\n if (socketPath === null) {\n throw new Error(`UdsTransport: invalid URL \"${url}\"`)\n }\n\n const timeoutMs = options?.timeoutMs ?? 10_000\n\n return new Promise<TransportConnection>((resolve, reject) => {\n const socket = new net.Socket()\n\n let settled = false\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n socket.destroy()\n reject(new Error(`UdsTransport: connect timeout after ${timeoutMs}ms (${url})`))\n }, timeoutMs)\n\n socket.once('error', (err) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(err)\n })\n\n socket.once('connect', () => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n socket.removeAllListeners('error')\n resolve(new UdsConnection(socket, () => { /* client-side dispose noop */ }))\n })\n\n socket.connect(socketPath)\n })\n }\n}\n\nfunction parseUdsUrl(url: string): string | null {\n if (!url.startsWith(URL_SCHEME)) return null\n const rest = url.slice(URL_SCHEME.length)\n // Expected form: `unix:///abs/path.sock` → rest starts with `/`\n if (!rest.startsWith('/')) return null\n return rest\n}\n\nclass UdsConnection implements TransportConnection {\n readonly id = randomUUID()\n readonly kind = 'uds' as const\n readonly remoteAddress: string\n\n private readonly dataHandlers = new Set<(chunk: Uint8Array) => void>()\n private readonly closeHandlers = new Set<(reason?: string) => void>()\n private closed = false\n\n constructor(\n private readonly socket: net.Socket,\n private readonly onDispose: () => void,\n ) {\n this.remoteAddress = 'unix-peer'\n\n socket.on('data', (chunk: Buffer) => {\n if (this.closed) return\n for (const handler of this.dataHandlers) handler(chunk)\n })\n\n const emitClose = (reason?: string) => {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.onDispose()\n }\n socket.on('close', () => emitClose('socket-closed'))\n socket.on('error', (err) => emitClose(err.message))\n socket.on('end', () => emitClose('peer-ended'))\n }\n\n send(chunk: Uint8Array): void {\n if (this.closed) return\n this.socket.write(chunk)\n }\n\n onData(handler: (chunk: Uint8Array) => void): Unsubscribe {\n this.dataHandlers.add(handler)\n return () => this.dataHandlers.delete(handler)\n }\n\n onClose(handler: (reason?: string) => void): Unsubscribe {\n this.closeHandlers.add(handler)\n return () => this.closeHandlers.delete(handler)\n }\n\n close(reason?: string): void {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.socket.destroy()\n this.onDispose()\n }\n\n get bufferedBytes(): number {\n return this.socket.writableLength\n }\n}\n","/**\n * In-process transport — connects producer and consumer via EventEmitter\n * pairs, no real I/O. Used for:\n *\n * - Unit tests of FrameServer/FrameClient without TCP/UDS setup\n * - Same-process fast path (broker and consumer in the same Node process,\n * e.g. hub-local addons consuming their own decoder)\n *\n * The URL scheme `inprocess://<namespace>` registers the endpoint in a\n * module-local registry; connects look it up by namespace. Each endpoint\n * is isolated by namespace so multiple in-process transports can coexist.\n *\n * No auth token enforcement at the transport level (the FrameServer still\n * runs its `authenticate` callback). In-process callers are assumed trusted.\n */\nimport { EventEmitter } from 'node:events'\nimport { randomUUID } from 'node:crypto'\nimport type {\n FrameTransport,\n TransportEndpoint,\n TransportConnection,\n TransportListenOptions,\n TransportConnectOptions,\n} from '../types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nconst URL_SCHEME = 'inprocess://'\n\n/** Module-level registry: namespace → endpoint. Allows `connect()` to locate `listen()`. */\nconst registry = new Map<string, InProcessEndpoint>()\n\nexport class InProcessTransport implements FrameTransport {\n readonly kind = 'inprocess' as const\n\n async listen(options?: TransportListenOptions): Promise<TransportEndpoint> {\n const namespace = options?.namespace ?? `ns-${randomUUID()}`\n if (registry.has(namespace)) {\n throw new Error(`InProcessTransport: namespace \"${namespace}\" already in use`)\n }\n const endpoint = new InProcessEndpoint(namespace)\n registry.set(namespace, endpoint)\n return endpoint\n }\n\n async connect(url: string, _options?: TransportConnectOptions): Promise<TransportConnection> {\n if (!url.startsWith(URL_SCHEME)) {\n throw new Error(`InProcessTransport: unsupported URL \"${url}\"`)\n }\n const namespace = url.slice(URL_SCHEME.length)\n const endpoint = registry.get(namespace)\n if (!endpoint) {\n throw new Error(`InProcessTransport: no endpoint for namespace \"${namespace}\"`)\n }\n return endpoint.acceptNewConsumer()\n }\n}\n\nclass InProcessEndpoint implements TransportEndpoint {\n readonly kind = 'inprocess' as const\n readonly url: string\n private readonly connectionHandlers = new Set<(conn: TransportConnection) => void>()\n private readonly connections = new Set<InProcessConnection>()\n private closed = false\n\n constructor(private readonly namespace: string) {\n this.url = `${URL_SCHEME}${namespace}`\n }\n\n get activeConnections(): number {\n return this.connections.size\n }\n\n onConnection(handler: (conn: TransportConnection) => void): Unsubscribe {\n this.connectionHandlers.add(handler)\n return () => this.connectionHandlers.delete(handler)\n }\n\n async close(): Promise<void> {\n if (this.closed) return\n this.closed = true\n for (const conn of [...this.connections]) {\n conn.close('endpoint-closed')\n }\n registry.delete(this.namespace)\n }\n\n /** Internal — called by the transport's `connect()` to wire a new pair. */\n acceptNewConsumer(): InProcessConnection {\n if (this.closed) {\n throw new Error(`InProcessEndpoint: \"${this.namespace}\" is closed`)\n }\n // Two linked connections — one for the server side (what handlers see),\n // one for the client side (what `connect()` returns). Each writes into\n // the peer's incoming emitter.\n const serverEmitter = new EventEmitter()\n const clientEmitter = new EventEmitter()\n const serverConn = new InProcessConnection(clientEmitter, serverEmitter, () => {\n this.connections.delete(serverConn)\n })\n const clientConn = new InProcessConnection(serverEmitter, clientEmitter, () => {\n // Closing the client side also tears down the server side.\n serverConn.close('peer-closed')\n })\n this.connections.add(serverConn)\n for (const handler of this.connectionHandlers) {\n // Fire synchronously so the server can register `onData` before the\n // client starts writing. Order of registration is the handler's\n // responsibility, but this matches real TCP semantics well enough.\n handler(serverConn)\n }\n return clientConn\n }\n}\n\nclass InProcessConnection implements TransportConnection {\n readonly id = randomUUID()\n readonly kind = 'inprocess' as const\n readonly remoteAddress = 'inprocess'\n\n private readonly dataHandlers = new Set<(chunk: Uint8Array) => void>()\n private readonly closeHandlers = new Set<(reason?: string) => void>()\n private closed = false\n\n constructor(\n /** The peer's emitter — we write to this one via `.emit('data', ...)`. */\n private readonly outbound: EventEmitter,\n /** Our own emitter — we listen to this one for incoming bytes. */\n private readonly inbound: EventEmitter,\n private readonly onDispose: () => void,\n ) {\n this.inbound.on('data', (chunk: Uint8Array) => {\n if (this.closed) return\n for (const handler of this.dataHandlers) handler(chunk)\n })\n this.inbound.on('close', (reason?: string) => {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.onDispose()\n })\n }\n\n send(chunk: Uint8Array): void {\n if (this.closed) return\n this.outbound.emit('data', chunk)\n }\n\n onData(handler: (chunk: Uint8Array) => void): Unsubscribe {\n this.dataHandlers.add(handler)\n return () => this.dataHandlers.delete(handler)\n }\n\n onClose(handler: (reason?: string) => void): Unsubscribe {\n this.closeHandlers.add(handler)\n return () => this.closeHandlers.delete(handler)\n }\n\n close(reason?: string): void {\n if (this.closed) return\n // Notify both sides. The peer listens to its own inbound, which is our\n // outbound, so emitting 'close' there tears down the peer too.\n this.outbound.emit('close', reason)\n this.inbound.emit('close', reason)\n }\n\n get bufferedBytes(): number {\n // No real buffer — delivery is synchronous via EventEmitter.\n return 0\n }\n}\n","/**\n * FrameServer — producer-side high-level API.\n *\n * Responsibilities (everything a consumer of the frame-transport layer\n * wants \"just to work\"):\n *\n * 1. Open a transport endpoint.\n * 2. On each incoming consumer connection:\n * a. Wait for a handshake message carrying the auth token.\n * b. Run the configured `authenticate` callback — reject on failure.\n * c. Register the connection as an active consumer.\n * 3. On `broadcast(msg)` / `sendTo(id, msg)`:\n * a. Encode the message via the codec.\n * b. For each target consumer, check backpressure.\n * c. If over threshold, apply drop policy (default drop-newest).\n * d. Otherwise `connection.send(bytes)`.\n * 4. Expose metrics (framesSent, framesDropped, activeConsumers).\n *\n * Handshake protocol (first bytes sent by the client):\n * [4 bytes length BE] [N bytes JSON: { \"authToken\": \"...\" }]\n *\n * The handshake reuses the length-prefix framing. The payload is a small\n * JSON blob (not the frame codec) so the server can read it without\n * needing the full codec chain. Subsequent bytes from the client are\n * currently unused but reserved for future control messages (pause,\n * resume, rate changes).\n */\nimport type {\n FrameServer,\n FrameServerOptions,\n FrameServerStats,\n TransportConnection,\n TransportEndpoint,\n} from './types.js'\nimport type { Unsubscribe } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\n\n/** Pending state while waiting for the handshake. */\ninterface PendingConsumer {\n readonly conn: TransportConnection\n readonly unsubscribeData: Unsubscribe\n readonly unsubscribeClose: Unsubscribe\n /** Bytes received before the handshake completes. Capped to avoid memory abuse. */\n buffer: Buffer\n readonly timer: ReturnType<typeof setTimeout>\n}\n\n/** An authenticated consumer ready to receive frames. */\ninterface ActiveConsumer {\n readonly conn: TransportConnection\n readonly unsubscribeClose: Unsubscribe\n}\n\nconst HANDSHAKE_MAX_BYTES = 4096\nconst HANDSHAKE_TIMEOUT_MS = 5_000\nconst DEFAULT_BACKPRESSURE_THRESHOLD = 4 * 1024 * 1024\n\nexport async function createFrameServer<TMessage>(\n options: FrameServerOptions<TMessage>,\n): Promise<FrameServer<TMessage>> {\n const endpoint = await options.transport.listen(options.listen)\n const server = new FrameServerImpl<TMessage>(endpoint, options)\n return server\n}\n\nclass FrameServerImpl<TMessage> implements FrameServer<TMessage> {\n private readonly pending = new Map<string, PendingConsumer>()\n private readonly active = new Map<string, ActiveConsumer>()\n private readonly connectHandlers = new Set<(id: string) => void>()\n private readonly disconnectHandlers = new Set<(id: string, reason?: string) => void>()\n private readonly startedAt = Date.now()\n private framesSent = 0\n private framesDropped = 0\n private bytesSent = 0\n private stopped = false\n\n private readonly backpressureThreshold: number\n\n constructor(\n private readonly endpoint: TransportEndpoint,\n private readonly options: FrameServerOptions<TMessage>,\n ) {\n this.backpressureThreshold = options.backpressureThresholdBytes ?? DEFAULT_BACKPRESSURE_THRESHOLD\n endpoint.onConnection((conn) => this.handleIncomingConnection(conn))\n }\n\n get url(): string {\n return this.endpoint.url\n }\n\n get activeConsumers(): number {\n return this.active.size\n }\n\n broadcast(msg: TMessage): void {\n if (this.stopped || this.active.size === 0) return\n const bytes = this.options.codec.encode(msg)\n for (const consumer of this.active.values()) {\n this.sendTo_unsafe(consumer, bytes)\n }\n }\n\n sendTo(connectionId: string, msg: TMessage): void {\n if (this.stopped) return\n const consumer = this.active.get(connectionId)\n if (!consumer) return\n const bytes = this.options.codec.encode(msg)\n this.sendTo_unsafe(consumer, bytes)\n }\n\n onConsumerConnect(handler: (id: string) => void): Unsubscribe {\n this.connectHandlers.add(handler)\n return () => this.connectHandlers.delete(handler)\n }\n\n onConsumerDisconnect(handler: (id: string, reason?: string) => void): Unsubscribe {\n this.disconnectHandlers.add(handler)\n return () => this.disconnectHandlers.delete(handler)\n }\n\n getStats(): FrameServerStats {\n return {\n activeConsumers: this.active.size,\n framesSent: this.framesSent,\n framesDropped: this.framesDropped,\n bytesSent: this.bytesSent,\n uptimeMs: Date.now() - this.startedAt,\n }\n }\n\n async stop(): Promise<void> {\n if (this.stopped) return\n this.stopped = true\n for (const pending of this.pending.values()) {\n clearTimeout(pending.timer)\n pending.unsubscribeData()\n pending.unsubscribeClose()\n pending.conn.close('server-stopped')\n }\n this.pending.clear()\n for (const consumer of this.active.values()) {\n consumer.unsubscribeClose()\n consumer.conn.close('server-stopped')\n }\n this.active.clear()\n await this.endpoint.close()\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private handleIncomingConnection(conn: TransportConnection): void {\n const timer = setTimeout(() => {\n const p = this.pending.get(conn.id)\n if (!p) return\n this.pending.delete(conn.id)\n p.unsubscribeData()\n p.unsubscribeClose()\n conn.close('handshake-timeout')\n }, HANDSHAKE_TIMEOUT_MS)\n\n const unsubscribeData = conn.onData((chunk) => {\n const pending = this.pending.get(conn.id)\n if (!pending) return\n pending.buffer = pending.buffer.length === 0\n ? Buffer.from(chunk)\n : Buffer.concat([pending.buffer, Buffer.from(chunk)])\n if (pending.buffer.length > HANDSHAKE_MAX_BYTES) {\n this.rejectPending(conn.id, 'handshake-too-large')\n return\n }\n this.tryCompleteHandshake(conn.id)\n })\n\n const unsubscribeClose = conn.onClose((reason) => {\n const p = this.pending.get(conn.id)\n if (p) {\n clearTimeout(p.timer)\n p.unsubscribeData()\n this.pending.delete(conn.id)\n }\n const a = this.active.get(conn.id)\n if (a) {\n this.active.delete(conn.id)\n for (const h of this.disconnectHandlers) h(conn.id, reason)\n }\n })\n\n this.pending.set(conn.id, {\n conn,\n buffer: Buffer.alloc(0),\n timer,\n unsubscribeData,\n unsubscribeClose,\n })\n }\n\n private tryCompleteHandshake(connectionId: string): void {\n const pending = this.pending.get(connectionId)\n if (!pending) return\n if (pending.buffer.length < 4) return\n const len = pending.buffer.readUInt32BE(0)\n if (pending.buffer.length < 4 + len) return\n\n const handshakeBytes = pending.buffer.subarray(4, 4 + len)\n let token: string | null = null\n try {\n const json: unknown = JSON.parse(handshakeBytes.toString('utf-8'))\n if (json !== null && typeof json === 'object' && 'authToken' in json) {\n const t = (json as { authToken: unknown }).authToken\n if (typeof t === 'string') token = t\n }\n } catch {\n this.rejectPending(connectionId, 'handshake-invalid-json')\n return\n }\n\n // Run the authenticate callback (if any). Async-safe.\n const runAuth = async () => {\n if (this.options.authenticate) {\n await this.options.authenticate(token, pending.conn)\n }\n }\n\n runAuth().then(\n () => {\n // Promote to active.\n const p = this.pending.get(connectionId)\n if (!p) return // already gone (e.g. closed mid-handshake)\n clearTimeout(p.timer)\n p.unsubscribeData()\n this.pending.delete(connectionId)\n\n this.active.set(connectionId, {\n conn: p.conn,\n unsubscribeClose: p.unsubscribeClose,\n })\n\n for (const h of this.connectHandlers) h(connectionId)\n },\n (err: unknown) => {\n const msg = errMsg(err)\n this.rejectPending(connectionId, `auth-failed:${msg}`)\n },\n )\n }\n\n private rejectPending(connectionId: string, reason: string): void {\n const p = this.pending.get(connectionId)\n if (!p) return\n clearTimeout(p.timer)\n p.unsubscribeData()\n p.unsubscribeClose()\n this.pending.delete(connectionId)\n p.conn.close(reason)\n }\n\n private sendTo_unsafe(consumer: ActiveConsumer, bytes: Uint8Array): void {\n const policy = this.options.dropPolicy ?? 'drop-newest'\n\n if (consumer.conn.bufferedBytes >= this.backpressureThreshold) {\n if (policy === 'drop-newest') {\n this.framesDropped++\n return\n }\n if (policy === 'drop-oldest') {\n // Best-effort for stream transports: we can't pop already-sent bytes\n // out of the socket buffer. Fall back to drop-newest semantics.\n this.framesDropped++\n return\n }\n // policy === 'block' → send anyway, let Node's internal buffer grow.\n }\n\n consumer.conn.send(bytes)\n this.framesSent++\n this.bytesSent += bytes.length\n }\n}\n","/**\n * FrameClient — consumer-side high-level API.\n *\n * Responsibilities:\n * 1. Connect the transport to the given URL.\n * 2. Send the handshake (length-prefixed JSON with authToken).\n * 3. Stream incoming bytes through the codec's incremental decoder.\n * 4. Fan out decoded messages to both:\n * - callback handlers (via `onMessage(fn)`)\n * - AsyncIterable (`for await (const msg of client.messages)`)\n * 5. Cleanly disconnect on `disconnect()` or remote close.\n *\n * The AsyncIterable is implemented with a small unbounded queue. If no\n * `for await` is iterating, messages are buffered; if the buffer grows\n * indefinitely this is a consumer-side memory leak. Callers who care\n * about backpressure should prefer `onMessage()` + explicit pacing, or\n * iterate promptly.\n */\nimport type {\n FrameClient,\n FrameClientOptions,\n FrameClientStats,\n FrameDecoder,\n TransportConnection,\n} from './types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nexport function createFrameClient<TMessage>(\n options: FrameClientOptions<TMessage>,\n): FrameClient<TMessage> {\n return new FrameClientImpl<TMessage>(options)\n}\n\ninterface IterableState<TMessage> {\n /** Messages waiting for a `for await` puller. */\n buffer: TMessage[]\n /** Pending puller's resolve function — called when a new message arrives. */\n waiter: ((value: IteratorResult<TMessage>) => void) | null\n done: boolean\n}\n\nclass FrameClientImpl<TMessage> implements FrameClient<TMessage> {\n private connection: TransportConnection | null = null\n private decoder: FrameDecoder<TMessage> | null = null\n private readonly messageHandlers = new Set<(msg: TMessage) => void>()\n private readonly disconnectHandlers = new Set<(reason?: string) => void>()\n private readonly iterState: IterableState<TMessage> = {\n buffer: [],\n waiter: null,\n done: false,\n }\n\n // Stats\n private framesReceived = 0\n private bytesReceived = 0\n private connectedAtMs: number | null = null\n\n constructor(private readonly options: FrameClientOptions<TMessage>) {}\n\n async connect(url: string): Promise<void> {\n if (this.connection !== null) {\n throw new Error('FrameClient: already connected')\n }\n\n const conn = await this.options.transport.connect(url, this.options.connect)\n this.connection = conn\n this.decoder = this.options.codec.createDecoder()\n this.connectedAtMs = Date.now()\n\n conn.onData((chunk) => this.handleIncomingChunk(chunk))\n conn.onClose((reason) => this.handleDisconnect(reason))\n\n // Send the handshake. Format matches FrameServerImpl.tryCompleteHandshake:\n // [4 bytes BE length][JSON payload]\n const payload = Buffer.from(JSON.stringify({\n authToken: this.options.connect?.authToken ?? null,\n }), 'utf-8')\n const header = Buffer.allocUnsafe(4)\n header.writeUInt32BE(payload.length, 0)\n conn.send(Buffer.concat([header, payload]))\n }\n\n get messages(): AsyncIterable<TMessage> {\n const state = this.iterState\n return {\n [Symbol.asyncIterator](): AsyncIterator<TMessage> {\n return {\n next(): Promise<IteratorResult<TMessage>> {\n if (state.buffer.length > 0) {\n const value = state.buffer.shift() as TMessage\n return Promise.resolve({ value, done: false })\n }\n if (state.done) {\n return Promise.resolve({ value: undefined, done: true })\n }\n return new Promise((resolve) => {\n state.waiter = resolve\n })\n },\n return(): Promise<IteratorResult<TMessage>> {\n state.done = true\n state.buffer = []\n if (state.waiter) {\n state.waiter({ value: undefined, done: true })\n state.waiter = null\n }\n return Promise.resolve({ value: undefined, done: true })\n },\n }\n },\n }\n }\n\n onMessage(handler: (msg: TMessage) => void): Unsubscribe {\n this.messageHandlers.add(handler)\n return () => this.messageHandlers.delete(handler)\n }\n\n onDisconnect(handler: (reason?: string) => void): Unsubscribe {\n this.disconnectHandlers.add(handler)\n return () => this.disconnectHandlers.delete(handler)\n }\n\n getStats(): FrameClientStats {\n return {\n framesReceived: this.framesReceived,\n bytesReceived: this.bytesReceived,\n connected: this.connection !== null,\n connectedAtMs: this.connectedAtMs,\n }\n }\n\n async disconnect(): Promise<void> {\n const conn = this.connection\n if (conn === null) return\n conn.close('client-disconnect')\n // handleDisconnect will be invoked by the onClose handler.\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private handleIncomingChunk(chunk: Uint8Array): void {\n if (this.decoder === null) return\n this.bytesReceived += chunk.length\n let messages: readonly TMessage[]\n try {\n messages = this.decoder.push(chunk)\n } catch (err) {\n // Framing error — tear down the connection. The decoder's internal\n // buffer has been reset already by its own error handling.\n const reason = err instanceof Error ? `decoder-error:${err.message}` : 'decoder-error'\n this.handleDisconnect(reason)\n if (this.connection) this.connection.close(reason)\n return\n }\n\n for (const msg of messages) {\n this.framesReceived++\n\n // Fan out to callbacks.\n for (const handler of this.messageHandlers) handler(msg)\n\n // Push to AsyncIterable buffer or resolve the pending waiter.\n if (this.iterState.done) continue\n if (this.iterState.waiter) {\n const w = this.iterState.waiter\n this.iterState.waiter = null\n w({ value: msg, done: false })\n } else {\n this.iterState.buffer.push(msg)\n }\n }\n }\n\n private handleDisconnect(reason?: string): void {\n if (this.connection === null) return\n this.connection = null\n this.decoder?.reset()\n this.decoder = null\n\n for (const handler of this.disconnectHandlers) handler(reason)\n\n // Drain AsyncIterable state.\n this.iterState.done = true\n if (this.iterState.waiter) {\n const w = this.iterState.waiter\n this.iterState.waiter = null\n w({ value: undefined, done: true })\n }\n }\n}\n","/**\n * HMAC-based auth tokens for FrameServer subscriptions.\n *\n * Flow:\n * 1. Server issues `createAuthToken(sessionId, expiryMs)` and returns the\n * token to the consumer via tRPC.\n * 2. Consumer sends the token in the first handshake message after\n * connecting the transport.\n * 3. Server validates via `verifyAuthToken(token, expectedSessionId)`\n * before accepting frame broadcasts.\n *\n * Tokens are self-contained: `${sessionId}.${expiryEpochMs}.${signature}`.\n * The signature is HMAC-SHA256 of the payload using the server's secret.\n * Base64url encoding — URL-safe, no padding.\n *\n * The secret is per-FrameServer instance and is generated at construction\n * time via `crypto.randomBytes(32)`. It never leaves the server process.\n * Rotating the secret invalidates all existing tokens.\n */\nimport * as crypto from 'node:crypto'\n\n/** Issue a signed token for a single subscription. */\nexport interface AuthSigner {\n sign(sessionId: string, expiryEpochMs: number): string\n}\n\n/** Verify a token against expected sessionId and current wall-clock. */\nexport interface AuthVerifier {\n verify(token: string, expectedSessionId: string): AuthVerifyResult\n}\n\nexport type AuthVerifyResult =\n | { readonly ok: true; readonly sessionId: string; readonly expiresAt: number }\n | { readonly ok: false; readonly reason: 'malformed' | 'bad-signature' | 'expired' | 'wrong-session' }\n\n/** Create a matched signer + verifier pair sharing the same secret. */\nexport function createAuthPair(): { signer: AuthSigner; verifier: AuthVerifier } {\n const secret = crypto.randomBytes(32)\n return {\n signer: {\n sign: (sessionId, expiryEpochMs) => signToken(secret, sessionId, expiryEpochMs),\n },\n verifier: {\n verify: (token, expectedSessionId) => verifyToken(secret, token, expectedSessionId),\n },\n }\n}\n\n// ── Internal helpers ────────────────────────────────────────────────────────\n\nconst SEPARATOR = '.'\n\nfunction signToken(secret: Buffer, sessionId: string, expiryEpochMs: number): string {\n if (sessionId.includes(SEPARATOR)) {\n throw new Error(`sessionId must not contain \"${SEPARATOR}\"`)\n }\n const payload = `${sessionId}${SEPARATOR}${expiryEpochMs}`\n const sig = crypto.createHmac('sha256', secret).update(payload).digest()\n return `${payload}${SEPARATOR}${toBase64Url(sig)}`\n}\n\nfunction verifyToken(secret: Buffer, token: string, expectedSessionId: string): AuthVerifyResult {\n const parts = token.split(SEPARATOR)\n if (parts.length !== 3) {\n return { ok: false, reason: 'malformed' }\n }\n const [sessionId, expiryStr, signatureB64] = parts as [string, string, string]\n\n const expiryEpochMs = Number(expiryStr)\n if (!Number.isFinite(expiryEpochMs) || expiryEpochMs <= 0) {\n return { ok: false, reason: 'malformed' }\n }\n\n if (sessionId !== expectedSessionId) {\n return { ok: false, reason: 'wrong-session' }\n }\n\n const expectedSig = crypto.createHmac('sha256', secret).update(`${sessionId}${SEPARATOR}${expiryStr}`).digest()\n const providedSig = fromBase64Url(signatureB64)\n\n if (providedSig === null || providedSig.length !== expectedSig.length) {\n return { ok: false, reason: 'bad-signature' }\n }\n if (!crypto.timingSafeEqual(providedSig, expectedSig)) {\n return { ok: false, reason: 'bad-signature' }\n }\n\n if (Date.now() > expiryEpochMs) {\n return { ok: false, reason: 'expired' }\n }\n\n return { ok: true, sessionId, expiresAt: expiryEpochMs }\n}\n\nfunction toBase64Url(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(s: string): Buffer | null {\n const normalized = s.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4)\n try {\n return Buffer.from(padded, 'base64')\n } catch {\n return null\n }\n}\n"],"names":["parseAudioSpecificConfig","hexToBytes","errMsg","type","parameterSets","net","randomUUID","createHash","AudioSoundFormat","Socket","messageTypeId","once","maskUrlCredentials","cache","startCode","randomBytes","CAM_PROFILE_ORDER","RingBuffer","EventCategory","DeviceFeature","i","tryConvertWithLengthReader","tryConvertWithLengthReader16","tryConvertWithLengthReader24","dnsLookup","console","layerId","tid","nalType","t","names","senderCodec","c","BaseAddon","asJsonObject","streamBrokerCapability","cameraStreamsCapability","webrtcSessionCapability","addonWidgetsSourceCapability","createEvent","spawn","URL_SCHEME","path","os","fs","EventEmitter","crypto"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/stream-broker/stream-broker/decoder-session-proxy.ts","../../src/stream-broker/rtsp/audio/aac-config.ts","../../src/stream-broker/stream-broker/audio-codec-session.ts","../../src/stream-broker/stream-broker/frame-dropper.ts","../../src/stream-broker/stream-broker/stream-pipe-server.ts","../../src/stream-broker/stream-broker/encoded-ring-buffer.ts","../../src/stream-broker/rtsp/rtsp-types.ts","../../src/stream-broker/rtsp/rtp-packetizer.ts","../../src/stream-broker/rtsp/annexb-deframer.ts","../../src/stream-broker/rtsp/sdp-builder.ts","../../src/stream-broker/rtsp/rtsp-restreamer.ts","../../src/stream-broker/rtsp/sdp-parser.ts","../../src/stream-broker/rtsp/rtsp-client.ts","../../src/stream-broker/rtsp/rfc4571-reader.ts","../../src/stream-broker/rtmp/flv-parser.ts","../../src/stream-broker/rtmp/rtmp-client.ts","../../src/stream-broker/rtmp/rtmp-reader.ts","../../src/stream-broker/rtsp/rtp-depacketizer.ts","../../src/stream-broker/rtsp/audio-rtp-decoder.ts","../../src/stream-broker/stream-broker/g711-mulaw.ts","../../src/stream-broker/stream-broker/placeholder-frames.ts","../../src/stream-broker/stream-broker/stream-broker.ts","../../src/stream-broker/stream-broker/audio-codec-proxy.ts","../../src/stream-broker/rtsp/rtsp-session.ts","../../src/stream-broker/rtsp/rtsp-listen-server.ts","../../src/stream-broker/rtsp/rtsp-restream-provider.ts","../../src/stream-broker/stream-broker/transcode-pipeline.ts","../../src/stream-broker/stream-broker/stream-broker-manager.ts","../../src/stream-broker/webrtc/nal-utils.ts","../../src/stream-broker/webrtc/h264-utils.ts","../../src/stream-broker/webrtc/h265-utils.ts","../../src/stream-broker/webrtc/mdns-resolve.ts","../../src/stream-broker/webrtc/jitter-buffer.ts","../../src/stream-broker/webrtc/h265-repacketizer.ts","../../src/stream-broker/webrtc/session.ts","../../src/stream-broker/webrtc/broker-webrtc-server.ts","../../src/stream-broker/device-scoped-providers.ts","../../src/stream-broker/addon.ts","../../src/stream-broker/stream-broker/ffmpeg-decoder-session.ts","../../src/stream-broker/stream-broker/ffmpeg-decoder-provider.ts","../../src/stream-broker/frame-transport/codecs/length-prefix-codec.ts","../../src/stream-broker/frame-transport/codecs/decoded-frame-codec.ts","../../src/stream-broker/frame-transport/transports/tcp-transport.ts","../../src/stream-broker/frame-transport/transports/uds-transport.ts","../../src/stream-broker/frame-transport/transports/inprocess-transport.ts","../../src/stream-broker/frame-transport/frame-server.ts","../../src/stream-broker/frame-transport/frame-client.ts","../../src/stream-broker/frame-transport/auth.ts"],"sourcesContent":["import type { EncodedPacket, DecodedFrame, DecoderSessionConfig, DecoderStats } from '@camstack/types'\n\nexport interface DecoderCapApi {\n supportsCodec(input: { readonly codec: string }): Promise<boolean>\n getInfo(): Promise<{ readonly id: string; readonly name: string; readonly isPullMode?: boolean; readonly priority?: number }>\n createSession(config: DecoderSessionConfig): Promise<{ readonly sessionId: string; readonly nodeId: string }>\n pushPacket(input: { readonly sessionId: string; readonly packet: EncodedPacket }): Promise<void>\n pullFrames(input: { readonly sessionId: string; readonly maxCount: number }): Promise<readonly DecodedFrame[]>\n destroySession(input: { readonly sessionId: string }): Promise<void>\n openStream(input: { readonly sessionId: string; readonly url: string }): Promise<void>\n updateConfig(input: { readonly sessionId: string; readonly config: Partial<DecoderSessionConfig> }): Promise<void>\n getStats(input: { readonly sessionId: string }): Promise<DecoderStats>\n}\n\nexport class DecoderSessionProxy {\n private polling = false\n\n constructor(\n private readonly api: DecoderCapApi,\n readonly sessionId: string,\n ) {}\n\n async pushPacket(packet: EncodedPacket): Promise<void> {\n await this.api.pushPacket({ sessionId: this.sessionId, packet })\n }\n\n async openStream(url: string): Promise<void> {\n await this.api.openStream({ sessionId: this.sessionId, url })\n }\n\n async startPolling(onFrame: (frame: DecodedFrame) => void): Promise<void> {\n this.polling = true\n while (this.polling) {\n const frames = await this.api.pullFrames({ sessionId: this.sessionId, maxCount: 4 })\n for (const frame of frames) onFrame(frame)\n if (frames.length === 0) await new Promise((r) => setTimeout(r, 1))\n }\n }\n\n stopPolling(): void {\n this.polling = false\n }\n\n async updateConfig(config: Partial<DecoderSessionConfig>): Promise<void> {\n await this.api.updateConfig({ sessionId: this.sessionId, config })\n }\n\n async getStats(): Promise<DecoderStats> {\n return this.api.getStats({ sessionId: this.sessionId })\n }\n\n async destroy(): Promise<void> {\n this.stopPolling()\n await this.api.destroySession({ sessionId: this.sessionId })\n }\n}\n","/**\n * Helpers for AAC stream framing.\n *\n * Two pieces:\n *\n * 1. `parseAudioSpecificConfig` — decode the hex `config=` blob from the\n * SDP `fmtp:` line for `mpeg4-generic`/`MP4A-LATM`. Returns the\n * samplerate + channel config the camera advertises.\n *\n * 2. `depacketizeRfc3640` — strip AU-headers from an RTP `mode=AAC-hbr`\n * payload and return one or more raw AAC AccessUnits ready to feed\n * libavcodec. Reolink uses `sizeLength=13; indexLength=3` which is\n * the RFC 3640 default for AAC-hbr.\n *\n * Both are pure functions over `Uint8Array` — keep them free of\n * libav/node-av deps so they're easy to unit-test.\n */\n\n// AAC sample-rate frequency table (ISO/IEC 14496-3 §1.6.3.4).\nconst SAMPLERATE_TABLE: readonly number[] = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,\n 16000, 12000, 11025, 8000, 7350,\n]\n\nexport interface AudioSpecificConfig {\n /** Object type (e.g. 2 = AAC-LC, 5 = HE-AAC, 29 = HE-AAC v2). */\n readonly objectType: number\n /** Decoded sample rate in Hz. */\n readonly sampleRate: number\n /** Channel configuration (1 = mono, 2 = stereo, 6 = 5.1, …). */\n readonly channelConfig: number\n}\n\nclass BitReader {\n private bitPos = 0\n constructor(private readonly buf: Uint8Array) {}\n read(n: number): number {\n let v = 0\n for (let i = 0; i < n; i++) {\n const bytePos = this.bitPos >> 3\n const bit = 7 - (this.bitPos & 7)\n const b = bytePos < this.buf.length ? this.buf[bytePos]! : 0\n v = (v << 1) | ((b >> bit) & 1)\n this.bitPos++\n }\n return v\n }\n remainingBits(): number {\n return this.buf.length * 8 - this.bitPos\n }\n}\n\n/**\n * Decode hex `config=` blob (or raw `Uint8Array`) into an\n * `AudioSpecificConfig`. Throws on malformed input.\n *\n * Layout per ISO/IEC 14496-3 §1.6.2.1:\n * - 5 bits: audioObjectType (extension-escape if 31)\n * - 4 bits: samplingFrequencyIndex (0xf = explicit 24-bit value follows)\n * - 4 bits: channelConfiguration\n */\nexport function parseAudioSpecificConfig(input: string | Uint8Array): AudioSpecificConfig {\n const buf = typeof input === 'string' ? hexToBytes(input) : input\n if (buf.length < 2) {\n throw new Error(`audio-codec: AudioSpecificConfig too short (${buf.length} bytes)`)\n }\n const reader = new BitReader(buf)\n let objectType = reader.read(5)\n if (objectType === 31) {\n objectType = 32 + reader.read(6)\n }\n const sampleRateIndex = reader.read(4)\n let sampleRate: number\n if (sampleRateIndex === 0xf) {\n sampleRate = reader.read(24)\n } else {\n const v = SAMPLERATE_TABLE[sampleRateIndex]\n if (!v) throw new Error(`audio-codec: invalid samplingFrequencyIndex ${sampleRateIndex}`)\n sampleRate = v\n }\n const channelConfig = reader.read(4)\n return { objectType, sampleRate, channelConfig }\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const trimmed = hex.replace(/\\s+/g, '')\n if (trimmed.length % 2 !== 0) {\n throw new Error(`audio-codec: hex string has odd length (${trimmed.length})`)\n }\n const out = new Uint8Array(trimmed.length / 2)\n for (let i = 0; i < out.length; i++) {\n const byte = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16)\n if (Number.isNaN(byte)) {\n throw new Error(`audio-codec: invalid hex at offset ${i * 2}`)\n }\n out[i] = byte\n }\n return out\n}\n\n/**\n * Depacketize an RFC 3640 `mode=AAC-hbr` RTP payload (typical\n * `sizeLength=13; indexLength=3`) into one or more raw AAC AccessUnits.\n *\n * RFC 3640 §3.2 layout:\n * AU Headers Length (16 bits, big-endian) — total bits in the AU\n * headers section.\n * AU headers — `sizeLength` bits AU-size, `indexLength` bits AU-index\n * (or AU-index-delta after the first). Padding aligns to byte.\n * AU data — `auCount` access units back-to-back, each `auSize` bytes.\n *\n * `payload` is the RTP payload (after the 12+ byte RTP header, i.e.\n * what callers pass via `onAudioRtp` minus the header strip).\n */\nexport interface Rfc3640Options {\n readonly sizeLength: number // bits per AU-size — default 13\n readonly indexLength: number // bits per AU-index — default 3\n readonly indexDeltaLength?: number\n}\n\nexport function depacketizeRfc3640(\n payload: Uint8Array,\n opts: Rfc3640Options = { sizeLength: 13, indexLength: 3 },\n): Uint8Array[] {\n if (payload.length < 2) return []\n const sizeLen = opts.sizeLength\n const idxLen = opts.indexLength\n const idxDeltaLen = opts.indexDeltaLength ?? idxLen\n const headersBitLength = (payload[0]! << 8) | payload[1]!\n if (headersBitLength === 0) return []\n\n const headersStartByte = 2\n const headersByteLength = (headersBitLength + 7) >> 3\n const headersEndByte = headersStartByte + headersByteLength\n if (headersEndByte > payload.length) return []\n\n const headersSlice = payload.subarray(headersStartByte, headersEndByte)\n const reader = new BitReader(headersSlice)\n const sizes: number[] = []\n let consumed = 0\n let firstAu = true\n while (consumed < headersBitLength) {\n if (reader.remainingBits() < sizeLen + (firstAu ? idxLen : idxDeltaLen)) break\n const auSize = reader.read(sizeLen)\n reader.read(firstAu ? idxLen : idxDeltaLen) // index/delta — ignored\n sizes.push(auSize)\n consumed += sizeLen + (firstAu ? idxLen : idxDeltaLen)\n firstAu = false\n }\n\n const units: Uint8Array[] = []\n let offset = headersEndByte\n for (const sz of sizes) {\n if (offset + sz > payload.length) break\n units.push(payload.subarray(offset, offset + sz))\n offset += sz\n }\n return units\n}\n","/**\n * Per-broker handle around an audio-codec decode session. Owns:\n * - sessionId (lazily created on first push)\n * - depacketizer choice based on RTP payload format\n * - polling timer that pulls PCM from the cap and fans it out via\n * a caller-supplied AudioRtpDecoder-shaped callback.\n *\n * Stays self-contained so the broker only needs to forward\n * `processPacket(rtpData)` and `close()` calls. Mirrors the surface\n * of `AudioRtpDecoder` (the legacy PCMU/PCMA path) so the broker's\n * call sites can swap implementations without further branching.\n */\nimport type { DecodedAudioChunk, IScopedLogger } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport type { AudioCodecCapApi } from './audio-codec-proxy.js'\nimport { depacketizeRfc3640, type Rfc3640Options } from '../rtsp/audio/aac-config.js'\n\nexport interface AudioCodecSessionConfig {\n readonly codec: string // canonical lowercase: 'aac', 'opus', ...\n readonly sourceSampleRate: number\n readonly sourceChannels: number\n readonly extraData?: Uint8Array // AAC AudioSpecificConfig\n readonly targetSampleRate: number // typically 16000 for ASA / yamnet\n readonly targetChannels: number // typically 1\n readonly tag?: string\n /** Set when the source uses MPEG4-GENERIC mode=AAC-hbr framing. */\n readonly rfc3640?: Rfc3640Options\n}\n\nconst POLL_INTERVAL_MS = 40\nconst MAX_PCM_PER_POLL = 16\nconst RTP_HEADER_LENGTH = 12\nconst WARN_THROTTLE_MS = 5_000\n// Match AudioRtpDecoder's emit cadence so YAMNet/Apple SoundAnalysis\n// see the same window size regardless of source codec. A YAMNet hop is\n// ~0.48s and Apple SA classifies on rolling ~0.96s windows; 0.5s of\n// target-rate samples gives both classifiers a stable input. Codec\n// frames are typically 64–128ms so ~4–8 frames coalesce into one chunk.\nconst TARGET_CHUNK_MS = 500\nconst F32_BYTES = 4\n// Matches the \"decode session 'dec-...' not found\" thrown by the\n// audio-codec addon when the upstream session has been reaped (idle\n// timeout, addon restart, host process resumed from sleep). We use it\n// to detect \"the cap forgot us\" vs a transient decode error.\nconst SESSION_NOT_FOUND_RE = /decode session\\b.*\\bnot found\\b/\n\nfunction isSessionGone(err: unknown): boolean {\n return SESSION_NOT_FOUND_RE.test(errMsg(err))\n}\n\nexport class AudioCodecSession {\n private sessionId: string | null = null\n private sessionNodeId: string | null = null\n private creating: Promise<string | null> | null = null\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private closed = false\n private packetsIn = 0\n private packetsDropped = 0\n // Coalesce warnings — without this a single reaped session produces\n // ~1000 warn/s (every RTP audio packet + every poll tick), and the\n // synchronous winston writes block the broker's event loop enough\n // to visibly slow the video forwarders sharing the process.\n private warnState = new Map<string, { lastLoggedAt: number; suppressed: number }>()\n // Accumulator for the ~0.5s emit window. The codec cap returns a PCM\n // frame per decoded codec packet (~21–128ms depending on codec), which\n // is too short for the audio classifiers downstream. We coalesce\n // until we've buffered TARGET_CHUNK_MS worth of samples, then emit\n // one chunk with the earliest packet's timestamp.\n private pendingPcm: Buffer[] = []\n private pendingBytes = 0\n private pendingSampleRate = 0\n private pendingChannels = 0\n private pendingFirstTs = 0\n private readonly chunkBytesTarget: number\n\n constructor(\n private readonly api: AudioCodecCapApi,\n private readonly cfg: AudioCodecSessionConfig,\n private readonly emit: (chunk: DecodedAudioChunk) => void,\n private readonly logger?: IScopedLogger,\n ) {\n this.chunkBytesTarget =\n Math.max(1, Math.round(cfg.targetSampleRate * (TARGET_CHUNK_MS / 1000))) *\n Math.max(1, cfg.targetChannels) *\n F32_BYTES\n }\n\n processPacket(rtpData: Buffer): void {\n if (this.closed) return\n if (rtpData.length <= RTP_HEADER_LENGTH) return\n const payload = rtpData.subarray(RTP_HEADER_LENGTH)\n this.packetsIn++\n\n void this.ensureSession().then((sid) => {\n if (!sid || this.closed) return\n const nodeId = this.sessionNodeId ?? undefined\n const units = this.cfg.rfc3640\n ? depacketizeRfc3640(payload, this.cfg.rfc3640)\n : [Uint8Array.from(payload)]\n for (const unit of units) {\n void this.api.pushEncodedFrame({\n sessionId: sid,\n ...(nodeId ? { nodeId } : {}),\n data: unit,\n }).catch((err) => {\n this.packetsDropped++\n this.handlePushError('audio-codec: pushEncodedFrame failed', sid, err)\n })\n }\n })\n }\n\n /**\n * Push a single pre-depacketized codec frame. Used by push-source\n * providers (Reolink Baichuan, Frigate) that emit raw codec frames\n * directly — no RTP wrapping, no rfc3640 depacketize. The payload\n * MUST be one complete frame the underlying decoder can consume\n * (e.g. an AAC raw frame with or without ADTS header per the\n * codec's AudioSpecificConfig).\n */\n pushRawFrame(unit: Uint8Array): void {\n if (this.closed) return\n if (unit.length === 0) return\n this.packetsIn++\n void this.ensureSession().then((sid) => {\n if (!sid || this.closed) return\n const nodeId = this.sessionNodeId ?? undefined\n void this.api.pushEncodedFrame({\n sessionId: sid,\n ...(nodeId ? { nodeId } : {}),\n data: unit,\n }).catch((err) => {\n this.packetsDropped++\n this.handlePushError('audio-codec: pushEncodedFrame (raw) failed', sid, err)\n })\n })\n }\n\n /** Stats surfaced by the broker for the streams tab / logs. */\n getStats(): { packetsIn: number; packetsDropped: number; sessionId: string | null } {\n return {\n packetsIn: this.packetsIn,\n packetsDropped: this.packetsDropped,\n sessionId: this.sessionId,\n }\n }\n\n async close(): Promise<void> {\n if (this.closed) return\n this.closed = true\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n // Emit whatever's in the accumulator so downstream audio\n // analyzers don't miss the last partial window. Skipped if the\n // accumulator is empty.\n this.flushPending()\n if (this.sessionId) {\n const sid = this.sessionId\n const nodeId = this.sessionNodeId ?? undefined\n try { await this.api.closeSession({ sessionId: sid, ...(nodeId ? { nodeId } : {}) }) }\n catch (err) {\n this.logger?.warn('audio-codec: closeSession failed', {\n meta: { error: errMsg(err) },\n })\n }\n this.sessionId = null\n this.sessionNodeId = null\n }\n }\n\n private async ensureSession(): Promise<string | null> {\n if (this.sessionId) return this.sessionId\n if (this.creating) return this.creating\n this.creating = (async () => {\n try {\n const res = await this.api.createDecodeSession({\n codec: this.cfg.codec,\n sourceSampleRate: this.cfg.sourceSampleRate,\n sourceChannels: this.cfg.sourceChannels,\n ...(this.cfg.extraData ? { extraData: this.cfg.extraData } : {}),\n targetSampleRate: this.cfg.targetSampleRate,\n targetChannels: this.cfg.targetChannels,\n targetFormat: 'f32le',\n ...(this.cfg.tag ? { tag: this.cfg.tag } : {}),\n })\n this.sessionId = res.sessionId\n this.sessionNodeId = res.nodeId\n this.startPollLoop()\n this.logger?.info('audio-codec: decode session ready', {\n meta: {\n sessionId: res.sessionId,\n nodeId: res.nodeId,\n codec: this.cfg.codec,\n target: `${this.cfg.targetSampleRate}Hz×${this.cfg.targetChannels}`,\n },\n })\n return res.sessionId\n } catch (err) {\n this.logger?.error('audio-codec: createDecodeSession failed', {\n meta: { codec: this.cfg.codec, error: errMsg(err) },\n })\n return null\n } finally {\n this.creating = null\n }\n })()\n return this.creating\n }\n\n private startPollLoop(): void {\n if (this.pollTimer) return\n this.pollTimer = setInterval(() => {\n void this.pollOnce()\n }, POLL_INTERVAL_MS)\n if (typeof this.pollTimer.unref === 'function') this.pollTimer.unref()\n }\n\n private async pollOnce(): Promise<void> {\n const sid = this.sessionId\n if (!sid || this.closed) return\n const nodeId = this.sessionNodeId ?? undefined\n try {\n const pcms = await this.api.pullPcm({\n sessionId: sid,\n ...(nodeId ? { nodeId } : {}),\n maxCount: MAX_PCM_PER_POLL,\n })\n for (const pcm of pcms) {\n this.appendPcm(pcm)\n }\n } catch (err) {\n // Stop polling and forget the dead session id when the upstream\n // cap reaped it; the next pushed RTP packet will reopen via\n // ensureSession(). Without this the timer fires every 40ms forever\n // and the broker logs ~25 warn/s per dead session per camera.\n if (isSessionGone(err)) {\n this.handleSessionGone(sid)\n return\n }\n this.warnThrottled('audio-codec: pullPcm failed', errMsg(err))\n }\n }\n\n // ── Sample accumulator ──────────────────────────────────────────────────\n\n /**\n * Stage a freshly-pulled PCM frame into the accumulator. When we've\n * buffered enough samples for the configured ~0.5s window we\n * concatenate, copy out, and emit a single DecodedAudioChunk so\n * downstream audio classifiers see a stable input length regardless\n * of source codec frame size (G.711 packets ≈ 20ms, AAC ≈ 21–43ms,\n * Opus ≈ 20ms).\n *\n * Format changes (sampleRate/channels) flush the previous window\n * first so a chunk never spans two formats.\n */\n private appendPcm(pcm: { data: Uint8Array; sampleRate: number; channels: number; pts: number }): void {\n const incoming = pcm.data.byteLength\n if (incoming === 0) return\n\n // Format change → flush the previous window before swapping params.\n if (\n this.pendingBytes > 0 &&\n (pcm.sampleRate !== this.pendingSampleRate ||\n pcm.channels !== this.pendingChannels)\n ) {\n this.flushPending()\n }\n\n if (this.pendingBytes === 0) {\n this.pendingSampleRate = pcm.sampleRate\n this.pendingChannels = pcm.channels\n // pcm.pts is a codec-relative PTS (often starting at 0), not a\n // wall-clock timestamp. Downstream consumers (audio-metrics\n // snapshot, UI \"X seconds ago\" chip) interpret the chunk\n // timestamp as ms-since-epoch — emitting `pts` produced\n // bogus values like \"1777832862s ago\" because Date.now() - 0\n // ≈ Date.now(). AudioRtpDecoder always uses Date.now() too,\n // so the codec path now matches.\n this.pendingFirstTs = Date.now()\n }\n // Buffer.from(uint8array) deep-copies — safe to keep across the\n // RPC tick that owns the source buffer.\n this.pendingPcm.push(Buffer.from(pcm.data))\n this.pendingBytes += incoming\n\n if (this.pendingBytes >= this.chunkBytesTarget) {\n this.flushPending()\n }\n }\n\n private flushPending(): void {\n if (this.pendingBytes === 0) return\n const merged = this.pendingPcm.length === 1\n ? this.pendingPcm[0]!\n : Buffer.concat(this.pendingPcm, this.pendingBytes)\n const sampleRate = this.pendingSampleRate\n const channels = this.pendingChannels\n const timestamp = this.pendingFirstTs\n this.pendingPcm = []\n this.pendingBytes = 0\n this.pendingSampleRate = 0\n this.pendingChannels = 0\n this.pendingFirstTs = 0\n this.emit({\n data: merged,\n sampleRate,\n channels,\n timestamp,\n })\n }\n\n // ── Recovery + warn coalescing ──────────────────────────────────────────\n\n /**\n * Invoked from any push/pull path when the cap returns\n * `decode session not found`. Drops our cached session id + tears\n * down the poll loop so subsequent traffic re-creates the session\n * lazily. Safe to call multiple times: second caller sees a null\n * sessionId and noops.\n */\n private handleSessionGone(staleSid: string): void {\n if (this.sessionId !== staleSid) return\n this.sessionId = null\n this.sessionNodeId = null\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n // Drain whatever survived from the dead session — the new session\n // may negotiate a different sampleRate/channels, and emitting the\n // tail keeps timeline continuity for the classifier.\n this.flushPending()\n this.warnThrottled(\n 'audio-codec: session reaped upstream — will re-create on next packet',\n `staleSessionId=${staleSid}`,\n )\n }\n\n private handlePushError(label: string, sid: string, err: unknown): void {\n if (isSessionGone(err)) {\n this.handleSessionGone(sid)\n return\n }\n this.warnThrottled(label, errMsg(err))\n }\n\n /**\n * Logs at most once per WARN_THROTTLE_MS per (label,detail) pair and\n * folds suppressed counts into the next log line so we keep visibility\n * into chronic failures without flooding winston (whose JSON encode +\n * sync write blocks the broker's event loop at >100/s).\n */\n private warnThrottled(label: string, detail: string): void {\n const key = `${label}|${detail}`\n const now = Date.now()\n const prev = this.warnState.get(key)\n if (prev && now - prev.lastLoggedAt < WARN_THROTTLE_MS) {\n prev.suppressed++\n return\n }\n const suppressed = prev?.suppressed ?? 0\n this.warnState.set(key, { lastLoggedAt: now, suppressed: 0 })\n this.logger?.warn(label, {\n meta: {\n codec: this.cfg.codec,\n error: detail,\n ...(suppressed > 0 ? { suppressedSinceLastLog: suppressed } : {}),\n },\n })\n }\n}\n","export class FrameDropper {\n private intervalMs: number\n private lastPassedAt = -Infinity\n\n constructor(maxFps: number) {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n\n shouldKeep(): boolean {\n if (this.intervalMs === 0) return true\n\n const now = Date.now()\n if (now - this.lastPassedAt >= this.intervalMs) {\n this.lastPassedAt = now\n return true\n }\n return false\n }\n\n setMaxFps(maxFps: number): void {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n}\n","import net from 'node:net'\nimport type { IScopedLogger } from '@camstack/types'\n\n/**\n * A lightweight TCP server that broadcasts raw encoded video bytes\n * (H.264/H.265 Annex-B) to all connected clients.\n *\n * Each StreamBroker owns one StreamPipeServer. Restreamers (e.g. go2rtc)\n * connect via `tcp://127.0.0.1:{port}` and receive the same byte stream\n * that the broker reads from the camera, eliminating duplicate RTSP\n * connections.\n */\n/** Max bytes queued in a client's write buffer before we consider it too slow */\nconst MAX_WRITE_BUFFER_BYTES = 2 * 1024 * 1024 // 2 MB\n\nexport class StreamPipeServer {\n private readonly server: net.Server\n private readonly clients: Set<net.Socket> = new Set()\n private port = 0\n private started = false\n private readonly logger: IScopedLogger | undefined\n private _onClientCountChanged: (() => void) | null = null\n\n /** Register a callback invoked when pipe client count changes. */\n onClientCountChanged(cb: () => void): void { this._onClientCountChanged = cb }\n\n constructor(logger?: IScopedLogger) {\n this.logger = logger\n this.server = net.createServer((socket) => {\n this.handleConnection(socket)\n })\n\n this.server.on('error', (err) => {\n this.logger?.error('StreamPipeServer error', { meta: { error: err.message } })\n })\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n this.server.once('error', reject)\n this.server.listen(0, '127.0.0.1', () => {\n this.server.removeListener('error', reject)\n const address = this.server.address() as net.AddressInfo\n this.port = address.port\n this.started = true\n this.logger?.debug('StreamPipeServer listening', { meta: { port: this.port } })\n resolve()\n })\n })\n }\n\n /**\n * Broadcast raw encoded bytes to all connected clients.\n * Silently drops data for clients that are slow or disconnected.\n */\n broadcast(data: Buffer): void {\n for (const client of this.clients) {\n if (client.destroyed) {\n this.clients.delete(client)\n continue\n }\n\n // Drop data for slow clients instead of letting buffers grow unbounded\n if (client.writableLength > MAX_WRITE_BUFFER_BYTES) {\n this.logger?.warn(\n 'StreamPipeServer client buffer overflow — dropping client',\n { meta: { bufferMB: Number((client.writableLength / 1024 / 1024).toFixed(1)) } },\n )\n this.removeClient(client)\n continue\n }\n\n client.write(data, (err) => {\n if (err) {\n this.logger?.debug(\n 'StreamPipeServer write error, removing client',\n { meta: { error: err.message } },\n )\n this.removeClient(client)\n }\n })\n }\n }\n\n getPort(): number {\n return this.port\n }\n\n getUrl(): string {\n return `tcp://127.0.0.1:${this.port}`\n }\n\n getClientCount(): number {\n return this.clients.size\n }\n\n isStarted(): boolean {\n return this.started\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return\n }\n\n this.started = false\n this._onClientCountChanged = null\n\n for (const client of this.clients) {\n client.destroy()\n }\n this.clients.clear()\n\n await new Promise<void>((resolve) => {\n this.server.close(() => {\n this.logger?.debug('StreamPipeServer stopped')\n resolve()\n })\n })\n }\n\n private handleConnection(socket: net.Socket): void {\n this.clients.add(socket)\n this.logger?.debug(\n 'StreamPipeServer client connected',\n { meta: { total: this.clients.size } },\n )\n this._onClientCountChanged?.()\n\n socket.on('close', () => {\n this.removeClient(socket)\n })\n\n socket.on('error', () => {\n this.removeClient(socket)\n })\n }\n\n private removeClient(socket: net.Socket): void {\n const existed = this.clients.delete(socket)\n if (existed) {\n this.logger?.debug(\n 'StreamPipeServer client disconnected',\n { meta: { total: this.clients.size } },\n )\n this._onClientCountChanged?.()\n }\n if (!socket.destroyed) {\n socket.destroy()\n }\n }\n}\n","import type { EncodedPacket } from '@camstack/types'\n\n/**\n * Time-based ring buffer for encoded video packets.\n *\n * Keeps the most recent N seconds of encoded packets, evicting old ones\n * based on timestamp. Used for pre-buffering: when an addon needs the last\n * N seconds of video (e.g., for clip creation), it calls `getPackets()`.\n *\n * The buffer always starts from the most recent keyframe to ensure\n * consumers can decode the returned data.\n */\nexport class EncodedRingBuffer {\n private readonly packets: EncodedPacket[] = []\n private maxDurationMs: number\n\n constructor(maxDurationSec: number = 10) {\n this.maxDurationMs = maxDurationSec * 1000\n }\n\n /** Update the buffer duration (in seconds). Evicts old packets immediately. */\n setDuration(seconds: number): void {\n this.maxDurationMs = seconds * 1000\n this.evict()\n }\n\n /** Get the current buffer duration in seconds. */\n getDuration(): number {\n return this.maxDurationMs / 1000\n }\n\n /** Push a new packet into the buffer and evict expired ones. */\n push(packet: EncodedPacket): void {\n this.packets.push(packet)\n this.evict()\n }\n\n /**\n * Get buffered packets, starting from the most recent keyframe.\n * Returns packets in chronological order (oldest first).\n * Returns an empty array if no keyframe is found.\n */\n getPackets(): readonly EncodedPacket[] {\n // Find the last keyframe index — consumers need a keyframe to start decoding\n let lastKeyframeIdx = -1\n for (let i = this.packets.length - 1; i >= 0; i--) {\n if (this.packets[i]!.keyframe && this.packets[i]!.type === 'video') {\n lastKeyframeIdx = i\n break\n }\n }\n\n if (lastKeyframeIdx < 0) return []\n\n return this.packets.slice(lastKeyframeIdx)\n }\n\n /** Get the approximate duration of buffered content in seconds. */\n getBufferedDurationMs(): number {\n if (this.packets.length < 2) return 0\n const first = this.packets[0]!\n const last = this.packets[this.packets.length - 1]!\n return last.pts - first.pts\n }\n\n /** Get the number of packets in the buffer. */\n getPacketCount(): number {\n return this.packets.length\n }\n\n /** Clear all buffered packets. */\n clear(): void {\n this.packets.length = 0\n }\n\n /** Remove packets older than maxDurationMs based on PTS. */\n private evict(): void {\n if (this.packets.length === 0) return\n\n const cutoff = this.packets[this.packets.length - 1]!.pts - this.maxDurationMs\n let removeCount = 0\n\n while (removeCount < this.packets.length && this.packets[removeCount]!.pts < cutoff) {\n removeCount++\n }\n\n if (removeCount > 0) {\n this.packets.splice(0, removeCount)\n }\n }\n}\n","/** RTP header + payload ready to send over TCP interleaved. */\nexport interface RtpPacket {\n /** RTP payload including 12-byte header. */\n readonly data: Buffer\n /** Whether this packet starts an access unit (for keyframe detection). */\n readonly marker: boolean\n}\n\n/** Parsed RTSP request from a client. */\nexport interface RtspRequest {\n readonly method: string\n readonly uri: string\n readonly cseq: number\n readonly headers: ReadonlyMap<string, string>\n readonly body?: string\n}\n\n/** Track setup state per client session. */\nexport interface TrackSetup {\n readonly trackId: number\n readonly rtpChannel: number\n readonly rtcpChannel: number\n}\n\n/** Client session state. */\nexport interface ClientSession {\n readonly sessionId: string\n readonly tracks: ReadonlyArray<TrackSetup>\n readonly playing: boolean\n}\n\n/** Codec parameters extracted from the stream. */\nexport interface CodecParams {\n readonly codec: 'h264' | 'h265'\n /** Parameter set NALs (SPS+PPS for H.264, VPS+SPS+PPS for H.265). */\n readonly parameterSets: ReadonlyArray<Buffer>\n readonly width?: number\n readonly height?: number\n}\n\n/** RTSP method constants. */\nexport const RTSP_METHODS = ['OPTIONS', 'DESCRIBE', 'SETUP', 'PLAY', 'TEARDOWN', 'GET_PARAMETER'] as const\n\n/** RTP constants. */\nexport const RTP_VERSION = 2\nexport const RTP_HEADER_SIZE = 12\nexport const RTP_MAX_PAYLOAD = 1400 // Safe MTU for TCP interleaved\n\n/** H.264 NAL type masks. */\nexport const H264_NAL_TYPE_MASK = 0x1f\nexport const H264_NAL_FUA = 28\n\n/** H.265 NAL type extraction: (byte0 >> 1) & 0x3f. */\nexport const H265_NAL_TYPE_SHIFT = 1\nexport const H265_NAL_TYPE_MASK = 0x3f\nexport const H265_NAL_FU = 49\n\n/** TCP interleaved framing: magic byte. */\nexport const INTERLEAVED_MAGIC = 0x24\n\n/** Max write buffer before dropping slow client. */\nexport const MAX_WRITE_BUFFER = 2 * 1024 * 1024\n","import {\n RTP_VERSION,\n RTP_HEADER_SIZE,\n RTP_MAX_PAYLOAD,\n H264_NAL_TYPE_MASK,\n H264_NAL_FUA,\n H265_NAL_TYPE_SHIFT,\n H265_NAL_TYPE_MASK,\n H265_NAL_FU,\n type RtpPacket,\n} from './rtsp-types.js'\n\nexport class RtpPacketizer {\n private sequenceNumber = 0\n private readonly ssrc: number\n\n constructor(\n private readonly codec: 'h264' | 'h265',\n private readonly payloadType: number,\n ) {\n this.ssrc = Math.floor(Math.random() * 0xffffffff)\n }\n\n /**\n * Split Annex-B byte stream into individual NAL units.\n * Strips 3-byte (0x000001) and 4-byte (0x00000001) start codes.\n */\n static splitAnnexB(data: Buffer): ReadonlyArray<Buffer> {\n const nals: Buffer[] = []\n let start = -1\n\n for (let i = 0; i < data.length - 2; i++) {\n const isStartCode3 = data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 1\n const isStartCode4 = i + 3 < data.length &&\n data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0 && data[i + 3] === 1\n\n if (isStartCode3 || isStartCode4) {\n if (start >= 0) {\n // Strip trailing zero bytes — they belong to the next start code prefix.\n // Emulation prevention (00 00 03) guarantees no false start codes in NAL payload.\n let end = i\n while (end > start && data[end - 1] === 0) end--\n if (end > start) nals.push(data.subarray(start, end))\n }\n start = isStartCode4 ? i + 4 : i + 3\n if (isStartCode4) i += 3\n else i += 2\n }\n }\n\n if (start >= 0 && start < data.length) {\n nals.push(data.subarray(start))\n }\n\n return nals\n }\n\n /**\n * Packetize a single NAL unit into one or more RTP packets.\n * @param nal NAL unit WITHOUT start code\n * @param timestamp RTP timestamp (90kHz clock)\n * @param marker true if this is the last NAL of the access unit\n */\n packetize(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n if (this.codec === 'h264') {\n return this.packetizeH264(nal, timestamp, marker)\n }\n return this.packetizeH265(nal, timestamp, marker)\n }\n\n private packetizeH264(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n if (nal.length <= RTP_MAX_PAYLOAD) {\n return [this.buildRtpPacket(nal, timestamp, marker)]\n }\n return this.fragmentH264FUA(nal, timestamp, marker)\n }\n\n private packetizeH265(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n if (nal.length <= RTP_MAX_PAYLOAD) {\n return [this.buildRtpPacket(nal, timestamp, marker)]\n }\n return this.fragmentH265FU(nal, timestamp, marker)\n }\n\n /** H.264 FU-A fragmentation (RFC 6184 Section 5.8). */\n private fragmentH264FUA(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n const nalHeader = nal[0]!\n const fnri = nalHeader & 0xe0\n const nalType = nalHeader & H264_NAL_TYPE_MASK\n const payload = nal.subarray(1)\n\n const maxFragment = RTP_MAX_PAYLOAD - 2\n const packets: RtpPacket[] = []\n let offset = 0\n\n while (offset < payload.length) {\n const end = Math.min(offset + maxFragment, payload.length)\n const isFirst = offset === 0\n const isLast = end === payload.length\n\n const fuIndicator = fnri | H264_NAL_FUA\n const fuHeader = (isFirst ? 0x80 : 0) | (isLast ? 0x40 : 0) | nalType\n\n const fragment = Buffer.allocUnsafe(2 + (end - offset))\n fragment[0] = fuIndicator\n fragment[1] = fuHeader\n payload.copy(fragment, 2, offset, end)\n\n packets.push(this.buildRtpPacket(fragment, timestamp, isLast && marker))\n offset = end\n }\n\n return packets\n }\n\n /** H.265 FU fragmentation (RFC 7798 Section 4.4.3). */\n private fragmentH265FU(nal: Buffer, timestamp: number, marker: boolean): ReadonlyArray<RtpPacket> {\n const nalType = (nal[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n const tidLayerId = nal[1]!\n const payload = nal.subarray(2)\n\n const maxFragment = RTP_MAX_PAYLOAD - 3\n const packets: RtpPacket[] = []\n let offset = 0\n\n while (offset < payload.length) {\n const end = Math.min(offset + maxFragment, payload.length)\n const isFirst = offset === 0\n const isLast = end === payload.length\n\n const payloadHdr0 = (H265_NAL_FU << H265_NAL_TYPE_SHIFT) | (nal[0]! & 0x81)\n const payloadHdr1 = tidLayerId\n const fuHeader = (isFirst ? 0x80 : 0) | (isLast ? 0x40 : 0) | nalType\n\n const fragment = Buffer.allocUnsafe(3 + (end - offset))\n fragment[0] = payloadHdr0\n fragment[1] = payloadHdr1\n fragment[2] = fuHeader\n payload.copy(fragment, 3, offset, end)\n\n packets.push(this.buildRtpPacket(fragment, timestamp, isLast && marker))\n offset = end\n }\n\n return packets\n }\n\n private buildRtpPacket(payload: Buffer, timestamp: number, marker: boolean): RtpPacket {\n const header = Buffer.allocUnsafe(RTP_HEADER_SIZE)\n header[0] = RTP_VERSION << 6\n header[1] = (marker ? 0x80 : 0) | (this.payloadType & 0x7f)\n header.writeUInt16BE(this.sequenceNumber & 0xffff, 2)\n this.sequenceNumber = (this.sequenceNumber + 1) & 0xffff\n header.writeUInt32BE(timestamp >>> 0, 4)\n header.writeUInt32BE(this.ssrc >>> 0, 8)\n\n return {\n data: Buffer.concat([header, payload]),\n marker,\n }\n }\n}\n","/**\n * Annex-B NAL unit deframer.\n *\n * ffmpeg outputs Annex-B via stdout in arbitrary chunk sizes — a single\n * `data` event may contain partial NALs, multiple NALs, or NALs split\n * across events. This class accumulates bytes and emits complete NAL units.\n *\n * Usage:\n * const deframer = new AnnexBDeframer((nal, isKeyframe) => { ... })\n * proc.stdout.on('data', chunk => deframer.push(chunk))\n */\nexport class AnnexBDeframer {\n private buffer: Buffer<ArrayBufferLike> = Buffer.alloc(0)\n private readonly codec: 'h264' | 'h265'\n\n constructor(\n codec: 'h264' | 'h265',\n private readonly onNal: (nal: Buffer, keyframe: boolean) => void,\n ) {\n this.codec = codec\n }\n\n /** Push raw Annex-B bytes. Complete NAL units are emitted via onNal callback. */\n push(chunk: Buffer): void {\n this.buffer = this.buffer.length > 0\n ? Buffer.concat([this.buffer, chunk])\n : chunk\n\n this.drain()\n }\n\n /** Flush remaining buffer (call on stream end). */\n flush(): void {\n if (this.buffer.length > 0) {\n const nal = this.stripLeadingStartCode(this.buffer)\n if (nal.length > 0) {\n this.onNal(nal, this.isKeyframeNal(nal))\n }\n this.buffer = Buffer.alloc(0)\n }\n }\n\n /** Extract complete NAL units from the buffer, leaving partial data for next push. */\n private drain(): void {\n while (true) {\n // Find first start code in buffer\n const firstSc = this.findStartCode(0)\n if (firstSc < 0) {\n // No start code found at all — keep everything (partial NAL or leading junk)\n return\n }\n\n // Find the NEXT start code after the first one\n const nalStart = firstSc + (this.isStartCode4(firstSc) ? 4 : 3)\n const nextSc = this.findStartCode(nalStart)\n\n if (nextSc < 0) {\n // Only one start code — NAL is not yet complete, wait for more data.\n // Keep buffer from firstSc onwards (discard junk before first start code).\n if (firstSc > 0) {\n this.buffer = Buffer.from(this.buffer.subarray(firstSc))\n }\n return\n }\n\n // We have a complete NAL between firstSc and nextSc\n const nal = this.buffer.subarray(nalStart, nextSc)\n if (nal.length > 0) {\n this.onNal(nal, this.isKeyframeNal(nal))\n }\n\n // Advance buffer past this NAL (keep from nextSc onwards)\n this.buffer = Buffer.from(this.buffer.subarray(nextSc))\n }\n }\n\n /** Find the byte offset of the next start code (00 00 01 or 00 00 00 01) at or after `from`. */\n private findStartCode(from: number): number {\n const buf = this.buffer\n const len = buf.length - 2\n for (let i = from; i < len; i++) {\n // Fast skip: if buf[i+2] is not 0 or 1, no start code can begin at i\n if (buf[i + 2]! > 1) {\n i += 2\n continue\n }\n if (buf[i] === 0 && buf[i + 1] === 0) {\n if (buf[i + 2] === 1) return i\n if (buf[i + 2] === 0 && i + 3 < buf.length && buf[i + 3] === 1) return i\n }\n }\n return -1\n }\n\n /** Check if start code at `pos` is 4-byte (00 00 00 01) vs 3-byte (00 00 01). */\n private isStartCode4(pos: number): boolean {\n return pos + 3 < this.buffer.length &&\n this.buffer[pos] === 0 &&\n this.buffer[pos + 1] === 0 &&\n this.buffer[pos + 2] === 0 &&\n this.buffer[pos + 3] === 1\n }\n\n /** Strip leading start code from a buffer (if present). */\n private stripLeadingStartCode(buf: Buffer): Buffer {\n if (buf.length >= 4 && buf[0] === 0 && buf[1] === 0 && buf[2] === 0 && buf[3] === 1) {\n return buf.subarray(4)\n }\n if (buf.length >= 3 && buf[0] === 0 && buf[1] === 0 && buf[2] === 1) {\n return buf.subarray(3)\n }\n return buf\n }\n\n /** Check if a NAL unit (without start code) is a keyframe. */\n private isKeyframeNal(nal: Buffer): boolean {\n if (nal.length === 0) return false\n if (this.codec === 'h264') {\n const type = nal[0]! & 0x1f\n return type === 5 // IDR\n }\n const type = (nal[0]! >> 1) & 0x3f\n return type >= 16 && type <= 21 // IRAP\n }\n}\n","export interface SdpOptions {\n readonly codec: 'h264' | 'h265'\n readonly parameterSets: ReadonlyArray<Buffer>\n readonly streamName: string\n}\n\nexport function buildSdp(options: SdpOptions): string {\n const { codec, parameterSets, streamName } = options\n const lines: string[] = [\n 'v=0',\n `o=- ${Date.now()} 1 IN IP4 0.0.0.0`,\n `s=${streamName}`,\n 't=0 0',\n 'a=tool:camstack-rtsp-restream',\n 'm=video 0 RTP/AVP 96',\n 'c=IN IP4 0.0.0.0',\n 'b=AS:10000',\n ]\n\n if (codec === 'h264') {\n lines.push('a=rtpmap:96 H264/90000')\n const sps = parameterSets[0]\n const spropSets = parameterSets.map(ps => ps.toString('base64')).join(',')\n const profileLevelId = sps && sps.length >= 4\n ? Buffer.from([sps[1]!, sps[2]!, sps[3]!]).toString('hex')\n : '42001e'\n lines.push(`a=fmtp:96 packetization-mode=1;profile-level-id=${profileLevelId};sprop-parameter-sets=${spropSets}`)\n } else {\n lines.push('a=rtpmap:96 H265/90000')\n const fmtpParts = parameterSets.map((ps, i) => {\n const prefix = i === 0 ? 'sprop-vps' : i === 1 ? 'sprop-sps' : 'sprop-pps'\n return `${prefix}=${ps.toString('base64')}`\n })\n lines.push(`a=fmtp:96 ${fmtpParts.join(';')}`)\n }\n\n lines.push('a=control:trackID=0')\n lines.push('')\n return lines.join('\\r\\n')\n}\n","import type { EncodedPacket, IScopedLogger } from '@camstack/types'\nimport { RtpPacketizer } from './rtp-packetizer.js'\nimport { AnnexBDeframer } from './annexb-deframer.js'\nimport { buildSdp } from './sdp-builder.js'\nimport { RtspSession } from './rtsp-session.js'\nimport type { CodecParams, RtpPacket } from './rtsp-types.js'\n\n/**\n * Per-broker RTSP restreamer.\n *\n * Operates in two modes:\n *\n * **Annex-B mode** (legacy, ffmpeg-based):\n * Receives raw Annex-B chunks via pushPacket(), deframes into NALs,\n * RTP-packetizes, and broadcasts to RTSP sessions.\n *\n * **RTP passthrough mode** (native RTSP client):\n * Receives the camera's SDP via setCameraSdp() and raw RTP packets\n * via pushRtpPassthrough(). Forwards RTP directly to clients — zero\n * re-packetization, preserving the camera's original framing.\n */\nexport class RtspRestreamer {\n private readonly sessions = new Map<string, RtspSession>()\n /** Sessions that need a keyframe before receiving regular frames. */\n private readonly pendingKeyframe = new Set<string>()\n private packetizer: RtpPacketizer | null = null\n private codecParams: CodecParams | null = null\n private sdp: string | null = null\n private mutedSdp: string | null = null\n private deframer: AnnexBDeframer | null = null\n private detectedCodec: 'h264' | 'h265' | null = null\n\n /** Cached last keyframe NALs (SPS/PPS + IDR) for late-joining clients. */\n private lastKeyframeNals: Buffer[] = []\n private lastKeyframeTimestamp = 0\n /** True if we're currently accumulating a keyframe's NALs. */\n private collectingKeyframe = false\n\n /** Current RTP timestamp for the batch of NALs being processed. */\n private currentTimestamp = 0\n\n /** RTP passthrough: cached last keyframe RTP packets for late-joining clients. */\n private lastKeyframeRtpPackets: RtpPacket[] = []\n /** True when in RTP passthrough mode (setCameraSdp was called). */\n private _rtpPassthroughMode = false\n /** Accumulating keyframe RTP packets in passthrough mode. */\n private collectingKeyframeRtp = false\n\n private logger: IScopedLogger | null = null\n private _onSessionCountChanged: (() => void) | null = null\n\n constructor(private readonly streamName: string) {}\n\n /** True when using RTP passthrough (native client) instead of Annex-B re-packetization. */\n get rtpPassthroughMode(): boolean { return this._rtpPassthroughMode }\n\n /** Register a callback invoked when RTSP client count changes. */\n onSessionCountChanged(cb: () => void): void { this._onSessionCountChanged = cb }\n\n setLogger(logger: IScopedLogger): void {\n this.logger = logger\n }\n\n /**\n * Reset all stream-session state. Called on broker suspend so stale\n * keyframe caches / codec params from the previous session don't leak\n * into the next one after resume.\n */\n resetStreamState(): void {\n this.lastKeyframeNals = []\n this.lastKeyframeTimestamp = 0\n this.collectingKeyframe = false\n this.currentTimestamp = 0\n this.packetizer = null\n this.codecParams = null\n this.sdp = null\n this.mutedSdp = null\n this.deframer = null\n this.detectedCodec = null\n this.lastKeyframeRtpPackets = []\n this._rtpPassthroughMode = false\n this.collectingKeyframeRtp = false\n }\n\n /** True when codec params are detected and SDP is available — safe to accept PLAY. */\n isReady(): boolean { return this.codecParams !== null && this.packetizer !== null }\n\n getCodecParams(): CodecParams | null { return this.codecParams }\n getSdp(): string | null { return this.sdp }\n getMutedSdp(): string | null { return this.mutedSdp ?? this.sdp }\n getSessionCount(): number { return this.sessions.size }\n\n /** Per-session diagnostic snapshots consumed by `StreamBroker.listClients`. */\n listSessionInfos(): readonly ReturnType<RtspSession['getInfo']>[] {\n return [...this.sessions.values()].map((s) => s.getInfo())\n }\n\n addSession(session: RtspSession): void {\n this.sessions.set(session.getSessionId(), session)\n this.pendingKeyframe.add(session.getSessionId())\n this.logger?.info('RTSP client connected', {\n meta: { streamName: this.streamName, total: this.sessions.size },\n })\n this._onSessionCountChanged?.()\n\n if (this._rtpPassthroughMode) {\n // RTP passthrough: serve cached keyframe RTP packets to late-joining clients\n if (this.lastKeyframeRtpPackets.length > 0) {\n setTimeout(() => this.servePendingSessionsRtp(), 0)\n }\n } else {\n // Annex-B mode: serve cached keyframe NALs via packetizer\n if (this.lastKeyframeNals.length > 0 && this.packetizer) {\n setTimeout(() => this.servePendingSessions(), 0)\n }\n }\n }\n\n // ── RTP Passthrough Mode ──────────��───────────────────────────────\n\n /**\n * Set the camera's SDP directly (from DESCRIBE response).\n * Switches the restreamer to RTP passthrough mode — subsequent calls\n * should use pushRtpPassthrough() instead of pushPacket().\n */\n setCameraSdp(sdpText: string, codec: 'h264' | 'h265', codecParams: CodecParams | null): void {\n this._rtpPassthroughMode = true\n this.detectedCodec = codec\n this.codecParams = codecParams\n this.sdp = sdpText\n // Build a video-only SDP for muted sessions (strip all m=audio sections)\n this.mutedSdp = stripAudioFromSdp(sdpText)\n this.logger?.info('RTP passthrough mode — SDP set', {\n meta: { streamName: this.streamName, codec },\n })\n }\n\n /**\n * Push a raw RTP packet from the native RTSP client.\n * Forwarded directly to all connected RTSP sessions — zero re-packetization.\n *\n * @param rtpData Complete RTP packet (header + payload)\n * @param isKeyframe True if this packet is part of a keyframe access unit\n * @param isParameterSet True if this packet contains SPS/PPS/VPS\n */\n pushRtpPassthrough(rtpData: Buffer, isKeyframe: boolean, isParameterSet: boolean): void {\n if (this.sessions.size === 0 && !isKeyframe && !isParameterSet) return\n\n const rtpPacket: RtpPacket = { data: rtpData, marker: (rtpData[1]! & 0x80) !== 0 }\n\n // Cache keyframe RTP packets for late-joining clients\n if (isKeyframe || isParameterSet) {\n if (!this.collectingKeyframeRtp) {\n this.collectingKeyframeRtp = true\n this.lastKeyframeRtpPackets = []\n }\n }\n\n if (this.collectingKeyframeRtp) {\n this.lastKeyframeRtpPackets.push({ data: Buffer.from(rtpData), marker: rtpPacket.marker })\n if (!isKeyframe && !isParameterSet) {\n this.collectingKeyframeRtp = false\n }\n }\n\n if (this.sessions.size === 0) return\n\n // Serve pending sessions (late joiners waiting for keyframe)\n this.servePendingSessionsRtp()\n\n // Broadcast to all active sessions\n for (const session of this.sessions.values()) {\n if (this.pendingKeyframe.has(session.getSessionId())) continue\n if (session.isPlaying()) {\n session.sendRtp(rtpPacket)\n }\n }\n }\n\n /** Send cached keyframe RTP packets to sessions that transitioned to PLAY. */\n private servePendingSessionsRtp(): void {\n if (this.pendingKeyframe.size === 0) return\n if (this.lastKeyframeRtpPackets.length === 0) return\n\n const ready: string[] = []\n for (const sessionId of this.pendingKeyframe) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) ready.push(sessionId)\n }\n\n if (ready.length === 0) return\n\n for (const rtpPacket of this.lastKeyframeRtpPackets) {\n for (const sessionId of ready) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) session.sendRtp(rtpPacket)\n }\n }\n\n for (const id of ready) this.pendingKeyframe.delete(id)\n }\n\n /**\n * Push a raw audio RTP packet from the native RTSP client.\n * Forwarded to all non-muted sessions. Muted sessions receive no audio.\n */\n pushAudioRtpPassthrough(rtpData: Buffer): void {\n if (this.sessions.size === 0) return\n\n const rtpPacket: RtpPacket = { data: rtpData, marker: (rtpData[1]! & 0x80) !== 0 }\n\n for (const session of this.sessions.values()) {\n if (session.isMuted()) continue\n if (this.pendingKeyframe.has(session.getSessionId())) continue\n if (session.isPlaying()) {\n session.sendAudioRtp(rtpPacket)\n }\n }\n }\n\n removeSession(sessionId: string): void {\n this.sessions.delete(sessionId)\n this.pendingKeyframe.delete(sessionId)\n this.logger?.info('RTSP client disconnected', {\n meta: { streamName: this.streamName, total: this.sessions.size },\n })\n this._onSessionCountChanged?.()\n }\n\n /**\n * Force-close + remove a session by id. Used by the admin `killClient`\n * cap to let operators kick a stuck holder. Returns `true` if the\n * session existed and was dropped, `false` if the id doesn't match.\n */\n killSession(sessionId: string): boolean {\n const session = this.sessions.get(sessionId)\n if (!session) return false\n try { session.close() } catch { /* best-effort close */ }\n this.removeSession(sessionId)\n return true\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (packet.type !== 'video') return\n\n this.currentTimestamp = Math.floor(packet.pts * 90) // ms → 90kHz\n\n // Initialize deframer on first packet (need codec to detect keyframes)\n if (!this.deframer) {\n this.detectedCodec = (packet.codec === 'h265' || packet.codec === 'hevc') ? 'h265' : 'h264'\n this.deframer = new AnnexBDeframer(this.detectedCodec, (nal, isKeyframe) => {\n this.handleNal(nal, isKeyframe)\n })\n }\n\n // NOTE: keyframe collection is triggered inside handleNal() when the\n // deframer emits a complete keyframe NAL — NOT from packet.keyframe,\n // which is unreliable because ffmpeg chunks don't align to NAL boundaries.\n\n // Push raw bytes — the deframer emits complete NALs via handleNal callback\n this.deframer.push(packet.data)\n }\n\n destroy(): void {\n this._onSessionCountChanged = null\n this.deframer?.flush()\n for (const session of this.sessions.values()) {\n session.close()\n }\n this.sessions.clear()\n this.packetizer = null\n this.deframer = null\n }\n\n /** Called by deframer for each complete NAL unit. */\n private handleNal(nal: Buffer, isKeyframe: boolean): void {\n // Initialize codec params from parameter set NALs (SPS/PPS/VPS)\n if (!this.codecParams) {\n this.tryInitCodec(nal)\n }\n\n // Start keyframe collection when the deframer emits a keyframe or\n // parameter set NAL. This is reliable because the deframer works on\n // complete NAL units — unlike packet.keyframe which scans raw chunks\n // that may split a keyframe across two ffmpeg data events.\n if (isKeyframe || this.isParameterSetNal(nal)) {\n if (!this.collectingKeyframe) {\n this.collectingKeyframe = true\n this.lastKeyframeNals = []\n this.lastKeyframeTimestamp = this.currentTimestamp\n }\n }\n\n // Cache keyframe NALs\n if (this.collectingKeyframe) {\n this.lastKeyframeNals.push(Buffer.from(nal))\n // Stop collecting after the first non-parameter-set, non-keyframe NAL\n // (parameter sets + IDR = the keyframe, then P/B frames start)\n if (!isKeyframe && !this.isParameterSetNal(nal)) {\n this.collectingKeyframe = false\n }\n }\n\n // Skip RTP if no clients connected or no packetizer yet\n if (this.sessions.size === 0) return\n if (!this.packetizer) return\n\n // Serve pending sessions: send cached keyframe when they transition to PLAY\n this.servePendingSessions()\n\n // RTP packetize and broadcast\n const rtpPackets = this.packetizer.packetize(nal, this.currentTimestamp, true)\n for (const rtp of rtpPackets) {\n for (const session of this.sessions.values()) {\n if (this.pendingKeyframe.has(session.getSessionId())) continue\n if (session.isPlaying()) {\n session.sendRtp(rtp)\n }\n }\n }\n }\n\n /** Send cached keyframe to sessions that have transitioned to PLAY. */\n private servePendingSessions(): void {\n if (this.pendingKeyframe.size === 0) return\n\n const ready: string[] = []\n for (const sessionId of this.pendingKeyframe) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) ready.push(sessionId)\n }\n\n if (ready.length === 0) return\n if (this.lastKeyframeNals.length === 0 || !this.packetizer) return\n\n // Send cached keyframe NALs to each ready session\n for (const keyNal of this.lastKeyframeNals) {\n const rtpPackets = this.packetizer.packetize(keyNal, this.lastKeyframeTimestamp, true)\n for (const rtp of rtpPackets) {\n for (const sessionId of ready) {\n const session = this.sessions.get(sessionId)\n if (session?.isPlaying()) session.sendRtp(rtp)\n }\n }\n }\n\n for (const id of ready) this.pendingKeyframe.delete(id)\n }\n\n /** Try to initialize codec params from a parameter set NAL (SPS/PPS for H264, VPS/SPS/PPS for H265). */\n private tryInitCodec(nal: Buffer): void {\n if (!this.isParameterSetNal(nal)) return\n if (!this.detectedCodec) return\n\n // Accumulate parameter sets until we have enough\n if (!this.codecParams) {\n // Start a new collection\n const existing = this.lastKeyframeNals.filter(n => this.isParameterSetNal(n))\n const all = [...existing, nal]\n\n const codec = this.detectedCodec\n const needed = codec === 'h264' ? 2 : 3 // SPS+PPS or VPS+SPS+PPS\n if (all.length >= needed) {\n this.codecParams = { codec, parameterSets: all.slice(0, needed) }\n this.packetizer = new RtpPacketizer(codec, 96)\n this.sdp = buildSdp({ codec, parameterSets: all.slice(0, needed), streamName: this.streamName })\n this.mutedSdp = buildSdp({ codec, parameterSets: all.slice(0, needed), streamName: `${this.streamName} (muted)` })\n }\n }\n }\n\n /** Check if a NAL is a parameter set (SPS, PPS, or VPS). */\n private isParameterSetNal(nal: Buffer): boolean {\n if (nal.length === 0) return false\n if (this.detectedCodec === 'h264') {\n const type = nal[0]! & 0x1f\n return type === 7 || type === 8 // SPS, PPS\n }\n const type = (nal[0]! >> 1) & 0x3f\n return type === 32 || type === 33 || type === 34 // VPS, SPS, PPS\n }\n}\n\n/**\n * Strip audio media sections from an SDP string.\n * Returns a video-only SDP for muted RTSP sessions.\n *\n * SDP structure: session-level lines, then m= blocks.\n * We keep session-level lines and all m= blocks that aren't audio.\n */\nfunction stripAudioFromSdp(sdpText: string): string {\n const lines = sdpText.split(/\\r?\\n/)\n const result: string[] = []\n let inAudioSection = false\n\n for (const line of lines) {\n if (line.startsWith('m=')) {\n inAudioSection = line.startsWith('m=audio')\n }\n if (!inAudioSection) {\n result.push(line)\n }\n }\n\n return result.join('\\r\\n')\n}\n","import type { CodecParams } from './rtsp-types.js'\n\n/** Parsed representation of an SDP media section (m= block). */\nexport interface SdpMediaSection {\n /** Media type: \"video\", \"audio\", \"application\". */\n readonly type: string\n /** RTP port (0 for TCP interleaved). */\n readonly port: number\n /** Transport protocol (e.g. \"RTP/AVP\"). */\n readonly protocol: string\n /** Payload types listed on the m= line. */\n readonly payloadTypes: ReadonlyArray<number>\n /** Codec name from a=rtpmap (e.g. \"H264\", \"H265\", \"PCMU\"). */\n readonly codec: string | null\n /** Clock rate from a=rtpmap (e.g. 90000 for video). */\n readonly clockRate: number | null\n /** Audio channel count from a=rtpmap (null for video). */\n readonly channels: number | null\n /** Parsed a=fmtp parameters as key-value pairs. */\n readonly fmtp: Readonly<Record<string, string>>\n /** Track control URL from a=control (may be relative or absolute). */\n readonly control: string | null\n /** Raw SDP lines for this section. */\n readonly lines: ReadonlyArray<string>\n}\n\n/** Result of parsing a full SDP response. */\nexport interface ParsedSdp {\n /** Session-level lines (before the first m= line). */\n readonly sessionLines: ReadonlyArray<string>\n /** Session-level a=control (base URL for relative track controls). */\n readonly sessionControl: string | null\n /** All parsed media sections. */\n readonly mediaSections: ReadonlyArray<SdpMediaSection>\n}\n\n/** Parsed video track info ready for native RTSP client use. */\nexport interface SdpVideoTrack {\n /** Index of this media section in the SDP. */\n readonly sectionIndex: number\n /** Codec: \"h264\" or \"h265\". */\n readonly codec: 'h264' | 'h265'\n /** RTP payload type (usually 96+). */\n readonly payloadType: number\n /** Clock rate (90000 for video). */\n readonly clockRate: number\n /** Track control URL from a=control. */\n readonly control: string | null\n /** H.264 profile-level-id (hex string, e.g. \"42001e\"). Null for H.265. */\n readonly profileLevelId: string | null\n /** Codec parameters extracted from SDP fmtp (SPS/PPS for H264, VPS/SPS/PPS for H265). */\n readonly codecParams: CodecParams | null\n}\n\n/** Parsed audio track info. */\nexport interface SdpAudioTrack {\n /** Index of this media section in the SDP. */\n readonly sectionIndex: number\n /** Codec name (e.g. \"PCMU\", \"PCMA\", \"MPEG4-GENERIC\", \"opus\"). */\n readonly codec: string\n /** RTP payload type. */\n readonly payloadType: number\n /** Clock rate (e.g. 8000, 48000). */\n readonly clockRate: number\n /** Channel count. */\n readonly channels: number\n /** Track control URL from a=control. */\n readonly control: string | null\n /** Raw fmtp parameters. */\n readonly fmtp: Readonly<Record<string, string>>\n}\n\n/**\n * Parse a raw SDP text into structured sections.\n *\n * Handles:\n * - Session-level and media-level attributes\n * - Multiple media sections (video, audio, application)\n * - a=rtpmap, a=fmtp, a=control extraction\n * - Tolerant of \\r\\n and \\n line endings\n */\nexport function parseSdp(sdpText: string): ParsedSdp {\n const lines = sdpText.split(/\\r?\\n/).filter(l => l.length > 0)\n\n const sessionLines: string[] = []\n const sections: Array<{ mLine: string; lines: string[] }> = []\n let currentSection: { mLine: string; lines: string[] } | null = null\n\n for (const line of lines) {\n if (line.startsWith('m=')) {\n currentSection = { mLine: line, lines: [line] }\n sections.push(currentSection)\n } else if (currentSection) {\n currentSection.lines.push(line)\n } else {\n sessionLines.push(line)\n }\n }\n\n const sessionControl = extractAttribute(sessionLines, 'control')\n\n const mediaSections = sections.map(s => parseMediaSection(s.mLine, s.lines))\n\n return { sessionLines, sessionControl, mediaSections }\n}\n\n/**\n * Extract the first video track from a parsed SDP.\n * Returns null if no supported video codec found.\n */\nexport function extractVideoTrack(parsed: ParsedSdp): SdpVideoTrack | null {\n for (let i = 0; i < parsed.mediaSections.length; i++) {\n const section = parsed.mediaSections[i]!\n if (section.type !== 'video') continue\n\n const codecUpper = section.codec?.toUpperCase() ?? null\n if (codecUpper !== 'H264' && codecUpper !== 'H265') continue\n\n const codec: 'h264' | 'h265' = codecUpper === 'H264' ? 'h264' : 'h265'\n const payloadType = section.payloadTypes[0] ?? 96\n const clockRate = section.clockRate ?? 90000\n\n const codecParams = extractCodecParams(codec, section.fmtp)\n const profileLevelId = codec === 'h264'\n ? (section.fmtp['profile-level-id'] ?? null)\n : null\n\n return {\n sectionIndex: i,\n codec,\n payloadType,\n clockRate,\n control: section.control,\n profileLevelId,\n codecParams,\n }\n }\n return null\n}\n\n/**\n * Extract the first audio track from a parsed SDP.\n * Returns null if no audio section found.\n */\nexport function extractAudioTrack(parsed: ParsedSdp): SdpAudioTrack | null {\n for (let i = 0; i < parsed.mediaSections.length; i++) {\n const section = parsed.mediaSections[i]!\n if (section.type !== 'audio') continue\n\n return {\n sectionIndex: i,\n codec: section.codec ?? 'unknown',\n payloadType: section.payloadTypes[0] ?? 0,\n clockRate: section.clockRate ?? 8000,\n channels: section.channels ?? 1,\n control: section.control,\n fmtp: section.fmtp,\n }\n }\n return null\n}\n\n/**\n * Resolve a track control URL against the session-level control / RTSP base URL.\n *\n * Cameras vary wildly:\n * - Absolute: \"rtsp://192.168.1.100/trackID=1\"\n * - Relative: \"trackID=1\" or \"track1\"\n * - Session control: \"rtsp://192.168.1.100/stream1\" with track \"trackID=1\"\n */\nexport function resolveTrackUrl(\n baseUrl: string,\n sessionControl: string | null,\n trackControl: string | null,\n): string {\n if (!trackControl) return baseUrl\n\n // Absolute URL — use as-is\n if (trackControl.startsWith('rtsp://') || trackControl.startsWith('rtsps://')) {\n return trackControl\n }\n\n // Use session control as base if it's an absolute URL\n const base = (sessionControl && (sessionControl.startsWith('rtsp://') || sessionControl.startsWith('rtsps://')))\n ? sessionControl\n : baseUrl\n\n // Append track control to base\n const separator = base.endsWith('/') ? '' : '/'\n return `${base}${separator}${trackControl}`\n}\n\n// ── Internal helpers ────────────────────────────────────────────────\n\nfunction parseMediaSection(mLine: string, lines: ReadonlyArray<string>): SdpMediaSection {\n // m=<type> <port> <protocol> <payloadType1> [payloadType2 ...]\n const mParts = mLine.substring(2).split(/\\s+/)\n const type = mParts[0] ?? 'unknown'\n const port = parseInt(mParts[1] ?? '0', 10)\n const protocol = mParts[2] ?? 'RTP/AVP'\n const payloadTypes = mParts.slice(3).map(p => parseInt(p, 10)).filter(n => !isNaN(n))\n\n const primaryPt = payloadTypes[0]\n\n // Parse a=rtpmap:<pt> <codec>/<clockRate>[/<channels>]\n let codec: string | null = null\n let clockRate: number | null = null\n let channels: number | null = null\n\n for (const line of lines) {\n const rtpmapMatch = line.match(/^a=rtpmap:(\\d+)\\s+([^/]+)\\/(\\d+)(?:\\/(\\d+))?/)\n if (rtpmapMatch) {\n const pt = parseInt(rtpmapMatch[1]!, 10)\n if (pt === primaryPt) {\n codec = rtpmapMatch[2]!\n clockRate = parseInt(rtpmapMatch[3]!, 10)\n channels = rtpmapMatch[4] ? parseInt(rtpmapMatch[4], 10) : null\n }\n }\n }\n\n // Fallback for well-known static payload types (no rtpmap line)\n if (!codec && primaryPt !== undefined) {\n const staticCodec = STATIC_PAYLOAD_TYPES.get(primaryPt)\n if (staticCodec) {\n codec = staticCodec.codec\n clockRate = staticCodec.clockRate\n channels = staticCodec.channels\n }\n }\n\n // Parse a=fmtp:<pt> <key>=<value>;<key>=<value>;...\n const fmtp = parseFmtp(lines, primaryPt)\n\n // Parse a=control:<url>\n const control = extractAttribute(lines, 'control')\n\n return { type, port, protocol, payloadTypes, codec, clockRate, channels, fmtp, control, lines }\n}\n\nfunction parseFmtp(\n lines: ReadonlyArray<string>,\n payloadType: number | undefined,\n): Record<string, string> {\n const result: Record<string, string> = {}\n\n for (const line of lines) {\n const fmtpMatch = line.match(/^a=fmtp:(\\d+)\\s+(.+)/)\n if (!fmtpMatch) continue\n const pt = parseInt(fmtpMatch[1]!, 10)\n if (payloadType !== undefined && pt !== payloadType) continue\n\n const paramsStr = fmtpMatch[2]!\n // Split on ';' then on first '='\n for (const part of paramsStr.split(/;\\s*/)) {\n const eqIdx = part.indexOf('=')\n if (eqIdx > 0) {\n const key = part.substring(0, eqIdx).trim().toLowerCase()\n const value = part.substring(eqIdx + 1).trim()\n result[key] = value\n }\n }\n }\n\n return result\n}\n\nfunction extractAttribute(lines: ReadonlyArray<string>, name: string): string | null {\n for (const line of lines) {\n if (line.startsWith(`a=${name}:`)) {\n return line.substring(name.length + 3).trim()\n }\n }\n return null\n}\n\n/**\n * Extract CodecParams (parameter set buffers) from SDP fmtp.\n *\n * H.264: sprop-parameter-sets=<base64-SPS>,<base64-PPS>\n * H.265: sprop-vps=<base64>; sprop-sps=<base64>; sprop-pps=<base64>\n *\n * Returns null if parameter sets are missing from SDP (some cameras\n * only send them in-band via RTP).\n */\nfunction extractCodecParams(\n codec: 'h264' | 'h265',\n fmtp: Readonly<Record<string, string>>,\n): CodecParams | null {\n if (codec === 'h264') {\n const spropSets = fmtp['sprop-parameter-sets']\n if (!spropSets) return null\n\n const parts = spropSets.split(',').filter(s => s.length > 0)\n if (parts.length < 2) return null\n\n const parameterSets = parts.map(p => Buffer.from(p, 'base64'))\n return { codec, parameterSets }\n }\n\n // H.265: need all three\n const vpsB64 = fmtp['sprop-vps']\n const spsB64 = fmtp['sprop-sps']\n const ppsB64 = fmtp['sprop-pps']\n if (!vpsB64 || !spsB64 || !ppsB64) return null\n\n const parameterSets = [\n Buffer.from(vpsB64, 'base64'),\n Buffer.from(spsB64, 'base64'),\n Buffer.from(ppsB64, 'base64'),\n ]\n return { codec, parameterSets }\n}\n\n/** Well-known static RTP payload types (RFC 3551). */\nconst STATIC_PAYLOAD_TYPES = new Map<number, { codec: string; clockRate: number; channels: number }>([\n [0, { codec: 'PCMU', clockRate: 8000, channels: 1 }],\n [8, { codec: 'PCMA', clockRate: 8000, channels: 1 }],\n])\n","import * as net from 'node:net'\nimport { createHash, randomUUID } from 'node:crypto'\nimport type { IScopedLogger } from '@camstack/types'\nimport { INTERLEAVED_MAGIC } from './rtsp-types.js'\nimport { parseSdp, extractVideoTrack, extractAudioTrack, resolveTrackUrl } from './sdp-parser.js'\nimport type { ParsedSdp, SdpVideoTrack, SdpAudioTrack } from './sdp-parser.js'\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface RtspClientOptions {\n /** RTSP URL (rtsp://user:pass@host:port/path). */\n readonly url: string\n /** Override username (takes precedence over URL credentials). */\n readonly username?: string\n /** Override password (takes precedence over URL credentials). */\n readonly password?: string\n /** TCP connect timeout in ms (default 10000). */\n readonly connectTimeoutMs?: number\n /** Keepalive interval in ms (default 30000). GET_PARAMETER sent periodically. */\n readonly keepaliveIntervalMs?: number\n /** Logger instance. */\n readonly logger?: IScopedLogger\n}\n\nexport interface RtspClientCallbacks {\n /** Called for every RTP packet received on a video track. */\n readonly onVideoRtp?: (packet: Buffer, channel: number) => void\n /** Called for every RTP packet received on an audio track. */\n readonly onAudioRtp?: (packet: Buffer, channel: number) => void\n /** Called when video SDP info is available (after DESCRIBE). */\n readonly onVideoTrack?: (track: SdpVideoTrack, sdpText: string) => void\n /** Called when audio SDP info is available (after DESCRIBE). */\n readonly onAudioTrack?: (track: SdpAudioTrack) => void\n /** Called when connection is fully established (PLAY response received). */\n readonly onPlaying?: () => void\n /** Called on unrecoverable error or connection lost. */\n readonly onError?: (error: Error) => void\n /** Called when the client is cleanly torn down. */\n readonly onTeardown?: () => void\n}\n\ntype RtspState = 'idle' | 'connecting' | 'options' | 'describe' | 'setup-video' | 'setup-audio' | 'play' | 'streaming' | 'teardown' | 'closed'\n\ninterface DigestChallenge {\n readonly realm: string\n readonly nonce: string\n readonly qop?: string\n readonly opaque?: string\n}\n\n// ── Constants ───────────────────────────────────────────────────────\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 10_000\nconst DEFAULT_KEEPALIVE_INTERVAL_MS = 30_000\nconst RTSP_PORT = 554\n\n// ── Implementation ──────────────────────────────────────────────────\n\n/**\n * Native RTSP client that connects directly to IP cameras via TCP.\n *\n * Replaces the ffmpeg RTSP reader. Instead of spawning ffmpeg to read\n * RTSP → Annex-B pipe, this client speaks RTSP natively and delivers\n * raw RTP packets — preserving RTP framing that ffmpeg's Annex-B output\n * destroys.\n *\n * State machine: connect → OPTIONS → DESCRIBE → SETUP(video) → SETUP(audio) → PLAY → streaming\n *\n * Features:\n * - TCP interleaved transport (RTP over RTSP connection)\n * - Basic + Digest authentication (auto-detect from 401 response)\n * - GET_PARAMETER keepalive\n * - Clean teardown\n */\nexport class NativeRtspClient {\n private state: RtspState = 'idle'\n private socket: net.Socket | null = null\n private cseq = 0\n private sessionId: string | null = null\n\n private readonly url: string\n private readonly host: string\n private readonly port: number\n private readonly username: string\n private readonly password: string\n private readonly connectTimeoutMs: number\n private readonly keepaliveIntervalMs: number\n private readonly logger: IScopedLogger | undefined\n private readonly callbacks: RtspClientCallbacks\n\n /** Parsed SDP from DESCRIBE response. */\n private parsedSdp: ParsedSdp | null = null\n private rawSdpText: string | null = null\n private videoTrack: SdpVideoTrack | null = null\n private audioTrack: SdpAudioTrack | null = null\n\n /** Channel assignments for TCP interleaved transport. */\n private videoRtpChannel = 0\n private videoRtcpChannel = 1\n private audioRtpChannel = 2\n private audioRtcpChannel = 3\n\n /** Authentication state. */\n private authMethod: 'none' | 'basic' | 'digest' = 'none'\n private digestChallenge: DigestChallenge | null = null\n private digestNc = 0\n\n /** TCP read buffer for interleaved framing + RTSP response parsing. */\n private readBuffer: Buffer = Buffer.alloc(0)\n\n /** Pending RTSP response handler (one request at a time). */\n private pendingResponse: ((statusCode: number, headers: ReadonlyMap<string, string>, body: string) => void) | null = null\n\n /** Keepalive timer. */\n private keepaliveTimer: ReturnType<typeof setInterval> | undefined\n\n /** Connect timeout. */\n private connectTimer: ReturnType<typeof setTimeout> | undefined\n\n /** Whether a teardown was requested (prevents reconnect on close). */\n private tornDown = false\n\n constructor(options: RtspClientOptions, callbacks: RtspClientCallbacks) {\n this.callbacks = callbacks\n this.logger = options.logger\n this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS\n this.keepaliveIntervalMs = options.keepaliveIntervalMs ?? DEFAULT_KEEPALIVE_INTERVAL_MS\n\n // Parse URL\n const parsed = new URL(options.url)\n this.url = options.url\n this.host = parsed.hostname\n this.port = parsed.port ? parseInt(parsed.port, 10) : RTSP_PORT\n this.username = options.username ?? decodeURIComponent(parsed.username)\n this.password = options.password ?? decodeURIComponent(parsed.password)\n }\n\n /** Start the RTSP session: connect → negotiate → stream. */\n async connect(): Promise<void> {\n if (this.state !== 'idle' && this.state !== 'closed') {\n throw new Error(`Cannot connect in state ${this.state}`)\n }\n\n this.tornDown = false\n this.state = 'connecting'\n this.cseq = 0\n this.sessionId = null\n this.authMethod = 'none'\n this.digestChallenge = null\n this.digestNc = 0\n this.readBuffer = Buffer.alloc(0)\n\n return new Promise<void>((resolve, reject) => {\n const socket = net.createConnection({ host: this.host, port: this.port })\n this.socket = socket\n\n this.connectTimer = setTimeout(() => {\n this.connectTimer = undefined\n socket.destroy(new Error(`RTSP connect timeout after ${this.connectTimeoutMs}ms`))\n }, this.connectTimeoutMs)\n\n socket.on('connect', () => {\n if (this.connectTimer) {\n clearTimeout(this.connectTimer)\n this.connectTimer = undefined\n }\n this.logger?.info('Connected', { meta: { host: this.host, port: this.port } })\n\n this.negotiate()\n .then(() => resolve())\n .catch((err) => {\n this.destroy()\n reject(err)\n })\n })\n\n socket.on('data', (chunk: Buffer) => {\n this.onData(chunk)\n })\n\n socket.on('close', () => {\n if (this.state === 'streaming' && !this.tornDown) {\n this.callbacks.onError?.(new Error('RTSP connection closed unexpectedly'))\n }\n this.cleanup()\n })\n\n socket.on('error', (err) => {\n if (this.connectTimer) {\n clearTimeout(this.connectTimer)\n this.connectTimer = undefined\n }\n if (this.state !== 'closed' && this.state !== 'teardown') {\n this.callbacks.onError?.(err)\n }\n this.cleanup()\n reject(err)\n })\n })\n }\n\n /** Gracefully tear down the RTSP session. */\n async teardown(): Promise<void> {\n if (this.state === 'closed' || this.state === 'idle') return\n this.tornDown = true\n this.state = 'teardown'\n\n try {\n if (this.socket && !this.socket.destroyed) {\n await this.sendRequest('TEARDOWN', this.url)\n }\n } catch {\n // Best-effort teardown\n }\n\n this.destroy()\n this.callbacks.onTeardown?.()\n }\n\n /** Forcefully destroy the connection without TEARDOWN. */\n destroy(): void {\n this.tornDown = true\n this.cleanup()\n }\n\n /** Get the parsed SDP text (available after DESCRIBE). */\n getSdpText(): string | null { return this.rawSdpText }\n\n /** Get the parsed SDP (available after DESCRIBE). */\n getParsedSdp(): ParsedSdp | null { return this.parsedSdp }\n\n /** Get the video track info (available after DESCRIBE). */\n getVideoTrack(): SdpVideoTrack | null { return this.videoTrack }\n\n /** Current client state. */\n getState(): RtspState { return this.state }\n\n // ── RTSP Negotiation ──────────────────────────────────────────────\n\n private async negotiate(): Promise<void> {\n // Step 1: OPTIONS (optional but standard — probes server capabilities)\n this.state = 'options'\n await this.sendRequest('OPTIONS', this.url)\n\n // Step 2: DESCRIBE — get SDP\n this.state = 'describe'\n const describeResult = await this.sendRequest('DESCRIBE', this.url, {\n 'Accept': 'application/sdp',\n })\n\n if (describeResult.statusCode === 401) {\n // Parse auth challenge and retry\n this.parseAuthChallenge(describeResult.headers)\n const retryResult = await this.sendRequest('DESCRIBE', this.url, {\n 'Accept': 'application/sdp',\n })\n if (retryResult.statusCode !== 200) {\n throw new Error(`DESCRIBE failed after auth: ${retryResult.statusCode}`)\n }\n this.processSdp(retryResult.body)\n } else if (describeResult.statusCode === 200) {\n this.processSdp(describeResult.body)\n } else {\n throw new Error(`DESCRIBE failed: ${describeResult.statusCode}`)\n }\n\n if (!this.videoTrack) {\n throw new Error('No supported video track in SDP')\n }\n\n // Step 3: SETUP video track\n this.state = 'setup-video'\n const videoControlUrl = resolveTrackUrl(\n this.url,\n this.parsedSdp?.sessionControl ?? null,\n this.videoTrack.control,\n )\n\n const videoSetupResult = await this.sendRequest('SETUP', videoControlUrl, {\n 'Transport': `RTP/AVP/TCP;unicast;interleaved=${this.videoRtpChannel}-${this.videoRtcpChannel}`,\n })\n\n if (videoSetupResult.statusCode !== 200) {\n throw new Error(`SETUP video failed: ${videoSetupResult.statusCode}`)\n }\n\n // Extract session ID from first SETUP response\n const sessionHeader = videoSetupResult.headers.get('session')\n if (sessionHeader) {\n // Session header may contain timeout: \"12345678;timeout=60\"\n this.sessionId = sessionHeader.split(';')[0]!.trim()\n }\n\n // Step 4: SETUP audio track (optional)\n if (this.audioTrack) {\n this.state = 'setup-audio'\n const audioControlUrl = resolveTrackUrl(\n this.url,\n this.parsedSdp?.sessionControl ?? null,\n this.audioTrack.control,\n )\n\n const audioSetupResult = await this.sendRequest('SETUP', audioControlUrl, {\n 'Transport': `RTP/AVP/TCP;unicast;interleaved=${this.audioRtpChannel}-${this.audioRtcpChannel}`,\n })\n\n if (audioSetupResult.statusCode !== 200) {\n this.logger?.warn('SETUP audio failed — continuing without audio', {\n meta: { statusCode: audioSetupResult.statusCode },\n })\n this.audioTrack = null\n }\n }\n\n // Step 5: PLAY\n this.state = 'play'\n const playResult = await this.sendRequest('PLAY', this.url, {\n 'Range': 'npt=0.000-',\n })\n\n if (playResult.statusCode !== 200) {\n throw new Error(`PLAY failed: ${playResult.statusCode}`)\n }\n\n this.state = 'streaming'\n this.logger?.info('RTSP streaming', { meta: { host: this.host, port: this.port } })\n\n // Start keepalive\n this.keepaliveTimer = setInterval(() => {\n this.sendKeepalive().catch((err) => {\n this.logger?.warn('Keepalive failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }, this.keepaliveIntervalMs)\n\n this.callbacks.onPlaying?.()\n }\n\n private processSdp(sdpText: string): void {\n this.rawSdpText = sdpText\n this.parsedSdp = parseSdp(sdpText)\n this.videoTrack = extractVideoTrack(this.parsedSdp)\n this.audioTrack = extractAudioTrack(this.parsedSdp)\n\n if (this.videoTrack) {\n this.callbacks.onVideoTrack?.(this.videoTrack, sdpText)\n this.logger?.info('Video track', {\n meta: {\n codec: this.videoTrack.codec,\n payloadType: this.videoTrack.payloadType,\n clockRate: this.videoTrack.clockRate,\n },\n })\n }\n\n if (this.audioTrack) {\n this.callbacks.onAudioTrack?.(this.audioTrack)\n this.logger?.info('Audio track', {\n meta: {\n codec: this.audioTrack.codec,\n payloadType: this.audioTrack.payloadType,\n clockRate: this.audioTrack.clockRate,\n },\n })\n }\n }\n\n // ── TCP Data Handling ─────────────────────────────────────────────\n\n /**\n * Process incoming TCP data. RTSP TCP interleaved transport mixes:\n * - RTSP text responses (e.g. \"RTSP/1.0 200 OK\\r\\n...\")\n * - Interleaved binary frames ($channel + 2-byte length + payload)\n */\n private onData(chunk: Buffer): void {\n this.readBuffer = this.readBuffer.length > 0\n ? Buffer.concat([this.readBuffer, chunk])\n : chunk\n\n while (this.readBuffer.length > 0) {\n if (this.readBuffer[0] === INTERLEAVED_MAGIC) {\n // Binary interleaved frame: $ + channel(1) + length(2) + payload\n if (this.readBuffer.length < 4) return // Need more data\n\n const channel = this.readBuffer[1]!\n const length = this.readBuffer.readUInt16BE(2)\n\n if (this.readBuffer.length < 4 + length) return // Need more data\n\n const payload = Buffer.from(this.readBuffer.subarray(4, 4 + length))\n this.readBuffer = this.readBuffer.subarray(4 + length)\n\n this.handleInterleavedPacket(channel, payload)\n } else {\n // RTSP text response\n const headerEnd = this.readBuffer.indexOf('\\r\\n\\r\\n')\n if (headerEnd < 0) return // Need more data\n\n const headerText = this.readBuffer.subarray(0, headerEnd).toString('ascii')\n let bodyStart = headerEnd + 4\n\n // Check for Content-Length\n const clMatch = headerText.match(/Content-Length:\\s*(\\d+)/i)\n const contentLength = clMatch ? parseInt(clMatch[1]!, 10) : 0\n\n if (this.readBuffer.length < bodyStart + contentLength) return // Need more data\n\n const body = contentLength > 0\n ? this.readBuffer.subarray(bodyStart, bodyStart + contentLength).toString('utf-8')\n : ''\n\n this.readBuffer = this.readBuffer.subarray(bodyStart + contentLength)\n\n this.handleRtspResponse(headerText, body)\n }\n }\n }\n\n private handleInterleavedPacket(channel: number, payload: Buffer): void {\n if (channel === this.videoRtpChannel) {\n this.callbacks.onVideoRtp?.(payload, channel)\n } else if (channel === this.audioRtpChannel) {\n this.callbacks.onAudioRtp?.(payload, channel)\n }\n // RTCP channels (odd) are silently ignored for now\n }\n\n private handleRtspResponse(headerText: string, body: string): void {\n const lines = headerText.split('\\r\\n')\n const statusLine = lines[0] ?? ''\n const statusMatch = statusLine.match(/RTSP\\/1\\.\\d\\s+(\\d+)/)\n const statusCode = statusMatch ? parseInt(statusMatch[1]!, 10) : 0\n\n const headers = new Map<string, string>()\n for (let i = 1; i < lines.length; i++) {\n const colonIdx = lines[i]!.indexOf(':')\n if (colonIdx < 0) continue\n const key = lines[i]!.substring(0, colonIdx).trim().toLowerCase()\n const value = lines[i]!.substring(colonIdx + 1).trim()\n headers.set(key, value)\n }\n\n if (this.pendingResponse) {\n const handler = this.pendingResponse\n this.pendingResponse = null\n handler(statusCode, headers, body)\n }\n }\n\n // ── RTSP Request Building ─────────────────────────────────────────\n\n private sendRequest(\n method: string,\n uri: string,\n extraHeaders?: Record<string, string>,\n ): Promise<{ statusCode: number; headers: ReadonlyMap<string, string>; body: string }> {\n return new Promise((resolve, reject) => {\n if (!this.socket || this.socket.destroyed) {\n reject(new Error('Socket not connected'))\n return\n }\n\n this.cseq++\n const headers: Record<string, string> = {\n 'CSeq': String(this.cseq),\n 'User-Agent': 'camstack-rtsp-client',\n ...extraHeaders,\n }\n\n if (this.sessionId) {\n headers['Session'] = this.sessionId\n }\n\n // Add authentication header\n const authHeader = this.buildAuthHeader(method, uri)\n if (authHeader) {\n headers['Authorization'] = authHeader\n }\n\n let request = `${method} ${uri} RTSP/1.0\\r\\n`\n for (const [key, value] of Object.entries(headers)) {\n request += `${key}: ${value}\\r\\n`\n }\n request += '\\r\\n'\n\n this.pendingResponse = (statusCode, responseHeaders, body) => {\n resolve({ statusCode, headers: responseHeaders, body })\n }\n\n // Timeout for response\n const timeout = setTimeout(() => {\n if (this.pendingResponse) {\n this.pendingResponse = null\n reject(new Error(`RTSP ${method} response timeout`))\n }\n }, this.connectTimeoutMs)\n\n const originalResolve = this.pendingResponse\n this.pendingResponse = (statusCode, responseHeaders, body) => {\n clearTimeout(timeout)\n originalResolve(statusCode, responseHeaders, body)\n }\n\n this.socket.write(request)\n })\n }\n\n private async sendKeepalive(): Promise<void> {\n if (this.state !== 'streaming' || !this.socket) return\n await this.sendRequest('GET_PARAMETER', this.url)\n }\n\n // ── Authentication ────────────────────────────────────────────────\n\n private parseAuthChallenge(headers: ReadonlyMap<string, string>): void {\n const wwwAuth = headers.get('www-authenticate')\n if (!wwwAuth) return\n\n if (wwwAuth.toLowerCase().startsWith('digest')) {\n this.authMethod = 'digest'\n const realm = extractQuoted(wwwAuth, 'realm') ?? ''\n const nonce = extractQuoted(wwwAuth, 'nonce') ?? ''\n const qop = extractQuoted(wwwAuth, 'qop')\n const opaque = extractQuoted(wwwAuth, 'opaque')\n this.digestChallenge = { realm, nonce, qop, opaque }\n this.digestNc = 0\n } else if (wwwAuth.toLowerCase().startsWith('basic')) {\n this.authMethod = 'basic'\n }\n }\n\n private buildAuthHeader(method: string, uri: string): string | null {\n if (!this.username && !this.password) return null\n\n if (this.authMethod === 'basic') {\n const encoded = Buffer.from(`${this.username}:${this.password}`).toString('base64')\n return `Basic ${encoded}`\n }\n\n if (this.authMethod === 'digest' && this.digestChallenge) {\n return this.buildDigestAuth(method, uri)\n }\n\n // First request — try Basic (some cameras accept it without 401)\n if (this.username || this.password) {\n const encoded = Buffer.from(`${this.username}:${this.password}`).toString('base64')\n return `Basic ${encoded}`\n }\n\n return null\n }\n\n private buildDigestAuth(method: string, uri: string): string {\n const { realm, nonce, qop, opaque } = this.digestChallenge!\n const ha1 = md5(`${this.username}:${realm}:${this.password}`)\n\n let response: string\n let authParams: string\n\n if (qop === 'auth' || qop?.includes('auth')) {\n this.digestNc++\n const nc = this.digestNc.toString(16).padStart(8, '0')\n const cnonce = randomUUID().replace(/-/g, '').slice(0, 16)\n const ha2 = md5(`${method}:${uri}`)\n response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:auth:${ha2}`)\n authParams = `username=\"${this.username}\", realm=\"${realm}\", nonce=\"${nonce}\", uri=\"${uri}\", qop=auth, nc=${nc}, cnonce=\"${cnonce}\", response=\"${response}\"`\n } else {\n const ha2 = md5(`${method}:${uri}`)\n response = md5(`${ha1}:${nonce}:${ha2}`)\n authParams = `username=\"${this.username}\", realm=\"${realm}\", nonce=\"${nonce}\", uri=\"${uri}\", response=\"${response}\"`\n }\n\n if (opaque) {\n authParams += `, opaque=\"${opaque}\"`\n }\n\n return `Digest ${authParams}`\n }\n\n // ── Cleanup ───────────────────────────────────────────────────────\n\n private cleanup(): void {\n this.state = 'closed'\n\n if (this.keepaliveTimer) {\n clearInterval(this.keepaliveTimer)\n this.keepaliveTimer = undefined\n }\n\n if (this.connectTimer) {\n clearTimeout(this.connectTimer)\n this.connectTimer = undefined\n }\n\n if (this.socket) {\n try { this.socket.destroy() } catch { /* already closed */ }\n this.socket = null\n }\n\n this.pendingResponse = null\n }\n}\n\n// ── Utilities ───────────────────────────────────────────────────────\n\nfunction md5(input: string): string {\n return createHash('md5').update(input).digest('hex')\n}\n\nfunction extractQuoted(header: string, key: string): string | undefined {\n // Match key=\"value\" or key=value\n const regex = new RegExp(`${key}=\"([^\"]*)\"`, 'i')\n const match = header.match(regex)\n if (match) return match[1]\n\n // Try unquoted\n const unquotedRegex = new RegExp(`${key}=([^,\\\\s]+)`, 'i')\n const unquotedMatch = header.match(unquotedRegex)\n return unquotedMatch?.[1]\n}\n","/**\n * RFC 4571 framed-RTP-over-TCP reader.\n *\n * Format on the wire (per RFC 4571):\n *\n * ┌─────────────┬────────────────────────────┐\n * │ length (BE) │ RTP packet (length bytes) │\n * │ uint16 │ │\n * └─────────────┴────────────────────────────┘\n *\n * No RTSP signalling, no '$' magic byte, no interleaved channel id —\n * just length-prefixed framed RTP. Stream descriptions arrive\n * out-of-band as an SDP string (handed in by the publisher), and\n * the reader routes packets to video / audio depacketizers based on\n * the RTP payload type each track advertised in its `m=` / rtpmap line.\n *\n * Used to ingest the local TCP server spawned by the Reolink lib's\n * `createRfc4571TcpServer` — same wire format the scrypted Reolink\n * plugin consumes through its `x-scrypted/x-rfc4571` MediaObject.\n *\n * The callbacks here mirror `NativeRtspClient` so the broker's\n * `startNativeRtspReader` glue code can be reused unchanged: same\n * `onVideoTrack` / `onVideoRtp` / `onAudioRtp` / `onConnected` /\n * `onError` / `onDisconnected` handles.\n */\nimport * as net from 'node:net'\nimport { parseSdp, extractVideoTrack, extractAudioTrack } from './sdp-parser.js'\nimport type { ParsedSdp, SdpVideoTrack, SdpAudioTrack } from './sdp-parser.js'\nimport type { IScopedLogger } from '@camstack/types'\n\nexport interface Rfc4571ReaderOptions {\n /** Loopback URL the lib's RFC 4571 server emitted (`tcp://[user:pass@]host:port`). */\n readonly url: string\n /** SDP describing the video + (optional) audio track, returned by the lib's server. */\n readonly sdp: string\n readonly logger?: IScopedLogger\n}\n\n/**\n * Reader callbacks. Signature deliberately mirrors `RtspClientCallbacks`\n * (channel arg is always 0 here — RFC 4571 has no interleaved channel\n * concept, but keeping the shape lets the broker reuse the same handler\n * objects across both readers without an adapter layer).\n */\nexport interface Rfc4571ReaderCallbacks {\n readonly onVideoTrack?: (track: SdpVideoTrack, sdpText: string) => void\n readonly onAudioTrack?: (track: SdpAudioTrack) => void\n readonly onVideoRtp?: (rtpData: Buffer, channel: number) => void\n readonly onAudioRtp?: (rtpData: Buffer, channel: number) => void\n readonly onPlaying?: () => void\n readonly onError?: (error: Error) => void\n readonly onTeardown?: () => void\n}\n\nexport class Rfc4571Reader {\n private readonly options: Rfc4571ReaderOptions\n private readonly callbacks: Rfc4571ReaderCallbacks\n private readonly logger: IScopedLogger | undefined\n private readonly host: string\n private readonly port: number\n private readonly authLine: Buffer | null\n private socket: net.Socket | null = null\n /** Concatenated TCP read buffer awaiting full frames. */\n private readBuffer: Buffer = Buffer.alloc(0)\n private parsedSdp: ParsedSdp | null = null\n private videoTrack: SdpVideoTrack | null = null\n private audioTrack: SdpAudioTrack | null = null\n private destroyed = false\n\n constructor(options: Rfc4571ReaderOptions, callbacks: Rfc4571ReaderCallbacks) {\n this.options = options\n this.callbacks = callbacks\n this.logger = options.logger\n const parsed = new URL(options.url)\n this.host = parsed.hostname\n this.port = parseInt(parsed.port || '0', 10)\n if (this.port === 0) {\n throw new Error(`Rfc4571Reader: URL must include a port — got '${options.url}'`)\n }\n // The lib's RFC 4571 server (when spawned with `requireAuth: true`)\n // expects `username:password\\n` as the very first bytes on the socket\n // before any framed RTP. After 5s without a match it drops the\n // connection. Username/password are percent-encoded in the URL —\n // decode them back to their raw form before composing the auth line.\n if (parsed.username || parsed.password) {\n const u = decodeURIComponent(parsed.username)\n const p = decodeURIComponent(parsed.password)\n this.authLine = Buffer.from(`${u}:${p}\\n`, 'utf8')\n } else {\n this.authLine = null\n }\n }\n\n async connect(): Promise<void> {\n // Parse the SDP up front. RFC 4571 has no DESCRIBE phase; the publisher\n // handed us the stream description out-of-band so the broker knows\n // codec + payload-type → track routing before the first byte arrives.\n this.parsedSdp = parseSdp(this.options.sdp)\n this.videoTrack = extractVideoTrack(this.parsedSdp)\n this.audioTrack = extractAudioTrack(this.parsedSdp)\n\n if (this.videoTrack) {\n this.callbacks.onVideoTrack?.(this.videoTrack, this.options.sdp)\n }\n if (this.audioTrack) {\n this.callbacks.onAudioTrack?.(this.audioTrack)\n }\n\n await this.openSocket()\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n if (this.socket) {\n try { this.socket.destroy() } catch { /* ignore */ }\n this.socket = null\n }\n }\n\n private openSocket(): Promise<void> {\n return new Promise((resolve, reject) => {\n let settled = false\n const socket = net.createConnection({ host: this.host, port: this.port }, () => {\n this.logger?.info('rfc4571: tcp connected', { meta: { host: this.host, port: this.port } })\n // The lib's server requires `username:password\\n` as the first\n // bytes when started with `requireAuth: true`. Send it before\n // anything else — the server starts framed-RTP delivery only\n // after a successful auth line.\n if (this.authLine) socket.write(this.authLine)\n // RFC 4571 has no PLAY phase — `onPlaying` fires the moment the TCP\n // socket is up, signalling \"data is about to flow\". Mirrors the\n // semantic the broker's RTSP path expects post-PLAY.\n this.callbacks.onPlaying?.()\n settled = true\n resolve()\n })\n this.socket = socket\n // Disable Nagle: video packets are small, latency matters more than\n // coalescing for a loopback connection.\n socket.setNoDelay(true)\n\n socket.on('data', (chunk) => this.onData(chunk))\n socket.on('error', (err) => {\n this.logger?.warn('rfc4571: tcp error', { meta: { error: err.message } })\n if (!this.destroyed) {\n this.callbacks.onError?.(err)\n }\n if (!settled) reject(err)\n })\n socket.on('close', () => {\n this.logger?.info('rfc4571: tcp closed')\n if (!this.destroyed) {\n this.callbacks.onTeardown?.()\n }\n })\n })\n }\n\n private onData(chunk: Buffer): void {\n this.readBuffer = this.readBuffer.length > 0\n ? Buffer.concat([this.readBuffer, chunk])\n : chunk\n\n while (this.readBuffer.length >= 2) {\n const length = this.readBuffer.readUInt16BE(0)\n if (this.readBuffer.length < 2 + length) return // need more bytes\n\n const rtpPacket = Buffer.from(this.readBuffer.subarray(2, 2 + length))\n this.readBuffer = this.readBuffer.subarray(2 + length)\n\n this.dispatchRtp(rtpPacket)\n }\n }\n\n private dispatchRtp(rtpPacket: Buffer): void {\n if (rtpPacket.length < 2) return\n // RTP payload type is the lower 7 bits of byte 1.\n const payloadType = rtpPacket[1]! & 0x7f\n if (this.videoTrack && payloadType === this.videoTrack.payloadType) {\n this.callbacks.onVideoRtp?.(rtpPacket, 0)\n return\n }\n if (this.audioTrack && payloadType === this.audioTrack.payloadType) {\n this.callbacks.onAudioRtp?.(rtpPacket, 0)\n return\n }\n // Unknown PT — drop silently. Cameras occasionally send RTCP-style\n // packets multiplexed on the same channel; the broker doesn't care.\n }\n}\n","/**\n * FLV Audio/Video tag payload parser (RTMP messages).\n *\n * Standard FLV (Adobe spec) supports H.264 (codecId=7) and AAC.\n *\n * Enhanced RTMP / Enhanced FLV (Veovera spec, used by YouTube Live and most\n * modern Reolink firmware for HEVC streams) extends the layout:\n * byte 0:\n * bit 7 : IsExHeader (1 = enhanced layout follows)\n * bits 4..6 : PacketType (0=SequenceStart, 1=CodedFrames, 2=SequenceEnd,\n * 3=CodedFramesX, 4=Metadata, 5=MPEG2TS-SequenceStart)\n * bits 0..3 : FrameType (1=key, 2=inter, ...)\n * bytes 1..4 : FOURCC ('hvc1' / 'hev1' = H.265, 'av01' = AV1, 'vp09' = VP9)\n * remainder : codec/packet-type-specific payload\n *\n * Ported from `koush/scrypted/plugins/prebuffer-mixin/src/flv.ts` (ISC).\n * H.265 / enhanced-FLV branches added here.\n */\n\n// ── Standard FLV enums ────────────────────────────────────────────────\n\nexport enum VideoCodecId {\n JPEG = 1,\n SORENSON_H263 = 2,\n SCREEN_VIDEO = 3,\n ON2_VP6 = 4,\n ON2_VP6_WITH_ALPHA = 5,\n SCREEN_VIDEO_V2 = 6,\n H264 = 7,\n /**\n * Non-standard codecId for HEVC, predating the Veovera Enhanced-FLV\n * spec. Used by Reolink RLC-810/820/etc., ZLMediaKit, ffmpeg builds\n * with HEVC FLV patches, and MediaMTX. Layout matches codecId=7\n * (H.264) — the only difference is that SEQUENCE_HEADER carries\n * HEVCDecoderConfigurationRecord and NALU payloads contain HEVC NAL\n * units.\n */\n H265_LEGACY = 12,\n}\n\nexport enum VideoFrameType {\n KEY = 1,\n INTER = 2,\n DISPOSABLE_INTER = 3,\n GENERATED_KEYFRAME = 4,\n VIDEO_INFO = 5,\n}\n\nexport enum AvcPacketType {\n SEQUENCE_HEADER = 0,\n NALU = 1,\n END_OF_SEQUENCE = 2,\n}\n\nexport enum AudioSoundFormat {\n PCM_BE = 0,\n ADPCM = 1,\n MP3 = 2,\n PCM_LE = 3,\n NELLYMOSER_16K = 4,\n NELLYMOSER_8K = 5,\n NELLYMOSER = 6,\n G711_A = 7,\n G711_U = 8,\n AAC = 10,\n SPEEX = 11,\n MP3_8K = 14,\n}\n\nexport enum AacPacketType {\n SEQUENCE_HEADER = 0,\n RAW = 1,\n}\n\n// ── Enhanced FLV (Veovera) ────────────────────────────────────────────\n\nexport enum EnhancedPacketType {\n SEQUENCE_START = 0,\n CODED_FRAMES = 1,\n SEQUENCE_END = 2,\n CODED_FRAMES_X = 3,\n METADATA = 4,\n MPEG2TS_SEQUENCE_START = 5,\n}\n\n/** Canonical codec discriminator for the parsed video tag. */\nexport type VideoCodec = 'h264' | 'h265'\n\n// ── Decoder configuration record types ────────────────────────────────\n\nexport interface AvcDecoderConfigurationRecord {\n readonly configurationVersion: number\n readonly avcProfileIndication: number\n readonly profileCompatibility: number\n readonly avcLevelIndication: number\n /** NALU length-size = lengthSizeMinusOne + 1 (1, 2, or 4) */\n readonly lengthSizeMinusOne: number\n readonly sps: ReadonlyArray<Buffer>\n readonly pps: ReadonlyArray<Buffer>\n}\n\nexport interface HevcDecoderConfigurationRecord {\n readonly configurationVersion: number\n readonly generalProfileSpace: number\n readonly generalTierFlag: number\n readonly generalProfileIdc: number\n readonly generalProfileCompatibilityFlags: number\n readonly generalLevelIdc: number\n /** NALU length-size = lengthSizeMinusOne + 1 (1, 2, or 4) */\n readonly lengthSizeMinusOne: number\n readonly vps: ReadonlyArray<Buffer>\n readonly sps: ReadonlyArray<Buffer>\n readonly pps: ReadonlyArray<Buffer>\n}\n\n// ── Audio decoder configuration ───────────────────────────────────────\n\nexport interface AudioSpecificConfig {\n readonly audioObjectType: number\n readonly samplingFrequencyIndex: number\n readonly channelConfiguration: number\n}\n\n// ── Parsed tag types ──────────────────────────────────────────────────\n\ninterface FlvVideoTagBase {\n readonly frameType: VideoFrameType\n readonly codec: VideoCodec\n /** Composition-time offset in milliseconds (0 for enhanced FLV CODED_FRAMES_X). */\n readonly compositionTime: number\n}\n\nexport interface FlvVideoSequenceHeaderH264 extends FlvVideoTagBase {\n readonly kind: 'video-sequence-header'\n readonly codec: 'h264'\n readonly avcConfig: AvcDecoderConfigurationRecord\n}\n\nexport interface FlvVideoSequenceHeaderH265 extends FlvVideoTagBase {\n readonly kind: 'video-sequence-header'\n readonly codec: 'h265'\n readonly hevcConfig: HevcDecoderConfigurationRecord\n}\n\nexport interface FlvVideoCodedFrames extends FlvVideoTagBase {\n readonly kind: 'video-coded-frames'\n readonly nalus: ReadonlyArray<Buffer>\n}\n\nexport interface FlvVideoSequenceEnd extends FlvVideoTagBase {\n readonly kind: 'video-sequence-end'\n}\n\nexport type FlvVideoTag =\n | FlvVideoSequenceHeaderH264\n | FlvVideoSequenceHeaderH265\n | FlvVideoCodedFrames\n | FlvVideoSequenceEnd\n\nexport interface FlvAudioSequenceHeader {\n readonly kind: 'audio-sequence-header'\n readonly soundFormat: AudioSoundFormat\n readonly audioSpecificConfig: AudioSpecificConfig\n /** Raw 2-byte AudioSpecificConfig — useful for SDP / pushAudioInfo. */\n readonly audioSpecificConfigBytes: Buffer\n}\n\nexport interface FlvAudioRaw {\n readonly kind: 'audio-raw'\n readonly soundFormat: AudioSoundFormat\n readonly data: Buffer\n}\n\nexport type FlvAudioTag = FlvAudioSequenceHeader | FlvAudioRaw\n\n// ── Errors ────────────────────────────────────────────────────────────\n\nexport class FlvParseError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'FlvParseError'\n }\n}\n\nexport class UnsupportedCodecError extends FlvParseError {\n constructor(message: string) {\n super(message)\n this.name = 'UnsupportedCodecError'\n }\n}\n\n// ── Configuration record parsers ──────────────────────────────────────\n\nfunction parseAvcDecoderConfigurationRecord(buffer: Buffer): AvcDecoderConfigurationRecord {\n if (buffer.length < 7) {\n throw new FlvParseError('AVCDecoderConfigurationRecord too short')\n }\n\n const configurationVersion = buffer.readUInt8(0)\n const avcProfileIndication = buffer.readUInt8(1)\n const profileCompatibility = buffer.readUInt8(2)\n const avcLevelIndication = buffer.readUInt8(3)\n const lengthSizeMinusOne = buffer.readUInt8(4) & 0x03\n\n const numSPS = buffer.readUInt8(5) & 0x1f\n let pos = 6\n\n const sps: Buffer[] = []\n for (let i = 0; i < numSPS; i++) {\n if (pos + 2 > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord truncated reading SPS length')\n const len = buffer.readUInt16BE(pos)\n pos += 2\n if (pos + len > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord SPS exceeds buffer')\n sps.push(buffer.subarray(pos, pos + len))\n pos += len\n }\n\n const pps: Buffer[] = []\n if (pos < buffer.length) {\n const numPPS = buffer.readUInt8(pos)\n pos += 1\n for (let i = 0; i < numPPS; i++) {\n if (pos + 2 > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord truncated reading PPS length')\n const len = buffer.readUInt16BE(pos)\n pos += 2\n if (pos + len > buffer.length) throw new FlvParseError('AVCDecoderConfigurationRecord PPS exceeds buffer')\n pps.push(buffer.subarray(pos, pos + len))\n pos += len\n }\n }\n\n return {\n configurationVersion,\n avcProfileIndication,\n profileCompatibility,\n avcLevelIndication,\n lengthSizeMinusOne,\n sps,\n pps,\n }\n}\n\n/**\n * Parse HEVCDecoderConfigurationRecord (HVCC) per ISO/IEC 14496-15 §8.\n * Layout (relevant fields only — reserved bits skipped):\n *\n * u8 configurationVersion (= 1)\n * u8 general_profile_space(2) | general_tier_flag(1) | general_profile_idc(5)\n * u32 general_profile_compatibility_flags\n * u48 general_constraint_indicator_flags\n * u8 general_level_idc\n * u16 reserved(4) | min_spatial_segmentation_idc(12)\n * u8 reserved(6) | parallelismType(2)\n * u8 reserved(6) | chromaFormat(2)\n * u8 reserved(5) | bitDepthLumaMinus8(3)\n * u8 reserved(5) | bitDepthChromaMinus8(3)\n * u16 avgFrameRate\n * u8 constantFrameRate(2) | numTemporalLayers(3) | temporalIdNested(1) | lengthSizeMinusOne(2)\n * u8 numOfArrays\n * for each array:\n * u8 array_completeness(1) | reserved(1) | NAL_unit_type(6)\n * u16 numNalus\n * for each nalu: u16 nalUnitLength, then nalUnit bytes\n *\n * The fixed-size header is exactly 22 bytes; arrays start at offset 22.\n * NAL unit types of interest: 32 = VPS, 33 = SPS, 34 = PPS.\n */\nfunction parseHevcDecoderConfigurationRecord(buffer: Buffer): HevcDecoderConfigurationRecord {\n if (buffer.length < 23) {\n throw new FlvParseError('HEVCDecoderConfigurationRecord too short')\n }\n\n const configurationVersion = buffer.readUInt8(0)\n const byte1 = buffer.readUInt8(1)\n const generalProfileSpace = (byte1 >> 6) & 0x03\n const generalTierFlag = (byte1 >> 5) & 0x01\n const generalProfileIdc = byte1 & 0x1f\n const generalProfileCompatibilityFlags = buffer.readUInt32BE(2)\n // bytes 6..11 = general_constraint_indicator_flags (48 bits) — preserved but not exposed\n const generalLevelIdc = buffer.readUInt8(12)\n // bytes 13..14 = min_spatial_segmentation_idc (low 12 bits)\n // byte 15 = parallelismType\n // byte 16 = chromaFormat\n // byte 17 = bitDepthLumaMinus8\n // byte 18 = bitDepthChromaMinus8\n // bytes 19..20 = avgFrameRate\n // byte 21 = constantFrameRate(2) | numTemporalLayers(3) | temporalIdNested(1) | lengthSizeMinusOne(2)\n const lengthSizeMinusOne = buffer.readUInt8(21) & 0x03\n\n const numOfArrays = buffer.readUInt8(22)\n let pos = 23\n\n const vps: Buffer[] = []\n const sps: Buffer[] = []\n const pps: Buffer[] = []\n\n for (let a = 0; a < numOfArrays; a++) {\n if (pos + 3 > buffer.length) throw new FlvParseError('HEVCDecoderConfigurationRecord truncated reading array header')\n const nalUnitType = buffer.readUInt8(pos) & 0x3f\n const numNalus = buffer.readUInt16BE(pos + 1)\n pos += 3\n\n for (let n = 0; n < numNalus; n++) {\n if (pos + 2 > buffer.length) throw new FlvParseError('HEVCDecoderConfigurationRecord truncated reading NALU length')\n const len = buffer.readUInt16BE(pos)\n pos += 2\n if (pos + len > buffer.length) throw new FlvParseError('HEVCDecoderConfigurationRecord NALU exceeds buffer')\n const nal = buffer.subarray(pos, pos + len)\n pos += len\n\n if (nalUnitType === 32) vps.push(nal)\n else if (nalUnitType === 33) sps.push(nal)\n else if (nalUnitType === 34) pps.push(nal)\n // SEI / other NAL arrays are dropped — not needed for decoder init.\n }\n }\n\n return {\n configurationVersion,\n generalProfileSpace,\n generalTierFlag,\n generalProfileIdc,\n generalProfileCompatibilityFlags,\n generalLevelIdc,\n lengthSizeMinusOne,\n vps,\n sps,\n pps,\n }\n}\n\nfunction parseAudioSpecificConfig(buffer: Buffer): AudioSpecificConfig {\n if (buffer.length < 2) throw new FlvParseError('AudioSpecificConfig too short')\n const b0 = buffer.readUInt8(0)\n const b1 = buffer.readUInt8(1)\n return {\n audioObjectType: (b0 >> 3) & 0x1f,\n samplingFrequencyIndex: ((b0 & 0x07) << 1) | ((b1 >> 7) & 0x01),\n channelConfiguration: (b1 >> 3) & 0x0f,\n }\n}\n\n// ── NALU extraction (length-prefixed → list of NAL bodies) ────────────\n\nfunction extractLengthPrefixedNalus(payload: Buffer, lengthSize: number): Buffer[] {\n if (lengthSize < 1 || lengthSize > 4) {\n throw new FlvParseError(`Invalid NALU length-size: ${lengthSize}`)\n }\n const out: Buffer[] = []\n let pos = 0\n while (pos + lengthSize <= payload.length) {\n const len = payload.readUIntBE(pos, lengthSize)\n pos += lengthSize\n if (len === 0) continue\n if (pos + len > payload.length) {\n throw new FlvParseError(`NALU exceeds payload at pos=${pos} len=${len}`)\n }\n out.push(payload.subarray(pos, pos + len))\n pos += len\n }\n return out\n}\n\n// ── AAC ADTS sniffing (audio path runtime helper) ─────────────────────\n\nconst ADTS_SYNC_WORD = 0xfff\n\n/**\n * Returns true when `buffer` starts with the 12-bit AAC ADTS sync word\n * (0xFFF). Used by the RTMP reader to disambiguate AAC raw frames coming\n * directly from cameras that bypass the AAC sequence header step.\n */\nexport function looksLikeAdts(buffer: Buffer): boolean {\n if (buffer.length < 2) return false\n return ((buffer.readUInt8(0) << 4) | (buffer.readUInt8(1) >> 4)) === ADTS_SYNC_WORD\n}\n\n// ── Public entry points ───────────────────────────────────────────────\n\n/**\n * Parse an RTMP video tag (FLV message-type 9) payload.\n *\n * Throws `UnsupportedCodecError` for codecs we cannot route to AnnexB\n * downstream (anything other than H.264 / H.265).\n */\nexport function parseFlvVideoTag(buffer: Buffer): FlvVideoTag {\n if (buffer.length < 1) throw new FlvParseError('Video tag empty')\n const byte0 = buffer.readUInt8(0)\n const isExHeader = (byte0 & 0x80) !== 0\n\n if (isExHeader) {\n return parseEnhancedVideoTag(buffer)\n }\n\n // Standard FLV layout (codecId=7 H.264, or codecId=12 H.265-legacy)\n const frameType = (byte0 >> 4) as VideoFrameType\n const codecId = byte0 & 0x0f\n\n const codec: VideoCodec | null =\n codecId === VideoCodecId.H264 ? 'h264'\n : codecId === VideoCodecId.H265_LEGACY ? 'h265'\n : null\n if (codec === null) {\n throw new UnsupportedCodecError(`FLV video codecId=${codecId} not supported (only H.264 / H.265-legacy / H.265-enhanced)`)\n }\n\n return parseStandardLayoutVideoTag(buffer, codec, frameType, 4)\n}\n\n/**\n * Shared parser for the codecId=7 (H.264) / codecId=12 (H.265 legacy)\n * layout: byte0 frameType+codecId, byte1 packetType, bytes2..4 composition\n * time offset (signed), then payload (decoder config or length-prefixed\n * NALUs).\n */\nfunction parseStandardLayoutVideoTag(\n buffer: Buffer,\n codec: VideoCodec,\n frameType: VideoFrameType,\n lengthSize: number,\n): FlvVideoTag {\n if (buffer.length < 5) throw new FlvParseError(`${codec.toUpperCase()} video tag header truncated`)\n const packetType = buffer.readUInt8(1) as AvcPacketType\n const compositionTime = buffer.readIntBE(2, 3)\n\n if (packetType === AvcPacketType.SEQUENCE_HEADER) {\n if (codec === 'h264') {\n const avcConfig = parseAvcDecoderConfigurationRecord(buffer.subarray(5))\n return { kind: 'video-sequence-header', codec: 'h264', frameType, compositionTime, avcConfig }\n }\n const hevcConfig = parseHevcDecoderConfigurationRecord(buffer.subarray(5))\n return { kind: 'video-sequence-header', codec: 'h265', frameType, compositionTime, hevcConfig }\n }\n\n if (packetType === AvcPacketType.NALU) {\n const nalus = extractLengthPrefixedNalus(buffer.subarray(5), lengthSize)\n return { kind: 'video-coded-frames', codec, frameType, compositionTime, nalus }\n }\n\n if (packetType === AvcPacketType.END_OF_SEQUENCE) {\n return { kind: 'video-sequence-end', codec, frameType, compositionTime }\n }\n\n throw new FlvParseError(`Unknown AVC packet type: ${packetType}`)\n}\n\n/**\n * Same as `parseFlvVideoTag` but uses the supplied NALU length-size\n * when extracting CodedFrames payloads. Callers that have parsed a\n * prior SequenceHeader should prefer this overload for correctness on\n * cameras that ship a non-default `lengthSizeMinusOne`.\n */\nexport function parseFlvVideoTagWithLengthSize(buffer: Buffer, lengthSize: number): FlvVideoTag {\n if (buffer.length < 1) throw new FlvParseError('Video tag empty')\n const byte0 = buffer.readUInt8(0)\n const isExHeader = (byte0 & 0x80) !== 0\n\n if (isExHeader) {\n return parseEnhancedVideoTag(buffer, lengthSize)\n }\n\n const frameType = (byte0 >> 4) as VideoFrameType\n const codecId = byte0 & 0x0f\n const codec: VideoCodec | null =\n codecId === VideoCodecId.H264 ? 'h264'\n : codecId === VideoCodecId.H265_LEGACY ? 'h265'\n : null\n if (codec === null) {\n throw new UnsupportedCodecError(`FLV video codecId=${codecId} not supported`)\n }\n return parseStandardLayoutVideoTag(buffer, codec, frameType, lengthSize)\n}\n\nfunction parseEnhancedVideoTag(buffer: Buffer, lengthSize: number = 4): FlvVideoTag {\n // byte 0 layout: bit7 IsExHeader | bits4..6 PacketType | bits0..3 FrameType\n if (buffer.length < 5) throw new FlvParseError('Enhanced FLV video tag truncated (need FOURCC)')\n const byte0 = buffer.readUInt8(0)\n const packetType = ((byte0 >> 4) & 0x07) as EnhancedPacketType\n const frameType = (byte0 & 0x0f) as VideoFrameType\n\n const fourcc = buffer.subarray(1, 5).toString('ascii')\n const codec = mapFourCcToCodec(fourcc)\n\n if (codec !== 'h265') {\n throw new UnsupportedCodecError(`Enhanced FLV FOURCC '${fourcc}' not supported (only hvc1/hev1)`)\n }\n\n if (packetType === EnhancedPacketType.SEQUENCE_START) {\n const hevcConfig = parseHevcDecoderConfigurationRecord(buffer.subarray(5))\n return {\n kind: 'video-sequence-header',\n codec: 'h265',\n frameType,\n compositionTime: 0,\n hevcConfig,\n }\n }\n\n if (packetType === EnhancedPacketType.CODED_FRAMES) {\n if (buffer.length < 8) throw new FlvParseError('Enhanced FLV CODED_FRAMES truncated')\n const compositionTime = buffer.readIntBE(5, 3)\n const nalus = extractLengthPrefixedNalus(buffer.subarray(8), lengthSize)\n return { kind: 'video-coded-frames', codec: 'h265', frameType, compositionTime, nalus }\n }\n\n if (packetType === EnhancedPacketType.CODED_FRAMES_X) {\n const nalus = extractLengthPrefixedNalus(buffer.subarray(5), lengthSize)\n return { kind: 'video-coded-frames', codec: 'h265', frameType, compositionTime: 0, nalus }\n }\n\n if (packetType === EnhancedPacketType.SEQUENCE_END) {\n return { kind: 'video-sequence-end', codec: 'h265', frameType, compositionTime: 0 }\n }\n\n throw new FlvParseError(`Unsupported enhanced FLV packet type: ${packetType}`)\n}\n\nfunction mapFourCcToCodec(fourcc: string): VideoCodec | null {\n if (fourcc === 'hvc1' || fourcc === 'hev1') return 'h265'\n if (fourcc === 'avc1') return 'h264'\n return null\n}\n\n/** Parse an RTMP audio tag (FLV message-type 8) payload. */\nexport function parseFlvAudioTag(buffer: Buffer): FlvAudioTag {\n if (buffer.length < 1) throw new FlvParseError('Audio tag empty')\n const byte0 = buffer.readUInt8(0)\n const soundFormat = ((byte0 >> 4) & 0x0f) as AudioSoundFormat\n\n if (soundFormat !== AudioSoundFormat.AAC) {\n return { kind: 'audio-raw', soundFormat, data: buffer.subarray(1) }\n }\n\n if (buffer.length < 2) throw new FlvParseError('AAC audio tag truncated')\n const aacPacketType = buffer.readUInt8(1) as AacPacketType\n\n if (aacPacketType === AacPacketType.SEQUENCE_HEADER) {\n const ascBytes = buffer.subarray(2)\n const audioSpecificConfig = parseAudioSpecificConfig(ascBytes)\n return {\n kind: 'audio-sequence-header',\n soundFormat,\n audioSpecificConfig,\n audioSpecificConfigBytes: Buffer.from(ascBytes),\n }\n }\n\n return {\n kind: 'audio-raw',\n soundFormat,\n data: buffer.subarray(2),\n }\n}\n\n// ── AnnexB assembly helper ────────────────────────────────────────────\n\n/** 4-byte AnnexB start code. */\nconst ANNEXB_START_CODE = Buffer.from([0x00, 0x00, 0x00, 0x01])\n\n/**\n * Assemble a sequence of NAL unit bodies into a single AnnexB buffer.\n * Each NAL is prefixed with `00 00 00 01`.\n */\nexport function nalusToAnnexB(nalus: ReadonlyArray<Buffer>): Buffer {\n if (nalus.length === 0) return Buffer.alloc(0)\n const parts: Buffer[] = []\n for (const nal of nalus) {\n parts.push(ANNEXB_START_CODE, nal)\n }\n return Buffer.concat(parts)\n}\n\n/**\n * Build the AnnexB parameter-set prefix for an H.264 IDR — concatenates\n * SPS + PPS NAL bodies with start codes.\n */\nexport function buildH264ParamSetsAnnexB(record: AvcDecoderConfigurationRecord): Buffer {\n return nalusToAnnexB([...record.sps, ...record.pps])\n}\n\n/**\n * Build the AnnexB parameter-set prefix for an H.265 IDR — concatenates\n * VPS + SPS + PPS NAL bodies with start codes.\n */\nexport function buildH265ParamSetsAnnexB(record: HevcDecoderConfigurationRecord): Buffer {\n return nalusToAnnexB([...record.vps, ...record.sps, ...record.pps])\n}\n\n// ── Frequency-index → sample-rate (AAC) ───────────────────────────────\n\nconst AAC_SAMPLE_RATES: ReadonlyArray<number> = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,\n 16000, 12000, 11025, 8000, 7350,\n]\n\n/**\n * Resolve the sample-rate for an AAC `samplingFrequencyIndex`. Returns\n * `null` for the reserved indices 13–15 so the caller can decide whether\n * to fall back or reject the stream.\n */\nexport function aacSamplingIndexToHz(index: number): number | null {\n return AAC_SAMPLE_RATES[index] ?? null\n}\n","/**\n * RTMP client (TCP, handshake, AMF0 connect/createStream/play, chunk parser).\n *\n * Ported from `koush/scrypted/plugins/prebuffer-mixin/src/rtmp-client.ts`\n * (ISC). Adaptations:\n * - `console` replaced with project `IScopedLogger`.\n * - Inlined `readLength` helper (no dependency on @scrypted/common).\n * - Strict TS — AMF0 encoder accepts a tagged union instead of `any`.\n * - Async-friendly `destroy()` for the broker's reconnect cycle.\n */\n\nimport { Socket, type SocketConnectOpts } from 'net'\nimport { Readable } from 'stream'\nimport { once } from 'events'\nimport type { IScopedLogger } from '@camstack/types'\n\n// ── Inlined readLength helper (replaces @scrypted/common/src/read-stream) ──\n\nclass StreamEndError extends Error {\n constructor(where: string) {\n super(`stream ended: ${where}`)\n this.name = 'StreamEndError'\n }\n}\n\nasync function readLength(readable: Readable, length: number): Promise<Buffer> {\n if (readable.readableEnded || readable.destroyed) {\n throw new StreamEndError('readLength start')\n }\n if (!length) return Buffer.alloc(0)\n\n const initial = readable.read(length) as Buffer | null\n if (initial) return initial\n\n return new Promise<Buffer>((resolve, reject) => {\n const onReadable = (): void => {\n const chunk = readable.read(length) as Buffer | null\n if (chunk) {\n cleanup()\n resolve(chunk)\n return\n }\n if (readable.readableEnded || readable.destroyed) {\n cleanup()\n reject(new StreamEndError('readLength readable'))\n }\n }\n const onEnd = (): void => {\n cleanup()\n reject(new StreamEndError('readLength end'))\n }\n const cleanup = (): void => {\n readable.removeListener('readable', onReadable)\n readable.removeListener('end', onEnd)\n }\n readable.on('readable', onReadable)\n readable.on('end', onEnd)\n })\n}\n\n// ── RTMP wire enums ────────────────────────────────────────────────────\n\nconst HANDSHAKE_SIZE = 1536\nconst RTMP_VERSION = 3\nconst DEFAULT_RTMP_PORT = 1935\n\nenum ChunkFormat {\n TYPE_0 = 0,\n TYPE_1 = 1,\n TYPE_2 = 2,\n TYPE_3 = 3,\n}\n\nenum RtmpMessageType {\n CHUNK_SIZE = 1,\n ABORT = 2,\n ACKNOWLEDGEMENT = 3,\n USER_CONTROL = 4,\n WINDOW_ACKNOWLEDGEMENT_SIZE = 5,\n SET_PEER_BANDWIDTH = 6,\n AUDIO = 8,\n VIDEO = 9,\n DATA_AMF0 = 18,\n COMMAND_AMF0 = 20,\n}\n\ninterface ChunkStreamState {\n chunkStreamId: number\n messageStreamId: number\n messageLength: number\n messageTypeId: number\n timestamp: number\n messageData: Buffer[]\n totalReceived: number\n hasExtendedTimestamp: boolean\n}\n\n// ── AMF0 encoder (strict types) ────────────────────────────────────────\n\ntype Amf0Value =\n | number\n | string\n | boolean\n | null\n | undefined\n | { readonly [key: string]: Amf0Value }\n\nfunction encodeAmf0(value: Amf0Value): Buffer {\n if (typeof value === 'number') {\n const buf = Buffer.alloc(9)\n buf[0] = 0x00 // Number marker\n buf.writeDoubleBE(value, 1)\n return buf\n }\n if (typeof value === 'string') {\n const buf = Buffer.alloc(3 + value.length)\n buf[0] = 0x02 // String marker\n buf.writeUInt16BE(value.length, 1)\n buf.write(value, 3, 'utf8')\n return buf\n }\n if (typeof value === 'boolean') {\n const buf = Buffer.alloc(2)\n buf[0] = 0x01 // Boolean marker\n buf[1] = value ? 1 : 0\n return buf\n }\n if (value === null || value === undefined) {\n return Buffer.from([0x05]) // Null marker\n }\n // Object\n const parts: Buffer[] = [Buffer.from([0x03])] // Object marker\n for (const [key, val] of Object.entries(value)) {\n const keyBuf = Buffer.alloc(2 + key.length)\n keyBuf.writeUInt16BE(key.length, 0)\n keyBuf.write(key, 2, 'utf8')\n parts.push(keyBuf)\n parts.push(encodeAmf0(val))\n }\n parts.push(Buffer.from([0x00, 0x00, 0x09])) // End-of-object marker\n return Buffer.concat(parts)\n}\n\nfunction encodeAmf0Command(\n commandName: string,\n transactionId: number,\n commandObject: Amf0Value,\n ...args: ReadonlyArray<Amf0Value>\n): Buffer {\n const parts: Buffer[] = [\n encodeAmf0(commandName),\n encodeAmf0(transactionId),\n encodeAmf0(commandObject),\n ]\n for (const arg of args) parts.push(encodeAmf0(arg))\n return Buffer.concat(parts)\n}\n\nfunction writeUInt24BE(buffer: Buffer, value: number, offset: number): void {\n buffer[offset] = (value >> 16) & 0xff\n buffer[offset + 1] = (value >> 8) & 0xff\n buffer[offset + 2] = value & 0xff\n}\n\n// ── Public RTMP packet shape ───────────────────────────────────────────\n\nexport type RtmpMediaCodec = 'video' | 'audio'\n\nexport interface RtmpMediaPacket {\n readonly codec: RtmpMediaCodec\n readonly packet: Buffer\n /** Timestamp in milliseconds, as advertised by the server. */\n readonly timestamp: number\n}\n\nexport class RtmpClientClosedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'RtmpClientClosedError'\n }\n}\n\n// ── RtmpClient ─────────────────────────────────────────────────────────\n\nexport class RtmpClient {\n readonly url: string\n private readonly logger: IScopedLogger | undefined\n private socket: Socket\n private chunkSize = 128\n private outgoingChunkSize = 128\n private windowAckSize = 5_000_000\n private streamId = 0\n private lastAcknowledgementBytes = 0\n private totalBytesReceived = 0\n private transactionId = 1\n private chunkStreams: Map<number, ChunkStreamState> = new Map()\n private destroyed = false\n\n constructor(url: string, logger?: IScopedLogger) {\n this.url = url\n this.logger = logger\n this.socket = new Socket()\n }\n\n /** Connect, handshake, and play. Throws on any failure. */\n async setup(): Promise<void> {\n await this.connect()\n\n this.logger?.debug('rtmp connect command sent')\n await this.sendConnect()\n\n while (true) {\n const msg = await this.readMessage()\n const { messageTypeId } = msg.chunkStream\n if (messageTypeId === RtmpMessageType.WINDOW_ACKNOWLEDGEMENT_SIZE) continue\n if (messageTypeId === RtmpMessageType.SET_PEER_BANDWIDTH) continue\n if (messageTypeId === RtmpMessageType.CHUNK_SIZE) {\n this.chunkSize = msg.message.readUInt32BE(0)\n this.logger?.debug('rtmp server chunk size', { meta: { chunkSize: this.chunkSize } })\n continue\n }\n if (messageTypeId === RtmpMessageType.COMMAND_AMF0) {\n const commandName = msg.message.subarray(3, 10).toString('utf8')\n if (commandName === '_result') {\n this.logger?.debug('rtmp connect _result received')\n break\n }\n throw new Error(`Unexpected RTMP command: ${commandName}`)\n }\n throw new Error(`Unexpected RTMP message type: ${messageTypeId}`)\n }\n\n this.sendWindowAckSize(5_000_000)\n\n this.streamId = await this.sendCreateStream()\n const createStreamResult = await this.readMessage()\n const { messageTypeId } = createStreamResult.chunkStream\n if (messageTypeId !== RtmpMessageType.COMMAND_AMF0) {\n throw new Error(`Unexpected message type for createStream result: ${messageTypeId}`)\n }\n this.logger?.debug('rtmp createStream _result received')\n\n const parsedUrl = new URL(this.url)\n const parts = parsedUrl.pathname.split('/')\n const streamName = parts.length > 2 ? parts.slice(2).join('/') : ''\n const playPath = streamName + parsedUrl.search\n\n const getStreamLengthData = encodeAmf0Command('getStreamLength', this.transactionId++, null, playPath)\n this.sendMessage(5, 0, RtmpMessageType.COMMAND_AMF0, 0, getStreamLengthData)\n\n this.sendPlay(this.streamId, playPath)\n this.setBufferLength(this.streamId, 3000)\n\n this.logger?.info('rtmp play sent', { meta: { playPath } })\n }\n\n /** Async-iterator over received video/audio packets. */\n async *readLoop(): AsyncGenerator<RtmpMediaPacket, void, void> {\n while (!this.destroyed) {\n const msg = await this.readMessage()\n const typeId = msg.chunkStream.messageTypeId\n if (typeId === RtmpMessageType.VIDEO) {\n yield { codec: 'video', packet: msg.message, timestamp: msg.chunkStream.timestamp }\n } else if (typeId === RtmpMessageType.AUDIO) {\n yield { codec: 'audio', packet: msg.message, timestamp: msg.chunkStream.timestamp }\n } else {\n this.logger?.debug('rtmp ignoring message', { meta: { messageTypeId: typeId } })\n }\n }\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n try {\n this.socket.destroy()\n } catch {\n // Socket already destroyed — ignore.\n }\n }\n\n // ── Connection / handshake ────────────────────────────────────────────\n\n private async connect(): Promise<void> {\n const parsedUrl = new URL(this.url)\n const host = parsedUrl.hostname\n const port = parseInt(parsedUrl.port || `${DEFAULT_RTMP_PORT}`, 10) || DEFAULT_RTMP_PORT\n\n this.logger?.debug('rtmp tcp connecting', { meta: { host, port } })\n\n this.socket.on('error', (err) => {\n this.logger?.warn('rtmp socket error', { meta: { error: err.message } })\n })\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => reject(err)\n this.socket.once('error', onError)\n const opts: SocketConnectOpts = { host, port }\n this.socket.connect(opts, () => {\n this.socket.removeListener('error', onError)\n resolve()\n })\n })\n\n await this.performHandshake()\n }\n\n private async performHandshake(): Promise<void> {\n // C0\n const c0 = Buffer.from([RTMP_VERSION])\n // C1: timestamp(4) + zero(4) + random(1528)\n const c1 = Buffer.alloc(HANDSHAKE_SIZE)\n c1.writeUInt32BE(Math.floor(Date.now() / 1000), 0)\n c1.writeUInt32BE(0, 4)\n this.socket.write(Buffer.concat([c0, c1]))\n\n const s0 = await readLength(this.socket, 1)\n const serverVersion = s0.readUInt8(0)\n if (serverVersion !== RTMP_VERSION) {\n throw new Error(`Unsupported RTMP server version: ${serverVersion}`)\n }\n const s1 = await readLength(this.socket, HANDSHAKE_SIZE)\n await readLength(this.socket, HANDSHAKE_SIZE) // S2 (discarded)\n\n // C2 = echo of S1\n this.socket.write(s1)\n }\n\n // ── Chunk parser ──────────────────────────────────────────────────────\n\n private async readMessage(): Promise<{ message: Buffer; chunkStream: ChunkStreamState }> {\n const stream = this.socket\n while (true) {\n const basicHeader = await readLength(stream, 1)\n const basic0 = basicHeader.readUInt8(0)\n const fmt = (basic0 >> 6) & 0x03\n let csId = basic0 & 0x3f\n\n if (csId === 0) {\n const secondByte = await readLength(stream, 1)\n csId = secondByte.readUInt8(0) + 64\n } else if (csId === 1) {\n const bytes = await readLength(stream, 2)\n csId = (bytes.readUInt8(1) << 8) | (bytes.readUInt8(0) + 64)\n }\n\n let chunkStream = this.chunkStreams.get(csId)\n if (!chunkStream) {\n chunkStream = {\n chunkStreamId: csId,\n messageStreamId: 0,\n messageLength: 0,\n messageTypeId: 0,\n timestamp: 0,\n messageData: [],\n totalReceived: 0,\n hasExtendedTimestamp: false,\n }\n this.chunkStreams.set(csId, chunkStream)\n }\n\n let hasExtendedTimestamp = false\n let headerSize: number\n\n if (fmt === ChunkFormat.TYPE_0) {\n headerSize = 11\n const header = await readLength(stream, 11)\n const timestamp = header.readUIntBE(0, 3)\n const messageLength = header.readUIntBE(3, 3)\n const messageTypeId = header.readUInt8(6)\n const messageStreamId = header.readUInt32LE(7)\n\n chunkStream.messageStreamId = messageStreamId\n chunkStream.messageLength = messageLength\n chunkStream.messageTypeId = messageTypeId\n chunkStream.timestamp = timestamp\n chunkStream.totalReceived = 0\n chunkStream.messageData = []\n\n if (timestamp >= 0xffffff) {\n hasExtendedTimestamp = true\n chunkStream.hasExtendedTimestamp = true\n }\n } else if (fmt === ChunkFormat.TYPE_1) {\n headerSize = 7\n const header = await readLength(stream, 7)\n const timestampDelta = header.readUIntBE(0, 3)\n const messageLength = header.readUIntBE(3, 3)\n const messageTypeId = header.readUInt8(6)\n chunkStream.messageLength = messageLength\n chunkStream.messageTypeId = messageTypeId\n chunkStream.timestamp += timestampDelta\n chunkStream.totalReceived = 0\n chunkStream.messageData = []\n if (timestampDelta >= 0xffffff) {\n hasExtendedTimestamp = true\n chunkStream.hasExtendedTimestamp = true\n }\n } else if (fmt === ChunkFormat.TYPE_2) {\n headerSize = 3\n const header = await readLength(stream, 3)\n const timestampDelta = header.readUIntBE(0, 3)\n chunkStream.timestamp += timestampDelta\n chunkStream.totalReceived = 0\n chunkStream.messageData = []\n if (timestampDelta >= 0xffffff) {\n hasExtendedTimestamp = true\n chunkStream.hasExtendedTimestamp = true\n }\n } else {\n headerSize = 0\n if (chunkStream.totalReceived === 0) {\n throw new Error('Type 3 chunk but no previous chunk in stream')\n }\n }\n\n if (hasExtendedTimestamp || chunkStream.hasExtendedTimestamp) {\n const extTs = await readLength(stream, 4)\n const extendedTimestamp = extTs.readUInt32BE(0)\n if (fmt === ChunkFormat.TYPE_0) {\n chunkStream.timestamp = extendedTimestamp\n }\n }\n\n const remainingInMessage = chunkStream.messageLength - chunkStream.totalReceived\n const chunkDataSize = Math.min(this.chunkSize, remainingInMessage)\n\n const MAX_CHUNK_SIZE = 1024 * 1024\n if (chunkDataSize > MAX_CHUNK_SIZE) {\n throw new Error(`Chunk size ${chunkDataSize} exceeds max ${MAX_CHUNK_SIZE}`)\n }\n\n const chunkData = await readLength(stream, chunkDataSize)\n chunkStream.messageData.push(chunkData)\n chunkStream.totalReceived += chunkDataSize\n\n const extTimestampSize = (hasExtendedTimestamp || chunkStream.hasExtendedTimestamp) ? 4 : 0\n this.totalBytesReceived += 1 + headerSize + extTimestampSize + chunkDataSize\n this.sendAcknowledgementIfNeeded()\n\n if (chunkStream.totalReceived >= chunkStream.messageLength) {\n const message = Buffer.concat(chunkStream.messageData)\n chunkStream.messageData = []\n chunkStream.totalReceived = 0\n chunkStream.hasExtendedTimestamp = false\n return { chunkStream, message }\n }\n }\n }\n\n private sendAcknowledgementIfNeeded(): void {\n const bytesToAck = this.totalBytesReceived - this.lastAcknowledgementBytes\n if (bytesToAck >= this.windowAckSize) {\n this.lastAcknowledgementBytes = this.totalBytesReceived\n const data = Buffer.alloc(4)\n data.writeUInt32BE(this.lastAcknowledgementBytes & 0xffffffff, 0)\n this.sendMessage(2, 0, RtmpMessageType.ACKNOWLEDGEMENT, 0, data)\n }\n }\n\n // ── Outgoing message helpers ─────────────────────────────────────────\n\n private sendMessage(\n chunkStreamId: number,\n messageStreamId: number,\n messageTypeId: number,\n timestamp: number,\n data: Buffer,\n ): void {\n const chunks: Buffer[] = []\n let offset = 0\n\n while (offset < data.length) {\n const chunkDataSize = Math.min(this.outgoingChunkSize, data.length - offset)\n const isType0 = offset === 0\n const headerSize = isType0 ? 12 : 1\n const header = Buffer.alloc(headerSize)\n\n if (chunkStreamId < 64) {\n header[0] = ((isType0 ? ChunkFormat.TYPE_0 : ChunkFormat.TYPE_3) << 6) | chunkStreamId\n } else {\n header[0] = ((isType0 ? ChunkFormat.TYPE_0 : ChunkFormat.TYPE_3) << 6) | 1\n }\n\n if (isType0) {\n writeUInt24BE(header, timestamp, 1)\n writeUInt24BE(header, data.length, 4)\n header[7] = messageTypeId\n header.writeUInt32LE(messageStreamId, 8)\n }\n\n chunks.push(header, data.subarray(offset, offset + chunkDataSize))\n offset += chunkDataSize\n }\n\n for (const chunk of chunks) this.socket.write(chunk)\n }\n\n private async sendConnect(): Promise<void> {\n const parsedUrl = new URL(this.url)\n const tcUrl = `${parsedUrl.protocol}//${parsedUrl.host}/${parsedUrl.pathname.split('/')[1]}`\n const connectObject = {\n app: parsedUrl.pathname.split('/')[1],\n flashVer: 'LNX 9,0,124,2',\n tcUrl,\n fpad: false,\n capabilities: 15,\n audioCodecs: 4071,\n videoCodecs: 252,\n videoFunction: 1,\n }\n const data = encodeAmf0Command('connect', this.transactionId++, connectObject)\n this.sendMessage(3, 0, RtmpMessageType.COMMAND_AMF0, 0, data)\n }\n\n private async sendCreateStream(): Promise<number> {\n const data = encodeAmf0Command('createStream', this.transactionId++, null)\n this.sendMessage(3, 0, RtmpMessageType.COMMAND_AMF0, 0, data)\n return 1\n }\n\n private sendPlay(streamId: number, playPath: string): void {\n const data = encodeAmf0Command('play', this.transactionId++, null, playPath, -2000)\n this.sendMessage(4, streamId, RtmpMessageType.COMMAND_AMF0, 0, data)\n }\n\n private setBufferLength(streamId: number, bufferLength: number): void {\n const data = Buffer.alloc(10)\n data.writeUInt16BE(3, 0)\n data.writeUInt32BE(streamId, 2)\n data.writeUInt32BE(bufferLength, 6)\n this.sendMessage(2, 0, RtmpMessageType.USER_CONTROL, 1, data)\n }\n\n private sendWindowAckSize(windowSize: number): void {\n const data = Buffer.alloc(4)\n data.writeUInt32BE(windowSize, 0)\n this.sendMessage(2, 0, RtmpMessageType.WINDOW_ACKNOWLEDGEMENT_SIZE, 0, data)\n }\n\n /** Wait for the underlying socket to close. */\n async waitForClose(): Promise<void> {\n if (this.socket.destroyed) return\n await once(this.socket, 'close')\n }\n\n get isDestroyed(): boolean {\n return this.destroyed\n }\n}\n","/**\n * RtmpReader — drives an `RtmpClient`, parses FLV tags from each RTMP\n * VIDEO/AUDIO message, builds AnnexB access units, and surfaces them to\n * the stream-broker via the supplied callbacks.\n *\n * Codec support:\n * - Video: H.264 (standard FLV) and H.265 (Enhanced FLV / Veovera spec\n * with FOURCC 'hvc1' or 'hev1'). Param sets are kept as the\n * AnnexB prefix and prepended to every IDR access unit so\n * Chrome's HEVC decoder can initialise from a single packet.\n * - Audio: AAC only. Other codecs are skipped with a warn log.\n *\n * The reader does NOT own the broker's reconnect/backoff loop — it\n * exposes `connect()` / `destroy()` and reports terminal failures via\n * `onError`. The broker re-instantiates a fresh reader on reconnect.\n */\n\nimport type { EncodedPacket, IScopedLogger, PushAudioInfo } from '@camstack/types'\nimport {\n parseFlvVideoTagWithLengthSize,\n parseFlvAudioTag,\n buildH264ParamSetsAnnexB,\n buildH265ParamSetsAnnexB,\n nalusToAnnexB,\n aacSamplingIndexToHz,\n AudioSoundFormat,\n UnsupportedCodecError,\n FlvParseError,\n} from './flv-parser.js'\nimport { RtmpClient, RtmpClientClosedError } from './rtmp-client.js'\n\nexport interface RtmpReaderCallbacks {\n /** Called when codec is first identified from the FLV sequence header. */\n onVideoCodec?: (codec: 'h264' | 'h265') => void\n /** Called for every assembled AnnexB access unit. */\n onEncodedPacket: (packet: EncodedPacket) => void\n /** Called once when the AAC sequence header is parsed. */\n onAudioInfo?: (info: PushAudioInfo) => void\n /** Called the first time the connection successfully starts streaming media. */\n onPlaying?: () => void\n /** Called on any terminal failure (TCP close, parse error, unsupported codec). */\n onError: (error: Error) => void\n /** Called once after the read-loop exits (successfully or otherwise). */\n onTeardown?: () => void\n}\n\nexport class RtmpReader {\n private readonly url: string\n private readonly logger: IScopedLogger | undefined\n private readonly callbacks: RtmpReaderCallbacks\n private client: RtmpClient | null = null\n private destroyed = false\n\n // Per-stream codec state — captured from the first SequenceHeader we see.\n private videoCodec: 'h264' | 'h265' | null = null\n private h264ParamPrefix: Buffer | null = null\n private h265ParamPrefix: Buffer | null = null\n private naluLengthSize = 4\n\n private audioInfoEmitted = false\n private firstMediaPacketSeen = false\n private packetCounter = 0\n\n constructor(url: string, logger: IScopedLogger | undefined, callbacks: RtmpReaderCallbacks) {\n this.url = url\n this.logger = logger\n this.callbacks = callbacks\n }\n\n async connect(): Promise<void> {\n const client = new RtmpClient(this.url, this.logger?.child('rtmp-client'))\n this.client = client\n\n try {\n await client.setup()\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n this.logger?.warn('rtmp setup failed', { meta: { error: error.message } })\n this.fail(error)\n return\n }\n\n void this.runReadLoop(client)\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.client?.destroy()\n }\n\n private async runReadLoop(client: RtmpClient): Promise<void> {\n try {\n for await (const media of client.readLoop()) {\n if (this.destroyed) break\n\n if (media.codec === 'video') {\n this.handleVideoPacket(media.packet, media.timestamp)\n } else {\n this.handleAudioPacket(media.packet, media.timestamp)\n }\n\n if (!this.firstMediaPacketSeen) {\n this.firstMediaPacketSeen = true\n this.callbacks.onPlaying?.()\n }\n }\n // readLoop exited cleanly — the only way this happens is destroy()\n this.callbacks.onTeardown?.()\n } catch (err) {\n if (this.destroyed) {\n this.callbacks.onTeardown?.()\n return\n }\n const error = err instanceof Error ? err : new Error(String(err))\n this.fail(error)\n }\n }\n\n private fail(error: Error): void {\n if (this.destroyed) return\n this.destroyed = true\n this.client?.destroy()\n this.callbacks.onError(error)\n }\n\n // ── Video path ────────────────────────────────────────────────────────\n\n private handleVideoPacket(payload: Buffer, timestamp: number): void {\n let tag\n try {\n tag = parseFlvVideoTagWithLengthSize(payload, this.naluLengthSize)\n } catch (err) {\n if (err instanceof UnsupportedCodecError) {\n this.fail(err)\n return\n }\n if (err instanceof FlvParseError) {\n this.logger?.warn('rtmp: skipping malformed video tag', { meta: { error: err.message } })\n return\n }\n throw err\n }\n\n if (tag.kind === 'video-sequence-header') {\n this.handleVideoSequenceHeader(tag.codec, tag)\n return\n }\n if (tag.kind === 'video-sequence-end') {\n // Treated as an end-of-stream signal — let the read loop exit naturally.\n this.logger?.info('rtmp: video sequence end received')\n return\n }\n // video-coded-frames\n if (!this.videoCodec) {\n // Coded frames before sequence header — skip silently. Some servers\n // start mid-GOP; the next SequenceHeader (or restart) will recover.\n return\n }\n\n const annexB = this.buildAnnexBAccessUnit(tag.codec, tag.nalus, this.isKeyframeFrameType(tag.frameType))\n if (annexB.length === 0) return\n\n this.packetCounter++\n const packet: EncodedPacket = {\n type: 'video',\n data: annexB,\n pts: timestamp + tag.compositionTime,\n dts: timestamp,\n keyframe: this.isKeyframeFrameType(tag.frameType),\n codec: tag.codec,\n }\n this.callbacks.onEncodedPacket(packet)\n }\n\n private handleVideoSequenceHeader(\n codec: 'h264' | 'h265',\n tag: ReturnType<typeof parseFlvVideoTagWithLengthSize>,\n ): void {\n if (tag.kind !== 'video-sequence-header') return\n\n if (this.videoCodec !== null && this.videoCodec !== codec) {\n this.fail(new Error(`RTMP video codec changed mid-stream: ${this.videoCodec} → ${codec}`))\n return\n }\n\n if (codec === 'h264' && tag.codec === 'h264') {\n this.videoCodec = 'h264'\n this.naluLengthSize = tag.avcConfig.lengthSizeMinusOne + 1\n this.h264ParamPrefix = buildH264ParamSetsAnnexB(tag.avcConfig)\n this.logger?.info('rtmp: H.264 sequence header', {\n meta: {\n profile: tag.avcConfig.avcProfileIndication,\n level: tag.avcConfig.avcLevelIndication,\n spsCount: tag.avcConfig.sps.length,\n ppsCount: tag.avcConfig.pps.length,\n lengthSize: this.naluLengthSize,\n },\n })\n this.callbacks.onVideoCodec?.('h264')\n return\n }\n\n if (codec === 'h265' && tag.codec === 'h265') {\n this.videoCodec = 'h265'\n this.naluLengthSize = tag.hevcConfig.lengthSizeMinusOne + 1\n this.h265ParamPrefix = buildH265ParamSetsAnnexB(tag.hevcConfig)\n this.logger?.info('rtmp: H.265 sequence header', {\n meta: {\n profile: tag.hevcConfig.generalProfileIdc,\n level: tag.hevcConfig.generalLevelIdc,\n vpsCount: tag.hevcConfig.vps.length,\n spsCount: tag.hevcConfig.sps.length,\n ppsCount: tag.hevcConfig.pps.length,\n lengthSize: this.naluLengthSize,\n },\n })\n this.callbacks.onVideoCodec?.('h265')\n }\n }\n\n /**\n * Build the AnnexB access unit. Prepends VPS/SPS/PPS prefix when the\n * frame is a keyframe so downstream consumers (decoder, prebuffer,\n * WebRTC) receive a self-contained access unit on every IDR.\n */\n private buildAnnexBAccessUnit(\n codec: 'h264' | 'h265',\n nalus: ReadonlyArray<Buffer>,\n keyframe: boolean,\n ): Buffer {\n if (nalus.length === 0) return Buffer.alloc(0)\n const body = nalusToAnnexB(nalus)\n if (!keyframe) return body\n\n const prefix = codec === 'h264' ? this.h264ParamPrefix : this.h265ParamPrefix\n if (!prefix || prefix.length === 0) return body\n return Buffer.concat([prefix, body])\n }\n\n private isKeyframeFrameType(frameType: number): boolean {\n // FrameType 1 = KEY, 4 = GENERATED_KEYFRAME\n return frameType === 1 || frameType === 4\n }\n\n // ── Audio path ────────────────────────────────────────────────────────\n\n private handleAudioPacket(payload: Buffer, timestamp: number): void {\n let tag\n try {\n tag = parseFlvAudioTag(payload)\n } catch (err) {\n if (err instanceof FlvParseError) {\n this.logger?.warn('rtmp: skipping malformed audio tag', { meta: { error: err.message } })\n return\n }\n throw err\n }\n\n if (tag.kind === 'audio-sequence-header') {\n if (tag.soundFormat !== AudioSoundFormat.AAC) {\n this.logger?.warn('rtmp: non-AAC sequence header — audio skipped', {\n meta: { soundFormat: tag.soundFormat },\n })\n return\n }\n const sampleRate = aacSamplingIndexToHz(tag.audioSpecificConfig.samplingFrequencyIndex)\n const channels = tag.audioSpecificConfig.channelConfiguration === 0\n ? 1\n : tag.audioSpecificConfig.channelConfiguration\n if (sampleRate === null) {\n this.logger?.warn('rtmp: AAC reserved sample-rate index — audio skipped', {\n meta: { samplingFrequencyIndex: tag.audioSpecificConfig.samplingFrequencyIndex },\n })\n return\n }\n this.audioInfoEmitted = true\n this.callbacks.onAudioInfo?.({\n codec: 'aac',\n sampleRate,\n channels,\n extraData: new Uint8Array(tag.audioSpecificConfigBytes),\n })\n this.logger?.info('rtmp: AAC sequence header', {\n meta: {\n audioObjectType: tag.audioSpecificConfig.audioObjectType,\n sampleRate,\n channels,\n },\n })\n return\n }\n\n if (tag.soundFormat !== AudioSoundFormat.AAC) {\n // Non-AAC audio (G.711, ADPCM, …) is not supported on the RTMP path.\n // Drop silently after the first warning to avoid log floods.\n return\n }\n\n if (!this.audioInfoEmitted) {\n // AAC raw arrived before the sequence header — uncommon but legal.\n // Skip until we see the header (decoder needs ASC to initialise).\n return\n }\n\n const packet: EncodedPacket = {\n type: 'audio',\n data: tag.data,\n pts: timestamp,\n dts: timestamp,\n keyframe: false,\n codec: 'aac',\n }\n this.callbacks.onEncodedPacket(packet)\n }\n}\n\nexport { RtmpClientClosedError }\n","import {\n RTP_HEADER_SIZE,\n H264_NAL_TYPE_MASK,\n H264_NAL_FUA,\n H265_NAL_TYPE_SHIFT,\n H265_NAL_TYPE_MASK,\n H265_NAL_FU,\n} from './rtsp-types.js'\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface DepacketizedNal {\n /** Complete NAL unit (without start code). */\n readonly nal: Buffer\n /** Whether this NAL is a keyframe (IDR for H.264, IRAP for H.265). */\n readonly keyframe: boolean\n /** Whether this NAL is a parameter set (SPS/PPS/VPS). */\n readonly parameterSet: boolean\n /** RTP timestamp (90kHz clock). */\n readonly timestamp: number\n /** True if the RTP marker bit was set (last packet of access unit). */\n readonly marker: boolean\n}\n\nexport type NalCallback = (nal: DepacketizedNal) => void\n\n// ── H.264 NAL types ────────────────────────────────────────────────\n\nconst H264_NAL_SPS = 7\nconst H264_NAL_PPS = 8\nconst H264_NAL_IDR = 5\nconst H264_NAL_STAP_A = 24\n\n// ── H.265 NAL types ────────────────────────────────────────────────\n\nconst H265_NAL_VPS = 32\nconst H265_NAL_SPS = 33\nconst H265_NAL_PPS = 34\nconst H265_NAL_IRAP_MIN = 16\nconst H265_NAL_IRAP_MAX = 21\nconst H265_NAL_AP = 48\n\n// ── Implementation ──────────────────────────────────────────────────\n\n/**\n * RTP → Annex-B NAL depacketizer.\n *\n * Takes raw RTP packets (as received from TCP interleaved RTSP) and\n * reassembles complete NAL units. Handles:\n *\n * H.264 (RFC 6184):\n * - Single NAL unit packets (type 1-23)\n * - STAP-A aggregation packets (type 24)\n * - FU-A fragmentation packets (type 28)\n *\n * H.265 (RFC 7798):\n * - Single NAL unit packets (type 0-47, except 48/49)\n * - AP aggregation packets (type 48)\n * - FU fragmentation packets (type 49)\n */\nexport class RtpDepacketizer {\n private readonly codec: 'h264' | 'h265'\n private readonly onNal: NalCallback\n\n /** FU reassembly state. */\n private fuBuffer: Buffer[] = []\n private fuTimestamp = 0\n /** H.264: stores the reconstructed first byte (NRI + type). */\n private fuFirstByte = 0\n /** H.265: stores the 2-byte NAL header from the FU payload header. */\n private fuH265Header: Buffer | null = null\n\n constructor(codec: 'h264' | 'h265', onNal: NalCallback) {\n this.codec = codec\n this.onNal = onNal\n }\n\n /** Reset reassembly state (e.g. on stream restart). */\n reset(): void {\n this.fuBuffer = []\n this.fuTimestamp = 0\n\n this.fuFirstByte = 0\n this.fuH265Header = null\n }\n\n /**\n * Process a raw RTP packet (including 12-byte RTP header).\n * Emits zero or more complete NAL units via the callback.\n */\n processPacket(rtpData: Buffer): void {\n if (rtpData.length < RTP_HEADER_SIZE) return\n\n const marker = (rtpData[1]! & 0x80) !== 0\n const timestamp = rtpData.readUInt32BE(4)\n const payload = rtpData.subarray(RTP_HEADER_SIZE)\n\n if (payload.length === 0) return\n\n if (this.codec === 'h264') {\n this.processH264(payload, timestamp, marker)\n } else {\n this.processH265(payload, timestamp, marker)\n }\n }\n\n // ── H.264 ─────────────────────────────────────────────────────────\n\n private processH264(payload: Buffer, timestamp: number, marker: boolean): void {\n const firstByte = payload[0]!\n const nalType = firstByte & H264_NAL_TYPE_MASK\n\n if (nalType >= 1 && nalType <= 23) {\n // Single NAL unit packet\n this.emitNal(payload, timestamp, marker)\n } else if (nalType === H264_NAL_STAP_A) {\n this.processH264StapA(payload, timestamp, marker)\n } else if (nalType === H264_NAL_FUA) {\n this.processH264FuA(payload, timestamp, marker)\n }\n // Types 25-27 (STAP-B, MTAP16, MTAP24) and 29 (FU-B) are extremely rare\n // in IP camera streams — ignore them.\n }\n\n /** STAP-A: Single-Time Aggregation Packet (multiple NALs in one RTP). */\n private processH264StapA(payload: Buffer, timestamp: number, marker: boolean): void {\n let offset = 1 // Skip STAP-A header byte\n while (offset + 2 <= payload.length) {\n const nalSize = payload.readUInt16BE(offset)\n offset += 2\n if (offset + nalSize > payload.length) break\n\n const nal = Buffer.from(payload.subarray(offset, offset + nalSize))\n offset += nalSize\n this.emitNal(nal, timestamp, marker && offset >= payload.length)\n }\n }\n\n /** FU-A: Fragmentation Unit (large NAL split across multiple RTP packets). */\n private processH264FuA(payload: Buffer, timestamp: number, marker: boolean): void {\n if (payload.length < 2) return\n\n const fuIndicator = payload[0]!\n const fuHeader = payload[1]!\n const isStart = (fuHeader & 0x80) !== 0\n const isEnd = (fuHeader & 0x40) !== 0\n const nalType = fuHeader & H264_NAL_TYPE_MASK\n\n if (isStart) {\n // Start fragment — reconstruct NAL first byte: NRI from indicator + type from header\n this.fuBuffer = [payload.subarray(2)]\n this.fuTimestamp = timestamp\n\n this.fuFirstByte = (fuIndicator & 0xe0) | nalType\n } else if (this.fuBuffer.length > 0 && this.fuTimestamp === timestamp) {\n // Middle or end fragment\n this.fuBuffer.push(payload.subarray(2))\n\n if (isEnd) {\n // Reassemble: prepend reconstructed NAL header byte\n const totalSize = this.fuBuffer.reduce((sum, b) => sum + b.length, 0)\n const nal = Buffer.allocUnsafe(1 + totalSize)\n nal[0] = this.fuFirstByte\n let offset = 1\n for (const fragment of this.fuBuffer) {\n fragment.copy(nal, offset)\n offset += fragment.length\n }\n this.fuBuffer = []\n this.emitNal(nal, timestamp, marker)\n }\n } else {\n // Out-of-order or gap — discard partial reassembly\n this.fuBuffer = []\n }\n }\n\n // ── H.265 ─────────────────────────────────────────────────────────\n\n private processH265(payload: Buffer, timestamp: number, marker: boolean): void {\n if (payload.length < 2) return\n\n const nalType = (payload[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n\n if (nalType < 48) {\n // Single NAL unit packet (types 0-47)\n this.emitNal(payload, timestamp, marker)\n } else if (nalType === H265_NAL_AP) {\n this.processH265Ap(payload, timestamp, marker)\n } else if (nalType === H265_NAL_FU) {\n this.processH265Fu(payload, timestamp, marker)\n }\n }\n\n /** AP: Aggregation Packet (multiple NALs in one RTP). */\n private processH265Ap(payload: Buffer, timestamp: number, marker: boolean): void {\n let offset = 2 // Skip 2-byte AP payload header\n while (offset + 2 <= payload.length) {\n const nalSize = payload.readUInt16BE(offset)\n offset += 2\n if (offset + nalSize > payload.length) break\n\n const nal = Buffer.from(payload.subarray(offset, offset + nalSize))\n offset += nalSize\n this.emitNal(nal, timestamp, marker && offset >= payload.length)\n }\n }\n\n /** FU: Fragmentation Unit (large NAL split across multiple RTP packets). */\n private processH265Fu(payload: Buffer, timestamp: number, marker: boolean): void {\n if (payload.length < 3) return\n\n const fuHeader = payload[2]!\n const isStart = (fuHeader & 0x80) !== 0\n const isEnd = (fuHeader & 0x40) !== 0\n const nalType = fuHeader & H265_NAL_TYPE_MASK\n\n if (isStart) {\n // Start fragment — store payload header bytes for NAL header reconstruction\n this.fuBuffer = [payload.subarray(3)]\n this.fuTimestamp = timestamp\n\n // Reconstruct 2-byte NAL header: replace FU type with actual NAL type\n const header = Buffer.allocUnsafe(2)\n header[0] = (payload[0]! & 0x81) | (nalType << H265_NAL_TYPE_SHIFT)\n header[1] = payload[1]!\n this.fuH265Header = header\n } else if (this.fuBuffer.length > 0 && this.fuTimestamp === timestamp) {\n // Middle or end fragment\n this.fuBuffer.push(payload.subarray(3))\n\n if (isEnd && this.fuH265Header) {\n // Reassemble: prepend 2-byte NAL header\n const totalSize = this.fuBuffer.reduce((sum, b) => sum + b.length, 0)\n const nal = Buffer.allocUnsafe(2 + totalSize)\n this.fuH265Header.copy(nal, 0)\n let offset = 2\n for (const fragment of this.fuBuffer) {\n fragment.copy(nal, offset)\n offset += fragment.length\n }\n this.fuBuffer = []\n this.fuH265Header = null\n this.emitNal(nal, timestamp, marker)\n }\n } else {\n // Gap — discard\n this.fuBuffer = []\n this.fuH265Header = null\n }\n }\n\n // ── Emit ──────────────────────────────────────────────────────────\n\n private emitNal(nal: Buffer, timestamp: number, marker: boolean): void {\n const keyframe = this.codec === 'h264'\n ? this.isH264Keyframe(nal)\n : this.isH265Keyframe(nal)\n\n const parameterSet = this.codec === 'h264'\n ? this.isH264ParameterSet(nal)\n : this.isH265ParameterSet(nal)\n\n this.onNal({ nal, keyframe, parameterSet, timestamp, marker })\n }\n\n private isH264Keyframe(nal: Buffer): boolean {\n if (nal.length === 0) return false\n return (nal[0]! & H264_NAL_TYPE_MASK) === H264_NAL_IDR\n }\n\n private isH264ParameterSet(nal: Buffer): boolean {\n if (nal.length === 0) return false\n const type = nal[0]! & H264_NAL_TYPE_MASK\n return type === H264_NAL_SPS || type === H264_NAL_PPS\n }\n\n private isH265Keyframe(nal: Buffer): boolean {\n if (nal.length < 2) return false\n const type = (nal[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n return type >= H265_NAL_IRAP_MIN && type <= H265_NAL_IRAP_MAX\n }\n\n private isH265ParameterSet(nal: Buffer): boolean {\n if (nal.length < 2) return false\n const type = (nal[0]! >> H265_NAL_TYPE_SHIFT) & H265_NAL_TYPE_MASK\n return type === H265_NAL_VPS || type === H265_NAL_SPS || type === H265_NAL_PPS\n }\n}\n","/**\n * Native audio RTP decoder for G.711 PCMU (μ-law) and PCMA (A-law).\n *\n * IP cameras almost universally use G.711 for audio (8kHz, mono).\n * This decoder converts raw RTP payloads → PCM float32 samples,\n * matching the format of the ffmpeg audio path (f32le mono 16kHz)\n * so downstream consumers (audio-classifier, VAD) receive identical data.\n *\n * Output: 16kHz mono f32le — upsampled from 8kHz via linear interpolation.\n */\n\nimport type { DecodedAudioChunk } from '@camstack/types'\n\nexport type AudioCodec = 'PCMU' | 'PCMA'\n\n/** Target sample rate for decoded audio (matches ffmpeg path). */\nconst TARGET_SAMPLE_RATE = 16000\n/** Buffer ~0.5s of decoded audio before emitting a chunk (matches ffmpeg). */\nconst CHUNK_DURATION_MS = 500\nconst CHUNK_SAMPLES = (TARGET_SAMPLE_RATE * CHUNK_DURATION_MS) / 1000 // 8000 samples\n\n/**\n * Decodes G.711 audio RTP packets into PCM f32le chunks.\n *\n * Buffers decoded samples and emits DecodedAudioChunk when enough\n * samples accumulate (~0.5s), matching the ffmpeg audio output cadence.\n */\nexport class AudioRtpDecoder {\n private readonly codec: AudioCodec\n private readonly onChunk: (chunk: DecodedAudioChunk) => void\n private buffer: Float32Array\n private bufferOffset = 0\n\n constructor(codec: AudioCodec, onChunk: (chunk: DecodedAudioChunk) => void) {\n this.codec = codec\n this.onChunk = onChunk\n this.buffer = new Float32Array(CHUNK_SAMPLES)\n }\n\n /**\n * Process a raw RTP packet (header + payload).\n * Strips the 12-byte RTP header, decodes G.711 payload, upsamples 8→16kHz.\n */\n processPacket(rtpData: Buffer): void {\n // RTP header is 12 bytes minimum (no CSRC, no extensions handled here)\n if (rtpData.length <= 12) return\n\n const payload = rtpData.subarray(12)\n const decode = this.codec === 'PCMU' ? decodeUlaw : decodeAlaw\n\n // Each byte = one G.711 sample at 8kHz.\n // Upsample 8→16kHz via linear interpolation (2x).\n for (let i = 0; i < payload.length; i++) {\n const current = decode(payload[i]!)\n const next = i + 1 < payload.length ? decode(payload[i + 1]!) : current\n\n // Original sample\n this.pushSample(current)\n // Interpolated sample (midpoint)\n this.pushSample((current + next) * 0.5)\n }\n }\n\n /** Flush any remaining buffered samples as a partial chunk. */\n flush(): void {\n if (this.bufferOffset === 0) return\n\n const data = Buffer.from(\n this.buffer.buffer,\n this.buffer.byteOffset,\n this.bufferOffset * 4,\n )\n\n this.onChunk({\n data: Buffer.from(data), // copy — buffer will be reused\n sampleRate: TARGET_SAMPLE_RATE,\n channels: 1,\n timestamp: Date.now(),\n })\n\n this.bufferOffset = 0\n }\n\n /** Reset the decoder state. */\n reset(): void {\n this.bufferOffset = 0\n }\n\n private pushSample(sample: number): void {\n this.buffer[this.bufferOffset++] = sample\n\n if (this.bufferOffset >= CHUNK_SAMPLES) {\n // Emit full chunk\n const data = Buffer.from(\n this.buffer.buffer,\n this.buffer.byteOffset,\n CHUNK_SAMPLES * 4,\n )\n\n this.onChunk({\n data: Buffer.from(data), // copy\n sampleRate: TARGET_SAMPLE_RATE,\n channels: 1,\n timestamp: Date.now(),\n })\n\n this.bufferOffset = 0\n }\n }\n}\n\n// ── G.711 μ-law decoding ──────────────────────────────────────────────\n\n/** μ-law compressed byte → normalized float [-1.0, 1.0]. */\nfunction decodeUlaw(byte: number): number {\n return ULAW_TABLE[byte]!\n}\n\n/** A-law compressed byte → normalized float [-1.0, 1.0]. */\nfunction decodeAlaw(byte: number): number {\n return ALAW_TABLE[byte]!\n}\n\n/**\n * Build the μ-law decode table (ITU-T G.711).\n * Each of the 256 possible byte values maps to a 16-bit PCM sample,\n * which we normalize to [-1.0, 1.0] for f32le output.\n */\nfunction buildUlawTable(): Float32Array {\n const table = new Float32Array(256)\n for (let i = 0; i < 256; i++) {\n const complemented = ~i & 0xff\n const sign = (complemented & 0x80) !== 0 ? -1 : 1\n const exponent = (complemented >> 4) & 0x07\n const mantissa = complemented & 0x0f\n const magnitude = ((2 * mantissa + 33) << exponent) - 33\n table[i] = (sign * magnitude) / 32768\n }\n return table\n}\n\n/**\n * Build the A-law decode table (ITU-T G.711).\n */\nfunction buildAlawTable(): Float32Array {\n const table = new Float32Array(256)\n for (let i = 0; i < 256; i++) {\n const xored = i ^ 0x55\n const sign = (xored & 0x80) !== 0 ? -1 : 1\n const exponent = (xored >> 4) & 0x07\n const mantissa = xored & 0x0f\n let magnitude: number\n if (exponent === 0) {\n magnitude = (2 * mantissa + 1)\n } else {\n magnitude = (2 * mantissa + 33) << (exponent - 1)\n }\n table[i] = (sign * magnitude) / 32768\n }\n return table\n}\n\nconst ULAW_TABLE = buildUlawTable()\nconst ALAW_TABLE = buildAlawTable()\n","/**\n * G.711 µ-law encoder — converts 16-bit signed PCM samples to µ-law (ITU-T G.711).\n *\n * Used to bridge non-native audio codecs (AAC, Opus) into WebRTC's\n * PCMU negotiated track. The audio-codec cap decodes to linear PCM;\n * this module encodes the PCM to µ-law for the RTP payload.\n */\n\n// µ-law compression bias\nconst MULAW_BIAS = 0x84\nconst MULAW_CLIP = 32635\n\n/** Encode a single 16-bit signed PCM sample to µ-law byte. */\nfunction linearToMulaw(sample: number): number {\n const sign = (sample >> 8) & 0x80\n if (sign !== 0) sample = -sample\n if (sample > MULAW_CLIP) sample = MULAW_CLIP\n sample = sample + MULAW_BIAS\n\n const exponent = MU_LAW_COMPRESS_TABLE[(sample >> 7) & 0xff]!\n const mantissa = (sample >> (exponent + 3)) & 0x0f\n const byte = ~(sign | (exponent << 4) | mantissa) & 0xff\n return byte\n}\n\n// Lookup table: maps (sample >> 7) to exponent (0-7)\nconst MU_LAW_COMPRESS_TABLE = new Uint8Array(256)\n;(() => {\n for (let i = 0; i < 256; i++) {\n let val = i\n let exp = 0\n val >>= 1\n while (val > 0 && exp < 7) { val >>= 1; exp++ }\n MU_LAW_COMPRESS_TABLE[i] = exp\n }\n})()\n\n/** Convert an Int16Array of PCM samples to a Uint8Array of µ-law bytes. */\nexport function pcmToMulaw(pcm: Int16Array): Uint8Array {\n const result = new Uint8Array(pcm.length)\n for (let i = 0; i < pcm.length; i++) {\n result[i] = linearToMulaw(pcm[i]!)\n }\n return result\n}\n\n/**\n * Downsample µ-law by picking every Nth sample. Used when source\n * sample rate exceeds 8kHz (e.g. AAC at 16kHz → G.711 at 8kHz).\n * Simple decimation is acceptable for speech/ambient audio.\n */\nexport function downsampleUlaw(data: Uint8Array, ratio: number): Uint8Array {\n const outLen = Math.floor(data.length / ratio)\n const result = new Uint8Array(outLen)\n for (let i = 0; i < outLen; i++) {\n result[i] = data[i * ratio]!\n }\n return result\n}\n","/**\n * Pre-encoded Annex-B placeholder keyframes for both H.264 and H.265.\n * Single 1280×720 black frame with a centred text label\n * (\"RECONNECTING\", \"SLEEPING\", …), encoded with x264/x265 ultrafast.\n * Embedded as base64 to avoid spawning ffmpeg at runtime.\n *\n * Frames carry SPS+PPS+IDR (H.264) or VPS+SPS+PPS+IDR (H.265) in a\n * single Annex-B blob so a fresh consumer (RTSP restream, WebRTC\n * AnnexB feed) decodes them without waiting for the next \"real\"\n * keyframe.\n *\n * Codec selection happens at the broker level: a broker carrying an\n * H.265 source emits H.265 placeholders so a WebRTC session\n * negotiated for H.265 sees correctly-coded fill while waking up.\n *\n * To regenerate (regression/refresh after font change):\n * for label in reconnecting sleeping offline disabled waking; do\n * upper=$(echo $label | tr '[:lower:]' '[:upper:]')\n * # H.264\n * ffmpeg -hide_banner -loglevel error -y -f lavfi \\\n * -i \"color=c=0x101010:s=1280x720:r=1:d=1\" \\\n * -vf \"drawtext=fontfile=/System/Library/Fonts/Supplemental/Arial.ttf:text='${upper}':fontsize=64:fontcolor=0xb8b8b8:x=(w-text_w)/2:y=(h-text_h)/2\" \\\n * -frames:v 1 -c:v libx264 -preset ultrafast -tune stillimage \\\n * -profile:v baseline -x264-params \"nal-hrd=none:aud=0:repeat-headers=1\" \\\n * -f h264 -bsf:v \"h264_mp4toannexb,filter_units=remove_types=6\" \\\n * ${label}.h264\n * # H.265\n * ffmpeg -hide_banner -loglevel error -y -f lavfi \\\n * -i \"color=c=0x101010:s=1280x720:r=1:d=1\" \\\n * -vf \"drawtext=fontfile=/System/Library/Fonts/Supplemental/Arial.ttf:text='${upper}':fontsize=64:fontcolor=0xb8b8b8:x=(w-text_w)/2:y=(h-text_h)/2\" \\\n * -frames:v 1 -c:v libx265 -preset ultrafast \\\n * -x265-params \"keyint=1:repeat-headers=1\" \\\n * -f hevc ${label}.h265\n * done\n */\n\n/**\n * State the broker is currently in. Drives which placeholder frame is\n * emitted to encoded subscribers (RTSP restream, WebRTC AnnexB feed)\n * while no live RTP is flowing. Default `reconnecting` is the\n * legacy behaviour for the dial-fail/reconnect path.\n */\nexport type PlaceholderKind =\n | 'reconnecting'\n | 'sleeping'\n | 'offline'\n | 'disabled'\n | 'waking'\n\n/**\n * Codec the placeholder frame is encoded as. Selection lives at the\n * broker level — an H.265 source surfaces H.265 placeholders so a\n * negotiated WebRTC session decodes them with the same codec as the\n * live stream. Only `'h264'` and `'h265'` are supported (the only\n * codecs that flow through the broker's encoded subscribers today).\n */\nexport type PlaceholderCodec = 'h264' | 'h265'\n\nconst FRAME_B64: Readonly<Record<PlaceholderCodec, Readonly<Record<PlaceholderKind, string>>>> = {\n h264: {\n reconnecting: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgARwAaAfgvaKICMwoLAOgADxEIjArGFj0HzJsgAA2AHATLIAAMgBQE3ACIwbKGUWeUI9Xg4ABsAOAmWHAANgBwEyywADYAAgMGRwADYAAgMGQ4ABkAKAm4QAAYAHAq4HAAMgBQE3A4ABgAcCrn4/ABwjgGMCXnxbX/yxvWnTbBwke+QAqCJtK/C/ANHYwBiaibAhFAwNAYBETMKEp/DdMAAVluoAAgKyf8BWwZFmQC+raGBG1IAwEgPQdQ7AEAYHCVS5MuqyLBIm21NwPwEdMjMJ8h2KQPTQfEi5poyZEw/1NvqAA8ZCKyiNLHIZ6XyAADIAQDbgYAro/YCAZstAw/vNEAqAAIBiyrw4ABkAIBtzDgAGQAgG3B/4BjvB/2xbteXFxm4BcTAgkL5YdfLwCIgyByUFDRzz1eDr5YdfLLAANgACAwZHAANgACAwZyAACgAgCrmHAAKACAKufvZY7AA7AACAQAQPJEWx8kFDGRyZ/eABHf9E/EeMqpJFVe/gAeJVOoQrZdIPEev4AH8J5YVwwpkr3+37/+/wGgClGk1MvAN9VJ6BIUWVMODsABAPgACACEiJKqGeGhQqwRGuD0AAYoXqu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RLHYAEH1zdzf/gBMRiKyHMpRkqWH47AARBqhDjKTaFvcon/4AD5EY1QhM4voRkAAGgA4CJYAQtDIw6J8ppDx2AAiMyIBjO3ee9zH/+GBI/NvyAADQAcBEsADBYIweqs4k4sfgAIADAgkqwysyUKlCrRIqQ/wACEUSqy5aKeEb/0JWYE4gmijPjBpr7TQ78AAkQAAQEhUmDIwVtWD8meJumEurCowj2gJxHrWF5hvElDlMS7OasJqgA2Y5AHeoAAgKVzv/oNWfEBRXSABffbEAAE1AAEBS4wAAQAAEA24AE0TAihBEW1YKjTLkAAGgA4HS8fgAIGHBQoVzpjA7v1bsT4PxEgrgFIFpyZIAPcgPZk4xdCozhgB6De+qQEPg0EOzwYwJxxFVGfMGQfJFhwG0eU+Q8DSdo86EALwQR/bIyIDJXBFlFl4E3kNhenCVOaDsBQAIBAkmNOyGGSSVsAjkDV3wI6MjMEXMOhrAxpjMxp4ijbpzzSk+A87YQeoiABfu/sQpLw2zMgCE+/pUAntEGlsl0IgB+UAQBdCsHAQuUFDsqfjw6ZYdNVslPB2AAgYAfhIwvZMuvFIEKDvPyBwAKeiEp3qDIWsbh9ENEH5R+FmHPA0cqMNUxNgC/mp4bj7pbSXp9DEAiQUaWx2rYrlD3E2AIS++5yZmgmAw7d8BLALbDw8skKUFK/v1qEQbDgUp5QNIQAAbACAfcXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXeG+/367w3+/33+/37777799d99/v994b77799/v9dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeA=',\n sleeping: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgAIBgAEPwDIl6AJKw3fccBLCZwAHKurzmDTjrCx0AC1cGKrrkpgC3X4FFZAP0Uaf7/6gZgeKmof6pKQDFucb/wYp75WNKMZMNLhTAUAVnFsltEAGDB64A7LxPeqngCF4/AEAgcCPCbK0wt32oU2+7FSzAg1zwoGYY6J5NiACWYMvJ4xAAWoI254HCYUE/1doEB1DA/VNnAlhlGCkobZhpf/euUyMgit1MDgPz//6ewJdgXXJoQEX0DzktM5h4U0h2AkYAK8DY5OrsAFl7Vq4BIA2V6cjTQdhKNqXYkqw0Ut8ACnIZCjJ8wppIEyAEBACbgYAH5v2DCCkY/LAsAA2AAIDBmjkDKJTBzRBfXvDgICAE3PDgICAE3B34bu7nOdwABBIAAEAze9gASAiIAANgBwEywAnMzGqlKMc30jsgAAoAwIuB/hwACgDAi5hwACgDAi5+uOwAGBRTAPA9QGwrbYCgnAuAAUaIZHGW0Qlv50gAA2AHATLIAAIgGAbcw4ABsAOAmWHAANgBwEywCIgbApKijwCmPV4sAA2AAIDBkOAARAMA25hwACIBgG3MgAAwAIBFzDgAGABAIub8dgAIAs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iKqL+gYHiILNZcsFvCN/7TQOklXDjIuln8BV+nTmgMEwFLygbIkJCH35eOw8AAiD7JgAcORoP8BzLgiAWU+vfhmNvSMEUAwAPN5nmcCBboVnqVIQUTiFiZzyAACoBQbcxDgGAB4jM8SgBBakVng6AwrALSNMvToZltGEAAFQCg25gp2jAGB/jQ8e8dgAoAAIEIAAgCAERjADRCK2tqF/+B1ncwFQADVtoAQDoi4gAA0AGARLH4ACAEGLASmcBNaIPDDzUpHKT//+ACWBDkZTThJEXIDgBDiLmoAAbELBYzxmDgAGwA4CZa6666666666666666666666666666666666666666666666666666666666647AAYAGHWAELGCgIvxicO8WuFYAJSiqBLnAC954PAZRlkFw48wBjdNbJpgAGcthxDGwsUX4QK6KKdCKCxy/3/1B31BhNY1iYGYYYAFC2ILP5UGFgJYAAvPYYiPuTXAIdfsbiMBS0wDgZ3fD8ABAAwDCoFzPz1a4pq2Y//wAKbIkxlVhMSvMoIDpiNGax3lAABAPADGCdsQasEP2eEAAEQIFywDKAAh1THGcvPUQswHTI3ppv9SpZikOQXjfYXAIcuYlKByRh2AEggQPVOkjRH9A4nMhK2/+AHJSIAGAXqwEjy0AEtVZVTO8VKyZas51kkk4/kTLs4fFtoEbODpKFTEWkYbgxSiAiM8BYToQcwwVQgABYAAQCYfgAVpjKQZEeW0hw7AAQBBCQMRYRZFGrUswC1NJn/AB+YSgzkvKANT9XiAADQAcBEsSAAaBR5sYU5iwADQAAgMmQcAA0AAIDJkgAA0AHARLDgAGgA4CJY/AAQ2YAF9alAw4dH63z5///ABzDTiIaSwr1eLAANgACAwZAhshJylENViRhAABsAMAmWOwAGhxDANI9WqgHIDEWqH/gBFIxlYZVJLW5hAABoAOAiWAD8wTghCyTgC1/V4sAA0AAIDJkSAAaAoabGGOYgAA0AHARLfHYAFgYwPTqkREPiuR6WOrf4OAAaAAEBkyAD4gNkBWGC1WvV4DgAGgA4CJYCxGIrIKwwchnhj4YAFeYihnJjixpTw7AAQBBCQORQZRFm7dtwCXuZP8AHYS4X2OaT6vFgAGgABAZMqAAGgoo2MI8xAABoAOAiWDgAGgABAZMg4ABoAAQGTIcAA0AHARLDgAGgA4CJY/AAiA/REZ5Ef/+ADmawkKKhnq9AhtEszCH7PMHYACIzIgGM7dx73Mf//D4HAANABwESwCMhiMNwUq0nu0u+OwAEYGiiY7W07Hu0n/wAQXkrekEAAGgA4CJeAFU8EYPFSITYTDHYACYD3Gp1vplPdb//wAMwpgASLMoA4FU+uOBDFzECF3kwx+AAhOAwe+XLfLD74RzmJ7dP4ALJXJiAtpYEDu37tFrI5i6oBGY0+cykFxS7tZgTiiKqL+nhgeIgt1lywW8I3/vjvwzgOkbjQnaYGUzWh46wSG9hcMNk2S6/jjNXFAA91AAEAgPyAgcAq5gBJIpYUQVuRu/xTQgAA0AHARLwAhNNIybF0UMfgKcAqAQLCQh7WF+Rdn0fvgMIJ7NygR8kAeB1f9ABOwXNNMoUZFwyBrAT30yAj4NAYPw7WIXGiLrreAU7MEWEAqjzF8XO4M3MBUHHw8GZJBpXARoxvrlyMTifGkLawCM2RobdCoz2wo1BNm8AEK/fD8AFAACoD4AAbWtAFsNT9/zfAkF+gkFgO1QGJqIoc6gYNQ5ZwABAKfk8x8oBLaIkXvvNCQEAAIAdzwcGUAAIFgtp+A4coBHVIY7/74tIBAACAPFh2IEQ/AHR3g7AAYAAIA6DgQoPIwhiOjamwn6R8ADGUsz0LKujNEL01cCoErP/f+oMo92eSJTz3cXyTAl+AAEBK2sFwtxqwBCH32v2QQkWYIScAg7ALfYHfrssBHC8gAEMJua6666666666666666666666666666666666666666666666666666666666677/f7994a79+++uu/313hvv9/rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrw',\n offline: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJydddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgAIAQcsOSSTTLlyxZIuXLf/wANhc8uVQgps2//QWgElpnArSQHn3h2AA4ADBMADxxeJWO1OLfdS9Bs/QADCh193AxANb38ABYlU8UMVsskHiLn+/7YELICKS04HAb9fDqNXOPveYDnMTLoBSwIaP2fQPHkQGAM13p+//nbhoKtUCn5CaBgpIA2DKJLlxxnYqkpEkg/Dj8AQjAAGAQTCxyPvtmpwKevTsuUcA0IZIYc1H2mrLxnjMgBArJmNE66YDDcB4PVTiQCUnBCAVG3DyiSs+w2ogYtWIF/9++5cjIADMXOEB4ds/8CMzI0GTsLyk4MwuSpRR03i/4GBtiexCgh8GAnuHYCwAwAOaLf0Ksjw9oPzoJL+EIuLJSAFwLKft8ACmkaZGGN26bIABBBdzGuxC807SzIA/GTxFRjoUgdpAK8JbPkkAQ4GHABBBdzw4AIILuD/wGK0P66UdLhIiO/gyLGwgAEFHyw4AIKPl4BoAQqZQFiSKq8HABBR8sOACCj5ZYABsAAQGDI4ABsAAQGDOQgCAAEAy5h0AQAAgGXPx+ADzQAX3wKYaZJZhv4AJIGQLltPIMA4oagCR64Bgkl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uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuv',\n disabled: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdgAIDBmgatGsRz4EOeLeNIaoAK4IoTYYcSaB6SIAgABANuEAAGwA4CZYcAA2AHATLwCkaABlQgoArnq9DwBAACAbch4AgABANuZAAQQVcw4AQQVcH4AOBDut5h5lXWn9tfoTCAFpcXTwIwAIBExOCkcZ4QItHXzzA41AEvZgwSXTL//x2AIYE8HHbnwLhNOR539HJQyAaKcqPtQcwCVj94YAFoaXVQUzrsCRB/9/AAuDkyBD/rKBQuBgU/dzrBAEZpphAvPAATjMf9dAh8ixQOxhpLUMArHgIH/vHYADQK0aHw4EAACAXBfepAdQAXECCkdJJgsmJpZAALgACATcAjNpgAaoQfw0RyDIACHEXOVAALgACATclQAC4AAgE3A4AQ4i5hwAhxFzHYACAMBDQCUih84AUyPVKvkE44cACXIlmH2MhSgAmhOL88YwskgR0Ag0TPvANTbaJLsZ+cD+JbfaIzlkCygK0c0/73QAGSw1BulYBuWZgRLcAAICP4GC1SAiBDKIFC8EHLUFQ4unmAPwBAQApVh7gt9NLRK0nnlCdgi2nDBGTfEAAFABAOl19AzyMAMr9zyoABgAAQAFidgO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rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrw=',\n waking: 'AAAAAWdCwB/aAUAW7ARAAAADAEAAAAMAg8YMqAAAAAFozg/IAAABZYiEOiYoAAjgycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJydddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcd/oH/47AAQBAGUBlZdJh5G9sxrA8HYAFbAAfbqQAFAm44CYXpxTUasssNGECENJJxx+tDI/9Ry8wBAOpYOO/f+DrFIcycwAAgIr3685zLaHgXN8IKM/o2feDBzo0Q4rHPBfP8AgCJWBh+7x2AAmAARwOv3gOBaB+1Tygfhc4CPXtgAOYUMFKd57jwafCAADYAcBMuQG07ivlZJgD8arZJMnA+ncYBgI5oNv/QcO3CKiGgDV78HQZJtQYpggXN/A67gggv/Cx97Fb6IAwEawG3/h2ADhnHDfssxKgIDDnB5vz8AE5hocrnuDCcsiQdcAAI0SyeaAI/r3/LDKKw0hoqq94YAWDbTjCEekj3v47AARgDA2nC+phF+FKPPlmgcoAHJCwOib+QsZpcAFQynCiPbJ1XgwJmExQr5/xbrIweDoOyFYDIYAQfe8NgAGmwTlgCXl4Z9/R2DQADTcH9YBry9ay7IAgHawHZ/qSoJjCQhxzw47AARgACAQFBfephQaQk9Bxxz+ErwAJTrkOCI6TmhoyQAAbADgJl0jlorTTSR594D0BpKKxXazT0AAy7yynAxBIE3/oSgKWDFpJANX9sGD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AAmkO4APxvh2AAiAAEA9IAARQDmQGaxcQU2GTKnhAMD1edgIvAo/E5vnsGpeH1KyYZ5QKaFqv/GAAF7QABAWXIkAAgPCSwDkEOQGTscjUFkQoWA0+AHmzIgOml+Awh37sN111111111111111111111111111111111111111111111111111111111111111133++/f77/ffvvrvDff7/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXg=',\n },\n h265: {\n reconnecting: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLjAFwEgEQEADwDgDgDgDQDJdd4QDwCwCwCgCgCQCQCQCQCYNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAV0oBlPeTVLjjiybt/79hr8egLGkfgRr4BMQkiA4Kvw8jeWLF+WRCVyWx9qYu5G9T5CK0jQtgcXtpIjS8fOF3Fs7TooZzWpZ8ReTWIvMHVoz1Khj/MspUENld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',\n sleeping: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLjAFwEgEQEADwDgDgDgDQDFjo4wDgCwCwCgCgCQCQCQCQCYNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAnJVW/5yV+komUy56aphjzFvh52pfB2PTfCwCh7ggQKBSBYFGbEzvodhyojZdGeFaSykK9Ax2TQRMp4vmNtSZ21tm+X8AAJbdd/W7GfmdQAeYoPMrNuiQvN/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',\n offline: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLiwLgSAiBAB4DgHAOAaAyPGfQGALAWAoBQCQEgJASAmNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAnIRJY7mt6jxDg1ZAEbP7/LhpeVRCRSHYPE9hJm0I9bQrAjX0yu6L7lrKwJuj+U+EieuvMXkRVod2huH5MMqkgjezPIXhMMMbzPxp9/3NjL4R6GTuXhhGpNVRUat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',\n disabled: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLjAFwEgEQEADwDgDgDgDQDFlYmgDACwCwCgCgCQCQCQCQCYNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAnJXh0f6dh91ZYSfYA2WZGi8L4B4Km93Ubr5/EFPTj6FOBGEL/s1k6y5ZvJx9Rlg/VUGc79Ok5oWDJetrtsLEUH4QjulhGTw2WWHi1fKx08W4ltGWipQDk4q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',\n waking: 'AAAAAUABDAH//wQIAAADAJ+oAAADAABdugJAAAAAAUIBAQQIAAADAJ+oAAADAABdoAKAgC0WW6SkwvAWgIAAAAMAgAAAAwCEAAAAAUQBwHGDEgAAAU4BBf///////////wQsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMjE1KSAtIDQuMSsxLTFkMTE3YmU6W01hYyBPUyBYXVtjbGFuZyAxNi4wLjBdWzY0IGJpdF0gOGJpdCsxMGJpdCsxMmJpdCAtIEguMjY1L0hFVkMgY29kZWMgLSBDb3B5cmlnaHQgMjAxMy0yMDE4IChjKSBNdWx0aWNvcmV3YXJlLCBJbmMgLSBodHRwOi8veDI2NS5vcmcgLSBvcHRpb25zOiBjcHVpZD05OCBmcmFtZS10aHJlYWRzPTMgd3BwIG5vLXBtb2RlIG5vLXBtZSBuby1wc25yIG5vLXNzaW0gbG9nLWxldmVsPTIgYml0ZGVwdGg9OCBpbnB1dC1jc3A9MSBmcHM9MS8xIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MSBuby1hbGxvdy1ub24tY29uZm9ybWFuY2UgcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1lb2Igbm8tZW9zIG5vLWhyZCBpbmZvIGhhc2g9MCB0ZW1wb3JhbC1sYXllcnM9MCBuby1vcGVuLWdvcCBtaW4ta2V5aW50PTEga2V5aW50PTEgZ29wLWxvb2thaGVhZD0wIGJmcmFtZXM9MCBiLWFkYXB0PTAgbm8tYi1weXJhbWlkIGJmcmFtZS1iaWFzPTAgcmMtbG9va2FoZWFkPTAgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTAgbm8taGlzdC1zY2VuZWN1dCByYWRsPTAgbm8tc3BsaWNlIG5vLWludHJhLXJlZnJlc2ggY3R1PTMyIG1pbi1jdS1zaXplPTE2IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTMyIHR1LWludGVyLWRlcHRoPTEgdHUtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBuby1zaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cmEgc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTAgbm8tbGltaXQtbW9kZXMgbWU9MCBzdWJtZT0wIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIG5vLWZyYW1lLWR1cCBuby1obWUgbm8td2VpZ2h0cCBuby13ZWlnaHRiIG5vLWFuYWx5emUtc3JjLXBpY3MgZGVibG9jaz0wOjAgbm8tc2FvIG5vLXNhby1ub24tZGVibG9jayByZD0yIHNlbGVjdGl2ZS1zYW89MCBlYXJseS1za2lwIHJza2lwIGZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludHJhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPTAuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIGFxLW1vZGU9MCBhcS1zdHJlbmd0aD0wLjAwIG5vLWN1dHJlZSB6b25lLWNvdW50PTAgbm8tc3RyaWN0LWNiciBxZy1zaXplPTMyIG5vLXJjLWdyYWluIHFwbWF4PTY5IHFwbWluPTAgbm8tY29uc3QtdmJ2IHNhcj0xIG92ZXJzY2FuPTAgdmlkZW9mb3JtYXQ9NSByYW5nZT0wIGNvbG9ycHJpbT0yIHRyYW5zZmVyPTIgY29sb3JtYXRyaXg9MiBjaHJvbWFsb2M9MCBkaXNwbGF5LXdpbmRvdz0wIGNsbD0wLDAgbWluLWx1bWE9MCBtYXgtbHVtYT0yNTUgbG9nMi1tYXgtcG9jLWxzYj04IHZ1aS10aW1pbmctaW5mbyB2dWktaHJkLWluZm8gc2xpY2VzPTEgbm8tb3B0LXFwLXBwcyBuby1vcHQtcmVmLWxpc3QtbGVuZ3RoLXBwcyBuby1tdWx0aS1wYXNzLW9wdC1ycHMgc2NlbmVjdXQtYmlhcz0wLjA1IG5vLW9wdC1jdS1kZWx0YS1xcCBuby1hcS1tb3Rpb24gbm8taGRyMTAgbm8taGRyMTAtb3B0IG5vLWRoZHIxMC1vcHQgbm8taWRyLXJlY292ZXJ5LXNlaSBhbmFseXNpcy1yZXVzZS1sZXZlbD0wIGFuYWx5c2lzLXNhdmUtcmV1c2UtbGV2ZWw9MCBhbmFseXNpcy1sb2FkLXJldXNlLWxldmVsPTAgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTEgcmVmaW5lLWN0dS1kaXN0b3J0aW9uPTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm8tbG93cGFzcy1kY3QgcmVmaW5lLWFuYWx5c2lzLXR5cGU9MCBjb3B5LXBpYz0xIG1heC1hdXNpemUtZmFjdG9yPTEuMCBuby1keW5hbWljLXJlZmluZSBuby1zaW5nbGUtc2VpIG5vLWhldmMtYXEgbm8tc3Z0IG5vLWZpZWxkIHFwLWFkYXB0YXRpb24tcmFuZ2U9MS4wMCBzY2VuZWN1dC1hd2FyZS1xcD0wY29uZm9ybWFuY2Utd2luZG93LW9mZnNldHMgcmlnaHQ9MCBib3R0b209MCBkZWNvZGVyLW1heC1yYXRlPTAgbm8tdmJ2LWxpdmUtbXVsdGktcGFzcyBuby1tY3N0ZiBuby1zYnJjIG5vLWZyYW1lLXJjgAAAASgBrcLiwLgSAiBAB4DgHAOAaAyfX/AGgLAWAoBQCQEgJASAmNEX/8/CcyLuuu2nGKAAAAMAAAMAAAMCEgdwaCTacYoAAAMAAAMAAAMABEQI1dtOMUAAAAMAAAMAAAMAElAMJyWgAAADAAADAAADAAAqYA5IgAAAAwAAAwAAAwAAGTARgAAAAwAAAwAAAwAAB9QAAAMAAAMAAAMAAAMAAQsAAAMAAAMAAAMAAAMAGBAAAAMAAAMAAAMAAAMBkQAAAwAAAwAAAwAADTgAAAMAAMsbsDkcoZgDIVg0QtuHSsRxkPs93x2tkl6E1gXx0vsrK+j+vQJ0cELr/DHTvsNk9haL5Nc9sUfbRjjmhBTJTjTszXpCeksgb575CVY08mk/BPfZmrL7JLcxTIpDTbIxA7r4+5r9qR6eGrPxop9e0P6i7wLtGazw5dnVUIDwp0foiNPFnCo46E/tHMJcNWZUN+NJ8E9rF7lrLtliTlqiQGinT7BvzCoM8Mk+ELBjySIOD6F0coboBs8jyJrikJyuBivEdikXt/KhTCx//YYLJ7hxR9xZxVy9/ur3Fc++xOECBLycmeXf4r/+K5XugarTvHbUXPuWYSB2TAZeRPARsJVcX/WBXHktQ5oiTqDCzVuCGEcDSEn97GSsMMU/hxzj5k6x6hv6jTjPHvBLKVDnHYQf9VPbtkkq1X6ntJeSDHrBw9irvDkLIVLgQodfa+Er0HVNLMHuSkzZxe9jPwmy8YFlPqO2GFz0JS+6axBJuFqixn0hV2Awr130pQDCVaIHv2SLA5my46N9CgYlSThMpGzZYGrwoFAVl2OE96powBtEDXPRk9egDfFd5uN7JSlRBwXgK/eeD+bCrDWrGtNtyRCXjw24dB2e8uF1StvGqrz2agIOl3BPLjEuwfn97PVri3uuL7i9ZC6ZFvylvOYbNdZYRIt9KrRSQA/hjgvDnYI62YJ24WyWUch32pVVgbImG+mlk7Banilr8nqbEodGNcVb5ClWqTuxPb57lQIeIDXsqTR3mZNFaTO0SwiU4FM+qb7UmLnhNPPwsMovt5XIcQjRMJWj5vwVfLTDypgNui7VSNv6Qp4agriRmDIK2ymemTQDz3FHYsqkcaQ6Am5w+4iyEZ878wt9J/eiWu9299r6CtWfBSJ+qfp3cLtqQ8SZ5AC/xIlDoL2Ezfbv4sTjlTZ/OpANcfiBQCSTY7bxP07Z1PdqbXuf/MzXveaEcW2XAronSqJBdVHNAP3B8I8eErA7xT2/OBv7tFcmbcot7ScKHBTGRZ6D0sVcNyb7jDDNNN1o+Pp6b7KC/bAZpvIRhOWRcYL2qeLBVS4njupG2eQPU7EwOQLme45aAwmyvMedb9OThlAzFxDFgTCaSND5gSQrUshQS3YsE6UX5rxP1qgVFwSIMqrQQzrKUByEIOW0WhWNmZPp0nfpiN3XG5E+TUrwRBIiHcCDcZRPQ9Vta3A84su2iCiaYMonrY7ErYdUxD9mdxHQOtKVcJYA5sVuO8gBI7hOGMnFo1EYBrMg743WyZvZ2UDyzMwJNrsGUfTQCOMDEJzrpshaX/eii8LeKqK3zv4Rp8/2bcB9siroRgpudcqBCxQtzL4c5DjTd232sjeuwipYCkUGN14nHQn+Q1yttlIgjDIg0r8h8HQM0H/m1SCN8Vy3ZnyaXZLq/VpH8xPiK3xyzBa46QTtdVcEo6aYW1EaPMDEK8xj2F3qEkEjaSF+tuDFv1NugxnFsv1PS7sFwg03pbc0fJsWCGrGQNQJ8DAYZUSJt9eFCT2rPlVmNIk8YGD5W0sNYGiDo/lomqLGWtTl5DCBsGrgX8zIXdxztDR+A0Tp9PyB7rioFnirsdooHfqxwSztwtOG2dzW7xQGQ4KfZ40upQexRK3Y04Uc9Hqwjfx7CLuLeMaglNnk+VDO5crl/IzvyXCIfoSbAcA2w9dWY+amZyr7yF2MuogAAAMAMSAAAAMABLyn4jHVTV4C9m65M58fLF/C9Xg2b1eiJRgan5Vf+kYz2iaxZSlaKcjlxT/Celn8V0jeacfMddGrADewQE1VKdHa+B4cEyX2NZuCmN2k8MAUiXiljQcAeUiISy9yAY3BAO+gl4IEac5/7UO4L2X+voIRT0SGzNkkJF4EDn/1qe4fHvDrCmErRN+sMIZ1yo04msI+v/MO4SLqAOee9k3kfig3Vzbk7lzM7bQE0700ZLJMhMl7xsLvRtJ2FKynDrnm7VPDGC+VAuQ/CEgyC55eZ0pqUvdc0HEVRIr5LTgkama/jH2VIHW2Ef2l4DFg3ulaUwPQtytqNG84/IyOeLLdU2LVkUwvXSnelBqvQkSB3Hmdd+MYq9hd4s5ph/Y939V/U1uoqYIbBiabBIxOO+K39bZH5G1Um43tOLHorEo8XoAbCppgjSojunpZ4yQE/xIAUci4Tc+/JXDY+ZCnCccHP6LjZtD0cn1Il+eTyOIGbth0cnfqfv2o8/gg723Vt5RsxxJqp8Jbb8fQdFH6h1z378Iv+RQN7ZsQMBJwlsg3TpQXGA50JuA3eK1PN9jpaw1f99p2SWK+e03RdGYWv1tPO789fmy/4LgJ5yjUEKWPS22+JfOej1jNqU791sbvEfpz4a5aPjWTcrx2wclGWEwW5bfg+aXIjLdxYQOEDZLJ9r+0srXwNyNsw+a0mYY9ZDWFPH1JAvSOr83IPAAJ0mVTKlRgpGsHQdtIHg2wfcLhiIZjL74sri8iYhVnjCrBdL1je2rtXoY6bLllVPManimEmLuwxMoMNH2ba/i7H9gIwKMwkX/w5WZLNOyKIFn9Qb0Xbf/aEXlqBvtpsxNuaJx2E2vperCLoquOOYSn+by70G800l9vwzEo4ITGcYOQGeO3P0jyyxv3SGbMGHNw5lDJTf6I6gI2GcplYY6NjH9ynT52Pkt5olIQFyUoI/cfpgdqJvDonrNSacH8haAzWUDjOhX1BD7SRFu8xZ48ePQIvQOyBWwYEF9nnD4iQp0YhbrZgUWZZGRdSsKghNjEZ6vTiQtZF2//n+WPhxQIfBeSo4/BlU7mn3DQ3j7qkgFPaBq354oL35UPOH/fWQ1jK8K6PZde6HCcmF3SifQkPoIsa3h8+E00MHDUvGTWNEQ9aNipct3S/2EtCfDF+K45vLn59m7/fdwvwo9SieoPLxWpzQ3KqoXq1Pj5Wfc3ibH6//d169HFWNB2n96yefMd5LK4cSzm/pwF6PPqblEeAc6YqS+TyjtXMSCOFi184xQm6XZq7cdaQbs600BijnDlk5tzuLtK6GDFQhDh5zZxLgRDJ+d63nVQBdXo7yANj5Ij5DaHkL4O6xKt039eCRmjKMBwq2IaEEEjnrKZszOJmmjGupvzIo+gcuq1M/49IdbiBaDrmqehU0Y182DPheuMzsZy88Mg/EBhv7L9gy6GReIZ/BWdU76G302t3sqOfdsg6a8KIO2kE1lijCmYuHyd8QB0HNf7Ms4HzCPDXRxXLiiOCZJ4KR3krjkcA5+TE6YR0twMWciWZr7QqCqEAZY+j42sv9qGskSEvLoA38n6JN0pjmdLD3qKoTf7XrRivX6hW8I9GIsx0hqEkeDCnnX+QxElUKjukydEGdxiNH0dUf2gBIzpxwPKY+WHqM41Gf02j1wi+rjcpiI5yOggrPqbcyTxTtdSC5GHGv2fyAA7b+IzRSjAiiE/HojdyPIgFOh3UOE4GgN8yP6U3igjHSW564FqyFPsxcN9Pdqfs0YcXvlirR/ImfnMvMKmnZgtey9wyX7vmi2FtOrhJaIafd/qvK4+f/xbuyhgJSkWxLsanzfgpAELQ/oRHH599W6m0RPe8u0BecY7JSO7GCGWhPI7g7rLe9fXZ+/vskgSPGlBPu0/Yj37mPmgrN3z+1U/mznVXoAg66zuwDHaHXSXYiUbYN4YHlF3nocbIDkslwZ/xkp6HM4Xq9mVRszFm4a8Y56KAY+wOXQLd4zfhj78m/jYbf02+JQFGW2NLsX0RC1ptjpFaS2GYkV/7cZKKNczQsWkYwu31XHtX0HHrD/sMOrjRiccQAA6cohGgHUmC14b7tqrQlipt+ZCz2yfysY6bJmE2oRtO+a9RM4TJMh6shUXKbIaSb0Q2V+IQbiiK2zIebRLeocfr31upuo6+TnV1BuWQr4F394y1Ah1cYGon6saVZm1iBisxcKksvpVVZQRjTPP1v3A3Q7PiT73kovMuCDx+SpmggFOc9yf+VA6jgWN0Wq9RlYx1CWMedmYOOfv0Re8eEZD+68Qj0y7TuCsdEijeGsbsRmZbGs+9T8zbAwJDYqO3RhcTT1zXBHqzS4OUqzka3eAYH+nak3czehwpG8vblVxUUlQ0n1jl9tu/7t2mpJnen1JJsd8g83oF97Nu4KpGDoVqhdqoy2rES30CCD5Rownu/2zSrNUX5i52ezlxPFDZStQ0/FaXlyKue9a4EHGxbvnSxJaI8KtpFKel7Zsb75k81GH0ItYxeEVmLD5CZbWvD9BzIBIaonrBAHduS//NfQJFCXgEB+x3l15kqXC4guCAbS4vkvO6QjYz0q9/YrHBQz/Ikkw2R09XZAC8Hkgmd1pW3WStA7hOHL77ZzmlfsMub5Fr2Tdq7/AwgeD7X2g2/Xdd6rw3KrH+2ov5QrsrOubhYvDM0eWI1Wu9qPScMRCjCP5fc31X+0IFsbdzhRn1n/u75nMy0BnQAAAAwAQSYOqpexiAAaEAAADAAADAAADACtgAAADAAADAAADAH+AAAADAAADAAADAWMAAAMAAAMAAAMDsgAAAwAAAwAABuQAAAMAAAMAAA4YAAADAAADAAAY0AAAAwAAAwAAKSAAAAMAAAMAADzghwAAAwAAAwAAAwAAAwAAAwAAAwFp',\n },\n}\n\nconst cache: Partial<Record<PlaceholderCodec, Partial<Record<PlaceholderKind, Buffer>>>> = {}\n\n/**\n * Get the pre-encoded placeholder frame for `(kind, codec)`. Buffer\n * is created once on first use per (codec, kind) pair and cached\n * permanently — safe to call from hot paths (~5-9KB per pair, ~60KB\n * worst-case across both codecs and all five states).\n */\nexport function getPlaceholderFrame(kind: PlaceholderKind, codec: PlaceholderCodec): Buffer {\n const codecCache = cache[codec] ?? (cache[codec] = {})\n const cached = codecCache[kind]\n if (cached) return cached\n const buf = Buffer.from(FRAME_B64[codec][kind], 'base64')\n codecCache[kind] = buf\n return buf\n}\n\n/**\n * Backwards-compatible alias for the legacy single-frame API. Returns\n * the H.264 `reconnecting` placeholder (the original \"black screen\n * during reconnect\" frame).\n *\n * @deprecated prefer `getPlaceholderFrame(kind, codec)` so the\n * consumer surfaces both the actual broker state and the matching\n * codec to the operator.\n */\nexport function getBlackPlaceholderFrame(): Buffer {\n return getPlaceholderFrame('reconnecting', 'h264')\n}\n","import type {\n IStreamBroker,\n StreamSource,\n StreamSourceType,\n EncodedPacket,\n DecodedFrame,\n DecodedAudioChunk,\n DecodeOptions,\n BrokerStatus,\n BrokerStats,\n Unsubscribe,\n IRestreamer,\n IScopedLogger,\n FrameFormat,\n} from '@camstack/types'\n\nimport { DecoderSessionProxy, type DecoderCapApi } from './decoder-session-proxy.js'\nimport type { AudioCodecCapApi } from './audio-codec-proxy.js'\nimport { AudioCodecSession } from './audio-codec-session.js'\nimport { parseAudioSpecificConfig } from '../rtsp/audio/aac-config.js'\nimport { FrameDropper } from './frame-dropper'\nimport { StreamPipeServer } from './stream-pipe-server'\nimport { EncodedRingBuffer } from './encoded-ring-buffer'\nimport { RtspRestreamer } from '../rtsp/rtsp-restreamer.js'\nimport { NativeRtspClient } from '../rtsp/rtsp-client.js'\nimport { Rfc4571Reader } from '../rtsp/rfc4571-reader.js'\nimport { RtmpReader } from '../rtmp/rtmp-reader.js'\nimport { RtpDepacketizer } from '../rtsp/rtp-depacketizer.js'\nimport type { DepacketizedNal } from '../rtsp/rtp-depacketizer.js'\nimport { AudioRtpDecoder } from '../rtsp/audio-rtp-decoder.js'\nimport type { AudioCodec } from '../rtsp/audio-rtp-decoder.js'\nimport { pcmToMulaw, downsampleUlaw } from './g711-mulaw.js'\nimport { getPlaceholderFrame, type PlaceholderKind, type PlaceholderCodec } from './placeholder-frames.js'\nimport { errMsg, maskUrlCredentials } from '@camstack/types'\nimport type { Sharp } from 'sharp'\n\ntype SharpFn = (input: Buffer | Uint8Array, opts?: Record<string, unknown>) => Sharp\n\nlet cachedSharp: SharpFn | null = null\nasync function getSharp(): Promise<SharpFn> {\n if (cachedSharp) return cachedSharp\n const mod = await import('sharp')\n cachedSharp = mod.default as unknown as SharpFn\n return cachedSharp\n}\n\n/**\n * In-place R↔B swap on a packed RGB24 buffer. Used by the broker's\n * per-frame conversion cache to satisfy `bgr` subscribers when the\n * decoder's canonical output is `rgb`. Returns a new Buffer so the\n * source frame stays untouched (other subscribers may still need it).\n */\nfunction swapRedBlue(rgb: Buffer): Buffer {\n const out = Buffer.allocUnsafe(rgb.length)\n for (let i = 0; i + 2 < rgb.length; i += 3) {\n out[i] = rgb[i + 2]!\n out[i + 1] = rgb[i + 1]!\n out[i + 2] = rgb[i]!\n }\n return out\n}\n\nconst DEFAULT_MAX_FPS = 5\nconst DEFAULT_SCALE = 1\n\nconst INITIAL_RECONNECT_DELAY_MS = 1_000\nconst MAX_RECONNECT_DELAY_MS = 30_000\nconst DECODER_TEARDOWN_GRACE_MS = 2_000\n/** Grace period before suspending after last consumer leaves. */\nconst SUSPEND_GRACE_MS = 5_000\n/** Timeout for push-mode sources: if no packet arrives within this window, emit error placeholder. */\nconst PUSH_STALL_TIMEOUT_MS = 15_000\n\n\ninterface DecodedSubscriber {\n readonly callback: (frame: DecodedFrame) => void\n readonly frameDropper: FrameDropper\n /** Caller-supplied identity — used by `listClients` for diagnostics. */\n readonly tag: string\n readonly maxFps: number\n /** Requested output format. Gray consumers don't need JPEG encoding. */\n readonly format: FrameFormat\n readonly subscribedAt: number\n framesDelivered: number\n framesDropped: number\n}\n\ninterface AudioSubscriber {\n readonly callback: (chunk: DecodedAudioChunk) => void\n readonly tag: string\n readonly subscribedAt: number\n chunksDelivered: number\n}\n\nexport class StreamBroker implements IStreamBroker {\n readonly deviceId: string\n\n private _status: BrokerStatus = 'idle'\n private source: StreamSource | undefined\n /**\n * Manager-supplied resolver that derives the current `StreamSource`\n * from the live cam-stream registry. When set, every dial attempt\n * goes through it so a re-publish (e.g. Reolink updating an\n * `rfc4571` URL after the lib's TCP server idle-tore-down and\n * rebound) is observed transparently — mirrors Scrypted's\n * `ensureRfcServer`-on-each-`getVideoStream` pattern. `null` for\n * tests and stand-alone callers; falls back to the source passed\n * to `start()`.\n */\n private sourceProvider: (() => StreamSource | null) | null = null\n /**\n * Manager-supplied notifier that asks the publishing addon to\n * refresh its loopback transport (e.g. re-run\n * `ensureRfc4571Server`). Called when a managed-loopback dial fails\n * — the publisher republishes with a fresh URL and the next\n * reconnect resolves it via `sourceProvider`. `null` when the\n * broker has no manager attached (tests).\n */\n private requestSourceRefresh: (() => void) | null = null\n /**\n * Last time we asked the publisher to refresh the source — used to\n * dedup the refresh notify. A single connect failure fans out into\n * two events (`socket.on('error')` → onError callback AND the\n * rejected `reader.connect()` promise → .catch); without dedup the\n * publisher's `publishToBroker` runs twice in the same tick.\n * Idempotent on the publisher side (the lib's shared pool catches\n * the second one as `awaiting in-flight create`), but the noise\n * isn't useful. 500 ms is wider than the worst-case fan-out delay.\n */\n private lastRefreshRequestAt = 0\n /** Detected or configured codec — persists across reconnects */\n private detectedCodec: string | undefined\n private decoderProxy: DecoderSessionProxy | null = null\n /** Current output format of the shared decoder session. */\n private decoderOutputFormat: FrameFormat | null = null\n /**\n * Moleculer nodeID of the decoder provider currently hosting\n * `decoderProxy`. Set from the `{ nodeId }` returned by\n * `decoderApi.createSession`; used by the broker-manager's\n * hwaccel-change subscription to decide which brokers need a forced\n * decoder rotation. Null when no shared decoder session exists.\n */\n private decoderNodeId: string | null = null\n private readonly decoderApi: DecoderCapApi | null\n private readonly audioCodecApi: AudioCodecCapApi | null\n private readonly logger: IScopedLogger | undefined\n private readonly startedAt: number = Date.now()\n\n private nativeClient: NativeRtspClient | null = null\n private rfc4571Reader: Rfc4571Reader | null = null\n private rtmpReader: RtmpReader | null = null\n private rtpDepacketizer: RtpDepacketizer | null = null\n private audioRtpDecoder: AudioRtpDecoder | null = null\n private audioCodecSession: AudioCodecSession | null = null\n private audioRtpSeen = 0\n /** Detected audio codec from RTSP DESCRIBE (e.g. 'PCMU', 'PCMA'). */\n private audioCodec: string | null = null\n /**\n * Full audio track snapshot from the SDP — surfaced in `BrokerStats.audio`\n * so the UI overview / audio-analyzer model picker can pre-select the\n * right model based on per-camera codec + sample rate.\n */\n private audioTrackInfo: { codec: string; sampleRate: number; channels: number; supported: boolean } | null = null\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined\n private placeholderTimer: ReturnType<typeof setInterval> | undefined\n private reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n /**\n * \"First video\" watchdog. Battery-cam rfc4571 sources sometimes\n * complete TCP handshake on the loopback but the lib's dedicated\n * session never delivers video bytes (camera fails to start the\n * upstream Baichuan stream after wake, or stream subscription gets\n * stuck). The user's manual workaround was to click restart on the\n * WebRTC viewer — that closed the broker, tore the lib's session\n * down, and on reopen the dedicated session attached cleanly.\n *\n * This watchdog automates that: it arms when the rfc4571 reader's\n * `onVideoTrack` fires (TCP up, SDP parsed) and cancels at the first\n * `onVideoRtp` callback. If it elapses without a single video RTP\n * packet, it destroys the reader, asks the publisher for a fresh\n * source, and re-dials — exactly what the manual restart did.\n */\n private firstVideoWatchdog: ReturnType<typeof setTimeout> | undefined\n /**\n * Wall-clock timeout for the first-video watchdog. Long enough that\n * a healthy live cam comfortably emits its first IDR within the\n * window (typical: ~1-2s after rfc4571 TCP connect on PoE cams,\n * ~2-5s on battery cams that need to align stream subscription with\n * the video pipeline post-wake), but short enough that a stuck\n * dedicated session is detected before the WebRTC client gives up\n * on the offer (Chrome's default ICE-disconnect timeout is ~15s).\n */\n private static readonly FIRST_VIDEO_TIMEOUT_MS = 8_000\n private manualStop = false\n private stopping = false\n private decoderTeardownTimer: ReturnType<typeof setTimeout> | undefined\n\n private restreamers: readonly IRestreamer[] = []\n private readonly pipeServer: StreamPipeServer\n private readonly rtspRestreamer: import('../rtsp/rtsp-restreamer.js').RtspRestreamer\n private readonly preBuffer: EncodedRingBuffer\n\n private readonly encodedCallbacks = new Set<(packet: EncodedPacket) => void>()\n /**\n * Source-RTP video subscribers — receive raw on-wire RTP bytes\n * (no depacketization). Used by the WebRTC server's H.265 path to\n * feed a packet-level repacketizer. See `onVideoRtp`.\n */\n private readonly rtpVideoCallbacks = new Set<(rtpData: Buffer) => void>()\n /**\n * Codec parameter sets harvested from the camera's SDP (a=fmtp\n * sprop-vps / sprop-sps / sprop-pps). Cameras that ONLY put their\n * VPS/SPS/PPS in the SDP (Reolink high-profile streams, IP-cam\n * vendors that strip param sets from the wire) leave WebRTC sessions\n * without an HEVC decoder configuration — Chrome receives every IDR\n * but VideoToolbox never initialises.\n *\n * We capture them in `onVideoTrack` and replay them as a synthetic\n * EncodedPacket the moment a new encoded-data subscriber registers\n * AND on every keyframe whose NAL stream is missing them. This way\n * `session.ts` populates `lastVps/Sps/Pps` regardless of how the\n * camera ships its parameter sets.\n */\n private sdpParameterSets: ReadonlyArray<Buffer> | null = null\n /**\n * Per-device streaming debug flag. Gates verbose info-level logs\n * around RTP/encoded subscriber lifecycle, SDP param set replay,\n * and codec parameter handling. Toggled via the device override\n * `streamingDebug` field. Off by default — production logs stay\n * quiet unless an operator explicitly enables debug for one device.\n */\n private streamingDebug = false\n private readonly decodedSubscribers = new Map<symbol, DecodedSubscriber>()\n private readonly audioSubscribers = new Map<symbol, AudioSubscriber>()\n /** Stored options from first onDecodedFrame call when codec was not yet known */\n private pendingDecodeOptions: DecodeOptions | undefined\n /** Pull mode decoder needs to wait for the first keyframe before opening */\n private pendingPullModeOpen: (() => void) | null = null\n private firstKeyframeReceived = false\n /** Retry handle for \"no decoder provider yet\" — addons register in\n * arbitrary order, the broker must wait rather than fail. Cancelled\n * on decoder attach or broker destroy. */\n private decoderResolveRetryTimer: ReturnType<typeof setTimeout> | undefined\n /** Attempt counter for the retry loop above — feeds bounded backoff. */\n private decoderResolveAttempts = 0\n\n /** Timer for push-mode stall detection. */\n private pushStallTimer: ReturnType<typeof setTimeout> | undefined\n /** Whether pre-buffer counts as demand (keeps the stream alive even with 0 subscribers). */\n private _preBufferEnabled = true\n /** Timer for delayed suspend after last consumer leaves. */\n private suspendTimer: ReturnType<typeof setTimeout> | undefined\n /** True while the stream is suspended (ffmpeg killed, no RTSP connection). */\n private _suspended = false\n\n /** Debug counters for decoder troubleshooting */\n private decoderPushCount = 0\n private decoderFrameCount = 0\n /** Counter for video RTP packets — drives the milestone diagnostic\n * log alongside `audioRtpSeen`, so a wake-up cycle that brings audio\n * back but never video is observable from the structured logs. */\n private videoRtpSeen = 0\n /** Cached decoder stats — updated by the polling loop, read synchronously by getStats(). */\n private cachedDecoderStats: import('@camstack/types').DecoderStats | undefined\n\n /** Tracking flags set synchronously by the RTP depacketizer callback. */\n private _lastNalKeyframe = false\n private _lastNalParamSet = false\n\n /** Stream stats tracking */\n private totalBytes = 0\n private bytesInWindow = 0\n private packetsInWindow = 0\n private windowStartMs = Date.now()\n private lastKeyframeMs = 0\n private idrIntervalMs = 0\n private bitrateKbps = 0\n private encodedInputFps = 0\n private packetCount = 0\n /**\n * Timestamp of the most recent video packet observed on this broker.\n * Used by the manager-level watchdog to drive `stream.online` /\n * `stream.offline` transitions. `0` until the first packet arrives.\n */\n private lastPacketAt = 0\n\n /** Default pre-buffer duration in seconds */\n static readonly DEFAULT_PRE_BUFFER_SEC = 10\n /** Maximum allowed pre-buffer duration in seconds */\n static readonly MAX_PRE_BUFFER_SEC = 30\n\n constructor(\n deviceId: string,\n decoderApi: DecoderCapApi | null,\n logger?: IScopedLogger,\n audioCodecApi?: AudioCodecCapApi | null,\n ) {\n this.deviceId = deviceId\n this.decoderApi = decoderApi\n this.audioCodecApi = audioCodecApi ?? null\n this.logger = logger\n this.pipeServer = new StreamPipeServer(logger?.child('pipe-server'))\n this.rtspRestreamer = new RtspRestreamer(deviceId)\n if (logger) this.rtspRestreamer.setLogger(logger.child('rtsp'))\n this.preBuffer = new EncodedRingBuffer(StreamBroker.DEFAULT_PRE_BUFFER_SEC)\n\n // Wire client-count callbacks so demand tracking reacts to RTSP/pipe changes\n this.rtspRestreamer.onSessionCountChanged(() => this.checkDemand())\n this.pipeServer.onClientCountChanged(() => this.checkDemand())\n }\n\n get status(): BrokerStatus {\n return this._status\n }\n\n /**\n * Ms epoch of the most recent video packet observed by the broker.\n * `0` if none yet. Read by the manager-level health watchdog.\n */\n getLastPacketAt(): number {\n return this.lastPacketAt\n }\n\n /** Source type of the active stream (rtsp/push/rtmp/placeholder/error). */\n getActiveSourceType(): string {\n return this.source?.type ?? 'unknown'\n }\n\n /**\n * Diagnostic snapshot of the active source for log-meta enrichment.\n * Always returns a stable shape so log readers can rely on the keys\n * being present whenever a connect / read error is reported. URL is\n * masked so credentials never reach disk.\n */\n private sourceMetaForLog(): {\n brokerId: string\n sourceType: string\n url: string\n } {\n const src = this.source\n const url = src && src.type !== 'placeholder' && src.type !== 'push' && src.type !== 'push-rtp'\n ? maskUrlCredentials(src.url ?? '')\n : ''\n return {\n brokerId: this.deviceId,\n sourceType: src?.type ?? 'unknown',\n url,\n }\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n }\n\n /**\n * Inject a manager-owned closure that derives the current\n * `StreamSource` from the live cam-stream registry. Every dial\n * attempt (initial start, scheduled reconnect, demand-driven resume)\n * routes through this resolver so URL changes from a re-publish are\n * picked up transparently. Calling without a provider keeps the\n * legacy \"use the source from start()\" behaviour for tests.\n */\n setSourceProvider(provider: () => StreamSource | null): void {\n this.sourceProvider = provider\n }\n\n /**\n * Inject a manager-owned notifier the broker fires when a\n * managed-loopback source (`rfc4571`) dial fails. The notifier\n * asks the publishing addon to republish with a fresh transport;\n * the next reconnect picks it up via `sourceProvider`.\n */\n setRequestSourceRefresh(notify: () => void): void {\n this.requestSourceRefresh = notify\n }\n\n /**\n * Fire `requestSourceRefresh` at most once per ~500 ms. The\n * dial-failure path fans into onError + reader.connect().catch; we\n * only want one round-trip to the publisher per real failure.\n */\n private notifySourceRefresh(): void {\n if (!this.requestSourceRefresh) return\n const now = Date.now()\n if (now - this.lastRefreshRequestAt < 500) return\n this.lastRefreshRequestAt = now\n this.requestSourceRefresh()\n }\n\n /**\n * Manager-side kick: the publisher just re-published with a fresh\n * URL after a connect-fail / refresh round-trip (e.g. Reolink\n * battery cam finished waking and bound a new rfc4571 port). Cancel\n * any pending exponential-backoff retry, reset the backoff so the\n * next failure starts from `INITIAL_RECONNECT_DELAY_MS` again, and\n * arm a fast retry. No-op when the broker isn't currently waiting\n * for a reconnect (already dialing, idle, or stopped).\n *\n * Without this, on a battery-cam wake-up the broker stays on its\n * exponential schedule (1s → 2s → 4s …) even though the publisher\n * has already published a working URL — wasting up to a full\n * `MAX_RECONNECT_DELAY_MS` window dialing stale loopback ports.\n */\n kickReconnect(): void {\n if (this.manualStop || this.stopping || !this.source) return\n if (!this.hasDemand()) return\n if (!this.reconnectTimer) return\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.lastRefreshRequestAt = 0\n this.logger?.info('reconnect kicked — publisher refreshed source')\n this.scheduleReconnect()\n }\n\n /**\n * Resolve the source to use for the next dial attempt. Prefers the\n * manager-supplied resolver — re-derives from the cam-stream\n * registry, so a re-publish that updated the URL is honoured on the\n * very next dial. Falls back to the cached `this.source` (set at\n * `start()` time) when no resolver is wired.\n */\n private resolveCurrentSource(): StreamSource | null {\n if (this.sourceProvider) {\n const fresh = this.sourceProvider()\n if (fresh) {\n this.source = fresh\n return fresh\n }\n }\n return this.source ?? null\n }\n\n\n\n async start(source: StreamSource): Promise<void> {\n this.source = source\n this.manualStop = false\n this.stopping = false\n\n // Seed `detectedCodec` from the source's declared `videoCodec`\n // so consumers (notably `BrokerWebrtcServer.createSession`) see\n // the right codec immediately — without waiting for the reader to\n // parse the SDP track. The race used to manifest on `adaptive`\n // selection: a fresh session created against a still-suspended /\n // connecting rfc4571 broker fell back to `'h264'` from\n // `getStats().codec === undefined`, negotiated an H.264 SDP, then\n // received H.265 packets once the reader got going → black\n // viewport. The SDP `onVideoTrack` callback overwrites this later\n // if it disagrees with the publisher's declaration.\n if (source.videoCodec) {\n this.detectedCodec = source.videoCodec\n }\n\n this.logger?.info('Broker starting', {\n meta: {\n sourceType: source.type,\n ...(source.type !== 'placeholder' ? { url: maskUrlCredentials(source.url) } : {}),\n ...(this.detectedCodec ? { codec: this.detectedCodec } : {}),\n },\n })\n\n await this.pipeServer.start()\n this.logger?.debug(\n 'Pipe server started',\n { meta: { url: this.pipeServer.getUrl() } },\n )\n\n // Strict on-demand semantics for every pull source:\n //\n // - `rtsp` / `rtmp` are dialed lazily because hammering a remote\n // camera with reconnect storms when nobody's watching is\n // useless and bandwidth-expensive.\n // - `rfc4571` is ALSO dialed lazily — same Scrypted prebuffer-\n // mixin contract. Holding the loopback open keeps the lib's\n // upstream Baichuan socket alive (good for reaction time) but\n // violates the user-visible rule \"stream open only when\n // someone's watching\". The lib's `ensureRfc4571Server` self-\n // heals if its TCP server idle-tore-down between dials, and\n // the manager's `sourceProvider` re-resolves a fresh URL on\n // every dial — a re-publish after recreate is observed\n // transparently.\n //\n // `checkDemand()` resumes the source the moment a subscriber\n // arrives or the pre-buffer flips on (both feed `hasDemand`).\n const isLazyDialSource =\n source.type === 'rtsp'\n || source.type === 'rtmp'\n || source.type === 'rfc4571'\n if (isLazyDialSource && !this.hasDemand()) {\n this._suspended = true\n this._status = 'idle'\n this.logger?.info('Broker registered idle — no demand, source dial deferred')\n return\n }\n\n this._status = 'connecting'\n\n if (source.type === 'rtsp') {\n this.startRtspReader(source)\n } else if (source.type === 'rfc4571') {\n this.startRfc4571Reader(source)\n } else if (source.type === 'placeholder') {\n this.startPlaceholderReader(source)\n } else if (source.type === 'push' || source.type === 'push-rtp') {\n // Push modes: the camera provider addon delivers data via\n // pushEncodedPacket() (and pushVideoRtp() for `push-rtp`).\n // Codec already seeded from `source.videoCodec` at the top of\n // `start()` — the push branch used to repeat that assignment;\n // it's redundant now and removed for clarity.\n this._status = 'streaming'\n this.logger?.info('Broker in push mode — waiting for data', {\n meta: { sourceType: source.type, codec: this.detectedCodec ?? null },\n })\n this.resetPushStallTimer()\n } else if (source.type === 'rtmp') {\n this.startRtmpReader(source)\n } else {\n // Unknown source type — assume external push.\n this._status = 'streaming'\n }\n }\n\n async stop(): Promise<void> {\n this.logger?.info('Broker stopping')\n this.stopping = true\n this.manualStop = true\n this._status = 'stopped'\n\n this.destroyNativeClient()\n this.destroyRtmpReader()\n this.stopErrorPlaceholder()\n this.cancelFirstVideoWatchdog()\n\n if (this.placeholderTimer) {\n clearInterval(this.placeholderTimer)\n this.placeholderTimer = undefined\n }\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n if (this.suspendTimer) {\n clearTimeout(this.suspendTimer)\n this.suspendTimer = undefined\n }\n\n if (this.pushStallTimer) {\n clearTimeout(this.pushStallTimer)\n this.pushStallTimer = undefined\n }\n\n if (this.decoderProxy) {\n await this.destroyDecoder()\n }\n\n await this.pipeServer.stop()\n this.rtspRestreamer.destroy()\n this.preBuffer.clear()\n\n this.pendingParamNals = []\n this.pendingPullModeOpen = null\n this.firstKeyframeReceived = false\n this.encodedCallbacks.clear()\n this.decodedSubscribers.clear()\n this.audioSubscribers.clear()\n }\n\n // ── Auto-suspend / resume ─────────────────────────────────────────────\n\n /** Whether the broker is currently suspended (idle, no RTSP connection). */\n get suspended(): boolean { return this._suspended }\n\n /** Set whether pre-buffer counts as demand. When false and no other consumers, the broker suspends. */\n setPreBufferEnabled(enabled: boolean): void {\n this._preBufferEnabled = enabled\n this.checkDemand()\n }\n\n /** True when at least one consumer needs the stream alive. */\n private hasDemand(): boolean {\n if (this.encodedCallbacks.size > 0) return true\n if (this.rtpVideoCallbacks.size > 0) return true\n if (this.decodedSubscribers.size > 0) return true\n // Audio analysis subscribers count as demand. Without this, a\n // camera with audio classification enabled but no decoded-video\n // consumer (motion-wasm off + motion phase idle) suspends the\n // broker between motion bursts; audio chunks stop flowing and\n // `device.state.audioMetrics` stays frozen — the operator sees a\n // stale \"X seconds ago\" chip in the UI even though the camera is\n // online and audible. Mirrors video demand semantics.\n if (this.audioSubscribers.size > 0) return true\n if (this.rtspRestreamer.getSessionCount() > 0) return true\n if (this.pipeServer.getClientCount() > 0) return true\n if (this._preBufferEnabled && this.preBuffer.getDuration() > 0) return true\n return false\n }\n\n /**\n * Check demand and schedule suspend/resume. Called after every subscriber\n * add/remove and after pre-buffer enable/disable.\n */\n /**\n * Pick the decoder's native output format given the current subscriber\n * mix. Phase 3 collapsed three concerns into this one decision:\n *\n * 1. Avoid wasted CPU when subscribers are homogeneous — `gray`-\n * only stays `gray` (motion-only camera), `jpeg`-only stays\n * `jpeg` (no detection consumer joined yet, broker doesn't have\n * to encode either).\n * 2. Pick the cheapest *canonical* mode for mixed subscribers —\n * `rgb` is the universal source for the broker-side conversion\n * cache: gray is one luminance pass, jpeg is one sharp encode,\n * bgr is a channel swap. Producing JPEG decoder-side and then\n * *decoding back* to feed a raw consumer would cost more than\n * both consumers combined.\n * 3. Stay stable when there are no subscribers — defaults to\n * `jpeg` so the legacy \"no subscriber yet\" code path keeps\n * behaving as it did pre-refactor.\n */\n private resolveDecoderFormat(): FrameFormat {\n if (this.decodedSubscribers.size === 0) return 'jpeg'\n let allGray = true\n let allJpeg = true\n for (const sub of this.decodedSubscribers.values()) {\n if (sub.format !== 'gray') allGray = false\n if (sub.format !== 'jpeg') allJpeg = false\n }\n if (allGray) return 'gray'\n if (allJpeg) return 'jpeg'\n return 'rgb'\n }\n\n /**\n * Check if the decoder's output format needs upgrading because a new\n * subscriber requires a richer format (e.g. gray → jpeg when detection\n * joins after motion). Calls updateConfig on the decoder proxy to\n * reinitialize the scaler without recreating the session.\n */\n private maybeUpgradeDecoderFormat(): void {\n if (!this.decoderProxy || !this.decoderOutputFormat) return\n const needed = this.resolveDecoderFormat()\n if (needed === this.decoderOutputFormat) return\n this.logger?.info('upgrading decoder output format', {\n meta: { from: this.decoderOutputFormat, to: needed },\n })\n this.decoderOutputFormat = needed\n this.decoderProxy.updateConfig({ outputFormat: needed }).catch((err: unknown) => {\n this.logger?.warn('decoder format upgrade failed', { meta: { error: errMsg(err) } })\n })\n }\n\n /**\n * Fan a single decoded frame out to every active subscriber. Each\n * subscriber may have requested a different `format` than the one the\n * decoder is currently producing — the per-frame `convertedCache` map\n * memoises any format the decoder didn't already supply, so multiple\n * subscribers asking for the same target share the conversion.\n *\n * Conversion paths covered today:\n * - rgb → jpeg (sharp encode)\n * - rgb → bgr (R/B channel swap, in-process)\n * - rgb → gray (BT.601 luminance)\n * - rgb → yuv420 (sharp toFormat)\n * - gray → jpeg (sharp encode 1ch)\n * - jpeg → rgb / bgr / gray / yuv420 — not supported in Phase 3.\n * Decoder picks `jpeg` as canonical only when ALL subscribers\n * want jpeg, so this branch should never trigger. Such a request\n * is logged and the subscriber receives the JPEG passthrough as a\n * graceful fallback (caller is expected to re-decode).\n */\n private fanoutDecodedFrame(frame: DecodedFrame): void {\n const convertedCache = new Map<FrameFormat, DecodedFrame | Promise<DecodedFrame>>()\n convertedCache.set(frame.format, frame)\n for (const subscriber of this.decodedSubscribers.values()) {\n if (!subscriber.frameDropper.shouldKeep()) {\n subscriber.framesDropped++\n continue\n }\n this.deliverConvertedFrame(subscriber, frame, convertedCache)\n }\n }\n\n private deliverConvertedFrame(\n subscriber: DecodedSubscriber,\n frame: DecodedFrame,\n cache: Map<FrameFormat, DecodedFrame | Promise<DecodedFrame>>,\n ): void {\n if (subscriber.format === frame.format) {\n subscriber.callback(frame)\n subscriber.framesDelivered++\n return\n }\n const cached = cache.get(subscriber.format)\n if (cached && !(cached instanceof Promise)) {\n subscriber.callback(cached)\n subscriber.framesDelivered++\n return\n }\n if (cached instanceof Promise) {\n cached.then((converted) => {\n subscriber.callback(converted)\n subscriber.framesDelivered++\n }).catch((err: unknown) => {\n subscriber.framesDropped++\n this.logger?.warn('frame conversion failed', {\n meta: { tag: subscriber.tag, target: subscriber.format, error: errMsg(err) },\n })\n })\n return\n }\n const conversion = this.convertFrame(frame, subscriber.format)\n cache.set(subscriber.format, conversion)\n conversion.then((converted) => {\n cache.set(subscriber.format, converted)\n subscriber.callback(converted)\n subscriber.framesDelivered++\n }).catch((err: unknown) => {\n subscriber.framesDropped++\n this.logger?.warn('frame conversion failed', {\n meta: { tag: subscriber.tag, target: subscriber.format, error: errMsg(err) },\n })\n })\n }\n\n private async convertFrame(source: DecodedFrame, target: FrameFormat): Promise<DecodedFrame> {\n if (source.format === target) return source\n const sharp = await getSharp()\n if (source.format === 'rgb' || source.format === 'gray') {\n const channels = source.format === 'gray' ? 1 : 3\n const raw = { width: source.width, height: source.height, channels: channels as 1 | 3 }\n const pipeline = sharp(source.data, { raw })\n if (target === 'jpeg') {\n const data = await pipeline.jpeg({ quality: 80, mozjpeg: false }).toBuffer()\n return { ...source, data, format: 'jpeg' }\n }\n if (target === 'bgr') {\n // Channel swap stays in-process — sharp's extractChannel + join\n // would require an extra encode/decode, so handle it directly.\n if (source.format !== 'rgb') {\n throw new Error(`bgr conversion requires rgb source, got ${source.format}`)\n }\n const data = swapRedBlue(source.data)\n return { ...source, data, format: 'bgr' }\n }\n if (target === 'gray') {\n const data = await pipeline.toColorspace('b-w').raw().toBuffer()\n return { ...source, data, format: 'gray' }\n }\n if (target === 'rgb') {\n // gray → rgb: replicate the single luminance channel into three\n // identical planes so consumers expecting RGB24 don't crash.\n const data = await pipeline.toColorspace('srgb').raw().toBuffer()\n return { ...source, data, format: 'rgb' }\n }\n if (target === 'yuv420') {\n const data = await pipeline.toColorspace('yuv').raw().toBuffer()\n return { ...source, data, format: 'yuv420' }\n }\n }\n if (source.format === 'jpeg') {\n // Decoder only picks `jpeg` when every subscriber wants jpeg,\n // so a non-jpeg request here is a misconfiguration. Log and pass\n // the JPEG through so the caller can decide what to do.\n this.logger?.warn('jpeg-source frame requested as non-jpeg — passthrough', {\n meta: { target },\n })\n return source\n }\n throw new Error(`unsupported conversion ${source.format} → ${target}`)\n }\n\n private checkDemand(): void {\n if (this.manualStop || this.stopping) return\n\n if (this.hasDemand()) {\n // Cancel pending suspend\n if (this.suspendTimer) {\n clearTimeout(this.suspendTimer)\n this.suspendTimer = undefined\n }\n // Resume if suspended — re-resolve the source from the manager\n // so a re-publish that landed during the idle window (e.g.\n // Reolink rebound the rfc4571 server on a different port) is\n // honoured on the very first dial of the resume.\n //\n // No proactive `notifySourceRefresh` here: forcing a publisher\n // re-publish on every resume would call `ensureApi` ->\n // `buildVideoStreamOptions` -> `ensureRfc4571Server` on the\n // provider side, which wakes battery cameras even when the\n // existing URL is still valid. The dial-failure path\n // (`startRfc4571Reader`'s `.catch`) already triggers a refresh\n // when the cached URL goes stale; that's the only case where\n // we actually need the publisher to wake the cam.\n if (this._suspended) {\n const fresh = this.resolveCurrentSource()\n if (fresh) {\n this._suspended = false\n this.logger?.info('demand detected — resuming stream')\n this.start(fresh).catch((err) => {\n this.logger?.error('resume failed', { meta: { error: errMsg(err) } })\n this._suspended = true\n this._status = 'idle'\n })\n }\n }\n } else if (!this._suspended) {\n // Schedule suspend after grace period\n if (!this.suspendTimer) {\n this.suspendTimer = setTimeout(() => {\n this.suspendTimer = undefined\n if (!this.hasDemand() && !this.manualStop && !this.stopping) {\n this.suspend()\n }\n }, SUSPEND_GRACE_MS)\n }\n }\n }\n\n /** Suspend the stream: kill ffmpeg/native client, clear buffers, go idle. Source is preserved for resume. */\n private suspend(): void {\n if (this._suspended) return\n this._suspended = true\n this._status = 'idle'\n\n this.logger?.info('no demand — suspending stream')\n\n this.destroyNativeClient()\n this.destroyRtmpReader()\n // Strict on-demand: drop the rfc4571 reader too. Holding the\n // loopback TCP connection keeps the lib's upstream Baichuan\n // socket pinned even with zero subscribers — exactly what the\n // user-visible \"stream open only on demand\" rule rejects. The\n // next demand triggers `start()` → `resolveCurrentSource()` →\n // dial against the (potentially refreshed) loopback URL.\n this.destroyRfc4571Reader()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n if (this.placeholderTimer) {\n clearInterval(this.placeholderTimer)\n this.placeholderTimer = undefined\n }\n\n this.preBuffer.clear()\n this.firstKeyframeReceived = false\n this.pendingParamNals = []\n this.rtspRestreamer.resetStreamState()\n\n // Reset stats\n this.totalBytes = 0\n this.bytesInWindow = 0\n this.packetsInWindow = 0\n this.packetCount = 0\n this.bitrateKbps = 0\n this.encodedInputFps = 0\n }\n\n /**\n * Declare push-source audio metadata. Push providers (Reolink Baichuan,\n * Frigate) call this BEFORE pushing the first audio EncodedPacket so\n * the broker can wire the audio-codec capability and emit decoded PCM\n * to audio subscribers + transcode to G.711 µ-law for WebRTC.\n *\n * Pass `null` to retract — broker tears down the audio decode session.\n */\n pushAudioInfo(info: import('@camstack/types').PushAudioInfo | null): void {\n if (this.stopping) return\n\n if (info === null) {\n if (this.audioCodecSession) {\n const session = this.audioCodecSession\n this.audioCodecSession = null\n void session.close().catch((err) => {\n this.logger?.warn('audio-codec: push session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n this.audioTrackInfo = null\n return\n }\n\n const codec = info.codec.toLowerCase()\n this.audioCodec = codec.toUpperCase()\n this.audioTrackInfo = {\n codec: this.audioCodec,\n sampleRate: info.sampleRate,\n channels: info.channels,\n supported: true,\n }\n\n // Native G.711 — no codec cap needed; pushEncodedPacket fans out\n // directly to encodedCallbacks.\n if (codec === 'pcmu' || codec === 'pcma') {\n this.logger?.info('push-audio: native codec — no decoder cap required', {\n meta: { codec, sampleRate: info.sampleRate, channels: info.channels },\n })\n return\n }\n\n if (!this.audioCodecApi) {\n this.logger?.warn('push-audio: codec needs audio-codec cap but cap is unavailable', {\n meta: { codec, sampleRate: info.sampleRate, channels: info.channels },\n })\n return\n }\n\n const cfg = this.buildAudioCodecSessionConfigFromPush(info)\n if (!cfg) {\n this.logger?.info('push-audio: codec not handled by cap — audio skipped', {\n meta: { codec, sampleRate: info.sampleRate, channels: info.channels },\n })\n return\n }\n\n if (this.audioCodecSession) {\n const prior = this.audioCodecSession\n this.audioCodecSession = null\n void prior.close().catch((err) => {\n this.logger?.debug('audio-codec: prior session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n\n this.audioCodecSession = new AudioCodecSession(\n this.audioCodecApi,\n cfg,\n this.buildAudioCodecEmitCallback(),\n this.logger?.child('audio-codec'),\n )\n this.logger?.info('push-audio: decode session armed', {\n meta: {\n codec: cfg.codec,\n sourceSampleRate: cfg.sourceSampleRate,\n sourceChannels: cfg.sourceChannels,\n },\n })\n }\n\n pushEncodedPacket(packet: EncodedPacket): void {\n if (this.stopping) return\n\n // Reset push-mode stall timer on every packet\n if (this.source?.type === 'push') {\n this.stopErrorPlaceholder()\n if (this._status === 'error') this._status = 'streaming'\n // Clear suspension if we were waiting on a battery camera to wake.\n if (this._suspended && this.source.allowStall) {\n this._suspended = false\n this.logger?.info('push source resumed after stall — first packet received')\n }\n this.resetPushStallTimer()\n }\n\n // Push audio: route non-native codecs (e.g. AAC) through the\n // audio-codec cap session if one was armed via pushAudioInfo. The\n // session emits decoded PCM to audioSubscribers and transcodes to\n // G.711 µ-law for WebRTC via the shared emit callback. Native\n // codecs (PCMU/PCMA) fall through to the encodedCallbacks fanout\n // below — no transcode required.\n if (packet.type === 'audio' && this.source?.type === 'push' && this.audioCodecSession) {\n const codec = packet.codec.toLowerCase()\n if (codec !== 'pcmu' && codec !== 'pcma') {\n this.audioCodecSession.pushRawFrame(packet.data)\n return\n }\n }\n\n // Track bitrate + IDR interval\n if (packet.type === 'video') {\n this.packetCount++\n this.totalBytes += packet.data.length\n this.bytesInWindow += packet.data.length\n this.packetsInWindow++\n this.lastPacketAt = Date.now()\n\n const now = Date.now()\n const windowMs = now - this.windowStartMs\n if (windowMs >= 2000) {\n this.bitrateKbps = Math.round((this.bytesInWindow * 8) / windowMs)\n this.encodedInputFps = (this.packetsInWindow / windowMs) * 1000\n this.bytesInWindow = 0\n this.packetsInWindow = 0\n this.windowStartMs = now\n }\n\n if (packet.keyframe) {\n if (this.lastKeyframeMs > 0) {\n this.idrIntervalMs = now - this.lastKeyframeMs\n }\n this.lastKeyframeMs = now\n }\n }\n\n // Buffer encoded video packets for pre-buffer (clip creation, late-join)\n if (packet.type === 'video') {\n this.preBuffer.push(packet)\n }\n\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n\n // Broadcast raw video bytes to TCP pipe clients (restreamers)\n if (packet.type === 'video') {\n this.pipeServer.broadcast(packet.data)\n\n // Feed RTSP restreamer (Annex-B mode only — in passthrough mode, raw RTP\n // is forwarded directly via pushRtpPassthrough in the onVideoRtp callback)\n if (!this.rtspRestreamer.rtpPassthroughMode) {\n this.rtspRestreamer.pushPacket(packet)\n }\n\n // Gate: wait for the first keyframe before sending data to decoder or opening pull mode.\n // H264 decoders need SPS/PPS (present in keyframes) to initialize.\n if (!this.firstKeyframeReceived && packet.keyframe) {\n this.firstKeyframeReceived = true\n this.logger?.info('First keyframe received — decoder can start')\n if (this.pendingPullModeOpen) {\n this.pendingPullModeOpen()\n this.pendingPullModeOpen = null\n }\n }\n }\n\n for (const restreamer of this.restreamers) {\n const streamId = `${this.deviceId}/video`\n restreamer.pushPacket(streamId, packet)\n }\n\n // Only push to decoder after the first keyframe — the decoder needs SPS/PPS to start.\n // Without this, streams that start mid-GOP silently produce 0 decoded frames.\n if (packet.type === 'video' && this.firstKeyframeReceived) {\n if (this.decoderProxy) {\n this.decoderPushCount++\n if (this.decoderPushCount === 1 || this.decoderPushCount % 500 === 0) {\n this.logger?.info('decoder push', {\n meta: {\n pushCount: this.decoderPushCount,\n size: packet.data.length,\n keyframe: packet.keyframe,\n decodedCount: this.decoderFrameCount,\n },\n })\n }\n this.decoderProxy.pushPacket(packet).catch((err: unknown) => {\n this.logger?.warn('decoder push error', { meta: { error: errMsg(err) } })\n })\n }\n }\n }\n\n onEncodedData(callback: (packet: EncodedPacket) => void): Unsubscribe {\n this.encodedCallbacks.add(callback)\n if (this.streamingDebug) {\n this.logger?.info('encoded subscriber added', { meta: { total: this.encodedCallbacks.size } })\n }\n\n // Seed the new subscriber with the SDP-derived param sets so\n // WebRTC sessions get VPS/SPS/PPS even when the camera doesn't\n // emit them in-band (Reolink high-profile streams).\n this.emitSdpParamSetsTo(callback)\n this.checkDemand()\n\n return () => {\n this.encodedCallbacks.delete(callback)\n if (this.streamingDebug) {\n this.logger?.info('encoded subscriber removed', { meta: { total: this.encodedCallbacks.size } })\n }\n this.checkDemand()\n }\n }\n\n /**\n * Emit the cached SDP param sets as a synthetic Annex-B EncodedPacket\n * to a single subscriber. No-op if no param sets are cached or no\n * codec is detected yet.\n */\n private emitSdpParamSetsTo(callback: (packet: EncodedPacket) => void): void {\n if (!this.sdpParameterSets || this.sdpParameterSets.length === 0) return\n const codec = this.detectedCodec\n if (!codec) return\n const startCode = Buffer.from([0x00, 0x00, 0x00, 0x01])\n const annexB = Buffer.concat(\n this.sdpParameterSets.flatMap((ps) => [startCode, ps]),\n )\n if (this.streamingDebug) {\n this.logger?.info('Emitting SDP param sets to encoded subscriber', {\n meta: {\n codec,\n psCount: this.sdpParameterSets.length,\n psSizes: this.sdpParameterSets.map((p) => p.length).join(','),\n psFirstBytesHex: this.sdpParameterSets.map((p) => p.subarray(0, 4).toString('hex')).join('|'),\n annexBLength: annexB.length,\n },\n })\n }\n callback({\n type: 'video',\n data: annexB,\n pts: 0,\n dts: 0,\n keyframe: false,\n codec,\n })\n }\n\n /** Replay SDP param sets to every existing encoded subscriber. */\n private replaySdpParamSetsToEncodedSubscribers(): void {\n if (!this.sdpParameterSets || this.encodedCallbacks.size === 0) return\n for (const cb of this.encodedCallbacks) this.emitSdpParamSetsTo(cb)\n }\n\n /**\n * Subscribers waiting on the SDP-derived VPS/SPS/PPS to land. The\n * H.265 source-RTP path needs to seed its `H265Repacketizer` with\n * these param sets so the AP codec packet can fire ahead of the\n * first IDR — without it Chrome's HEVC decoder never initialises on\n * cameras that only ship parameter sets in the SDP (Reolink high-\n * profile streams). Sessions created while the broker is still\n * dialing rfc4571 (battery-cam wake-up) have null `sdpParameterSets`\n * at create time; this subscription delivers them as soon as\n * `onVideoTrack` fires. Mirrors `replaySdpParamSetsToEncodedSubscribers`\n * for the source-RTP path.\n */\n private readonly sdpParameterSetsCallbacks = new Set<(ps: ReadonlyArray<Buffer>) => void>()\n\n /**\n * Subscribe to SDP-derived VPS/SPS/PPS deliveries. The callback\n * fires once immediately if the broker already has them (synchronous\n * delivery for late subscribers), and again every time\n * `onVideoTrack` repopulates them on a reader rebuild.\n */\n onSdpParameterSets(callback: (ps: ReadonlyArray<Buffer>) => void): Unsubscribe {\n this.sdpParameterSetsCallbacks.add(callback)\n if (this.sdpParameterSets && this.sdpParameterSets.length > 0) {\n try {\n callback(this.sdpParameterSets)\n } catch (err) {\n this.logger?.warn('sdp param-set subscriber threw on initial delivery', {\n meta: { error: errMsg(err) },\n })\n }\n }\n return () => {\n this.sdpParameterSetsCallbacks.delete(callback)\n }\n }\n\n /** Fan out the current SDP param sets to every waiting subscriber. */\n private replaySdpParamSetsToRtpSubscribers(): void {\n if (!this.sdpParameterSets || this.sdpParameterSetsCallbacks.size === 0) return\n const ps = this.sdpParameterSets\n for (const cb of this.sdpParameterSetsCallbacks) {\n try {\n cb(ps)\n } catch (err) {\n this.logger?.warn('sdp param-set subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n\n onVideoRtp(callback: (rtpData: Buffer) => void): Unsubscribe {\n this.rtpVideoCallbacks.add(callback)\n if (this.streamingDebug) {\n this.logger?.info('rtp video subscriber added', {\n meta: { total: this.rtpVideoCallbacks.size },\n })\n }\n this.checkDemand()\n return () => {\n this.rtpVideoCallbacks.delete(callback)\n if (this.streamingDebug) {\n this.logger?.info('rtp video subscriber removed', {\n meta: { total: this.rtpVideoCallbacks.size },\n })\n }\n this.checkDemand()\n }\n }\n\n getSdpParameterSets(): ReadonlyArray<Buffer> | null {\n return this.sdpParameterSets\n }\n\n getSourceType(): StreamSourceType | null {\n return this.source?.type ?? null\n }\n\n /**\n * True when the broker emits source RTP packets via `onVideoRtp`.\n * RTSP demuxes incoming RTP directly; rfc4571 (RTP-over-TCP framed\n * per RFC 4571) deserializes into the same wire shape and runs\n * through the same `buildRtpStreamCallbacks.onVideoRtp` path;\n * `push-rtp` sources synthesize it via `pushVideoRtp()`. Bare\n * `push` (AnnexB-only) and `rtmp` (no RTP wire) return false.\n *\n * Crucial for the WebRTC dispatch decision: H.265 sources marked\n * as RTP route through `H265Repacketizer` for source-RTP forwarding\n * (Chrome's HEVC depacketizer requires the original RTP header\n * layout); non-RTP sources fall back to AnnexB → `writeVideoNals`\n * with cached-keyframe-on-PLI. Marking rfc4571 as non-RTP put the\n * Reolink Baichuan H.265 native:main stream on the wrong path —\n * Chrome got stuck in a PLI loop because each cached re-send\n * collided with the live decode state.\n */\n isRtpSource(): boolean {\n const t = this.source?.type\n return t === 'rtsp' || t === 'rfc4571' || t === 'push-rtp'\n }\n\n /**\n * Push a raw RTP packet from an external `push-rtp` source. Fans\n * out to every `onVideoRtp` subscriber. Mirrors the dispatch the\n * native RTSP reader performs in its `onVideoRtp` callback. No-ops\n * when no subscribers are attached so providers can fire-and-forget.\n */\n pushVideoRtp(rtpData: Buffer): void {\n if (this.stopping) return\n if (this.rtpVideoCallbacks.size === 0) return\n for (const cb of this.rtpVideoCallbacks) {\n try {\n cb(rtpData)\n } catch (err) {\n this.logger?.warn('rtp video subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n\n /** Toggle the per-device streaming debug flag. */\n setStreamingDebug(enabled: boolean): void {\n this.streamingDebug = enabled\n }\n\n onDecodedFrame(callback: (frame: DecodedFrame) => void, options?: DecodeOptions): Unsubscribe {\n const maxFps = options?.maxFps ?? DEFAULT_MAX_FPS\n const subscriberId = Symbol('decoded-subscriber')\n const frameDropper = new FrameDropper(maxFps)\n const tag = options?.tag\n if (!tag) {\n this.logger?.warn(`onDecodedFrame called without tag — listClients will show 'unknown' (Phase 10: every caller must pass options.tag)`)\n }\n\n const subscriber: DecodedSubscriber = {\n callback,\n frameDropper,\n tag: tag ?? 'unknown',\n maxFps,\n format: options?.format ?? 'jpeg',\n subscribedAt: Date.now(),\n framesDelivered: 0,\n framesDropped: 0,\n }\n this.decodedSubscribers.set(subscriberId, subscriber)\n this.logger?.info('decoded subscriber added', {\n meta: { tag: subscriber.tag, total: this.decodedSubscribers.size, maxFps, format: subscriber.format },\n })\n this.checkDemand()\n\n // Cancel pending teardown if a new subscriber joins while grace period is active\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n // Create shared decoder on first subscriber (or reuse existing).\n // If codec is not yet known (probe in progress), defer creation until codec is detected.\n if (!this.decoderProxy && this.source) {\n const codec = this.detectedCodec ?? this.source.videoCodec\n if (codec) {\n this.createSharedDecoderSession(options)\n } else {\n this.pendingDecodeOptions = options\n this.logger?.info('Decoder creation deferred — waiting for codec probe')\n }\n } else if (this.decoderProxy) {\n // Decoder already running — check if new subscriber requires a format upgrade\n this.maybeUpgradeDecoderFormat()\n }\n\n return () => {\n this.decodedSubscribers.delete(subscriberId)\n this.logger?.info('decoded subscriber removed', { meta: { total: this.decodedSubscribers.size } })\n this.checkDemand()\n\n if (this.decodedSubscribers.size === 0 && this.decoderProxy) {\n this.logger?.info('last decoded subscriber left — scheduling decoder teardown', {\n meta: { graceMs: DECODER_TEARDOWN_GRACE_MS },\n })\n this.decoderTeardownTimer = setTimeout(() => {\n this.decoderTeardownTimer = undefined\n if (this.decodedSubscribers.size === 0 && this.decoderProxy) {\n this.logger?.info('decoder teardown — no subscribers reconnected')\n this.destroyDecoder()\n }\n }, DECODER_TEARDOWN_GRACE_MS)\n }\n }\n }\n\n onDecodedAudioChunk(\n callback: (chunk: DecodedAudioChunk) => void,\n options?: { readonly tag?: string },\n ): Unsubscribe {\n const tag = options?.tag\n if (!tag) {\n this.logger?.warn(`onDecodedAudioChunk called without tag — listClients will show 'unknown' (Phase 10)`)\n }\n const subscriberId = Symbol('audio-subscriber')\n const subscriber: AudioSubscriber = {\n callback,\n tag: tag ?? 'unknown',\n subscribedAt: Date.now(),\n chunksDelivered: 0,\n }\n this.audioSubscribers.set(subscriberId, subscriber)\n // Audio counts as demand (see hasDemand). Trigger the same\n // suspend/resume reconcile that video subscribers do — without\n // this, attaching to a suspended broker leaves it suspended and\n // no chunks ever arrive.\n this.checkDemand()\n\n return () => {\n this.audioSubscribers.delete(subscriberId)\n this.checkDemand()\n }\n }\n\n getStats(): BrokerStats {\n const decoderStats = this._suspended ? undefined : this.cachedDecoderStats\n\n return {\n status: this._suspended ? 'idle' : this._status,\n inputFps: decoderStats?.inputFps ?? this.encodedInputFps,\n decodeFps: decoderStats?.outputFps ?? 0,\n encodedSubscribers: this.encodedCallbacks.size,\n decodedSubscribers: this.decodedSubscribers.size,\n uptimeMs: Date.now() - this.startedAt,\n bitrateKbps: this.bitrateKbps,\n idrIntervalMs: this.idrIntervalMs,\n codec: this.detectedCodec ?? this.source?.videoCodec,\n totalBytes: this.totalBytes,\n packetCount: this.packetCount,\n rtspClients: this.rtspRestreamer.getSessionCount(),\n pipeClients: this.pipeServer.getClientCount(),\n preBufferSec: this.preBuffer.getDuration(),\n preBufferMs: this.preBuffer.getBufferedDurationMs(),\n preBufferPackets: this.preBuffer.getPacketCount(),\n decoderNodeId: this.decoderNodeId,\n audio: this.audioTrackInfo ?? null,\n }\n }\n\n /**\n * Per-broker consumer inventory (Phase 10). Returns the live subscriber\n * list grouped by transport — every RTSP client, decoded-frame\n * subscriber, and audio-chunk subscriber. Used by the admin UI's\n * Streams tab and by operators diagnosing \"phantom\" holders keeping a\n * broker open.\n */\n listClients(): {\n readonly rtsp: readonly {\n readonly sessionId: string\n readonly remoteAddr: string\n readonly playing: boolean\n readonly muted: boolean\n readonly connectedAt: number\n readonly lastRtpAt: number\n readonly bytesSent: number\n }[]\n readonly decoded: readonly {\n readonly tag: string\n readonly subscribedAt: number\n readonly maxFps: number\n readonly framesDelivered: number\n readonly framesDropped: number\n }[]\n readonly audio: readonly {\n readonly tag: string\n readonly subscribedAt: number\n readonly chunksDelivered: number\n }[]\n readonly pipeClients: number\n readonly encodedSubscribers: number\n } {\n return {\n rtsp: this.rtspRestreamer.listSessionInfos(),\n decoded: [...this.decodedSubscribers.values()].map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n maxFps: s.maxFps,\n framesDelivered: s.framesDelivered,\n framesDropped: s.framesDropped,\n })),\n audio: [...this.audioSubscribers.values()].map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n chunksDelivered: s.chunksDelivered,\n })),\n pipeClients: this.pipeServer.getClientCount(),\n encodedSubscribers: this.encodedCallbacks.size,\n }\n }\n\n /**\n * Force-disconnect a single consumer. Matches by channel:\n * - `rtsp`: by `sessionId` (calls `RtspRestreamer.killSession`).\n * - `decoded` / `audio`: by `tag` — drops every subscriber whose tag\n * matches, then re-evaluates demand so the decoder can wind down\n * if nothing else is holding it.\n * Returns `true` when at least one consumer was dropped.\n */\n killClient(channel: 'rtsp' | 'decoded' | 'audio', handle: string): boolean {\n if (channel === 'rtsp') {\n return this.rtspRestreamer.killSession(handle)\n }\n const subscribers = channel === 'decoded' ? this.decodedSubscribers : this.audioSubscribers\n const victims: symbol[] = []\n for (const [key, sub] of subscribers) {\n if (sub.tag === handle) victims.push(key)\n }\n if (victims.length === 0) return false\n for (const key of victims) subscribers.delete(key)\n this.logger?.info('subscribers force-disconnected', {\n meta: { channel, handle, killed: victims.length, remaining: subscribers.size },\n })\n this.checkDemand()\n return true\n }\n\n /**\n * Returns the local TCP URL that restreamers should connect to\n * instead of the camera's RTSP URL. This ensures the broker is\n * the sole reader from the camera.\n */\n getLocalStreamUrl(): string {\n return this.pipeServer.getUrl()\n }\n\n /** Get the RTSP restreamer instance (for RtspListenServer registration) */\n getRtspRestreamer(): RtspRestreamer {\n return this.rtspRestreamer\n }\n\n /** Returns the number of TCP pipe clients currently connected */\n getPipeClientCount(): number {\n return this.pipeServer.getClientCount()\n }\n\n /**\n * Get the pre-buffer: the most recent N seconds of encoded video packets,\n * starting from the last keyframe. Returns packets in chronological order.\n * Useful for clip creation (get the last N seconds on demand) and\n * late-joining consumers that need to start from a keyframe.\n */\n getPreBuffer(): readonly EncodedPacket[] {\n return this.preBuffer.getPackets()\n }\n\n /** Set the pre-buffer duration in seconds (clamped to 0..MAX_PRE_BUFFER_SEC). */\n setPreBufferDuration(seconds: number): void {\n const clamped = Math.max(0, Math.min(seconds, StreamBroker.MAX_PRE_BUFFER_SEC))\n this.preBuffer.setDuration(clamped)\n this.logger?.info('Pre-buffer duration set', { meta: { seconds: clamped } })\n }\n\n /** Get the current pre-buffer duration in seconds. */\n getPreBufferDuration(): number {\n return this.preBuffer.getDuration()\n }\n\n private startRtspReader(source: StreamSource): void {\n this.startNativeRtspReader(source)\n }\n\n /**\n * Native RTMP reader — pulls media from an RTMP URL, demuxes FLV tags\n * into AnnexB access units, and routes them through `pushEncodedPacket`\n * (same fan-out as Reolink Baichuan / Frigate push sources).\n *\n * Codec support: H.264 standard FLV + H.265 Enhanced FLV (FOURCC\n * 'hvc1' / 'hev1'). Audio: AAC only — non-AAC tracks are skipped.\n *\n * On terminal error: schedules a reconnect with the same exponential\n * backoff used by the RTSP path.\n */\n private startRtmpReader(source: StreamSource): void {\n this.logger?.info('starting RTMP reader', { meta: { url: maskUrlCredentials(source.url) } })\n\n this.destroyRtmpReader()\n\n const reader = new RtmpReader(source.url, this.logger?.child('rtmp'), {\n onVideoCodec: (codec) => {\n this.detectedCodec = codec\n this.logger?.info('rtmp: video codec detected', { meta: { codec } })\n },\n onEncodedPacket: (packet) => {\n if (this.stopping) return\n this.pushEncodedPacket(packet)\n },\n onAudioInfo: (info) => {\n if (this.stopping) return\n this.pushAudioInfo(info)\n },\n onPlaying: () => {\n this.logger?.info('rtmp: streaming')\n this._status = 'streaming'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.stopErrorPlaceholder()\n },\n onError: (error) => {\n if (this.manualStop) return\n this.logger?.warn('rtmp error', { meta: { error: error.message } })\n this.destroyRtmpReader()\n this._status = 'error'\n this.scheduleReconnect()\n },\n onTeardown: () => {\n this.rtmpReader = null\n },\n })\n\n this.rtmpReader = reader\n\n reader.connect().catch((err) => {\n if (this.manualStop || this._suspended) return\n this.logger?.warn('rtmp connect failed', {\n meta: { error: errMsg(err), ...this.sourceMetaForLog() },\n })\n this.destroyRtmpReader()\n this._status = 'error'\n this.scheduleReconnect()\n })\n }\n\n private destroyRtmpReader(): void {\n if (this.rtmpReader) {\n this.rtmpReader.destroy()\n this.rtmpReader = null\n }\n }\n\n /**\n * Native RTSP reader: connect directly to the camera, receive raw RTP.\n *\n * - Preserves RTP framing (no Annex-B corruption on low-bitrate streams)\n * - RTP passthrough to RTSP restreamer (zero re-packetization)\n * - No ffprobe needed (codec detected from SDP DESCRIBE)\n * - Lower latency (no ffmpeg buffering/probing)\n * - Native G.711 audio decode (PCMU/PCMA → PCM f32le 16kHz)\n *\n * On error: reconnects with exponential backoff, emits placeholder frames.\n */\n /**\n * Shared RTP-stream callback factory. Used by both the native RTSP\n * reader and the RFC 4571 reader — they ingest RTP from different\n * transports but the broker's downstream pipeline (depacketize →\n * AnnexB → prebuffer / pipe / decoder / restream) is identical.\n *\n * `label` is the source-tag we attach to log messages so it's clear\n * which transport surfaced the event. `onTerminate` is the\n * transport-specific cleanup invoked from `onError` (the native client\n * runs `destroyNativeClient`; the RFC 4571 reader closes the TCP\n * socket and clears its handle).\n */\n private buildRtpStreamCallbacks(\n label: string,\n onTerminate: () => void,\n ): import('../rtsp/rtsp-client.js').RtspClientCallbacks {\n return {\n onVideoTrack: (track, sdpText) => {\n this.detectedCodec = track.codec\n this.logger?.info('native: video track detected', {\n meta: {\n codec: track.codec,\n payloadType: track.payloadType,\n hasSdpParamSets: Boolean(track.codecParams?.parameterSets?.length),\n },\n })\n\n // Capture codec parameter sets from SDP. Replay these to any\n // already-registered encoded subscribers so WebRTC sessions\n // that connected before the SDP arrived still get the VPS/\n // SPS/PPS they need to initialise the HEVC decoder.\n if (track.codecParams?.parameterSets?.length) {\n this.sdpParameterSets = track.codecParams.parameterSets.map((p) => Buffer.from(p))\n this.replaySdpParamSetsToEncodedSubscribers()\n this.replaySdpParamSetsToRtpSubscribers()\n }\n\n // Set up RTP depacketizer for Annex-B output (prebuffer, pipe, decoder)\n this.rtpDepacketizer = new RtpDepacketizer(track.codec, (depacketized) => {\n this.handleDepacketizedNal(depacketized)\n })\n\n // Arm the watchdog: TCP is up + SDP parsed, so we now expect\n // RTP. If nothing arrives, force a reconnect from scratch.\n this.armFirstVideoWatchdog()\n\n // Switch restreamer to RTP passthrough mode\n this.rtspRestreamer.setCameraSdp(sdpText, track.codec, track.codecParams)\n\n // Create deferred decoder session if subscribers were waiting\n if (!this.decoderProxy && this.decodedSubscribers.size > 0 && this.pendingDecodeOptions !== undefined) {\n this.logger?.info('native: codec detected — creating deferred decoder', {\n meta: { codec: track.codec },\n })\n this.createSharedDecoderSession(this.pendingDecodeOptions)\n this.pendingDecodeOptions = undefined\n }\n },\n\n onVideoRtp: (rtpData) => {\n if (this.stopping) return\n\n this._status = 'streaming'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.stopErrorPlaceholder()\n // First RTP packet of this connection cycle — the watchdog\n // can stand down. Cheap to call when already cancelled.\n this.cancelFirstVideoWatchdog()\n\n // Diagnostic milestones: same cadence as `audio: rtp packet\n // received` so a wake-up cycle that brings audio back but\n // never video is visible side-by-side in the log. Without\n // this, on a battery cam wake-up there's no signal between\n // \"rfc4571: streaming\" and \"decoder push\" — we can't tell\n // if the camera is silent or only the consumer pipeline is\n // misconfigured.\n this.videoRtpSeen++\n if (this.videoRtpSeen === 1 || this.videoRtpSeen === 50) {\n this.logger?.info('video: rtp packet received', {\n meta: { seq: this.videoRtpSeen, bytes: rtpData.length },\n })\n }\n\n // Depacketize RTP → NALs for Annex-B consumers (prebuffer, pipe, decoder)\n // The depacketizer callback (handleDepacketizedNal) runs synchronously\n // within processPacket, so _lastNalKeyframe/_lastNalParamSet are set\n // before we reach the pushRtpPassthrough call below.\n this._lastNalKeyframe = false\n this._lastNalParamSet = false\n this.rtpDepacketizer?.processPacket(rtpData)\n\n // Forward raw RTP to RTSP restreamer (zero re-packetization)\n this.rtspRestreamer.pushRtpPassthrough(\n rtpData,\n this._lastNalKeyframe,\n this._lastNalParamSet,\n )\n\n // Fan out source RTP to direct-RTP subscribers (WebRTC\n // H.265 path uses this to feed a packet-level repacketizer).\n if (this.rtpVideoCallbacks.size > 0) {\n for (const cb of this.rtpVideoCallbacks) {\n try {\n cb(rtpData)\n } catch (err) {\n this.logger?.warn('rtp video subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n },\n\n onAudioTrack: (audioTrack) => {\n const codecUpper = audioTrack.codec.toUpperCase()\n this.audioCodec = codecUpper\n const supportedNative = codecUpper === 'PCMU' || codecUpper === 'PCMA'\n\n // Diagnostic: surface SDP fmtp keys + audio-codec cap availability\n // so we can tell at a glance why the broker did or didn't pick the\n // cap path for this camera.\n this.logger?.info('audio: track detected', {\n meta: {\n codec: audioTrack.codec,\n codecUpper,\n clockRate: audioTrack.clockRate,\n channels: audioTrack.channels,\n fmtpKeys: Object.keys(audioTrack.fmtp),\n hasConfigHex: 'config' in audioTrack.fmtp || 'CONFIG' in audioTrack.fmtp,\n audioCodecApiAvailable: this.audioCodecApi !== null,\n supportedNative,\n },\n })\n\n let supported = supportedNative\n let effectiveSampleRate = audioTrack.clockRate\n let effectiveChannels = audioTrack.channels\n\n if (supportedNative) {\n this.audioRtpDecoder = new AudioRtpDecoder(codecUpper as AudioCodec, (chunk) => {\n for (const sub of this.audioSubscribers.values()) {\n sub.callback(chunk)\n sub.chunksDelivered++\n }\n })\n this.logger?.info('native: audio decoder initialized', {\n meta: { codec: codecUpper, sampleRate: audioTrack.clockRate, channels: audioTrack.channels },\n })\n } else if (this.audioCodecApi) {\n const capCfg = this.buildAudioCodecSessionConfig(audioTrack)\n if (capCfg) {\n effectiveSampleRate = capCfg.sourceSampleRate\n effectiveChannels = capCfg.sourceChannels\n supported = true\n // Tear down any prior session before replacing — without\n // this an RTSP reconnect leaks the previous AudioCodecSession\n // (its poll timer keeps ticking and its upstream cap session\n // lingers until idle reap).\n if (this.audioCodecSession) {\n const prior = this.audioCodecSession\n this.audioCodecSession = null\n void prior.close().catch((err) => {\n this.logger?.debug('audio-codec: prior RTSP session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n this.audioCodecSession = new AudioCodecSession(\n this.audioCodecApi,\n capCfg,\n this.buildAudioCodecEmitCallback(),\n this.logger?.child('audio-codec'),\n )\n this.logger?.info('audio-codec: decode session armed', {\n meta: {\n codec: capCfg.codec,\n sourceSampleRate: capCfg.sourceSampleRate,\n sourceChannels: capCfg.sourceChannels,\n rfc3640: capCfg.rfc3640 ? 'aac-hbr' : 'one-frame',\n },\n })\n } else {\n this.logger?.info('audio-codec: codec not handled by cap — audio skipped', {\n meta: { codec: audioTrack.codec, sampleRate: audioTrack.clockRate, channels: audioTrack.channels },\n })\n }\n } else {\n this.logger?.info('native: audio codec not supported natively — audio skipped', {\n meta: { codec: audioTrack.codec, sampleRate: audioTrack.clockRate, channels: audioTrack.channels },\n })\n }\n\n this.audioTrackInfo = {\n codec: codecUpper,\n sampleRate: effectiveSampleRate,\n channels: effectiveChannels,\n supported,\n }\n },\n\n onAudioRtp: (rtpData) => {\n if (this.stopping) return\n this.audioRtpSeen++\n if (this.audioRtpSeen === 1 || this.audioRtpSeen === 50) {\n this.logger?.info('audio: rtp packet received', {\n meta: {\n seq: this.audioRtpSeen,\n bytes: rtpData.length,\n hasNativeDecoder: this.audioRtpDecoder !== null,\n hasCodecSession: this.audioCodecSession !== null,\n },\n })\n }\n this.audioRtpDecoder?.processPacket(rtpData)\n this.audioCodecSession?.processPacket(rtpData)\n\n // Emit raw G.711 audio payload as EncodedPacket so WebRTC and\n // other onEncodedData consumers can forward it without transcoding.\n // ONLY for native codecs (PCMU/PCMA) — AAC/Opus raw RTP payloads\n // are NOT G.711 and must go through audio-codec decode first.\n const isNativeAudioCodec = this.audioCodec === 'PCMU' || this.audioCodec === 'PCMA'\n if (isNativeAudioCodec && this.encodedCallbacks.size > 0 && rtpData.length > 12) {\n const payload = rtpData.subarray(12)\n if (payload.length > 0) {\n // Extract RTP timestamp (bytes 4-7, big-endian) for PTS\n const rtpTimestamp = rtpData.readUInt32BE(4)\n const pts = Math.floor(rtpTimestamp / 8) // 8kHz → ms\n const packet: EncodedPacket = {\n type: 'audio',\n data: payload,\n pts,\n dts: pts,\n keyframe: false,\n codec: this.audioCodec?.toLowerCase() ?? 'pcmu',\n }\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n }\n }\n\n // Forward raw audio RTP to RTSP restreamer for audio passthrough\n this.rtspRestreamer.pushAudioRtpPassthrough(rtpData)\n },\n\n onPlaying: () => {\n this.logger?.info(`${label}: streaming`)\n this._status = 'streaming'\n },\n\n onError: (error) => {\n if (this.manualStop) return\n this.logger?.warn(`${label} error`, {\n meta: {\n error: error.message,\n ...this.sourceMetaForLog(),\n },\n })\n onTerminate()\n this._status = 'error'\n // rfc4571 sources are loopback to a publisher-owned TCP\n // server. A read error after the lib's idle teardown means\n // the cached URL is stale — ask the publisher to republish\n // before the next reconnect tick.\n if (this.source?.type === 'rfc4571') {\n this.notifySourceRefresh()\n }\n this.scheduleReconnect()\n },\n\n onTeardown: () => {\n onTerminate()\n },\n }\n }\n\n private startNativeRtspReader(source: StreamSource): void {\n this.logger?.info('starting native RTSP reader')\n\n const client = new NativeRtspClient(\n {\n url: source.url,\n logger: this.logger?.child('rtsp-native'),\n },\n this.buildRtpStreamCallbacks('native RTSP', () => this.destroyNativeClient()),\n )\n\n this.nativeClient = client\n\n client.connect().catch((err) => {\n if (this.manualStop || this._suspended) return\n this.logger?.warn('native RTSP connect failed', {\n meta: { error: errMsg(err), ...this.sourceMetaForLog() },\n })\n this.destroyNativeClient()\n this._status = 'error'\n this.scheduleReconnect()\n })\n }\n\n /**\n * Handle a complete NAL from the RTP depacketizer.\n * Converts to EncodedPacket and feeds all Annex-B consumers\n * (prebuffer, pipe server, decoder, restreamers).\n *\n * Called synchronously from processPacket — sets _lastNalKeyframe and\n * _lastNalParamSet so the onVideoRtp callback can forward them to the\n * RTSP restreamer's pushRtpPassthrough.\n */\n /**\n * Pending parameter-set NALs (SPS/PPS for H.264, VPS/SPS/PPS for H.265).\n * Cameras emit these as separate RTP packets before the IDR/IRAP frame.\n * We buffer them here and prepend to the next VCL NAL so downstream\n * consumers (WebRTC, prebuffer, decoder) receive complete access units\n * with a single `EncodedPacket` that contains SPS+PPS+IDR together.\n */\n private pendingParamNals: Buffer[] = []\n\n /**\n * handleDepacketizedNal receives individual NALs from the RTP depacketizer\n * and emits them as EncodedPackets.\n *\n * Parameter-set NALs (SPS/PPS type 7/8 for H.264, VPS/SPS/PPS type 32/33/34\n * for H.265) are buffered and prepended to the next VCL NAL (slice/IDR).\n * This guarantees every keyframe EncodedPacket is a self-contained access\n * unit that a decoder can initialise from — critical for WebRTC (Chrome\n * can't decode an IDR without preceding SPS/PPS).\n *\n * The RTSP restreamer handles its own RTP passthrough separately and is\n * not affected by this merging.\n */\n private handleDepacketizedNal(depacketized: DepacketizedNal): void {\n const { nal, keyframe, parameterSet, timestamp } = depacketized\n const codec = this.detectedCodec ?? 'h264'\n\n // Track flags for the parent onVideoRtp call\n if (keyframe) this._lastNalKeyframe = true\n if (parameterSet) this._lastNalParamSet = true\n\n // Detect NAL type\n const isH264 = codec === 'h264'\n const nalType = isH264 ? (nal[0]! & 0x1f) : ((nal[0]! & 0x7e) >> 1)\n\n // H.264 parameter sets: SPS=7, PPS=8\n // H.265 parameter sets: VPS=32, SPS=33, PPS=34\n const isParamSet = isH264\n ? (nalType === 7 || nalType === 8)\n : (nalType === 32 || nalType === 33 || nalType === 34)\n\n if (isParamSet) {\n // Buffer parameter set — don't emit yet\n const startCode = Buffer.from([0x00, 0x00, 0x00, 0x01])\n this.pendingParamNals.push(Buffer.concat([startCode, nal]))\n return\n }\n\n // VCL NAL (slice/IDR) — prepend any buffered parameter sets\n const startCode = Buffer.from([0x00, 0x00, 0x00, 0x01])\n const hadParamSets = this.pendingParamNals.length > 0\n let annexBData: Buffer\n if (hadParamSets) {\n this.pendingParamNals.push(Buffer.concat([startCode, nal]))\n annexBData = Buffer.concat(this.pendingParamNals)\n this.pendingParamNals = []\n } else {\n annexBData = Buffer.concat([startCode, nal])\n }\n\n const pts = Math.floor(timestamp / 90) // 90kHz → ms\n\n const packet: EncodedPacket = {\n type: 'video',\n data: annexBData,\n pts,\n dts: pts,\n keyframe: keyframe || hadParamSets,\n codec,\n }\n\n // Feed all Annex-B consumers (prebuffer, pipe, decoder).\n // RTSP restream is handled separately via RTP passthrough in onVideoRtp.\n this.pushEncodedPacket(packet)\n }\n\n /**\n * Start emitting a pre-encoded H.264 keyframe at 1fps for an\n * explicit `placeholder`-typed source (i.e. disabled / not-yet-\n * configured cameras). The default reason is `'disabled'` because\n * the only path that reaches this is \"operator turned the device\n * off\" or \"device was created without credentials\"; callers who\n * arrive here via a different upstream state can adjust reason via\n * `setPlaceholderReason()` before calling.\n *\n * Frame is re-resolved on every tick from `placeholderReason` so\n * upstream state transitions (`disabled` → `offline`, etc.) propagate\n * within one second.\n */\n private startPlaceholderReader(_source: StreamSource): void {\n this.logger?.debug('Starting placeholder')\n\n if (this.placeholderReason === 'reconnecting') {\n // Default the reason to `'disabled'` for the explicit placeholder\n // source, but only if the caller hasn't already set something\n // more specific.\n this.placeholderReason = 'disabled'\n }\n this._status = 'streaming'\n\n // Emit immediately\n this.emitPlaceholderFrame()\n\n // Re-emit at 1fps so consumers see an active stream\n this.placeholderTimer = setInterval(() => {\n if (this.manualStop || this.stopping) {\n if (this.placeholderTimer) {\n clearInterval(this.placeholderTimer)\n this.placeholderTimer = undefined\n }\n return\n }\n this.emitPlaceholderFrame()\n }, 1000)\n }\n\n /**\n * Resolve the codec the placeholder frame should be encoded as.\n * Picks the broker's currently-detected codec when a reader has\n * already parsed the camera's SDP, otherwise falls back to the\n * source's published codec, otherwise H.264. The choice MUST match\n * the negotiated WebRTC session codec — sending an H.264 placeholder\n * over an H.265 RTP track corrupts the decoder state on Chrome and\n * leaves the viewer frozen on the last placeholder until the\n * session is recreated. Mirroring the broker codec keeps the\n * placeholder feed bit-stream-compatible across the same path the\n * live camera uses.\n */\n private placeholderCodec(): PlaceholderCodec {\n const codec = (this.detectedCodec ?? this.source?.videoCodec ?? '').toLowerCase()\n if (codec === 'h265' || codec === 'hevc') return 'h265'\n return 'h264'\n }\n\n /**\n * Push the current placeholder frame (selected by `placeholderReason`\n * + `placeholderCodec()`) to all encoded subscribers. No-op-safe to\n * call from any timer.\n */\n private emitPlaceholderFrame(): void {\n const codec: PlaceholderCodec = this.placeholderCodec()\n const packet: EncodedPacket = {\n type: 'video',\n data: getPlaceholderFrame(this.placeholderReason, codec),\n pts: Date.now(),\n dts: Date.now(),\n keyframe: true, // single-frame encode is always a keyframe\n codec,\n // Tag so consumers (notably the WebRTC encoded-data subscriber,\n // which keeps a sticky SPS/PPS to prepend to keyframes lacking\n // inline param sets) can drop the packet's parameter sets from\n // any per-stream cache they maintain. Without this tag, a\n // 1280×720 placeholder SPS gets prepended to the camera's real\n // (different resolution) IDR and Chrome's decoder freezes on\n // the last placeholder frame until the user manually restarts.\n isPlaceholder: true,\n }\n this.pushEncodedPacket(packet)\n }\n\n // ── First-video watchdog ──────────────────────────────────────────\n\n /**\n * Arm the first-video watchdog. Called when the rfc4571 reader\n * reports a working SDP (`onVideoTrack`), at which point the broker\n * has a live TCP connection to the lib's loopback server and is\n * waiting for the upstream Baichuan dedicated session to start\n * pushing RTP. No-op when already armed (so duplicate `onVideoTrack`\n * fires don't reset the timer).\n */\n private armFirstVideoWatchdog(): void {\n if (this.firstVideoWatchdog) return\n if (this.manualStop || this.stopping) return\n this.firstVideoWatchdog = setTimeout(() => {\n this.firstVideoWatchdog = undefined\n if (this.manualStop || this.stopping) return\n // Race-safe check: a packet may have arrived between the timer\n // firing and this callback running. Be permissive.\n if (this.videoRtpSeen > 0) return\n this.logger?.warn(\n 'first-video watchdog: no video RTP after rfc4571 connect — forcing reconnect',\n { meta: { timeoutMs: StreamBroker.FIRST_VIDEO_TIMEOUT_MS } },\n )\n // Reset videoRtpSeen so the next watchdog cycle gets a clean\n // measurement, and `video: rtp packet received` re-fires for\n // the fresh connection.\n this.videoRtpSeen = 0\n // Tear down the rfc4571 reader → lib detects TCP close →\n // releases its dedicated session → next dial triggers a fresh\n // session. This mirrors the user's \"click restart\" workaround.\n this.destroyRfc4571Reader()\n this._status = 'error'\n // Treat this as a fresh recovery cycle rather than a continuation\n // of an exponential-backoff sequence — the user noticed the\n // stuck state after wake-up, the manual restart resets backoff,\n // and we're emulating that.\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.lastRefreshRequestAt = 0\n // Provider needs to know its source might be stale (the lib's\n // server may have idle-torn-down between dial attempts).\n this.notifySourceRefresh()\n this.scheduleReconnect()\n }, StreamBroker.FIRST_VIDEO_TIMEOUT_MS)\n }\n\n /** Cancel the first-video watchdog. Idempotent. */\n private cancelFirstVideoWatchdog(): void {\n if (!this.firstVideoWatchdog) return\n clearTimeout(this.firstVideoWatchdog)\n this.firstVideoWatchdog = undefined\n }\n\n // ── Error/idle placeholder ────────────────────────────────────────\n\n /** Timer emitting error placeholder at 1fps while reconnecting. */\n private errorPlaceholderTimer: ReturnType<typeof setInterval> | undefined\n\n /**\n * Reason currently displayed by the placeholder image. Drives\n * `getPlaceholderFrame()` lookups so a sleeping cam shows\n * \"SLEEPING\", an offline cam shows \"OFFLINE\", and so on. Default\n * `'reconnecting'` matches the legacy single-frame behaviour.\n *\n * Mutated via `setPlaceholderReason()` from upstream surfaces (the\n * Reolink provider transitions on sleep events; the broker manager\n * may surface explicit disabled/offline states); the running\n * `errorPlaceholderTimer` re-reads it on every tick so transitions\n * appear within ~1 frame of the upstream signal.\n */\n private placeholderReason: PlaceholderKind = 'reconnecting'\n\n /**\n * Update the reason shown by the placeholder image. Idempotent —\n * a no-op when the reason hasn't changed. Safe to call from\n * upstream callers regardless of whether the placeholder timer is\n * currently active; the next tick (or next `startErrorPlaceholder`\n * invocation) picks up the new reason.\n */\n setPlaceholderReason(reason: PlaceholderKind): void {\n if (this.placeholderReason === reason) return\n this.placeholderReason = reason\n // If the placeholder is actively emitting, push a fresh frame so\n // the operator sees the new label immediately rather than waiting\n // for the next 1Hz tick. Cheap (~5-9KB per emit) and matches user\n // expectation when toggling state from the admin UI.\n if (this.errorPlaceholderTimer) {\n this.emitPlaceholderFrame()\n }\n }\n\n private stopErrorPlaceholder(): void {\n if (this.errorPlaceholderTimer) {\n clearInterval(this.errorPlaceholderTimer)\n this.errorPlaceholderTimer = undefined\n }\n }\n\n private scheduleReconnect(): void {\n if (this.manualStop || !this.source) {\n return\n }\n // Hard rule: a broker never holds an open dial loop when no client\n // is consuming the stream. Without demand, suspend instead of\n // re-arming the reconnect timer; the next subscriber triggers\n // `checkDemand()` → `start(this.source)` to dial again.\n if (!this.hasDemand()) {\n this._suspended = true\n this._status = 'idle'\n this.stopErrorPlaceholder()\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n this.logger?.info('Reconnect skipped — no demand, broker suspended')\n return\n }\n\n // Dedup: a single connect failure fans out into two notifications\n // (`socket.on('error')` → `onError` callback AND the rejected\n // `client.connect()` promise → `.catch`). Both end up here. When a\n // reconnect timer is already armed for the same failure window, do\n // nothing — otherwise every doubled call doubles the live timers,\n // and a few generations later one connect failure expands into\n // hundreds of scheduled retries firing in the same tick.\n if (this.reconnectTimer) {\n return\n }\n\n // Placeholder pump is intentionally NOT started during reconnect.\n // We tried both H.264 and H.265 cross-codec injection paths and\n // they confused Chrome's HEVC decoder on the H.265 RTP path\n // (placeholder SPS conflicting with the repacketizer's seeded\n // camera SPS). Reconnect/wake-up windows now leave the existing\n // last-frame on screen — the WebRTC viewer's UI overlay is the\n // surface for reconnect feedback. Placeholders remain only for\n // the explicit `placeholder` source type (disabled cameras),\n // handled by `startPlaceholderReader`.\n\n this.logger?.info(\n 'Reconnecting RTSP reader',\n { meta: { delayMs: this.reconnectDelayMs } },\n )\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined\n if (this.manualStop) return\n // Re-resolve the source on every reconnect so an URL change\n // landed while we were waiting (e.g. Reolink republished after\n // an `ensureRfc4571Server` recreate) is observed.\n const src = this.resolveCurrentSource()\n if (!src) return\n this._status = 'connecting'\n if (src.type === 'rtmp') {\n this.startRtmpReader(src)\n } else if (src.type === 'rfc4571') {\n this.startRfc4571Reader(src)\n } else {\n this.startRtspReader(src)\n }\n }, this.reconnectDelayMs)\n\n this.reconnectDelayMs = Math.min(\n this.reconnectDelayMs * 2,\n MAX_RECONNECT_DELAY_MS,\n )\n }\n\n /**\n * Reset the push-mode stall detection timer.\n * If no packet arrives within PUSH_STALL_TIMEOUT_MS, switch to error\n * state and emit placeholder frames so RTSP clients don't freeze.\n *\n * Sources flagged `allowStall: true` (battery cameras that\n * intentionally go silent between motion events) bypass the error\n * placeholder — the broker logs at debug, marks the broker\n * `suspended`, and waits silently for the next packet. The next\n * `pushEncodedPacket` clears the suspension and resumes streaming.\n */\n private resetPushStallTimer(): void {\n if (this.pushStallTimer) {\n clearTimeout(this.pushStallTimer)\n }\n this.pushStallTimer = setTimeout(() => {\n this.pushStallTimer = undefined\n if (this.stopping || this.manualStop) return\n if (this.source?.type !== 'push') return\n\n if (this.source?.allowStall) {\n this.logger?.debug('push source stall (allowStall=true) — suspending until next packet', {\n meta: { timeoutMs: PUSH_STALL_TIMEOUT_MS },\n })\n this._suspended = true\n return\n }\n\n this.logger?.warn('push source stalled — no data received', {\n meta: { timeoutMs: PUSH_STALL_TIMEOUT_MS },\n })\n this._status = 'error'\n // Placeholder intentionally not emitted — see scheduleReconnect\n // for the rationale (cross-codec issues on H.265 RTP path).\n // `_status='error'` is enough for stats consumers; UI overlay\n // handles operator-facing feedback.\n }, PUSH_STALL_TIMEOUT_MS)\n }\n\n private destroyDecoder(): Promise<void> {\n if (this.decoderResolveRetryTimer) {\n clearTimeout(this.decoderResolveRetryTimer)\n this.decoderResolveRetryTimer = undefined\n }\n this.decoderResolveAttempts = 0\n const proxy = this.decoderProxy\n this.decoderProxy = null\n this.decoderNodeId = null\n this.cachedDecoderStats = undefined\n return proxy?.destroy() ?? Promise.resolve()\n }\n\n /**\n * Moleculer nodeID of the decoder provider currently owning this broker's\n * shared session. `null` when no session is active. Used by the\n * broker-manager's `rotateDecodersOnNode` method to drop only the\n * sessions affected by a per-agent event (e.g. hwaccel override change).\n */\n getDecoderNodeId(): string | null {\n return this.decoderNodeId\n }\n\n /**\n * Force the current shared decoder session to tear down. The next\n * subscriber will trigger a fresh `createSharedDecoderSession` which\n * re-pulls the latest per-agent preferences (hwaccel backend, …). Safe\n * to call when there's no active session — resolves to a no-op.\n */\n async rotateDecoderSession(reason: string): Promise<void> {\n if (!this.decoderProxy) return\n this.logger?.info('rotating decoder session', { meta: { reason, decoderNodeId: this.decoderNodeId } })\n await this.destroyDecoder()\n // If subscribers are still attached, rebuild immediately so streaming\n // resumes without waiting for a new subscribe event.\n if (this.decodedSubscribers.size > 0 && this.source) {\n const codec = this.detectedCodec ?? this.source.videoCodec\n if (codec) {\n this.createSharedDecoderSession(this.pendingDecodeOptions).catch((err: unknown) => {\n this.logger?.warn('decoder session rebuild after rotation failed', { meta: { error: errMsg(err) } })\n })\n }\n }\n }\n\n /**\n * Poll the decoder resolver until a provider for `codec` shows up, then\n * build the session. Addons register asynchronously and out-of-order:\n * the broker must NOT fail when the decoder addon hasn't yet finished\n * spawning its isolated process — it must wait.\n *\n * Bounded backoff: 500ms → 1s → 2s → 4s → 8s (capped), with a hard\n * ceiling of ~60s total wait. If the decoder still isn't registered\n * after that, log once at error level and stop retrying — at that\n * point something is genuinely wrong with the addon config.\n *\n * The first failure is logged at debug so a healthy boot (decoder\n * arriving within the first few hundred ms) doesn't pollute the log\n * with noisy errors.\n */\n private scheduleDecoderResolveRetry(codec: string): void {\n if (this.decoderResolveRetryTimer) return\n const attempt = this.decoderResolveAttempts++\n const backoffMs = Math.min(500 * Math.pow(2, attempt), 8000)\n const cumulativeMs = (Math.pow(2, this.decoderResolveAttempts) - 1) * 500\n if (cumulativeMs > 60000) {\n this.logger?.error(\n 'No decoder provider — giving up. Check that addon-decoder-nodeav or addon-decoder-ffmpeg is installed and enabled.',\n { meta: { codec, attempts: this.decoderResolveAttempts } },\n )\n this.decoderResolveAttempts = 0\n return\n }\n if (attempt === 0) {\n this.logger?.debug(\n 'Decoder not yet registered — will retry (addon boot race)',\n { meta: { codec } },\n )\n }\n this.decoderResolveRetryTimer = setTimeout(() => {\n this.decoderResolveRetryTimer = undefined\n // Only retry if we still have subscribers and no session yet — a\n // teardown between schedule and fire is valid and should no-op.\n if (this.decoderProxy) return\n if (this.decodedSubscribers.size === 0) {\n this.decoderResolveAttempts = 0\n this.pendingDecodeOptions = undefined\n return\n }\n this.createSharedDecoderSession(this.pendingDecodeOptions)\n }, backoffMs)\n }\n\n\n private destroyNativeClient(): void {\n if (this.nativeClient) {\n this.nativeClient.destroy()\n this.nativeClient = null\n }\n this.resetRtpPipeline()\n }\n\n /**\n * RFC 4571 reader — pulls framed RTP from a local TCP server (e.g. the\n * Reolink lib's `createRfc4571TcpServer`). Same downstream pipeline as\n * the native RTSP reader: SDP arrives out-of-band on the StreamSource,\n * RTP packets feed the same depacketizer, AnnexB output goes through\n * the same prebuffer / pipe / decoder paths.\n *\n * `source.metadata.sdp` is required and must describe the video\n * (and optionally audio) track the publisher is multiplexing on the\n * single TCP connection.\n */\n private startRfc4571Reader(source: StreamSource): void {\n this.logger?.info('starting RFC 4571 reader')\n\n // Lazy publish — when the URL carries the `lazy:rfc4571:` sentinel\n // the publisher hasn't materialized the upstream socket yet (it's\n // deferred to first-consumer demand). Trigger a refresh; the\n // publisher's `StreamBrokerOnRequestStreamSourceRefresh` listener\n // is responsible for opening the real rfc4571 server and\n // re-publishing with a real `tcp://...` URL — the reconnect tick\n // resolves the fresh source via `sourceProvider`.\n if (typeof source.url === 'string' && source.url.startsWith('lazy:rfc4571:')) {\n this.logger?.info('rfc4571: lazy URL — requesting publisher to materialize upstream', {\n meta: { url: source.url },\n })\n this._status = 'error'\n this.notifySourceRefresh()\n this.scheduleReconnect()\n return\n }\n\n const sdp = typeof source.metadata?.['sdp'] === 'string' ? source.metadata['sdp'] as string : null\n if (!sdp) {\n // No SDP is the same situation as a stale lazy URL — the\n // publisher needs to repopulate. Mirror the lazy-URL path: emit\n // `notifySourceRefresh` so the publisher gets a chance to\n // materialize, instead of failing silently.\n this.logger?.warn('rfc4571: source missing metadata.sdp — requesting publisher refresh')\n this._status = 'error'\n this.notifySourceRefresh()\n this.scheduleReconnect()\n return\n }\n\n const reader = new Rfc4571Reader(\n {\n url: source.url,\n sdp,\n ...(this.logger ? { logger: this.logger.child('rfc4571') } : {}),\n },\n this.buildRtpStreamCallbacks('rfc4571', () => this.destroyRfc4571Reader()),\n )\n\n this.rfc4571Reader = reader\n\n reader.connect().catch((err) => {\n if (this.manualStop || this._suspended) return\n this.logger?.warn('rfc4571 connect failed', {\n meta: { error: errMsg(err), ...this.sourceMetaForLog() },\n })\n this.destroyRfc4571Reader()\n this._status = 'error'\n // Ask the publisher to refresh its loopback transport. The lib's\n // RFC 4571 TCP server idle-tears-down after ~15s with no\n // clients and may rebind to a different port on recreate, so a\n // cached URL goes stale. The publisher's response (re-running\n // `ensureRfc4571Server` + `publishCameraStream`) updates the\n // cam-stream registry; the next reconnect picks the fresh URL\n // up via `resolveCurrentSource()`. Deduped against the parallel\n // onError emit by `notifySourceRefresh`.\n this.notifySourceRefresh()\n this.scheduleReconnect()\n })\n }\n\n private destroyRfc4571Reader(): void {\n if (this.rfc4571Reader) {\n this.rfc4571Reader.destroy()\n this.rfc4571Reader = null\n }\n this.resetRtpPipeline()\n // The watchdog only makes sense while a reader is alive — drop it\n // here so a teardown initiated outside `armFirstVideoWatchdog`'s\n // own fire path doesn't leave a stale timer that would force a\n // useless reconnect later.\n this.cancelFirstVideoWatchdog()\n }\n\n /** Tear down the shared RTP/audio pipeline state used by both readers. */\n private resetRtpPipeline(): void {\n if (this.rtpDepacketizer) {\n this.rtpDepacketizer.reset()\n this.rtpDepacketizer = null\n }\n if (this.audioRtpDecoder) {\n this.audioRtpDecoder.flush()\n this.audioRtpDecoder = null\n }\n if (this.audioCodecSession) {\n const session = this.audioCodecSession\n this.audioCodecSession = null\n void session.close().catch((err) => {\n this.logger?.warn('audio-codec: session close failed', {\n meta: { error: errMsg(err) },\n })\n })\n }\n }\n\n private async createSharedDecoderSession(options?: DecodeOptions): Promise<void> {\n const codec = this.detectedCodec ?? this.source?.videoCodec ?? 'h264'\n\n if (!this.decoderApi) {\n this.logger?.warn('no decoder API available — decoded frames will not be produced')\n return\n }\n\n // Check if the decoder supports this codec before creating a session.\n const supported = await this.decoderApi.supportsCodec({ codec })\n if (!supported) {\n // Decoder addon may not be registered yet (boot race).\n this.pendingDecodeOptions = options\n this.scheduleDecoderResolveRetry(codec)\n return\n }\n\n // A provider is now available — clear any pending retry so we don't\n // double-create the session on the next tick.\n if (this.decoderResolveRetryTimer) {\n clearTimeout(this.decoderResolveRetryTimer)\n this.decoderResolveRetryTimer = undefined\n }\n this.decoderResolveAttempts = 0\n\n // Shared decoder runs at unlimited FPS — each subscriber throttles locally.\n // Use resolveDecoderFormat() to start with the cheapest format that\n // satisfies all current subscribers (e.g. gray when only motion is active).\n const resolvedFormat = this.resolveDecoderFormat()\n // Stream-broker `deviceId` is actually the brokerId (`<numeric>/<profile>`).\n // Parse out the numeric for the cap config so decoder logs land with\n // a clean integer `deviceId` tag — the full brokerId goes through `tag`.\n const slash = this.deviceId.indexOf('/')\n const numericDeviceId = slash >= 0\n ? Number.parseInt(this.deviceId.slice(0, slash), 10)\n : Number.parseInt(this.deviceId, 10)\n const config = {\n codec,\n maxFps: 0,\n outputFormat: resolvedFormat,\n scale: options?.scale ?? DEFAULT_SCALE,\n ...(Number.isFinite(numericDeviceId) ? { deviceId: numericDeviceId } : {}),\n tag: `broker:${this.deviceId}`,\n }\n this.decoderOutputFormat = resolvedFormat\n\n const { sessionId, nodeId } = await this.decoderApi.createSession(config)\n const proxy = new DecoderSessionProxy(this.decoderApi, sessionId)\n this.decoderProxy = proxy\n this.decoderNodeId = nodeId\n\n // Fan-out decoded frames to all subscribers with per-subscriber\n // throttling. In Phase 3 the decoder produces one *canonical*\n // output (gray / rgb / jpeg per `resolveDecoderFormat`) and the\n // broker derives any other format on demand via a per-frame\n // conversion cache, so that an `rgb`-source frame fanned out to\n // five `jpeg` subscribers performs exactly one sharp encode and\n // pushes the same buffer to each. Subscribers that match the\n // canonical format short-circuit the cache entirely.\n const onFrame = (frame: DecodedFrame) => {\n this.decoderFrameCount++\n if (this.decoderFrameCount === 1) {\n this.logger?.info('first decoded frame', {\n meta: { width: frame.width, height: frame.height, format: frame.format },\n })\n }\n // Update cached stats periodically (every 30 frames)\n if (this.decoderFrameCount % 30 === 0) {\n proxy.getStats().then((stats) => {\n this.cachedDecoderStats = stats\n }).catch(() => { /* stats refresh failure is non-fatal */ })\n }\n this.fanoutDecodedFrame(frame)\n }\n\n // Start the polling loop in the background — it runs until stopPolling or destroy\n proxy.startPolling(onFrame).catch((err: unknown) => {\n this.logger?.warn('decoder polling error', { meta: { error: errMsg(err) } })\n })\n\n // Pull mode: decoder reads from the broker's local pipe server (no duplicate RTSP connection)\n // Must wait for the first keyframe so the pipe contains SPS/PPS for the decoder.\n const info = await this.decoderApi.getInfo()\n if (info.isPullMode) {\n const localUrl = this.pipeServer.getUrl()\n const isHevc = codec === 'h265' || codec === 'hevc'\n const inputUrl = `${localUrl}?format=${isHevc ? 'hevc' : 'h264'}`\n\n const doOpen = () => {\n this.logger?.info('Pull mode decoder: opening local pipe', {\n meta: { localUrl, codec },\n })\n proxy.openStream(inputUrl).catch((err: unknown) => {\n this.logger?.error('Pull mode decoder failed', { meta: { error: errMsg(err) } })\n })\n }\n\n if (this.firstKeyframeReceived) {\n doOpen()\n } else {\n this.logger?.info('Pull mode decoder: waiting for first keyframe before opening')\n this.pendingPullModeOpen = doOpen\n }\n }\n }\n\n /**\n * Map an SDP audio track to an `AudioCodecSession` config the\n * audio-codec cap can consume. Returns `null` when the codec is\n * outside the cap's catalogue (broker logs and skips audio in that\n * case). Pure — relies only on parsed SDP fields.\n */\n /**\n * Shared emit callback for `AudioCodecSession`. Decoded PCM is fanned\n * out to audio subscribers and re-encoded as G.711 µ-law for WebRTC\n * consumers. Used by both the RTSP onAudioTrack path and the push\n * `pushAudioInfo` path.\n */\n private buildAudioCodecEmitCallback(): (chunk: import('@camstack/types').DecodedAudioChunk) => void {\n return (chunk) => {\n // The analyzer pipeline relies on the AudioCodecSession's ~500ms\n // window to give the sound classifier a stable input length, so\n // we hand the big chunk over to `audioSubscribers` unchanged.\n for (const sub of this.audioSubscribers.values()) {\n sub.callback(chunk)\n sub.chunksDelivered++\n }\n if (this.encodedCallbacks.size > 0) {\n const f32 = new Float32Array(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength / 4)\n const pcm16 = new Int16Array(f32.length)\n for (let i = 0; i < f32.length; i++) {\n const s = Math.max(-1, Math.min(1, f32[i]!))\n pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF\n }\n const ulaw = pcmToMulaw(pcm16)\n const ratio = Math.max(1, Math.round(chunk.sampleRate / 8000))\n const resampled = ratio > 1 ? downsampleUlaw(ulaw, ratio) : ulaw\n\n // Split the 500ms-window mu-law buffer into RFC 3551-canonical\n // 20ms RTP packets (160 bytes @ 8kHz µ-law). The analyzer\n // pipeline can stomach the long window because it integrates\n // over half-second classifier frames, but WebRTC senders\n // can't: a single 4000-byte payload exceeds typical UDP MTU\n // (frags drop on any single-fragment loss) AND arrives at the\n // browser in 500ms-bursts the audio jitter buffer can't\n // smooth out — net effect is \"no audio output on the player\".\n // 20ms per packet is what every Reolink-style RTSP/rfc4571\n // restream produces on the wire, so the WebRTC sender now\n // looks identical to a passthrough G.711 source.\n const PCMU_BYTES_PER_PACKET = 160 // 8kHz × 20ms × 1 byte/sample\n const baseTs = chunk.timestamp\n let offsetBytes = 0\n // Stamp each sub-packet's `pts` with a monotonically advancing\n // ms timestamp (20ms per slice). The WebRTC session uses its\n // own RTP timestamp counter (`audioTimestampBase` in\n // `session.ts:1032`), so this value mostly drives metadata —\n // but keeping it monotonic lets any future RTP-aware consumer\n // reconstruct the original timing without surprises.\n let chunkOffsetMs = 0\n while (offsetBytes < resampled.length) {\n const end = Math.min(offsetBytes + PCMU_BYTES_PER_PACKET, resampled.length)\n const slice = resampled.subarray(offsetBytes, end)\n const packet: EncodedPacket = {\n type: 'audio',\n data: Buffer.from(slice),\n pts: baseTs + chunkOffsetMs,\n dts: baseTs + chunkOffsetMs,\n keyframe: false,\n codec: 'pcmu',\n }\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n offsetBytes = end\n chunkOffsetMs += 20\n }\n }\n }\n }\n\n /**\n * Build an `AudioCodecSessionConfig` from `PushAudioInfo` declared by\n * a push-source provider. Mirrors `buildAudioCodecSessionConfig`\n * (which builds from an SDP audio track) for sources that don't\n * carry SDP — Reolink Baichuan, Frigate, etc.\n *\n * Push sources don't carry rfc3640 framing — they emit one complete\n * codec frame per packet — so the session is configured without\n * `rfc3640` and the broker calls `pushRawFrame` instead of\n * `processPacket`.\n */\n private buildAudioCodecSessionConfigFromPush(\n info: import('@camstack/types').PushAudioInfo,\n ): import('./audio-codec-session.js').AudioCodecSessionConfig | null {\n const codec = info.codec.toLowerCase()\n const tag = `broker:${this.deviceId}`\n if (codec === 'aac') {\n return {\n codec: 'aac',\n sourceSampleRate: info.sampleRate,\n sourceChannels: info.channels,\n ...(info.extraData ? { extraData: info.extraData } : {}),\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n }\n }\n if (codec === 'opus') {\n return {\n codec: 'opus',\n sourceSampleRate: info.sampleRate,\n sourceChannels: info.channels,\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n }\n }\n return null\n }\n\n private buildAudioCodecSessionConfig(\n audioTrack: import('../rtsp/sdp-parser.js').SdpAudioTrack,\n ): import('./audio-codec-session.js').AudioCodecSessionConfig | null {\n const codecUpper = audioTrack.codec.toUpperCase()\n const fmtp = audioTrack.fmtp\n const tag = `broker:${this.deviceId}`\n\n if (codecUpper === 'MPEG4-GENERIC' || codecUpper === 'AAC') {\n let extraData: Uint8Array | undefined\n let sourceSampleRate = audioTrack.clockRate\n let sourceChannels = audioTrack.channels\n const configHex = fmtp['config'] ?? fmtp['CONFIG']\n if (configHex) {\n try {\n const asc = parseAudioSpecificConfig(configHex)\n sourceSampleRate = asc.sampleRate\n sourceChannels = asc.channelConfig > 0 ? asc.channelConfig : sourceChannels\n extraData = hexToBytes(configHex)\n } catch (err) {\n this.logger?.warn('audio-codec: AudioSpecificConfig parse failed', {\n meta: { configHex, error: errMsg(err) },\n })\n }\n }\n const sizeLength = parseFmtpInt(fmtp, 'sizelength', 13)\n const indexLength = parseFmtpInt(fmtp, 'indexlength', 3)\n const indexDeltaLength = parseFmtpInt(fmtp, 'indexdeltalength', indexLength)\n return {\n codec: 'aac',\n sourceSampleRate,\n sourceChannels,\n ...(extraData ? { extraData } : {}),\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n rfc3640: { sizeLength, indexLength, indexDeltaLength },\n }\n }\n\n if (codecUpper === 'OPUS') {\n return {\n codec: 'opus',\n sourceSampleRate: audioTrack.clockRate,\n sourceChannels: audioTrack.channels,\n targetSampleRate: 16000,\n targetChannels: 1,\n tag,\n }\n }\n\n return null\n }\n}\n\nfunction parseFmtpInt(\n fmtp: Readonly<Record<string, string>>,\n key: string,\n fallback: number,\n): number {\n const raw = fmtp[key] ?? fmtp[key.toLowerCase()] ?? fmtp[key.toUpperCase()]\n if (raw === undefined) return fallback\n const n = Number(raw)\n return Number.isFinite(n) && n > 0 ? n : fallback\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const trimmed = hex.replace(/\\s+/g, '')\n if (trimmed.length % 2 !== 0) {\n throw new Error(`audio-codec: hex string has odd length (${trimmed.length})`)\n }\n const out = new Uint8Array(trimmed.length / 2)\n for (let i = 0; i < out.length; i++) {\n const byte = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16)\n if (Number.isNaN(byte)) {\n throw new Error(`audio-codec: invalid hex at offset ${i * 2}`)\n }\n out[i] = byte\n }\n return out\n}\n","/**\n * Thin tRPC-backed proxy for the `audio-codec` capability — the broker\n * uses this to decode AAC / opus / pcm_alaw / pcm_mulaw audio into PCM\n * for downstream subscribers (audio-analyzer, WebRTC audio leg, …).\n *\n * The cap is a system singleton; one provider serves all brokers on the\n * cluster. Plumbing here is a pass-through to `ctx.api.audioCodec.*` —\n * the broker doesn't know whether the audio decode runs locally or on a\n * remote node, the cap router resolves it.\n */\nimport type {\n AudioDecodeSessionConfig,\n AudioPcmChunk,\n AudioEncodeSessionConfig,\n AudioEncodedChunk,\n AudioCodecInfo,\n} from '@camstack/types'\n\n// Session-bound methods accept an optional `nodeId` so callers can pin\n// every call to the node that owns the session. The generated cap router\n// auto-routes any input field literally named `nodeId` to that node's\n// provider — sessions are process-local, so without sticky routing the\n// round-robin across nodes serves \"session not found\" on every other call.\n\nexport interface AudioCodecCapApi {\n listSupportedCodecs(): Promise<readonly AudioCodecInfo[]>\n canHandle(input: { codec: string; kind: 'decode' | 'encode' }): Promise<boolean>\n createDecodeSession(input: AudioDecodeSessionConfig): Promise<{ sessionId: string; nodeId: string }>\n createEncodeSession(input: AudioEncodeSessionConfig): Promise<{ sessionId: string; nodeId: string }>\n closeSession(input: { sessionId: string; nodeId?: string }): Promise<void>\n pushEncodedFrame(input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }): Promise<void>\n pullPcm(input: { sessionId: string; nodeId?: string; maxCount: number }): Promise<readonly AudioPcmChunk[]>\n pushPcm(input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }): Promise<void>\n pullEncoded(input: { sessionId: string; nodeId?: string; maxCount: number }): Promise<readonly AudioEncodedChunk[]>\n flushEncode(input: { sessionId: string; nodeId?: string }): Promise<readonly AudioEncodedChunk[]>\n}\n\ninterface AudioCodecRouter {\n listSupportedCodecs: { query: (input?: void) => Promise<readonly AudioCodecInfo[]> }\n canHandle: { query: (input: { codec: string; kind: 'decode' | 'encode' }) => Promise<boolean> }\n createDecodeSession: { mutate: (input: AudioDecodeSessionConfig) => Promise<{ sessionId: string; nodeId: string }> }\n createEncodeSession: { mutate: (input: AudioEncodeSessionConfig) => Promise<{ sessionId: string; nodeId: string }> }\n closeSession: { mutate: (input: { sessionId: string; nodeId?: string }) => Promise<void> }\n pushEncodedFrame: { mutate: (input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }) => Promise<void> }\n pullPcm: { query: (input: { sessionId: string; nodeId?: string; maxCount: number }) => Promise<readonly AudioPcmChunk[]> }\n pushPcm: { mutate: (input: { sessionId: string; nodeId?: string; data: Uint8Array; pts?: number }) => Promise<void> }\n pullEncoded: { query: (input: { sessionId: string; nodeId?: string; maxCount: number }) => Promise<readonly AudioEncodedChunk[]> }\n flushEncode: { mutate: (input: { sessionId: string; nodeId?: string }) => Promise<readonly AudioEncodedChunk[]> }\n}\n\n/**\n * Build a tRPC-backed `AudioCodecCapApi`. Returns `null` when the cap\n * isn't mounted (no audio-codec addon installed) so callers can fall\n * back to native PCMU/PCMA path or skip audio entirely.\n */\nexport function buildAudioCodecCapApi(api: unknown | null): AudioCodecCapApi | null {\n if (!api) return null\n const router = (api as Record<string, unknown>)['audioCodec'] as AudioCodecRouter | undefined\n if (!router) return null\n\n return {\n listSupportedCodecs: () => router.listSupportedCodecs.query(),\n canHandle: (input) => router.canHandle.query(input),\n createDecodeSession: (input) => router.createDecodeSession.mutate(input),\n createEncodeSession: (input) => router.createEncodeSession.mutate(input),\n closeSession: (input) => router.closeSession.mutate(input),\n pushEncodedFrame: (input) => router.pushEncodedFrame.mutate(input),\n pullPcm: (input) => router.pullPcm.query(input),\n pushPcm: (input) => router.pushPcm.mutate(input),\n pullEncoded: (input) => router.pullEncoded.query(input),\n flushEncode: (input) => router.flushEncode.mutate(input),\n }\n}\n","import type { Socket } from 'node:net'\nimport { RTSP_METHODS, INTERLEAVED_MAGIC, MAX_WRITE_BUFFER, type RtpPacket, type RtspRequest, type TrackSetup } from './rtsp-types.js'\nimport { randomUUID } from 'node:crypto'\n\n/**\n * Handles one RTSP client connection.\n * Parses requests, responds to OPTIONS/DESCRIBE/SETUP/PLAY/TEARDOWN,\n * and sends RTP data via TCP interleaved framing.\n */\nexport class RtspSession {\n private readonly sessionId: string\n private readonly tracks: TrackSetup[] = []\n private playing = false\n private buffer = ''\n private closed = false\n private readonly sdp: string\n private readonly onClose?: () => void\n /** Epoch ms when the TCP connection was accepted. Used by listClients. */\n private readonly connectedAt: number = Date.now()\n /** Epoch ms of the last RTP packet sent successfully. 0 if none yet. */\n private lastRtpAt = 0\n /** Total video RTP bytes written to the socket. */\n private bytesSent = 0\n\n /** When true, this session receives video-only (no audio RTP). */\n private readonly muted: boolean\n\n constructor(\n private readonly socket: Socket,\n sdp: string,\n onClose?: () => void,\n muted?: boolean,\n ) {\n this.sdp = sdp\n this.onClose = onClose\n this.muted = muted ?? false\n this.sessionId = randomUUID().replace(/-/g, '').slice(0, 16)\n\n socket.on('data', (chunk: Buffer) => {\n if (this.closed) return\n this.buffer += chunk.toString('ascii')\n this.processBuffer()\n })\n\n socket.on('close', () => { this.closed = true; this.onClose?.() })\n socket.on('error', () => { this.closed = true; this.onClose?.() })\n }\n\n getSessionId(): string { return this.sessionId }\n isPlaying(): boolean { return this.playing && !this.closed }\n isMuted(): boolean { return this.muted }\n\n /**\n * Diagnostic snapshot consumed by `StreamBroker.listClients` (Phase 10).\n * Gives operators the information needed to identify a specific RTSP\n * session: where it's coming from, whether it's active, when it\n * connected, and the last RTP activity timestamp.\n */\n getInfo(): {\n readonly sessionId: string\n readonly remoteAddr: string\n readonly playing: boolean\n readonly muted: boolean\n readonly connectedAt: number\n readonly lastRtpAt: number\n readonly bytesSent: number\n } {\n const remoteIp = this.socket.remoteAddress ?? 'unknown'\n const remotePort = this.socket.remotePort ?? 0\n return {\n sessionId: this.sessionId,\n remoteAddr: `${remoteIp}:${remotePort}`,\n playing: this.isPlaying(),\n muted: this.muted,\n connectedAt: this.connectedAt,\n lastRtpAt: this.lastRtpAt,\n bytesSent: this.bytesSent,\n }\n }\n\n /** Send a video RTP packet via TCP interleaved framing. */\n sendRtp(packet: RtpPacket): void {\n if (!this.playing || this.closed) return\n if (this.tracks.length === 0) return\n\n // Drop slow clients\n if (this.socket.writableLength > MAX_WRITE_BUFFER) {\n this.close()\n return\n }\n\n const channel = this.tracks[0]!.rtpChannel\n const frame = Buffer.allocUnsafe(4 + packet.data.length)\n frame[0] = INTERLEAVED_MAGIC\n frame[1] = channel\n frame.writeUInt16BE(packet.data.length, 2)\n packet.data.copy(frame, 4)\n\n this.socket.write(frame)\n this.lastRtpAt = Date.now()\n this.bytesSent += frame.length\n }\n\n /** Send an audio RTP packet via TCP interleaved framing on the audio track channel. */\n sendAudioRtp(packet: RtpPacket): void {\n if (!this.playing || this.closed || this.muted) return\n if (this.tracks.length < 2) return // no audio track set up\n\n if (this.socket.writableLength > MAX_WRITE_BUFFER) {\n this.close()\n return\n }\n\n const channel = this.tracks[1]!.rtpChannel\n const frame = Buffer.allocUnsafe(4 + packet.data.length)\n frame[0] = INTERLEAVED_MAGIC\n frame[1] = channel\n frame.writeUInt16BE(packet.data.length, 2)\n packet.data.copy(frame, 4)\n\n this.socket.write(frame)\n this.lastRtpAt = Date.now()\n this.bytesSent += frame.length\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.playing = false\n try { this.socket.destroy() } catch { /* already closed */ }\n this.onClose?.()\n }\n\n private processBuffer(): void {\n while (true) {\n // Skip any interleaved data from client (starts with $)\n if (this.buffer.length > 0 && this.buffer.charCodeAt(0) === INTERLEAVED_MAGIC) {\n if (this.buffer.length < 4) return\n const len = (this.buffer.charCodeAt(2) << 8) | this.buffer.charCodeAt(3)\n if (this.buffer.length < 4 + len) return\n this.buffer = this.buffer.slice(4 + len)\n continue\n }\n\n const endIdx = this.buffer.indexOf('\\r\\n\\r\\n')\n if (endIdx < 0) return\n\n const requestText = this.buffer.slice(0, endIdx)\n this.buffer = this.buffer.slice(endIdx + 4)\n\n const request = this.parseRequest(requestText)\n if (request) this.handleRequest(request)\n }\n }\n\n private parseRequest(text: string): RtspRequest | null {\n const lines = text.split('\\r\\n')\n const firstLine = lines[0]\n if (!firstLine) return null\n\n const parts = firstLine.split(' ')\n if (parts.length < 3) return null\n\n const headers = new Map<string, string>()\n for (let i = 1; i < lines.length; i++) {\n const colonIdx = lines[i]!.indexOf(':')\n if (colonIdx < 0) continue\n const key = lines[i]!.slice(0, colonIdx).trim().toLowerCase()\n const value = lines[i]!.slice(colonIdx + 1).trim()\n headers.set(key, value)\n }\n\n return {\n method: parts[0]!,\n uri: parts[1]!,\n cseq: parseInt(headers.get('cseq') ?? '0', 10),\n headers,\n }\n }\n\n private handleRequest(req: RtspRequest): void {\n switch (req.method) {\n case 'OPTIONS': return this.handleOptions(req)\n case 'DESCRIBE': return this.handleDescribe(req)\n case 'SETUP': return this.handleSetup(req)\n case 'PLAY': return this.handlePlay(req)\n case 'TEARDOWN': return this.handleTeardown(req)\n case 'GET_PARAMETER': return this.respond(req, 200, 'OK')\n default: return this.respond(req, 405, 'Method Not Allowed')\n }\n }\n\n private handleOptions(req: RtspRequest): void {\n this.respond(req, 200, 'OK', {\n 'Public': RTSP_METHODS.join(', '),\n })\n }\n\n private handleDescribe(req: RtspRequest): void {\n this.respond(req, 200, 'OK', {\n 'Content-Type': 'application/sdp',\n 'Content-Length': String(Buffer.byteLength(this.sdp)),\n }, this.sdp)\n }\n\n private handleSetup(req: RtspRequest): void {\n const transport = req.headers.get('transport') ?? ''\n const interleavedMatch = transport.match(/interleaved=(\\d+)-(\\d+)/)\n const rtpChannel = interleavedMatch ? parseInt(interleavedMatch[1]!, 10) : 0\n const rtcpChannel = interleavedMatch ? parseInt(interleavedMatch[2]!, 10) : 1\n\n this.tracks.push({ trackId: this.tracks.length, rtpChannel, rtcpChannel })\n\n this.respond(req, 200, 'OK', {\n 'Transport': `RTP/AVP/TCP;unicast;interleaved=${rtpChannel}-${rtcpChannel}`,\n 'Session': this.sessionId,\n })\n }\n\n private handlePlay(req: RtspRequest): void {\n this.playing = true\n this.respond(req, 200, 'OK', {\n 'Session': this.sessionId,\n 'Range': 'npt=0.000-',\n })\n }\n\n private handleTeardown(req: RtspRequest): void {\n this.respond(req, 200, 'OK')\n this.close()\n }\n\n private respond(req: RtspRequest, code: number, reason: string, headers?: Record<string, string>, body?: string): void {\n if (this.closed) return\n\n let response = `RTSP/1.0 ${code} ${reason}\\r\\nCSeq: ${req.cseq}\\r\\n`\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n response += `${k}: ${v}\\r\\n`\n }\n }\n response += '\\r\\n'\n if (body) response += body\n\n this.socket.write(response)\n }\n}\n","import * as net from 'node:net'\nimport { randomBytes } from 'node:crypto'\nimport { RtspSession } from './rtsp-session.js'\nimport { RtspRestreamer } from './rtsp-restreamer.js'\n\n/**\n * Single TCP server for all RTSP restream connections.\n * Routes clients to the correct RtspRestreamer by a random token path\n * (not the predictable brokerId) for security.\n */\nexport class RtspListenServer {\n private server: net.Server | null = null\n private port = 0\n /** token → restreamer */\n private readonly restreamers = new Map<string, RtspRestreamer>()\n /** brokerId → token (for lookup/regeneration) */\n private readonly brokerTokens = new Map<string, string>()\n /** token → brokerId (reverse map) */\n private readonly tokenBrokers = new Map<string, string>()\n /** brokerId → enabled (default true) */\n private readonly brokerEnabled = new Map<string, boolean>()\n\n /** Generate a random token for a stream path */\n static generateToken(): string {\n return randomBytes(16).toString('hex')\n }\n\n /**\n * Register a restreamer with a token.\n * If token is provided, use it (restored from persistence).\n * If not, generate a new one.\n * Returns the token used.\n */\n registerRestreamer(brokerId: string, restreamer: RtspRestreamer, existingToken?: string): string {\n // Unregister old token if broker is being re-registered\n const oldToken = this.brokerTokens.get(brokerId)\n if (oldToken) {\n this.restreamers.delete(oldToken)\n this.tokenBrokers.delete(oldToken)\n }\n\n const token = existingToken ?? RtspListenServer.generateToken()\n this.restreamers.set(token, restreamer)\n this.brokerTokens.set(brokerId, token)\n this.tokenBrokers.set(token, brokerId)\n return token\n }\n\n unregisterRestreamer(brokerId: string): void {\n const token = this.brokerTokens.get(brokerId)\n if (token) {\n this.restreamers.delete(token)\n this.tokenBrokers.delete(token)\n }\n this.brokerTokens.delete(brokerId)\n }\n\n /** Regenerate a random token for a broker. Returns the new token. */\n regenerateToken(brokerId: string): string | null {\n const oldToken = this.brokerTokens.get(brokerId)\n if (!oldToken) return null\n\n const restreamer = this.restreamers.get(oldToken)\n if (!restreamer) return null\n\n // Remove old\n this.restreamers.delete(oldToken)\n this.tokenBrokers.delete(oldToken)\n\n // Create new\n const newToken = RtspListenServer.generateToken()\n this.restreamers.set(newToken, restreamer)\n this.brokerTokens.set(brokerId, newToken)\n this.tokenBrokers.set(newToken, brokerId)\n return newToken\n }\n\n /** Get the current token for a broker. */\n getToken(brokerId: string): string | undefined {\n return this.brokerTokens.get(brokerId)\n }\n\n /** Get all broker → token mappings. */\n getAllTokens(): ReadonlyMap<string, string> {\n return this.brokerTokens\n }\n\n /** Set the enabled state for a broker's restream. Default is true. */\n setEnabled(brokerId: string, enabled: boolean): void {\n this.brokerEnabled.set(brokerId, enabled)\n }\n\n /** Check if a broker's restream is enabled. Default true. */\n isEnabled(brokerId: string): boolean {\n return this.brokerEnabled.get(brokerId) ?? true\n }\n\n /** Get all broker → enabled mappings (only stores explicit overrides). */\n getAllEnabled(): ReadonlyMap<string, boolean> {\n return this.brokerEnabled\n }\n\n /** Load persisted enabled states (called at boot). */\n loadEnabled(states: ReadonlyMap<string, boolean>): void {\n for (const [k, v] of states) {\n this.brokerEnabled.set(k, v)\n }\n }\n\n getPort(): number { return this.port }\n\n async start(port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n this.server = net.createServer((socket) => this.handleConnection(socket))\n this.server.on('error', reject)\n this.server.listen(port, () => {\n const addr = this.server!.address()\n this.port = typeof addr === 'object' && addr ? addr.port : port\n resolve()\n })\n })\n }\n\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (!this.server) { resolve(); return }\n this.server.close(() => resolve())\n this.server = null\n })\n }\n\n private handleConnection(socket: net.Socket): void {\n let requestBuffer = ''\n\n const onData = (chunk: Buffer) => {\n requestBuffer += chunk.toString('ascii')\n\n // Wait for first complete RTSP request to determine the stream\n const endIdx = requestBuffer.indexOf('\\r\\n\\r\\n')\n if (endIdx < 0) return\n\n socket.removeListener('data', onData)\n\n // Parse the URI from the first line to determine stream path\n const firstLine = requestBuffer.split('\\r\\n')[0] ?? ''\n const uriMatch = firstLine.match(/\\s(rtsp:\\/\\/[^\\s]+)\\s/)\n const uri = uriMatch ? uriMatch[1]! : ''\n\n // Extract token path: rtsp://host:port/{token} → {token}\n const pathMatch = uri.match(/rtsp:\\/\\/[^/]+(\\/.*?)(?:\\s|$)/)\n const streamPath = pathMatch ? pathMatch[1]!.replace(/^\\//, '').replace(/\\/trackID=\\d+$/, '') : ''\n\n // Check for /muted suffix — same token, audio-free variant\n const isMuted = streamPath.endsWith('/muted')\n const lookupPath = isMuted ? streamPath.slice(0, -'/muted'.length) : streamPath\n\n const restreamer = this.restreamers.get(lookupPath)\n if (!restreamer) {\n socket.write(`RTSP/1.0 404 Not Found\\r\\nCSeq: 1\\r\\n\\r\\n`)\n socket.destroy()\n return\n }\n\n // Check if restream is enabled for this broker\n const brokerId = this.tokenBrokers.get(lookupPath)\n if (brokerId && !this.isEnabled(brokerId)) {\n socket.write(`RTSP/1.0 403 Forbidden\\r\\nCSeq: 1\\r\\n\\r\\n`)\n socket.destroy()\n return\n }\n\n const sdp = isMuted\n ? (restreamer.getMutedSdp() ?? restreamer.getSdp() ?? '')\n : (restreamer.getSdp() ?? '')\n const session = new RtspSession(socket, sdp, () => {\n restreamer.removeSession(session.getSessionId())\n }, isMuted)\n restreamer.addSession(session)\n\n // Re-inject the buffered data so the session processes the first request\n socket.unshift(Buffer.from(requestBuffer, 'ascii'))\n }\n\n socket.on('data', onData)\n socket.on('error', () => {})\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\nimport type { RtspListenServer } from './rtsp-listen-server.js'\n\nexport type EnabledPersister = (states: ReadonlyMap<string, boolean>) => void\nexport type TokenPersister = (tokens: ReadonlyMap<string, string>) => void\n\n/**\n * Per-broker RTSP restream entry.\n *\n * Kept local to `addon-stream-broker` after the `rtsp-restream` capability\n * was absorbed into `stream-broker`. The shape is still validated at the\n * `stream-broker.cap.ts` boundary via `RtspRestreamEntrySchema`.\n */\nexport interface RtspRestreamEntry {\n readonly brokerId: string\n readonly url: string\n readonly mutedUrl: string\n readonly enabled: boolean\n}\n\n/**\n * Internal sub-object of `StreamBrokerManager` that implements the RTSP\n * restream bookkeeping. No longer registered as a separate capability —\n * accessed via passthrough methods on `StreamBrokerManager`.\n */\nexport class RtspRestreamProviderImpl {\n private tokenPersister: TokenPersister | null = null\n private enabledPersister: EnabledPersister | null = null\n\n constructor(\n private readonly server: RtspListenServer,\n private readonly logger: IScopedLogger,\n ) {}\n\n // ── Persistence ────────────────────────────────────────────────────────\n\n setTokenPersister(persister: TokenPersister): void {\n this.tokenPersister = persister\n }\n\n setEnabledPersister(persister: EnabledPersister): void {\n this.enabledPersister = persister\n }\n\n loadPersistedEnabled(states: ReadonlyMap<string, boolean>): void {\n this.server.loadEnabled(states)\n }\n\n // ── IRtspRestreamProvider ──────────────────────────────────────────────\n\n getRtspPort(): number {\n return this.server.getPort()\n }\n\n getAllEntries(hostname?: string): readonly RtspRestreamEntry[] {\n const port = this.server.getPort()\n if (port === 0) return []\n const host = hostname ?? '127.0.0.1'\n const result: RtspRestreamEntry[] = []\n for (const [brokerId, token] of this.server.getAllTokens()) {\n result.push({\n brokerId,\n url: `rtsp://${host}:${port}/${token}`,\n mutedUrl: `rtsp://${host}:${port}/${token}/muted`,\n enabled: this.server.isEnabled(brokerId),\n })\n }\n return result\n }\n\n /**\n * Server-side filter by device — returns only entries whose\n * `brokerId` is keyed `${deviceId}/${camStreamId}`. Mirrors\n * `getAllEntries` but lets device-scoped consumers skip the\n * cluster-wide scan + client-side filter that was the cost of\n * routing through `getAllEntries`.\n */\n getEntriesForDevice(deviceId: number, hostname?: string): readonly RtspRestreamEntry[] {\n const port = this.server.getPort()\n if (port === 0) return []\n const host = hostname ?? '127.0.0.1'\n const prefix = `${deviceId}/`\n const result: RtspRestreamEntry[] = []\n for (const [brokerId, token] of this.server.getAllTokens()) {\n if (!brokerId.startsWith(prefix)) continue\n result.push({\n brokerId,\n url: `rtsp://${host}:${port}/${token}`,\n mutedUrl: `rtsp://${host}:${port}/${token}/muted`,\n enabled: this.server.isEnabled(brokerId),\n })\n }\n return result\n }\n\n getEntry(brokerId: string, hostname?: string): RtspRestreamEntry | null {\n const token = this.server.getToken(brokerId)\n if (!token) return null\n const port = this.server.getPort()\n const host = hostname ?? '127.0.0.1'\n return {\n brokerId,\n url: `rtsp://${host}:${port}/${token}`,\n mutedUrl: `rtsp://${host}:${port}/${token}/muted`,\n enabled: this.server.isEnabled(brokerId),\n }\n }\n\n regenerateToken(brokerId: string): string | null {\n const token = this.server.regenerateToken(brokerId)\n if (token) {\n this.persistTokens()\n this.logger.info('RTSP token regenerated', {\n meta: { brokerId, tokenPrefix: token.slice(0, 8) },\n })\n }\n return token\n }\n\n setEnabled(brokerId: string, enabled: boolean): void {\n this.server.setEnabled(brokerId, enabled)\n this.persistEnabled()\n this.logger.info('RTSP restream state changed', { meta: { brokerId, enabled } })\n }\n\n isEnabled(brokerId: string): boolean {\n return this.server.isEnabled(brokerId)\n }\n\n // ── Internal (called by StreamBrokerManager) ──────────────────────────\n\n /** Persist tokens after registration/regeneration triggered by broker manager */\n persistTokens(): void {\n if (this.tokenPersister) {\n this.tokenPersister(this.server.getAllTokens())\n }\n }\n\n private persistEnabled(): void {\n if (this.enabledPersister) {\n this.enabledPersister(this.server.getAllEnabled())\n }\n }\n}\n","/**\n * Transcode-pipeline manager — backs `streamBroker.getStreamWithCodec`.\n *\n * Responsibilities:\n * 1. Source-select: when the device already publishes a stream in the\n * requested codec (and within `maxResolution`), return its URL\n * without spawning anything.\n * 2. Spawn + refcount transcode pipelines: when transcode is needed,\n * one ffmpeg child per unique (deviceId, videoCodec, audioCodec,\n * resolution) tuple, multi-consumer via refcount. Stops the child\n * N seconds after the refcount drops to 0 (`releaseGraceMs`).\n * 3. Pick the right encoder: consults\n * `platform-probe.getHardwareEncoders` and falls back to\n * `libx264` / `libx265`.\n *\n * Egress today: ffmpeg writes to a `rtsp://127.0.0.1:<port>/<token>`\n * loopback exposed by the broker's RTSP restream server.\n */\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { errMsg } from '@camstack/types'\nimport type {\n CameraStream,\n IScopedLogger,\n AddonApi,\n GetStreamWithCodecInput,\n RtpSource,\n VideoCodecTarget,\n AudioCodecTarget,\n HardwareEncoders,\n HardwareEncoderId,\n} from '@camstack/types'\n\ntype CameraStreamLookup = (deviceId: number) => readonly CameraStream[]\ntype RtspEntryLookup = (brokerId: string) => { url: string } | null\n\ninterface PipelineEntry {\n readonly key: string\n readonly source: RtpSource\n readonly child: ChildProcess | null\n refcount: number\n releaseTimer: NodeJS.Timeout | null\n}\n\nexport interface TranscodePipelineManagerOptions {\n readonly logger: IScopedLogger\n readonly api: AddonApi | null\n readonly cameraStreamLookup: CameraStreamLookup\n readonly rtspEntryLookup: RtspEntryLookup\n readonly localRtspPort: () => number\n readonly releaseGraceMs?: number\n readonly ffmpegPath?: string\n}\n\nfunction normaliseCodec(codec: string | undefined): 'H264' | 'H265' | null {\n if (!codec) return null\n const c = codec.toLowerCase()\n if (c === 'h264' || c === 'avc' || c === 'avc1') return 'H264'\n if (c === 'h265' || c === 'hevc' || c === 'hvc1' || c === 'hev1') return 'H265'\n return null\n}\n\nfunction pipelineKeyFor(\n deviceId: number,\n videoCodec: 'H264' | 'H265',\n audioCodec: AudioCodecTarget,\n width: number,\n height: number,\n): string {\n return `${deviceId}|v=${videoCodec}|a=${audioCodec}|${width}x${height}`\n}\n\nfunction pickVideoEncoder(\n target: 'H264' | 'H265',\n encoders: HardwareEncoders | null,\n): HardwareEncoderId {\n if (!encoders) return target === 'H264' ? 'libx264' : 'libx265'\n return target === 'H264' ? encoders.defaultH264 : encoders.defaultH265\n}\n\nfunction pickAudioEncoderArgs(audio: AudioCodecTarget): readonly string[] {\n switch (audio) {\n case 'AAC':\n return ['-c:a', 'aac', '-b:a', '128k', '-ar', '48000', '-ac', '2']\n case 'Opus':\n return ['-c:a', 'libopus', '-b:a', '64k', '-ar', '48000', '-ac', '2']\n case 'PCMU':\n return ['-c:a', 'pcm_mulaw', '-ar', '8000', '-ac', '1']\n case 'none':\n return ['-an']\n default:\n return ['-c:a', 'aac', '-b:a', '128k']\n }\n}\n\nexport class TranscodePipelineManager {\n private readonly entries = new Map<string, PipelineEntry>()\n private readonly logger: IScopedLogger\n private readonly api: AddonApi | null\n private readonly cameraStreamLookup: CameraStreamLookup\n private readonly rtspEntryLookup: RtspEntryLookup\n private readonly localRtspPort: () => number\n private readonly releaseGraceMs: number\n private readonly ffmpegPath: string\n\n constructor(opts: TranscodePipelineManagerOptions) {\n this.logger = opts.logger\n this.api = opts.api\n this.cameraStreamLookup = opts.cameraStreamLookup\n this.rtspEntryLookup = opts.rtspEntryLookup\n this.localRtspPort = opts.localRtspPort\n this.releaseGraceMs = opts.releaseGraceMs ?? 5_000\n this.ffmpegPath = opts.ffmpegPath ?? 'ffmpeg'\n }\n\n async acquire(input: GetStreamWithCodecInput): Promise<RtpSource> {\n const wantedVideo: VideoCodecTarget = input.videoCodec\n const wantedAudio: AudioCodecTarget = input.audioCodec ?? 'AAC'\n\n const direct = this.findDirectMatch(input.deviceId, wantedVideo, input.maxResolution)\n if (direct) {\n const key = pipelineKeyFor(\n input.deviceId,\n direct.videoCodec,\n wantedAudio,\n direct.resolution.width,\n direct.resolution.height,\n )\n return this.shareOrCreate(key, () => ({\n url: direct.url,\n videoCodec: direct.videoCodec,\n audioCodec: 'passthrough',\n resolution: direct.resolution,\n transcoded: false,\n encoder: 'copy',\n pipelineKey: key,\n }), null)\n }\n\n const sourceStream = this.pickTranscodeSource(input.deviceId, input.maxResolution)\n if (!sourceStream || !sourceStream.url) {\n throw new Error(\n `getStreamWithCodec: no source stream available for device ${input.deviceId}`,\n )\n }\n\n const targetCodec: 'H264' | 'H265' = wantedVideo === 'auto'\n ? (normaliseCodec(sourceStream.codec) ?? 'H264')\n : wantedVideo\n const targetResolution = input.maxResolution\n ?? sourceStream.resolution\n ?? { width: 1280, height: 720 }\n\n const key = pipelineKeyFor(\n input.deviceId,\n targetCodec,\n wantedAudio,\n targetResolution.width,\n targetResolution.height,\n )\n\n const existing = this.entries.get(key)\n if (existing) {\n return this.shareEntry(existing).source\n }\n\n const encoders = await this.loadHardwareEncoders()\n const encoder = pickVideoEncoder(targetCodec, encoders)\n const dialableUrl = this.allocateLoopbackUrl(input.deviceId, key)\n const child = this.spawnFfmpeg({\n sourceUrl: sourceStream.url,\n outputUrl: dialableUrl,\n encoder,\n targetCodec,\n width: targetResolution.width,\n height: targetResolution.height,\n audio: wantedAudio,\n tag: input.tag ?? `stream-with-codec:${input.deviceId}`,\n })\n\n const source: RtpSource = {\n url: dialableUrl,\n videoCodec: targetCodec,\n audioCodec: wantedAudio,\n resolution: targetResolution,\n transcoded: true,\n encoder,\n pipelineKey: key,\n }\n const entry: PipelineEntry = { key, source, child, refcount: 1, releaseTimer: null }\n this.entries.set(key, entry)\n this.logger.info('Transcode pipeline started', {\n meta: { key, encoder, sourceUrl: sourceStream.url, outputUrl: dialableUrl },\n })\n\n if (child) {\n child.once('exit', (code, signal) => {\n this.logger.info('Transcode pipeline exited', {\n meta: { key, code, signal },\n })\n const cur = this.entries.get(key)\n if (cur && cur.child === child) {\n this.entries.delete(key)\n }\n })\n }\n return source\n }\n\n release(pipelineKey: string): { released: boolean; refcount: number } {\n const entry = this.entries.get(pipelineKey)\n if (!entry) return { released: false, refcount: 0 }\n entry.refcount = Math.max(0, entry.refcount - 1)\n if (entry.refcount > 0) {\n return { released: true, refcount: entry.refcount }\n }\n if (entry.releaseTimer) {\n clearTimeout(entry.releaseTimer)\n }\n entry.releaseTimer = setTimeout(() => {\n const cur = this.entries.get(pipelineKey)\n if (!cur || cur.refcount > 0) return\n this.shutdownEntry(cur)\n }, this.releaseGraceMs)\n return { released: true, refcount: 0 }\n }\n\n shutdownAll(): void {\n for (const entry of this.entries.values()) {\n this.shutdownEntry(entry)\n }\n this.entries.clear()\n }\n\n private findDirectMatch(\n deviceId: number,\n wanted: VideoCodecTarget,\n maxRes: { width: number; height: number } | undefined,\n ): { url: string; videoCodec: 'H264' | 'H265'; resolution: { width: number; height: number } } | null {\n if (wanted === 'auto') return null\n const streams = this.cameraStreamLookup(deviceId)\n for (const s of streams) {\n if (!s.url) continue\n const codec = normaliseCodec(s.codec)\n if (codec !== wanted) continue\n if (maxRes && s.resolution) {\n if (s.resolution.width > maxRes.width || s.resolution.height > maxRes.height) continue\n }\n const resolution = s.resolution ?? { width: 1280, height: 720 }\n return { url: s.url, videoCodec: codec, resolution }\n }\n return null\n }\n\n private pickTranscodeSource(\n deviceId: number,\n maxRes: { width: number; height: number } | undefined,\n ): CameraStream | null {\n const streams = this.cameraStreamLookup(deviceId)\n if (streams.length === 0) return null\n if (!maxRes) return streams[0] ?? null\n let best: CameraStream | null = null\n let bestArea = -1\n for (const s of streams) {\n if (!s.url) continue\n const w = s.resolution?.width ?? 1280\n const h = s.resolution?.height ?? 720\n if (w > maxRes.width || h > maxRes.height) continue\n const area = w * h\n if (area > bestArea) {\n bestArea = area\n best = s\n }\n }\n if (best) return best\n let smallest: CameraStream | null = null\n let smallestArea = Number.MAX_SAFE_INTEGER\n for (const s of streams) {\n if (!s.url) continue\n const w = s.resolution?.width ?? 1280\n const h = s.resolution?.height ?? 720\n const area = w * h\n if (area < smallestArea) {\n smallestArea = area\n smallest = s\n }\n }\n return smallest\n }\n\n private allocateLoopbackUrl(deviceId: number, pipelineKey: string): string {\n const port = this.localRtspPort()\n const token = `t-${deviceId}-${randomUUID().slice(0, 8)}`\n void pipelineKey\n if (!port) {\n return `rtsp://0.0.0.0:0/${token}`\n }\n return `rtsp://127.0.0.1:${port}/${token}`\n }\n\n private async loadHardwareEncoders(): Promise<HardwareEncoders | null> {\n if (!this.api) return null\n try {\n const api = this.api as Record<string, unknown>\n const probe = api['platformProbe']\n if (!probe || typeof probe !== 'object') return null\n const fn = (probe as Record<string, unknown>)['getHardwareEncoders']\n if (!fn || typeof fn !== 'object') return null\n const query = (fn as Record<string, unknown>)['query']\n if (typeof query !== 'function') return null\n const callable = query as (input?: void) => Promise<HardwareEncoders>\n return await callable()\n } catch (err) {\n this.logger.warn('platformProbe.getHardwareEncoders failed — using software fallback', {\n meta: { error: errMsg(err) },\n })\n return null\n }\n }\n\n private spawnFfmpeg(opts: {\n sourceUrl: string\n outputUrl: string\n encoder: HardwareEncoderId\n targetCodec: 'H264' | 'H265'\n width: number\n height: number\n audio: AudioCodecTarget\n tag: string\n }): ChildProcess | null {\n const args: string[] = [\n '-hide_banner',\n '-loglevel', 'warning',\n '-rtsp_transport', 'tcp',\n '-i', opts.sourceUrl,\n '-vf', `scale=${opts.width}:${opts.height}`,\n '-c:v', opts.encoder,\n ...pickAudioEncoderArgs(opts.audio),\n '-f', 'rtsp',\n '-rtsp_transport', 'tcp',\n opts.outputUrl,\n ]\n\n try {\n const child = spawn(this.ffmpegPath, args, { stdio: ['ignore', 'ignore', 'pipe'] })\n child.stderr?.setEncoding('utf8')\n child.stderr?.on('data', (chunk: string) => {\n this.logger.debug('ffmpeg', { meta: { tag: opts.tag, line: chunk.trim() } })\n })\n child.once('error', (err) => {\n this.logger.error('ffmpeg spawn error', {\n meta: { tag: opts.tag, error: errMsg(err) },\n })\n })\n return child\n } catch (err) {\n this.logger.error('Failed to spawn ffmpeg', {\n meta: { tag: opts.tag, error: errMsg(err) },\n })\n return null\n }\n }\n\n private shareEntry(entry: PipelineEntry): PipelineEntry {\n entry.refcount += 1\n if (entry.releaseTimer) {\n clearTimeout(entry.releaseTimer)\n entry.releaseTimer = null\n }\n return entry\n }\n\n private shareOrCreate(\n key: string,\n factory: () => RtpSource,\n child: ChildProcess | null,\n ): RtpSource {\n const existing = this.entries.get(key)\n if (existing) {\n this.shareEntry(existing)\n return existing.source\n }\n const source = factory()\n const entry: PipelineEntry = { key, source, child, refcount: 1, releaseTimer: null }\n this.entries.set(key, entry)\n return source\n }\n\n private shutdownEntry(entry: PipelineEntry): void {\n if (entry.releaseTimer) {\n clearTimeout(entry.releaseTimer)\n entry.releaseTimer = null\n }\n if (entry.child && !entry.child.killed) {\n try {\n entry.child.kill('SIGTERM')\n } catch (err) {\n this.logger.warn('ffmpeg kill error', {\n meta: { key: entry.key, error: errMsg(err) },\n })\n }\n }\n this.entries.delete(entry.key)\n }\n}\n","import type {\n StreamSource, IRestreamer, IDecoderProvider, IScopedLogger,\n StreamFormat, ConfigUISchemaWithValues, ConfigFieldWithValue,\n ConfigSectionWithValues, ConfigSubTabDefinitionWithValue,\n CameraStream, CamProfile, CamStreamKind, ProfileSlot, ProfileSlotStatus,\n IEventBus, SystemEvent, IStreamBroker, BrokerStats, AddonApi,\n PlaceholderReason,\n GetStreamWithCodecInput, RtpSource,\n} from '@camstack/types'\nimport { StreamBroker } from './stream-broker'\nimport type { DecoderCapApi } from './decoder-session-proxy.js'\nimport { buildAudioCodecCapApi } from './audio-codec-proxy.js'\nimport { RtspListenServer } from '../rtsp/rtsp-listen-server.js'\nimport { RtspRestreamProviderImpl, type RtspRestreamEntry } from '../rtsp/rtsp-restream-provider.js'\nimport { TranscodePipelineManager } from './transcode-pipeline.js'\nimport { CAM_PROFILE_ORDER, DeviceFeature, EventCategory, errMsg, RingBuffer } from '@camstack/types'\n\n/**\n * Runtime capability accessor injected by the addon on initialization.\n * Mirrors the pattern used by other addons — the server extends\n * AddonContext at runtime with this shape so the broker can consume\n * other capabilities (decoder, restreamer, webrtc) without a backend\n * dependency.\n */\nexport interface CapabilitiesAccess {\n getCollection?<T = unknown>(name: string): readonly T[] | undefined\n getCollectionEntries?<T = unknown>(name: string): readonly (readonly [string, T])[] | undefined\n get?<T = unknown>(name: string): T | undefined\n}\n\ninterface StreamPreBuffer {\n readonly enabled: boolean\n readonly seconds: number\n}\n\ninterface DeviceOverride {\n /** Legacy single-value override — kept for migration compat. */\n preBufferSecOverride?: number\n /**\n * Per-stream pre-buffer settings keyed by `camStreamId` (e.g.\n * `native:main`, `rtsp:sub`). Each stream sourceconfigures its own\n * buffer independently; profile assignment is decoupled — a profile\n * always inherits the pre-buffer of the cam stream currently mapped\n * to it via `assignments[deviceId].map[profile]`.\n */\n preBuffer?: Record<string, StreamPreBuffer>\n /**\n * Per-device debug flag for streaming subsystem (broker + WebRTC\n * session + repacketizer). When true, verbose info-level logs about\n * SDP param sets, RTP forwarding counts, repacketizer codecInfo\n * seeding, and source-RTP subscriber lifecycle become enabled.\n * Off by default to keep production logs quiet.\n */\n streamingDebug?: boolean\n}\n\n/**\n * Per-device assignment state persisted via `setProfileMapPersister`.\n * `auto` is true until the operator makes a manual assignment — used\n * so providers can keep publishing streams and have the broker\n * re-run `computeInitialAssignment` without overwriting a user's\n * chosen mapping.\n */\ninterface DeviceAssignment {\n readonly map: Partial<Record<CamProfile, string>>\n readonly auto: boolean\n}\n\nexport type ProfileMapPersister = (\n assignments: ReadonlyMap<number, DeviceAssignment>,\n) => void\n\ntype CameraStreamInternal = CameraStream & {\n /** Insertion order within (deviceId) — drives UI dropdown ordering. */\n readonly order: number\n readonly deviceFeatures: readonly string[]\n /**\n * Whether this stream is eligible for the broker's automatic profile\n * assignment. `false` streams stay published (visible + manually\n * assignable), but `computeInitialAssignment` skips them. Defaults\n * `true` for back-compat with publishers that don't set the flag.\n */\n readonly autoEligible: boolean\n}\n\nconst PUSH_KINDS: ReadonlySet<CamStreamKind> = new Set(['push-annexb'])\n\n/** Walk a profile map and return the slot that points at `camStreamId`, or null. */\nfunction findProfileForStream(\n map: Partial<Record<CamProfile, string>>,\n camStreamId: string,\n): CamProfile | null {\n for (const profile of CAM_PROFILE_ORDER) {\n if (map[profile] === camStreamId) return profile\n }\n return null\n}\n\n\n/**\n * Watchdog parameters for the stream-health emitter. Brokers that have\n * received zero video packets for STREAM_STALE_TIMEOUT_MS are reported\n * as `stream.offline`; the next packet flips them back to `stream.online`.\n */\nconst STREAM_STALE_TIMEOUT_MS = 120_000\nconst STREAM_HEALTH_POLL_MS = 15_000\n\n/** Compose a broker id from a `(deviceId, camStreamId)` tuple. */\nfunction brokerIdFor(deviceId: number, camStreamId: string): string {\n return `${deviceId}/${camStreamId}`\n}\n\nexport class StreamBrokerManager {\n /**\n * brokers keyed by brokerId = `${deviceId}/${camStreamId}`.\n *\n * Single source of truth: one StreamBroker per cam stream source.\n * Profiles are an alias layer over this map — the profile slot\n * `(deviceId, profile)` resolves to the broker via\n * `assignments[deviceId].map[profile] -> camStreamId`.\n *\n * Lifecycle is tied 1:1 to publish/retract: a broker exists for as\n * long as the cam stream is published. Pre-buffer defaults OFF —\n * idle cam streams cost roughly nothing (the upstream readers stay\n * dormant until something subscribes), but we keep the broker\n * registered so callers (WebRTC, RTSP restream) can latch on at any\n * time without an activation handshake.\n */\n private readonly brokers = new Map<string, StreamBroker>()\n /** Per-device published cam-stream registry. */\n private readonly cameraStreams = new Map<number, Map<string, CameraStreamInternal>>()\n /** Monotonic insertion counter per device — feeds `CameraStreamInternal.order`. */\n private readonly streamOrderSeq = new Map<number, number>()\n /** Persisted operator intent: which cam stream serves which profile. */\n private readonly assignments = new Map<number, DeviceAssignment>()\n /** Per-device overrides (pre-buffer + legacy). */\n private readonly deviceOverrides = new Map<number, DeviceOverride>()\n private deviceOverridePersister: ((overrides: ReadonlyMap<number, DeviceOverride>) => void) | null = null\n private profileMapPersister: ProfileMapPersister | null = null\n private eventBus: IEventBus | null = null\n /**\n * Per-broker stream health flag set by the watchdog. `undefined`\n * before the first transition — emit `stream.online` on first packet\n * regardless of prior state.\n */\n private readonly streamHealthByBroker = new Map<string, boolean>()\n private streamHealthTimer: ReturnType<typeof setInterval> | undefined\n // Legacy static restreamers list — production reads via capabilities.\n private restreamers: readonly IRestreamer[] = []\n private readonly staticDecoders: readonly IDecoderProvider[]\n private readonly logger: IScopedLogger\n private readonly rtspServer = new RtspListenServer()\n private readonly rtspProvider: RtspRestreamProviderImpl\n private persistedTokens = new Map<string, string>()\n private capabilities: CapabilitiesAccess | null = null\n /**\n * tRPC api proxy injected from the host addon. Used to resolve the\n * `decoder` capability (and any future cross-cap consumers) via the\n * canonical API surface — `localProviderLink` short-circuits when\n * the provider lives in the same broker / group, otherwise the call\n * goes over `brokerTransportLink`. Replaces the previous direct\n * `CapabilityRegistry` lookup, which only worked on the hub.\n */\n private api: AddonApi | null = null\n private defaultPreBufferSec = 10\n private webrtcServer: import('../webrtc/broker-webrtc-server.js').BrokerWebrtcServer | null = null\n /**\n * Transcode-pipeline manager — backs `getStreamWithCodec`. Lazily\n * created on first acquire, lives until `destroyAll`.\n */\n private transcodeManager: TranscodePipelineManager | null = null\n\n constructor(\n staticDecoders: readonly IDecoderProvider[] | undefined,\n logger: IScopedLogger,\n ) {\n this.staticDecoders = staticDecoders ?? []\n this.logger = logger\n this.rtspProvider = new RtspRestreamProviderImpl(this.rtspServer, logger.child('rtsp-restream'))\n }\n\n /**\n * Lazily create the transcode manager wired with closures rather than\n * direct refs so it only sees the surface it needs.\n */\n private getTranscodeManager(): TranscodePipelineManager {\n if (!this.transcodeManager) {\n this.transcodeManager = new TranscodePipelineManager({\n logger: this.logger.child('transcode'),\n api: this.api,\n cameraStreamLookup: (deviceId) => this.getCameraStreamsForDevice(deviceId),\n rtspEntryLookup: (brokerId) => {\n const entry = this.rtspProvider.getEntry(brokerId)\n return entry ? { url: entry.url } : null\n },\n localRtspPort: () => this.rtspProvider.getRtspPort(),\n })\n }\n return this.transcodeManager\n }\n\n // ── Cap methods: getStreamWithCodec / releaseStreamWithCodec ─────────\n\n async getStreamWithCodec(input: GetStreamWithCodecInput): Promise<RtpSource> {\n return this.getTranscodeManager().acquire(input)\n }\n\n async releaseStreamWithCodec(input: { pipelineKey: string }): Promise<{ released: boolean; refcount: number }> {\n return this.getTranscodeManager().release(input.pipelineKey)\n }\n\n // ── Decoder resolution ───────────────────────────────────────────────\n //\n // Routes every decoder call through `ctx.api.decoder.*` (tRPC). The\n // proxy is wired by the host addon via `setApiAccess(ctx.api)`. When\n // the decoder addon lives in the same broker (e.g. inside the\n // `pipeline` group on the hub) the call is short-circuited by\n // `localProviderLink` with no serialization. When the decoder lives\n // on a different node, the call falls through to\n // `brokerTransportLink` which dispatches the matching Moleculer\n // action. Either way, the broker stays unaware of the addon's\n // physical placement.\n\n private buildDecoderCapApi(_deviceId: number | null): DecoderCapApi | null {\n const localApi = this.buildLocalDecoderCapApi()\n const apiDecoder = (): {\n supportsCodec: { query: (input: { codec: string }) => Promise<boolean> }\n getInfo: { query: (input?: void) => Promise<{ id: string; name: string; isPullMode?: boolean; priority?: number }> }\n createSession: { query: (input: import('@camstack/types').DecoderSessionConfig) => Promise<{ sessionId: string; nodeId: string }> }\n pushPacket: { query: (input: { sessionId: string; packet: import('@camstack/types').EncodedPacket }) => Promise<void> }\n pullFrames: { query: (input: { sessionId: string; maxCount: number }) => Promise<readonly import('@camstack/types').DecodedFrame[]> }\n destroySession: { query: (input: { sessionId: string }) => Promise<void> }\n openStream: { query: (input: { sessionId: string; url: string }) => Promise<void> }\n updateConfig: { query: (input: { sessionId: string; config: Partial<import('@camstack/types').DecoderSessionConfig> }) => Promise<void> }\n getStats: { query: (input: { sessionId: string }) => Promise<import('@camstack/types').DecoderStats> }\n } | null => {\n if (!this.api) return null\n const decoder = (this.api as Record<string, unknown>)['decoder']\n return decoder ? (decoder as ReturnType<typeof apiDecoder> extends infer R ? R extends null ? never : R : never) : null\n }\n\n return {\n supportsCodec: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.supportsCodec.query(input)\n if (!localApi) return false\n return localApi.supportsCodec(input)\n },\n getInfo: async () => {\n const proxy = apiDecoder()\n if (proxy) return proxy.getInfo.query()\n if (!localApi) return { id: 'none', name: 'No decoder' }\n return localApi.getInfo()\n },\n createSession: async (config) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.createSession.query(config)\n if (!localApi) throw new Error('No decoder provider available')\n return localApi.createSession(config)\n },\n pushPacket: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.pushPacket.query(input)\n if (!localApi) return\n return localApi.pushPacket(input)\n },\n pullFrames: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.pullFrames.query(input)\n if (!localApi) return []\n return localApi.pullFrames(input)\n },\n destroySession: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.destroySession.query(input)\n if (!localApi) return\n return localApi.destroySession(input)\n },\n openStream: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.openStream.query(input)\n if (!localApi) return\n return localApi.openStream(input)\n },\n updateConfig: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.updateConfig.query(input)\n if (!localApi) return\n return localApi.updateConfig(input)\n },\n getStats: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.getStats.query(input)\n if (!localApi) return { inputFps: 0, outputFps: 0, avgDecodeTimeMs: 0, droppedFrames: 0 }\n return localApi.getStats(input)\n },\n }\n }\n\n private buildLocalDecoderCapApi(): DecoderCapApi | null {\n if (this.staticDecoders.length === 0) return null\n // Bounded frame buffer per session — when `pullFrames` falls behind the\n // decoder's frame rate, old frames get evicted rather than accumulating\n // unboundedly. Mirrors the capacity used by the nodeav addon.\n const FRAME_BUFFER_CAPACITY = 32\n type DecodedFrame = import('@camstack/types').DecodedFrame\n const frameBuffers = new Map<string, RingBuffer<DecodedFrame>>()\n const sessions = new Map<string, import('@camstack/types').IDecoderSession>()\n const unsubscribers = new Map<string, () => void>()\n let nextId = 0\n\n const resolveDecoder = async (codec: string): Promise<IDecoderProvider | null> => {\n for (const p of this.staticDecoders) {\n if (await p.supportsCodec({ codec })) return p\n }\n return null\n }\n\n return {\n supportsCodec: async (input) => (await resolveDecoder(input.codec)) !== null,\n getInfo: async () => {\n const provider = this.staticDecoders[0]\n if (!provider) return { id: 'none', name: 'No decoder' }\n return { id: provider.id, name: provider.name, isPullMode: provider.isPullMode, priority: provider.priority }\n },\n createSession: async (config) => {\n const provider = await resolveDecoder(config.codec)\n if (!provider) throw new Error(`No decoder provider for codec \"${config.codec}\"`)\n const session = await provider.createSession(config)\n const sessionId = `dec-${++nextId}`\n sessions.set(sessionId, session)\n const buffer = new RingBuffer<DecodedFrame>(FRAME_BUFFER_CAPACITY)\n frameBuffers.set(sessionId, buffer)\n const unsub = session.onFrame((frame) => { buffer.push(frame) })\n unsubscribers.set(sessionId, unsub)\n return { sessionId, nodeId: 'local' }\n },\n pushPacket: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session) session.pushPacket(input.packet)\n },\n pullFrames: async (input) => {\n const buffer = frameBuffers.get(input.sessionId)\n if (!buffer) return []\n return buffer.drain(input.maxCount)\n },\n destroySession: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session) {\n const unsub = unsubscribers.get(input.sessionId)\n if (unsub) unsub()\n sessions.delete(input.sessionId)\n frameBuffers.delete(input.sessionId)\n unsubscribers.delete(input.sessionId)\n await session.destroy()\n }\n },\n openStream: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session?.openStream) await session.openStream(input.url)\n },\n updateConfig: async (input) => {\n const session = sessions.get(input.sessionId)\n if (session) session.updateConfig(input.config)\n },\n getStats: async (input) => {\n const session = sessions.get(input.sessionId)\n if (!session) return { inputFps: 0, outputFps: 0, avgDecodeTimeMs: 0, droppedFrames: 0 }\n return session.getStats()\n },\n }\n }\n\n // ── Setters (wiring from the addon) ──────────────────────────────────\n\n setCapabilitiesAccess(access: CapabilitiesAccess): void {\n this.capabilities = access\n }\n\n /**\n * Wire the addon's `ctx.api` proxy. Required for the broker to\n * resolve the `decoder` capability via tRPC — see\n * `buildDecoderCapApi` for the full rationale.\n */\n setApiAccess(api: AddonApi): void {\n this.api = api\n }\n\n setDefaultPreBufferSec(sec: number): void {\n this.defaultPreBufferSec = sec\n }\n\n setEventBus(bus: IEventBus): void {\n this.eventBus = bus\n this.startStreamHealthWatchdog()\n this.subscribePlaceholderStateSources(bus)\n }\n\n /**\n * Subscribe to the cap-scoped bus events that drive the per-broker\n * placeholder image. Lives in the manager (not the broker) because\n * a single device can host multiple brokers (`high`/`mid`/`low`)\n * and one listener fans out to all of them. Lives at the broker\n * layer (not the provider) because providers must stay oblivious\n * to the placeholder pipeline — they only mutate their runtime-\n * state slices.\n *\n * Each cap defines its own state-change event (`BatteryStatus`\n * surfaces `battery.onStatusChanged`), wired automatically by the\n * `subscribeCap('battery')` bridge in the provider's\n * `registerBatteryIfSupported`. We subscribe to the cap event\n * directly rather than the catch-all `device.state-changed`\n * firehose so we don't filter the entire device-state stream\n * client-side and we get a typed payload.\n */\n private subscribePlaceholderStateSources(bus: IEventBus): void {\n bus.subscribe({ category: EventCategory.BatteryOnStatusChanged }, (event: SystemEvent) => {\n const data = event.data as {\n deviceId?: unknown\n status?: { sleeping?: unknown } | null\n }\n const deviceId = typeof data.deviceId === 'number' ? data.deviceId : null\n if (deviceId === null) return\n // Slice may be `undefined` (cap slice cleared) or `null` (slice\n // dropped); both map to \"no longer sleeping\" — fall back to the\n // generic reconnecting placeholder.\n const sleeping = data.status?.sleeping === true\n this.applyPlaceholderReasonToDevice(deviceId, sleeping ? 'sleeping' : 'reconnecting')\n })\n\n const handlerForReason = (reason: PlaceholderReason) =>\n (event: SystemEvent) => {\n const data = event.data as { deviceId?: unknown }\n const deviceId = typeof data.deviceId === 'number' ? data.deviceId : null\n if (deviceId === null) return\n this.applyPlaceholderReasonToDevice(deviceId, reason)\n }\n bus.subscribe({ category: EventCategory.DeviceOffline }, handlerForReason('offline'))\n bus.subscribe({ category: EventCategory.DeviceDisabled }, handlerForReason('disabled'))\n }\n\n setWebrtcServer(server: import('../webrtc/broker-webrtc-server.js').BrokerWebrtcServer): void {\n this.webrtcServer = server\n // Inject the assignments-aware tier resolver so the WebRTC adaptive\n // bitrate logic can map cam-stream-keyed brokers back to their\n // current profile tier ('high'/'mid'/'low'). Without this, all\n // candidates appear \"untiered\" and `prefersTier` hints fall through\n // to the order-based pick.\n server.setProfileTierResolver((brokerId) => {\n const parsed = this.parseBrokerId(brokerId)\n if (!parsed) return null\n return findProfileForStream(this.assignments.get(parsed.deviceId)?.map ?? {}, parsed.camStreamId)\n })\n }\n\n setProfileMapPersister(persister: ProfileMapPersister): void {\n this.profileMapPersister = persister\n }\n\n loadPersistedProfileMap(entries: ReadonlyMap<number, DeviceAssignment>): void {\n this.assignments.clear()\n for (const [k, v] of entries) this.assignments.set(k, v)\n }\n\n // ── Legacy restreamer accessors (kept for tests) ────────────────────\n\n private getLiveRestreamers(): readonly IRestreamer[] {\n const live = this.capabilities?.getCollection?.<IRestreamer>('restreamer')\n if (live && live.length > 0) return live\n return this.restreamers\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n for (const broker of this.brokers.values()) broker.setRestreamers(restreamers)\n this.logger.info('Updated restreamers', { meta: { count: restreamers.length } })\n }\n\n getRestreamers(): readonly IRestreamer[] {\n return this.restreamers\n }\n\n getRtspRestreamProvider(): RtspRestreamProviderImpl {\n return this.rtspProvider\n }\n\n loadPersistedTokens(tokens: ReadonlyMap<string, string>): void {\n this.persistedTokens = new Map(tokens)\n }\n\n // ── Cap methods: cam stream lifecycle ────────────────────────────────\n\n async publishCameraStream(input: {\n deviceId: number\n camStreamId: string\n kind: CamStreamKind\n url?: string\n codec?: string\n resolution?: { width: number; height: number }\n fps?: number\n label?: string\n /**\n * Device-level features the publisher advertised — single source of\n * truth for downstream policy (battery → relaxed stall watchdog,\n * prebuffer off, snapshot rate-limit longer). Re-publishes overwrite\n * the prior list so device feature changes (e.g. live battery\n * detection) propagate.\n */\n deviceFeatures?: readonly string[]\n /**\n * Whether this stream is eligible for automatic profile assignment.\n * Re-publishes overwrite — flipping a stream from auto-eligible to\n * not (or vice versa) takes effect on the next assignment recompute.\n * Default `true`.\n */\n autoEligible?: boolean\n /**\n * Transport-specific opaque metadata. Stored alongside the stream\n * record and forwarded to the source reader via\n * `StreamSource.metadata`. `pull-rfc4571` publishers put the SDP\n * here.\n */\n metadata?: Readonly<Record<string, unknown>>\n }): Promise<{ success: true }> {\n const { deviceId, camStreamId, kind, url, codec, resolution, fps, label, metadata } = input\n\n let streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) {\n streamMap = new Map()\n this.cameraStreams.set(deviceId, streamMap)\n }\n const existing = streamMap.get(camStreamId)\n const order = existing?.order ?? this.nextOrder(deviceId)\n // Detect a refreshed loopback transport: the publisher just re-ran\n // its publish pipeline (typically in response to an\n // `OnRequestStreamSourceRefresh` round-trip after a wake-up or an\n // idle-tear-down) and bound a new URL. Capture it now so we can\n // kick the broker's reconnect timer once the registry has\n // absorbed the new entry — without this nudge the broker stays\n // on its exponential-backoff schedule and may dial the stale URL\n // for several more seconds before observing the change.\n const urlRefreshed = existing !== undefined\n && url !== undefined\n && existing.url !== url\n // Re-publish updates the feature snapshot — battery detection can\n // happen post-creation (live probe), so keeping the latest signal\n // matters more than the first.\n const deviceFeatures: string[] = input.deviceFeatures\n ? [...input.deviceFeatures]\n : (existing?.deviceFeatures ? [...existing.deviceFeatures] : [])\n const autoEligible = input.autoEligible ?? existing?.autoEligible ?? true\n const cam: CameraStreamInternal = {\n camStreamId,\n deviceId,\n kind,\n ...(url !== undefined ? { url } : {}),\n ...(codec !== undefined ? { codec } : {}),\n ...(resolution !== undefined ? { resolution } : {}),\n ...(fps !== undefined ? { fps } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(metadata !== undefined ? { metadata } : (existing?.metadata !== undefined ? { metadata: existing.metadata } : {})),\n order,\n deviceFeatures,\n autoEligible,\n }\n streamMap.set(camStreamId, cam)\n\n // Re-run auto-assignment when the device has never had a manual\n // assignment AND (a) profileMap is empty, or (b) auto flag is true.\n // Re-publish of a known camStreamId with the SAME id keeps map stable.\n const current = this.assignments.get(deviceId)\n const streams = [...streamMap.values()].sort((a, b) => a.order - b.order)\n const isAuto = current?.auto ?? true\n let newMap: Partial<Record<CamProfile, string>>\n if (!current || (isAuto && this.wouldChangeAutoMap(current.map, streams))) {\n newMap = this.computeInitialAssignment(streams)\n } else {\n newMap = current.map\n }\n\n // Snapshot whether the just-published stream is already in a profile\n // slot before applyAssignmentUpdate runs. If it is — and the slot\n // still points at it after the update (i.e. no transition will fire\n // demand naturally) — we need to re-emit demand explicitly. A\n // re-publish from a freshly-restarted provider wipes its in-memory\n // demand state, so the broker must re-tell it which streams are\n // wanted. Demand handlers are idempotent on the provider side\n // (`if (active.has(id)) return`), so re-emitting is always safe.\n const prePublishProfile = PUSH_KINDS.has(kind)\n ? findProfileForStream(current?.map ?? {}, camStreamId)\n : null\n const wasAlreadyAssigned = prePublishProfile !== null && existing !== undefined\n\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: isAuto, reason: 'publish' })\n\n if (wasAlreadyAssigned) {\n const finalProfile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId)\n if (finalProfile !== null) {\n this.emitCamStreamDemand(deviceId, camStreamId, finalProfile)\n }\n }\n\n // Every published cam stream gets a live broker. Pre-buffer is\n // OFF by default so idle streams don't pin RAM or wake battery\n // cameras; the broker just sits ready for the first subscriber\n // (WebRTC viewer, RTSP restream client, detection pipeline) to\n // attach.\n await this.ensureBroker(deviceId, camStreamId)\n\n // The broker that survived this re-publish may have been waiting\n // on its exponential-backoff timer — kick it so the next dial\n // uses the fresh URL right away instead of after several more\n // wasted retries against the stale loopback port.\n if (urlRefreshed) {\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n broker?.kickReconnect()\n }\n\n this.emitCamStreamsChanged(deviceId)\n return { success: true as const }\n }\n\n async retractCameraStream(input: {\n deviceId: number\n camStreamId: string\n }): Promise<{ success: true }> {\n const { deviceId, camStreamId } = input\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap?.has(camStreamId)) return { success: true as const }\n\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n let newMap = { ...current.map }\n for (const profile of CAM_PROFILE_ORDER) {\n if (newMap[profile] === camStreamId) delete newMap[profile]\n }\n if (current.auto) {\n const remaining = [...streamMap.values()]\n .filter((s) => s.camStreamId !== camStreamId)\n .sort((a, b) => a.order - b.order)\n newMap = this.computeInitialAssignment(remaining)\n }\n\n // Apply the assignment BEFORE removing from streamMap so\n // `countPushConsumers` can still identify the retracted stream as\n // push-kind and emit the N→0 idle event.\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: current.auto, reason: 'retract' })\n\n // Stream lifecycle is tied 1:1 to publish/retract — destroy the\n // broker now that its source is gone.\n await this.releaseBroker(deviceId, camStreamId)\n\n streamMap.delete(camStreamId)\n if (streamMap.size === 0) this.cameraStreams.delete(deviceId)\n\n this.emitCamStreamsChanged(deviceId)\n return { success: true as const }\n }\n\n // ── Cap methods: profile assignment ─────────────────────────────────\n\n async assignProfile(input: {\n deviceId: number\n profile: CamProfile\n camStreamId: string\n }): Promise<{ success: true }> {\n const { deviceId, profile, camStreamId } = input\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap || !streamMap.has(camStreamId)) {\n throw new Error(\n `assignProfile: camStreamId \"${camStreamId}\" is not published for device ${deviceId}`,\n )\n }\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const newMap = { ...current.map, [profile]: camStreamId }\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: false, reason: 'assign' })\n return { success: true as const }\n }\n\n async unassignProfile(input: {\n deviceId: number\n profile: CamProfile\n }): Promise<{ success: true }> {\n const { deviceId, profile } = input\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n if (current.map[profile] === undefined) {\n // Still mark manual so subsequent publishes don't auto-reassign.\n if (current.auto) {\n await this.applyAssignmentUpdate(deviceId, current.map, { auto: false, reason: 'unassign' })\n }\n return { success: true as const }\n }\n const newMap = { ...current.map }\n delete newMap[profile]\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: false, reason: 'unassign' })\n return { success: true as const }\n }\n\n async restartProfile(input: {\n deviceId: number\n profile: CamProfile\n }): Promise<{ success: boolean }> {\n const { deviceId, profile } = input\n const camStreamId = this.assignments.get(deviceId)?.map[profile]\n if (!camStreamId) return { success: false }\n await this.restartBroker(deviceId, camStreamId)\n this.emitProfileSlotsChanged(deviceId)\n return { success: true }\n }\n\n /**\n * Recreate the broker for a cam stream — destroy the existing\n * instance then re-create it from the same source. Used by\n * `restartProfile`; lifecycle stays tied to publish/retract so the\n * broker comes back unconditionally as long as the source is still\n * published.\n */\n private async restartBroker(deviceId: number, camStreamId: string): Promise<void> {\n if (!this.cameraStreams.get(deviceId)?.has(camStreamId)) return\n await this.releaseBroker(deviceId, camStreamId)\n await this.ensureBroker(deviceId, camStreamId)\n }\n\n // ── Cap methods: system-wide views ──────────────────────────────────\n\n async listAllCameraStreams(): Promise<readonly CameraStream[]> {\n const out: CameraStream[] = []\n for (const streamMap of this.cameraStreams.values()) {\n const sorted = [...streamMap.values()].sort((a, b) => a.order - b.order)\n for (const s of sorted) out.push(this.toCameraStream(s))\n }\n return out\n }\n\n async listAllProfileSlots(): Promise<readonly ProfileSlot[]> {\n const out: ProfileSlot[] = []\n for (const deviceId of this.assignments.keys()) {\n for (const slot of this.snapshotProfileSlots(deviceId)) out.push(slot)\n }\n return out\n }\n\n // ── Device-scoped facades (for camera-streams cap) ───────────────────\n\n getCameraStreamsForDevice(deviceId: number): readonly CameraStream[] {\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) return []\n return [...streamMap.values()]\n .sort((a, b) => a.order - b.order)\n .map((s) => this.toCameraStream(s))\n }\n\n getProfileSlotsForDevice(deviceId: number): readonly ProfileSlot[] {\n // Return empty for devices the broker has never touched — avoids\n // phantom `unassigned` slots for every numeric id in the universe.\n if (!this.cameraStreams.has(deviceId) && !this.assignments.has(deviceId)) return []\n return this.snapshotProfileSlots(deviceId)\n }\n\n // ── Cap methods: broker runtime (stats + client inventory) ──────────\n\n async getBroker(input: { brokerId: string }): Promise<IStreamBroker | null> {\n return this.brokers.get(input.brokerId) ?? null\n }\n\n async getBrokerStats(input: { brokerId: string }): Promise<BrokerStats> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) {\n return {\n status: 'stopped',\n inputFps: 0,\n decodeFps: 0,\n encodedSubscribers: 0,\n decodedSubscribers: 0,\n uptimeMs: 0,\n bitrateKbps: 0,\n idrIntervalMs: 0,\n totalBytes: 0,\n packetCount: 0,\n rtspClients: 0,\n pipeClients: 0,\n preBufferSec: 0,\n preBufferMs: 0,\n preBufferPackets: 0,\n decoderNodeId: null,\n audio: null,\n }\n }\n return broker.getStats()\n }\n\n async listClients(input: { brokerId: string }): Promise<import('@camstack/types').BrokerClients> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) {\n return { rtsp: [], decoded: [], audio: [], pipeClients: 0, encodedSubscribers: 0 }\n }\n return broker.listClients()\n }\n\n async killClient(input: {\n brokerId: string\n channel: 'rtsp' | 'decoded' | 'audio'\n handle: string\n }): Promise<{ killed: boolean }> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) return { killed: false }\n return { killed: broker.killClient(input.channel, input.handle) }\n }\n\n async rotateDecodersOnNode(agentNodeId: string, reason: string): Promise<number> {\n let rotated = 0\n for (const broker of this.brokers.values()) {\n const nodeId = broker.getDecoderNodeId()\n if (!nodeId) continue\n const brokerAgent = nodeId.includes('/') ? nodeId.split('/')[0]! : nodeId\n if (brokerAgent !== agentNodeId) continue\n try {\n await broker.rotateDecoderSession(reason)\n rotated++\n } catch (err) {\n this.logger.warn('decoder rotation failed', {\n meta: { brokerId: broker.deviceId, agentNodeId, reason, error: errMsg(err) },\n })\n }\n }\n return rotated\n }\n\n /** Raw broker instances — internal use only. */\n getBrokerInstances(): readonly StreamBroker[] {\n return [...this.brokers.values()]\n }\n\n async destroyAll(): Promise<void> {\n if (this.streamHealthTimer) {\n clearInterval(this.streamHealthTimer)\n this.streamHealthTimer = undefined\n }\n if (this.transcodeManager) {\n this.transcodeManager.shutdownAll()\n this.transcodeManager = null\n }\n const stopPromises = [...this.brokers.values()].map((broker) => broker.stop())\n await Promise.all(stopPromises)\n this.brokers.clear()\n this.streamHealthByBroker.clear()\n await this.rtspServer.stop()\n }\n\n // ── Stream health watchdog ──────────────────────────────────────────\n //\n // Polls every active broker every STREAM_HEALTH_POLL_MS. A broker is\n // considered stale when its last video packet is older than\n // STREAM_STALE_TIMEOUT_MS. State transitions emit `stream.online` /\n // `stream.offline` on the event bus; payload includes the camStreamId\n // currently bound to the profile slot (the operator-visible \"key\").\n //\n // Brokers whose slot is unassigned (no camStreamId) skip emission —\n // there is no published stream to report on.\n\n private startStreamHealthWatchdog(): void {\n if (this.streamHealthTimer) return\n this.streamHealthTimer = setInterval(() => this.evaluateStreamHealth(), STREAM_HEALTH_POLL_MS)\n }\n\n private evaluateStreamHealth(): void {\n if (!this.eventBus) return\n const now = Date.now()\n for (const [brokerId, broker] of this.brokers) {\n const parsed = this.parseBrokerId(brokerId)\n if (!parsed) continue\n const { deviceId, camStreamId } = parsed\n const lastPacketAt = broker.getLastPacketAt()\n const wasHealthy = this.streamHealthByBroker.get(brokerId)\n const isHealthy = lastPacketAt > 0 && now - lastPacketAt <= STREAM_STALE_TIMEOUT_MS\n if (wasHealthy === isHealthy) continue\n this.streamHealthByBroker.set(brokerId, isHealthy)\n this.emitStreamHealth(isHealthy, {\n deviceId,\n camStreamId,\n profile: findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId),\n brokerId,\n sourceType: broker.getActiveSourceType(),\n lastPacketAt,\n reason: isHealthy ? (wasHealthy === undefined ? 'first-packet' : 'recovered') : 'stale-timeout',\n })\n }\n }\n\n private emitStreamHealth(\n online: boolean,\n payload: {\n deviceId: number\n camStreamId: string\n profile: CamProfile | null\n brokerId: string\n sourceType: string\n lastPacketAt: number\n reason: string\n },\n ): void {\n const bus = this.eventBus\n if (!bus) return\n const category = online ? 'stream.online' : 'stream.offline'\n bus.emit({\n id: `${category}-${payload.brokerId}-${Date.now()}`,\n timestamp: new Date(),\n category,\n source: { type: 'stream-broker', id: payload.brokerId },\n data: payload,\n })\n // Slot status flipped — re-snapshot and propagate to the device's\n // `camera-streams` runtime-state slice so consumers (RTSP autonomous\n // online tracking, UI dashboards) get push semantics without having\n // to subscribe to the per-stream events themselves. SLICE-ONLY:\n // we deliberately do NOT fire `onProfileSlotsChanged` here — the\n // orchestrator listens to that event to restart detection, but a\n // health flip (lazy-dial suspend / on-demand resume) is not a slot\n // mutation and must not tear down the runner attachment. Without\n // this guard each `suspend()`/`start()` cycle would race a\n // `stopDetection`+`startDetection` pair, surfacing as\n // `PipelineCameraUnassigned`+`PipelineCameraAssigned` pairs in the\n // UI events panel.\n void this.writeCameraStreamsSlice(payload.deviceId, this.snapshotProfileSlots(payload.deviceId))\n }\n\n /**\n * Decompose a brokerId `${deviceId}/${camStreamId}` back into its\n * components. `camStreamId` may contain `:` (e.g. `native:main`), so\n * we split on the FIRST `/` only — everything after is the cam stream\n * id verbatim.\n */\n private parseBrokerId(brokerId: string): { deviceId: number; camStreamId: string } | null {\n const idx = brokerId.indexOf('/')\n if (idx <= 0) return null\n const deviceId = Number(brokerId.slice(0, idx))\n const camStreamId = brokerId.slice(idx + 1)\n if (!Number.isFinite(deviceId) || camStreamId.length === 0) return null\n return { deviceId, camStreamId }\n }\n\n async startRtspServer(port: number = 8554): Promise<void> {\n await this.rtspServer.start(port)\n this.logger.info('RTSP restream server listening', { meta: { port: this.rtspServer.getPort() } })\n }\n\n // ── Cap methods: pre-buffer ─────────────────────────────────────────\n\n async setPreBufferDuration(input: { brokerId: string; seconds: number }): Promise<void> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) return\n broker.setPreBufferDuration(input.seconds)\n }\n\n async getPreBufferInfo(input: {\n brokerId: string\n }): Promise<{ configuredSec: number; bufferedMs: number; packetCount: number }> {\n const broker = this.brokers.get(input.brokerId)\n if (!broker) return { configuredSec: 0, bufferedMs: 0, packetCount: 0 }\n const stats = broker.getStats()\n return {\n configuredSec: stats.preBufferSec,\n bufferedMs: stats.preBufferMs,\n packetCount: stats.preBufferPackets,\n }\n }\n\n // ── Cap methods: stream URLs ────────────────────────────────────────\n\n async getStreamUrl(input: { streamId: string; format: StreamFormat }): Promise<{ url: string }> {\n const prefix = input.streamId.split('/')[0]\n if (!prefix) return { url: '' }\n const deviceId = Number(prefix)\n if (!Number.isFinite(deviceId)) return { url: '' }\n for (const restreamer of this.getLiveRestreamers()) {\n const resources = restreamer.getExposedResources(deviceId)\n const match = resources.find((r) => r.format === input.format)\n if (match) return { url: match.value }\n }\n return { url: '' }\n }\n\n // ── Cap methods: RTSP restream ──────────────────────────────────────\n\n async getRtspPort(): Promise<number> {\n return this.rtspProvider.getRtspPort()\n }\n\n async getAllRtspEntries(input?: { hostname?: string }): Promise<readonly RtspRestreamEntry[]> {\n return this.rtspProvider.getAllEntries(input?.hostname)\n }\n\n /** Device-scoped RTSP entry fetch — backs `cameraStreams.getRtspEntries`. */\n getRtspEntriesForDevice(deviceId: number, hostname?: string): readonly RtspRestreamEntry[] {\n return this.rtspProvider.getEntriesForDevice(deviceId, hostname)\n }\n\n async getRtspEntry(input: { brokerId: string; hostname?: string }): Promise<RtspRestreamEntry | null> {\n return this.rtspProvider.getEntry(input.brokerId, input.hostname)\n }\n\n async regenerateRtspToken(input: { brokerId: string }): Promise<string | null> {\n this.logger.info('regenerateRtspToken called', { meta: { brokerId: input.brokerId } })\n const result = this.rtspProvider.regenerateToken(input.brokerId)\n if (result) {\n this.logger.info('regenerateRtspToken succeeded', {\n meta: { brokerId: input.brokerId, newTokenPrefix: result.slice(0, 8) },\n })\n } else {\n this.logger.warn('regenerateRtspToken failed — broker not found', {\n meta: { brokerId: input.brokerId },\n })\n }\n return result\n }\n\n async setRtspEnabled(input: { brokerId: string; enabled: boolean }): Promise<void> {\n this.rtspProvider.setEnabled(input.brokerId, input.enabled)\n }\n\n async isRtspEnabled(input: { brokerId: string }): Promise<boolean> {\n return this.rtspProvider.isEnabled(input.brokerId)\n }\n\n /**\n * Fan-out a placeholder reason to every broker registered for a\n * given device. Internal — drives the runtime-state-driven\n * placeholder behaviour: the broker manager subscribes to events\n * that reflect device state (battery sleep, online/offline,\n * disabled), translates them to a `PlaceholderReason`, and pushes\n * to the per-device broker set. Providers stay oblivious — they\n * only mutate their own runtime state.\n */\n private applyPlaceholderReasonToDevice(deviceId: number, reason: PlaceholderReason): void {\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) return\n for (const camStreamId of streamMap.keys()) {\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n broker?.setPlaceholderReason(reason)\n }\n }\n\n // ── Device overrides (kept for per-profile pre-buffer tuning) ──────\n\n setDeviceOverridePersister(cb: (overrides: ReadonlyMap<number, DeviceOverride>) => void): void {\n this.deviceOverridePersister = cb\n }\n\n loadPersistedDeviceOverrides(overrides: ReadonlyMap<number, DeviceOverride>): void {\n this.deviceOverrides.clear()\n for (const [k, v] of overrides) this.deviceOverrides.set(k, v)\n }\n\n /**\n * Effective pre-buffer duration for a brokerId's profile.\n *\n * Priority: per-profile override → legacy single-value override →\n * battery-aware default (0 when the device has BatteryOperated and\n * no override) → addon default.\n */\n getEffectivePreBufferSec(\n deviceId: number,\n camStreamId: string,\n addonDefault: number,\n forceForDetection = false,\n ): number {\n const override = this.deviceOverrides.get(deviceId)\n const perStream = override?.preBuffer?.[camStreamId]\n if (perStream) {\n const enabled = perStream.enabled || forceForDetection\n return enabled ? perStream.seconds : 0\n }\n if (override?.preBufferSecOverride !== undefined) return override.preBufferSecOverride\n if (this.deviceHasFeature(deviceId, DeviceFeature.BatteryOperated)) return 0\n return addonDefault\n }\n\n /**\n * Per-device streaming debug flag — controls verbose info-level\n * logging across the broker, WebRTC server, and session for one\n * device. Off by default.\n */\n isStreamingDebug(deviceId: number): boolean {\n return this.deviceOverrides.get(deviceId)?.streamingDebug === true\n }\n\n // ── Device-details aggregator contribution ─────────────────────────\n\n async getDeviceSettingsContribution(input: {\n deviceId: number\n }): Promise<ConfigUISchemaWithValues | null> {\n const deviceId = input.deviceId\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap || streamMap.size === 0) return null\n const assignment = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const streams = [...streamMap.values()].sort((a, b) => a.order - b.order)\n const override = this.deviceOverrides.get(deviceId)\n\n const streamOptions = [\n { value: '', label: 'Not assigned' },\n ...streams.map((s) => ({\n value: s.camStreamId,\n label: this.labelForDropdown(s),\n })),\n ]\n\n const PROFILES: ReadonlyArray<{ key: CamProfile; label: string }> = [\n { key: 'high', label: 'High' },\n { key: 'mid', label: 'Mid' },\n { key: 'low', label: 'Low' },\n ]\n\n // ── Sub-tab \"Assignment\" — three profile→stream selectors ──────\n const assignmentTab: ConfigSubTabDefinitionWithValue = {\n id: 'assignment',\n label: 'Assignment',\n fields: PROFILES.map(({ key: profile, label }) => ({\n type: 'select' as const,\n key: `streamProfile:${profile}`,\n label: `${label} Quality`,\n description: `Which camera stream serves the \"${label}\" profile slot`,\n options: streamOptions,\n span: 2 as const,\n value: assignment.map[profile] ?? '',\n })),\n }\n\n // ── One sub-tab per stream source with its broker settings ─────\n // Every published cam stream owns a broker for as long as the\n // source is published — there is no \"Active\" toggle. Pre-buffer\n // is OFF by default and opt-in per stream.\n const streamTabs: ConfigSubTabDefinitionWithValue[] = streams.map((stream) => {\n const camStreamId = stream.camStreamId\n const brokerId = brokerIdFor(deviceId, camStreamId)\n const profile = findProfileForStream(assignment.map, camStreamId)\n const rtspEnabled = this.rtspProvider.isEnabled(brokerId)\n const rtspEntry = this.rtspProvider.getEntry(brokerId)\n const perStream = override?.preBuffer?.[camStreamId]\n const preBufferEnabled = perStream?.enabled ?? false\n const preBufferSec = perStream?.seconds ?? this.defaultPreBufferSec\n\n const fields: ConfigFieldWithValue[] = [\n {\n type: 'boolean' as const,\n style: 'switch' as const,\n key: `rtspEnabled:${camStreamId}`,\n label: 'RTSP Restream',\n description: 'Expose this stream via the RTSP restream server',\n default: true,\n value: rtspEnabled,\n },\n {\n type: 'text' as const,\n key: `rtspUrl:${camStreamId}`,\n label: 'RTSP URL',\n readonlyField: true,\n span: 2 as const,\n value: rtspEntry?.url ?? 'inactive',\n actions: [\n { action: 'copy-value', icon: 'copy', tooltip: 'Copy URL to clipboard' },\n {\n action: 'regenerate-rtsp-token',\n icon: 'refresh-cw',\n tooltip: 'Regenerate RTSP token',\n variant: 'danger' as const,\n confirmMessage: `Rotate the RTSP token for ${stream.label ?? camStreamId}? Current URL will stop working immediately.`,\n },\n ],\n },\n {\n type: 'text' as const,\n key: `rtspMutedUrl:${camStreamId}`,\n label: 'RTSP URL (muted)',\n description: 'Video-only stream — no audio track',\n readonlyField: true,\n span: 2 as const,\n value: rtspEntry?.url ? `${rtspEntry.url}/muted` : 'inactive',\n actions: [\n { action: 'copy-value', icon: 'copy', tooltip: 'Copy muted URL to clipboard' },\n ],\n },\n {\n type: 'boolean' as const,\n style: 'switch' as const,\n key: `preBufferEnabled:${camStreamId}`,\n label: 'Pre-buffer',\n description:\n 'Keep a rolling buffer for instant playback on events. Forced on when detection uses this stream.',\n default: false,\n value: preBufferEnabled,\n },\n {\n type: 'slider' as const,\n key: `preBufferSec:${camStreamId}`,\n label: 'Pre-buffer Duration',\n min: 0,\n max: 30,\n step: 1,\n unit: 's',\n default: this.defaultPreBufferSec,\n showValue: true,\n value: preBufferSec,\n },\n ]\n\n const tabBadge = profile\n ? PROFILES.find((p) => p.key === profile)?.label\n : undefined\n const tabLabel = stream.label ?? camStreamId\n const def: ConfigSubTabDefinitionWithValue = {\n id: `stream:${camStreamId}`,\n label: tabLabel,\n fields,\n ...(tabBadge ? { badge: tabBadge } : {}),\n }\n return def\n })\n\n const streamBrokerSection: ConfigSectionWithValues = {\n id: 'stream-broker-streams',\n title: '',\n tab: 'stream-broker',\n order: 10,\n fields: [\n {\n type: 'sub-tabs' as const,\n key: 'streamBroker:streams',\n label: '',\n span: 2 as const,\n tabs: [assignmentTab, ...streamTabs],\n },\n ],\n }\n\n const diagnosticsSection: ConfigSectionWithValues = {\n id: 'stream-broker-diagnostics',\n title: 'Diagnostics',\n tab: 'stream-broker',\n order: 100,\n fields: [\n {\n type: 'boolean' as const,\n style: 'switch' as const,\n key: 'streamingDebug',\n label: 'Streaming debug logs',\n description:\n 'Enable verbose info-level logs for the broker, WebRTC server, and H.265 repacketizer for this device. Off by default; turn on while diagnosing playback issues.',\n default: false,\n value: override?.streamingDebug === true,\n },\n ],\n }\n\n return { sections: [streamBrokerSection, diagnosticsSection] }\n }\n\n async getDeviceLiveContribution(_input: {\n deviceId: number\n }): Promise<ConfigUISchemaWithValues | null> {\n return null\n }\n\n async applyDeviceSettingsPatch(input: {\n deviceId: number\n patch: Record<string, unknown>\n }): Promise<{ success: true }> {\n const deviceId = input.deviceId\n if (!this.cameraStreams.has(deviceId)) {\n this.logger.debug('applyDeviceSettingsPatch: unknown deviceId — no-op', {\n tags: { deviceId },\n })\n return { success: true as const }\n }\n\n let overrideDirty = false\n const nextOverride: DeviceOverride = { ...(this.deviceOverrides.get(deviceId) ?? {}) }\n\n for (const [fieldKey, value] of Object.entries(input.patch)) {\n // Profile → camStreamId selectors live in the Assignment sub-tab\n // (`streamProfile:<profile>`).\n if (fieldKey.startsWith('streamProfile:')) {\n const profile = fieldKey.slice('streamProfile:'.length) as CamProfile\n if (!CAM_PROFILE_ORDER.includes(profile)) continue\n const newCamId = typeof value === 'string' && value !== '' ? value : null\n if (newCamId === null) {\n await this.unassignProfile({ deviceId, profile })\n } else {\n await this.assignProfile({ deviceId, profile, camStreamId: newCamId })\n }\n continue\n }\n if (fieldKey.startsWith('rtspEnabled:')) {\n const camStreamId = fieldKey.slice('rtspEnabled:'.length)\n const brokerId = brokerIdFor(deviceId, camStreamId)\n this.rtspProvider.setEnabled(brokerId, Boolean(value))\n continue\n }\n if (fieldKey === 'preBufferSecOverride') {\n overrideDirty = true\n if (value === null || value === undefined) {\n delete nextOverride.preBufferSecOverride\n } else if (typeof value === 'number' && Number.isFinite(value)) {\n nextOverride.preBufferSecOverride = value\n }\n continue\n }\n if (fieldKey.startsWith('preBufferEnabled:')) {\n const camStreamId = fieldKey.slice('preBufferEnabled:'.length)\n overrideDirty = true\n const pb = { ...(nextOverride.preBuffer ?? {}) }\n const current = pb[camStreamId] ?? this.defaultStreamPreBuffer(deviceId, camStreamId)\n const enabled = Boolean(value)\n pb[camStreamId] = { ...current, enabled }\n nextOverride.preBuffer = pb\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n if (broker) {\n broker.setPreBufferDuration(enabled ? pb[camStreamId]?.seconds ?? this.defaultPreBufferSec : 0)\n broker.setPreBufferEnabled(enabled)\n }\n continue\n }\n if (fieldKey.startsWith('preBufferSec:')) {\n const camStreamId = fieldKey.slice('preBufferSec:'.length)\n const seconds =\n typeof value === 'number' && Number.isFinite(value) ? value : this.defaultPreBufferSec\n overrideDirty = true\n const pb = { ...(nextOverride.preBuffer ?? {}) }\n const current = pb[camStreamId] ?? this.defaultStreamPreBuffer(deviceId, camStreamId)\n pb[camStreamId] = { ...current, seconds }\n nextOverride.preBuffer = pb\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n if (broker && pb[camStreamId]?.enabled !== false) {\n broker.setPreBufferDuration(seconds)\n }\n continue\n }\n if (fieldKey === 'streamingDebug') {\n overrideDirty = true\n const next = value === null || value === undefined ? undefined : Boolean(value)\n if (next === undefined) {\n delete nextOverride.streamingDebug\n } else {\n nextOverride.streamingDebug = next\n }\n // Push to every active broker for this device immediately so\n // the toggle takes effect without restarting the stream.\n for (const [bid, broker] of this.brokers) {\n if (bid.startsWith(`${deviceId}/`)) {\n broker.setStreamingDebug(next === true)\n }\n }\n continue\n }\n }\n\n if (overrideDirty) {\n if (\n nextOverride.preBufferSecOverride === undefined &&\n (!nextOverride.preBuffer || Object.keys(nextOverride.preBuffer).length === 0) &&\n nextOverride.streamingDebug === undefined\n ) {\n this.deviceOverrides.delete(deviceId)\n } else {\n this.deviceOverrides.set(deviceId, nextOverride)\n }\n this.deviceOverridePersister?.(this.deviceOverrides)\n }\n return { success: true as const }\n }\n\n // ── Private helpers ─────────────────────────────────────────────────\n\n private nextOrder(deviceId: number): number {\n const next = (this.streamOrderSeq.get(deviceId) ?? 0) + 1\n this.streamOrderSeq.set(deviceId, next)\n return next\n }\n\n private toCameraStream(s: CameraStreamInternal): CameraStream {\n const { order: _order, deviceFeatures: _df, ...rest } = s\n return rest\n }\n\n private deviceHasFeature(deviceId: number, feature: string): boolean {\n const streamMap = this.cameraStreams.get(deviceId)\n if (!streamMap) return false\n for (const s of streamMap.values()) {\n if (s.deviceFeatures.includes(feature)) return true\n }\n return false\n }\n\n private labelForDropdown(s: CameraStreamInternal): string {\n const base = s.label ?? s.camStreamId\n if (s.resolution) return `${base} (${s.resolution.width}×${s.resolution.height})`\n return base\n }\n\n /**\n * Compute the canonical profile map for a given ordered stream list.\n *\n * 1 stream → { mid }\n * 2 streams → { high, low } (higher resolution → high)\n * 3+ streams → { high, mid, low }\n *\n * Ties on resolution fall back to publish order (stream.order asc).\n * Streams with no resolution sort below those with one — publish\n * order breaks ties within each rank.\n *\n * Streams marked `autoEligible: false` are skipped — they remain\n * published (visible + manually assignable via `assignProfile`) but\n * never become the default. If every stream is non-eligible the\n * algorithm degrades gracefully and considers them all (so a device\n * with only ineligible streams still gets some default assignment).\n */\n private computeInitialAssignment(\n streams: readonly CameraStreamInternal[],\n ): Partial<Record<CamProfile, string>> {\n if (streams.length === 0) return {}\n const eligible = streams.filter((s) => s.autoEligible)\n const pool = eligible.length > 0 ? eligible : streams\n const ranked = [...pool].sort((a, b) => {\n const aPx = a.resolution ? a.resolution.width * a.resolution.height : -1\n const bPx = b.resolution ? b.resolution.width * b.resolution.height : -1\n if (aPx !== bPx) return bPx - aPx\n return a.order - b.order\n })\n if (ranked.length === 1) return { mid: ranked[0]!.camStreamId }\n if (ranked.length === 2) {\n return { high: ranked[0]!.camStreamId, low: ranked[1]!.camStreamId }\n }\n return {\n high: ranked[0]!.camStreamId,\n mid: ranked[1]!.camStreamId,\n low: ranked[2]!.camStreamId,\n }\n }\n\n private wouldChangeAutoMap(\n current: Partial<Record<CamProfile, string>>,\n streams: readonly CameraStreamInternal[],\n ): boolean {\n const computed = this.computeInitialAssignment(streams)\n for (const p of CAM_PROFILE_ORDER) {\n if (current[p] !== computed[p]) return true\n }\n return false\n }\n\n /**\n * Core transition: diff `current.map` vs `newMap`, then translate the\n * profile-level diff to demand/idle events for push-kind streams.\n * Brokers are NOT touched here — every published cam stream owns\n * its broker for as long as the source exists, regardless of which\n * profile slot points at it. Profile reassignments are pure\n * metadata changes from the broker's point of view.\n */\n private async applyAssignmentUpdate(\n deviceId: number,\n newMap: Partial<Record<CamProfile, string>>,\n opts: { auto: boolean; reason: 'publish' | 'retract' | 'assign' | 'unassign' },\n ): Promise<void> {\n const current = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const nextAssignment: DeviceAssignment = { map: newMap, auto: opts.auto }\n\n // Pre-change demand view: which push cam streams have active consumers?\n const oldPushConsumers = this.countPushConsumers(current.map, deviceId)\n const newPushConsumers = this.countPushConsumers(newMap, deviceId)\n\n let anyChange = false\n for (const profile of CAM_PROFILE_ORDER) {\n if (current.map[profile] !== newMap[profile]) {\n anyChange = true\n break\n }\n }\n\n this.assignments.set(deviceId, nextAssignment)\n\n // Demand transitions for push-kind streams.\n for (const [camId, prevCount] of oldPushConsumers) {\n if ((newPushConsumers.get(camId) ?? 0) === 0 && prevCount > 0) {\n this.emitCamStreamIdle(deviceId, camId)\n }\n }\n for (const [camId, nextCount] of newPushConsumers) {\n if ((oldPushConsumers.get(camId) ?? 0) === 0 && nextCount > 0) {\n // Figure out which profile triggered the demand (first one in canonical order).\n let triggerProfile: CamProfile = 'mid'\n for (const p of CAM_PROFILE_ORDER) {\n if (newMap[p] === camId) { triggerProfile = p; break }\n }\n this.emitCamStreamDemand(deviceId, camId, triggerProfile)\n }\n }\n\n if (anyChange || current.auto !== nextAssignment.auto) {\n this.persistProfileMap()\n this.emitProfileSlotsChanged(deviceId)\n } else if (opts.reason === 'publish') {\n // Re-publish with unchanged assignment (e.g. provider restart): still\n // emit so the orchestrator can re-dispatch detection on provider reconnect.\n this.emitProfileSlotsChanged(deviceId)\n }\n }\n\n private countPushConsumers(\n map: Partial<Record<CamProfile, string>>,\n deviceId: number,\n ): Map<string, number> {\n const out = new Map<string, number>()\n const streamMap = this.cameraStreams.get(deviceId)\n for (const profile of CAM_PROFILE_ORDER) {\n const camId = map[profile]\n if (!camId) continue\n const cam = streamMap?.get(camId)\n if (!cam || !PUSH_KINDS.has(cam.kind)) continue\n out.set(camId, (out.get(camId) ?? 0) + 1)\n }\n return out\n }\n\n private persistProfileMap(): void {\n this.profileMapPersister?.(this.assignments)\n }\n\n private buildSourceFromCamStream(cam: CameraStreamInternal): StreamSource {\n const label = cam.label ?? cam.camStreamId\n const codec = cam.codec !== undefined ? { videoCodec: cam.codec } : {}\n // Battery-flagged devices want the stall watchdog relaxed (Reolink\n // Argus and friends intentionally go silent between motion events).\n // Derive the per-source flag from the device-level feature list so\n // publishers don't have to carry their own per-stream flag.\n const stall = cam.deviceFeatures.includes(DeviceFeature.BatteryOperated)\n ? { allowStall: true as const }\n : {}\n // Pre-compose pass-through metadata so the publisher's opaque payload\n // (e.g. `pull-rfc4571` SDP) reaches the broker reader unchanged.\n const passthroughMeta = cam.metadata ?? {}\n switch (cam.kind) {\n case 'push-annexb':\n return { type: 'push', url: '', ...codec, ...stall, metadata: { kind: 'push-annexb', label, ...passthroughMeta } }\n case 'pull-rtsp':\n return { type: 'rtsp', url: cam.url ?? '', ...codec, metadata: { kind: 'pull-rtsp', label, ...passthroughMeta } }\n case 'pull-rtmp':\n return { type: 'rtmp', url: cam.url ?? '', ...codec, metadata: { kind: 'pull-rtmp', label, ...passthroughMeta } }\n case 'pull-http':\n return { type: 'http-mjpeg', url: cam.url ?? '', ...codec, metadata: { kind: 'pull-http', label, ...passthroughMeta } }\n case 'pull-rfc4571':\n return { type: 'rfc4571', url: cam.url ?? '', ...codec, ...stall, metadata: { kind: 'pull-rfc4571', label, ...passthroughMeta } }\n default: {\n const _exhaustive: never = cam.kind\n throw new Error(`Unknown cam stream kind: ${String(_exhaustive)}`)\n }\n }\n }\n\n /**\n * Acquire (or create) the broker for a (deviceId, camStreamId) pair.\n * Idempotent — returns the existing broker when one is already\n * registered. Tied to publish lifecycle: `publishCameraStream` calls\n * this once per stream; `retractCameraStream` releases it.\n */\n private async ensureBroker(\n deviceId: number,\n camStreamId: string,\n ): Promise<void> {\n const cam = this.cameraStreams.get(deviceId)?.get(camStreamId)\n if (!cam) return\n const brokerId = brokerIdFor(deviceId, camStreamId)\n if (this.brokers.has(brokerId)) return\n\n const brokerLogger = this.logger\n .child(camStreamId)\n .withTags({ deviceId, camStreamId })\n const source = this.buildSourceFromCamStream(cam)\n const broker = new StreamBroker(\n brokerId,\n this.buildDecoderCapApi(deviceId),\n brokerLogger,\n buildAudioCodecCapApi(this.api),\n )\n broker.setRestreamers(this.restreamers)\n this.applyPreBufferConfig(broker, deviceId, camStreamId, cam)\n const override = this.deviceOverrides.get(deviceId)\n if (override?.streamingDebug) broker.setStreamingDebug(true)\n\n // Hook the broker into the cam-stream registry: every dial\n // attempt re-derives the source so a re-publish (e.g. Reolink\n // refreshing its rfc4571 URL) is observed transparently. Mirrors\n // Scrypted's per-`getVideoStream()` `ensureRfcServer` resolution.\n broker.setSourceProvider(() => {\n const latest = this.cameraStreams.get(deviceId)?.get(camStreamId)\n return latest ? this.buildSourceFromCamStream(latest) : null\n })\n broker.setRequestSourceRefresh(() => {\n this.emitRequestStreamSourceRefresh(deviceId, camStreamId, brokerId)\n })\n\n await broker.start(source)\n this.brokers.set(brokerId, broker)\n\n const existingToken = this.persistedTokens.get(brokerId)\n this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), existingToken)\n if (!existingToken) this.rtspProvider.persistTokens()\n\n await this.fanOutRegistration(deviceId, brokerId, cam, broker)\n }\n\n /**\n * Tear down the broker for a `(deviceId, camStreamId)` pair. No-op\n * when no broker exists. Called by `retractCameraStream` and by the\n * teardown half of `restartBroker`.\n */\n private async releaseBroker(\n deviceId: number,\n camStreamId: string,\n ): Promise<void> {\n const brokerId = brokerIdFor(deviceId, camStreamId)\n const broker = this.brokers.get(brokerId)\n if (!broker) return\n\n await this.fanOutUnregistration(deviceId, brokerId)\n this.rtspServer.unregisterRestreamer(brokerId)\n // Emit terminal stream.offline so subscribers can clear their\n // aggregate before the broker disappears. Skip if the broker never\n // became healthy (no transition to report).\n const wasHealthy = this.streamHealthByBroker.get(brokerId)\n if (wasHealthy && this.eventBus) {\n this.emitStreamHealth(false, {\n deviceId,\n camStreamId,\n profile: findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId),\n brokerId,\n sourceType: broker.getActiveSourceType(),\n lastPacketAt: broker.getLastPacketAt(),\n reason: 'broker-destroyed',\n })\n }\n this.streamHealthByBroker.delete(brokerId)\n await broker.stop()\n this.brokers.delete(brokerId)\n this.rtspProvider.persistTokens()\n }\n\n /**\n * Default `StreamPreBuffer` for a brand-new override entry — mirrors\n * the UI-side default in `getDeviceSettingsContribution`. Pre-buffer\n * is OFF by default for every stream. Operators opt in per-stream\n * via the device-settings sub-tabs; detection / recording paths\n * pass `forceForDetection` to `getEffectivePreBufferSec` when they\n * need a buffered window irrespective of the operator toggle.\n */\n private defaultStreamPreBuffer(_deviceId: number, _camStreamId: string): StreamPreBuffer {\n return { enabled: false, seconds: this.defaultPreBufferSec }\n }\n\n /**\n * Apply the effective pre-buffer config to a fresh broker instance.\n * Default behaviour is OFF for every cam stream — keeps RAM and the\n * upstream reader idle until the operator opts in or a detection /\n * recording subscriber attaches.\n */\n private applyPreBufferConfig(\n broker: StreamBroker,\n deviceId: number,\n camStreamId: string,\n _cam: CameraStreamInternal,\n ): void {\n const override = this.deviceOverrides.get(deviceId)\n const perStream = override?.preBuffer?.[camStreamId]\n if (perStream) {\n broker.setPreBufferEnabled(perStream.enabled)\n broker.setPreBufferDuration(perStream.enabled ? perStream.seconds : 0)\n return\n }\n broker.setPreBufferEnabled(false)\n broker.setPreBufferDuration(0)\n }\n\n private async fanOutRegistration(\n deviceId: number,\n brokerId: string,\n cam: CameraStreamInternal,\n broker: StreamBroker,\n ): Promise<void> {\n const log = this.logger.withTags({ deviceId, brokerId, camStreamId: cam.camStreamId })\n const localStreamUrl = broker.getLocalStreamUrl()\n const codec = 'h264'\n const label = cam.label ?? brokerId\n\n for (const restreamer of this.getLiveRestreamers()) {\n try {\n await restreamer.registerDevice(deviceId, [\n { streamId: brokerId, label, codec, type: 'video' as const, sourceUrl: localStreamUrl },\n ])\n } catch (err) {\n log.error('Failed to register with restreamer', {\n meta: { restreamerId: restreamer.id, error: errMsg(err) },\n })\n }\n }\n\n // Register broker with the built-in WebRTC server (direct encoded\n // packet subscription — no ffmpeg, no RTSP double-hop).\n if (this.webrtcServer) {\n this.webrtcServer.registerBroker(brokerId, broker)\n }\n }\n\n private async fanOutUnregistration(deviceId: number, brokerId: string): Promise<void> {\n const log = this.logger.withTags({ deviceId, brokerId })\n for (const restreamer of this.getLiveRestreamers()) {\n try {\n await restreamer.unregisterDevice(deviceId)\n } catch (err) {\n log.error('Failed to unregister from restreamer', {\n meta: { restreamerId: restreamer.id, error: errMsg(err) },\n })\n }\n }\n // Unregister from built-in WebRTC server.\n if (this.webrtcServer) {\n this.webrtcServer.unregisterBroker(brokerId)\n }\n }\n\n private brokerStatusToSlotStatus(status: BrokerStats['status']): ProfileSlotStatus {\n // BrokerStatus has 'stopped' (broker tore down cleanly) which the\n // ProfileSlotStatus schema does not model — collapse to 'idle' since\n // a stopped-but-assigned slot from the consumer's POV is equivalent\n // to idle awaiting restart.\n if (status === 'stopped') return 'idle'\n return status\n }\n\n private snapshotProfileSlots(deviceId: number): ProfileSlot[] {\n const assignment = this.assignments.get(deviceId) ?? { map: {}, auto: true }\n const streamMap = this.cameraStreams.get(deviceId)\n const slots: ProfileSlot[] = []\n for (const profile of CAM_PROFILE_ORDER) {\n const camId = assignment.map[profile] ?? null\n // brokerId is keyed by camStreamId (the canonical broker identity);\n // for unassigned slots there's no live broker, so we surface a\n // synthetic profile-shaped key purely so consumers that ignore\n // the field for unassigned slots keep working unchanged.\n const brokerId = camId ? brokerIdFor(deviceId, camId) : `${deviceId}/${profile}`\n const broker = camId ? this.brokers.get(brokerId) : undefined\n const stats = broker?.getStats()\n const cam = camId ? streamMap?.get(camId) : undefined\n const status: ProfileSlotStatus = camId\n ? stats ? this.brokerStatusToSlotStatus(stats.status) : 'idle'\n : 'unassigned'\n // Codec resolution: prefer the live broker's detected codec\n // (`stats.codec` — populated after first frame), fall back to the\n // provider-declared `cam.codec`. Reolink cameras don't always\n // populate cam.codec at registration, so without this fallback\n // every slot reported codec=undefined and the UI rendered \"—\".\n const resolvedCodec = stats?.codec ?? cam?.codec\n const slot: ProfileSlot = {\n deviceId,\n profile,\n brokerId,\n sourceCamStreamId: camId,\n status,\n ...(cam?.resolution !== undefined ? { resolution: cam.resolution } : {}),\n ...(resolvedCodec !== undefined ? { codec: resolvedCodec } : {}),\n ...(stats !== undefined ? { preBufferSec: stats.preBufferSec } : {}),\n }\n slots.push(slot)\n }\n return slots\n }\n\n // ── Event emission (via IEventBus) ──────────────────────────────────\n\n private emitCapEvent(category: string, deviceId: number, data: Record<string, unknown>): void {\n const bus = this.eventBus\n if (!bus) return\n bus.emit({\n id: `cap-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date(),\n source: { type: 'device', id: deviceId, addonId: 'stream-broker', deviceId },\n category,\n data,\n })\n }\n\n private emitCamStreamDemand(deviceId: number, camStreamId: string, profile: CamProfile): void {\n this.emitCapEvent(EventCategory.StreamBrokerOnCamStreamDemand, deviceId, { deviceId, camStreamId, profile })\n }\n\n private emitCamStreamIdle(deviceId: number, camStreamId: string): void {\n this.emitCapEvent(EventCategory.StreamBrokerOnCamStreamIdle, deviceId, { deviceId, camStreamId })\n }\n\n private emitRequestStreamSourceRefresh(deviceId: number, camStreamId: string, brokerId: string): void {\n this.emitCapEvent(EventCategory.StreamBrokerOnRequestStreamSourceRefresh, deviceId, {\n deviceId,\n camStreamId,\n brokerId,\n })\n }\n\n private emitCamStreamsChanged(deviceId: number): void {\n const camStreams = this.getCameraStreamsForDevice(deviceId)\n this.emitCapEvent('camera-streams.onCamStreamsChanged', deviceId, { deviceId, camStreams })\n }\n\n private emitProfileSlotsChanged(deviceId: number): void {\n const profileSlots = this.snapshotProfileSlots(deviceId)\n this.emitCapEvent('camera-streams.onProfileSlotsChanged', deviceId, { deviceId, profileSlots })\n void this.writeCameraStreamsSlice(deviceId, profileSlots)\n }\n\n /**\n * Mirror per-device runtime state into the `camera-streams` slice.\n * Fire-and-forget — failures land on the debug log and don't impact\n * broker behavior (the slice is observability + RTSP autonomous-online\n * source, both fault-tolerant). Mirrors the same `setCapSlice` pattern\n * pipeline-runner uses for the `motion` slice.\n */\n private async writeCameraStreamsSlice(deviceId: number, slots: readonly ProfileSlot[]): Promise<void> {\n const api = this.api\n if (!api) return\n const slotStatuses: { high?: ProfileSlotStatus; mid?: ProfileSlotStatus; low?: ProfileSlotStatus } = {}\n const slotErrors: { high?: string; mid?: string; low?: string } = {}\n let online = false\n for (const slot of slots) {\n if (slot.status !== 'unassigned') {\n slotStatuses[slot.profile] = slot.status\n if (slot.errorMessage) slotErrors[slot.profile] = slot.errorMessage\n if (slot.status === 'streaming') online = true\n }\n }\n try {\n await api.deviceState.setCapSlice.mutate({\n deviceId,\n capName: 'camera-streams',\n slice: { online, slotStatuses, slotErrors, lastChangedAt: Date.now() },\n })\n } catch (err) {\n this.logger.debug('writeCameraStreamsSlice failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n}\n","/**\n * NAL unit parsing utilities.\n * Protocol-agnostic functions for working with Annex-B NAL units.\n */\n\nimport type { VideoCodec } from \"./types.js\";\n\nconst NAL_START_CODE_4B = Buffer.from([0x00, 0x00, 0x00, 0x01]);\nconst NAL_START_CODE_3B = Buffer.from([0x00, 0x00, 0x01]);\n\n/**\n * Returns true if the buffer starts with an Annex-B start code (0x00000001 or 0x000001).\n */\nexport function hasStartCodes(data: Buffer): boolean {\n if (data.length < 4) return false;\n if (data.subarray(0, 4).equals(NAL_START_CODE_4B)) return true;\n if (data.subarray(0, 3).equals(NAL_START_CODE_3B)) return true;\n return false;\n}\n\n/**\n * Split Annex-B data into individual NAL unit payloads (without start codes).\n * Works for both H.264 and H.265.\n */\nexport function splitAnnexBToNals(annexB: Buffer): Buffer[] {\n const nals: Buffer[] = [];\n const len = annexB.length;\n\n const isStartCodeAt = (i: number): number => {\n if (i + 3 <= len && annexB[i] === 0x00 && annexB[i + 1] === 0x00) {\n if (annexB[i + 2] === 0x01) return 3;\n if (i + 4 <= len && annexB[i + 2] === 0x00 && annexB[i + 3] === 0x01)\n return 4;\n }\n return 0;\n };\n\n let i = 0;\n // Find first start code\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (sc) break;\n i++;\n }\n\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (!sc) {\n i++;\n continue;\n }\n const nalStart = i + sc;\n let j = nalStart;\n while (j < len) {\n const sc2 = isStartCodeAt(j);\n if (sc2) break;\n j++;\n }\n if (nalStart < j) {\n const nal = annexB.subarray(nalStart, j);\n if (nal.length > 0) nals.push(nal);\n }\n i = j;\n }\n\n return nals;\n}\n\n/**\n * Prepend a 4-byte Annex-B start code to a NAL payload.\n */\nexport function prependStartCode(nal: Buffer): Buffer {\n return Buffer.concat([NAL_START_CODE_4B, nal]);\n}\n\n/**\n * Join multiple NAL payloads (without start codes) into an Annex-B access unit.\n */\nexport function joinNalsToAnnexB(\n ...nals: Array<Buffer | null | undefined>\n): Buffer | undefined {\n const present = nals.filter((n): n is Buffer => !!n && n.length > 0);\n if (!present.length) return;\n const parts: Buffer[] = [];\n for (const nal of present) {\n parts.push(NAL_START_CODE_4B, nal);\n }\n return Buffer.concat(parts);\n}\n\n/**\n * Detect the actual video codec from raw NAL data.\n * Some cameras report wrong codec (e.g. \"H264\" but send H.265 data).\n * Analyzes the NAL header to determine the real codec.\n *\n * @param data - Raw video data (either Annex-B or length-prefixed)\n * @returns Detected codec type or null if detection fails\n */\nexport function detectVideoCodecFromNal(data: Buffer): VideoCodec | null {\n if (!data || data.length < 5) return null;\n\n // Find start code (00 00 00 01 or 00 00 01)\n let nalStart = -1;\n for (let i = 0; i < Math.min(data.length - 4, 100); i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4;\n break;\n }\n if (data[i + 2] === 1) {\n nalStart = i + 3;\n break;\n }\n }\n }\n\n // If no start code, try length-prefixed (AVCC/HVCC)\n if (nalStart < 0 && data.length >= 5) {\n const len = data.readUInt32BE(0);\n if (len > 0 && len <= data.length - 4) {\n nalStart = 4;\n }\n }\n\n if (nalStart < 0 || nalStart >= data.length) return null;\n\n const nalByte = data[nalStart];\n if (nalByte === undefined) return null;\n\n // Check H.264 FIRST because H.264 NAL bytes can be misinterpreted as H.265.\n const forbiddenBit264 = (nalByte >> 7) & 1;\n const h264Type = nalByte & 0x1f;\n\n if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {\n if (h264Type === 7 || h264Type === 8) return \"H264\"; // SPS, PPS\n if (h264Type === 5) return \"H264\"; // IDR\n if (h264Type === 1) {\n const nalRefIdc = (nalByte >> 5) & 0x03;\n if (nalRefIdc >= 1) return \"H264\";\n }\n }\n\n // H.265/HEVC detection\n if (nalStart + 1 < data.length) {\n const nalByte2 = data[nalStart + 1];\n if (nalByte2 !== undefined) {\n const forbiddenBit = (nalByte >> 7) & 1;\n const hevcType = (nalByte >> 1) & 0x3f;\n const temporalId = nalByte2 & 0x07;\n\n if (forbiddenBit === 0 && temporalId > 0 && hevcType <= 40) {\n if (hevcType === 32 || hevcType === 33 || hevcType === 34)\n return \"H265\"; // VPS, SPS, PPS\n if (hevcType === 19 || hevcType === 20 || hevcType === 21)\n return \"H265\"; // IDR, CRA\n if (hevcType <= 1 && nalByte <= 0x03) return \"H265\"; // TRAIL\n }\n }\n }\n\n return null;\n}\n\nexport { NAL_START_CODE_4B, NAL_START_CODE_3B };\n","/**\n * H.264/AVC utilities.\n * AVCC to Annex-B conversion, SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- AVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH264Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 1) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = b0 & 0x1f;\n return nalType >= 1 && nalType <= 23;\n}\n\nfunction depacketizeRtpAggregationToAnnexB(payload: Buffer): Buffer | null {\n if (payload.length < 1) return null;\n const nalHeader = payload[0]!;\n const nalType = nalHeader & 0x1f;\n const out: Buffer[] = [];\n const pushNal = (nal: Buffer) => {\n if (nal.length === 0) return;\n out.push(NAL_START_CODE_4B, nal);\n };\n\n // STAP-A (24)\n if (nalType === 24) {\n let off = 1;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // STAP-B (25)\n if (nalType === 25) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP16 (26)\n if (nalType === 26) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 2 > payload.length) return null;\n off += 1 + 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP24 (27)\n if (nalType === 27) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 3 > payload.length) return null;\n off += 1 + 3;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n return null;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.264 data from length-prefixed (AVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH264ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n // Some models prepend a small header before an Annex-B access unit.\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try AVCC → AnnexB conversion (BE then LE)\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n // 3-byte lengths\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n // 2-byte lengths\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n // RTP aggregation payload\n const agg = depacketizeRtpAggregationToAnnexB(data);\n if (agg) return agg;\n\n // Single NAL without start code\n if (looksLikeSingleH264Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n\n return data;\n}\n\n/**\n * Check if an H.264 Annex-B access unit is a keyframe (IDR + SPS + PPS).\n */\nexport function isH264KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasSps = false;\n let hasPps = false;\n let hasIdr = false;\n for (const nal of nals) {\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 7) hasSps = true;\n if (t === 8) hasPps = true;\n if (t === 5) hasIdr = true;\n }\n return hasIdr && hasSps && hasPps;\n}\n\n/**\n * Check if an H.264 Annex-B access unit contains an IDR slice.\n */\n/**\n * Detect H.264 keyframe (IDR or I-frame with SPS).\n *\n * Some cameras emit non-IDR I-frames (NAL type 1 with slice_type=I)\n * instead of IDR frames (NAL type 5). Both are intra-coded and valid\n * as decoder entry points, but only IDR carries NAL type 5.\n *\n * We detect keyframes by:\n * 1. NAL type 5 (IDR) — canonical\n * 2. SPS (type 7) present — parameter sets are always sent with keyframes\n */\nexport function isH264IdrAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasSps = false;\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 5) return true; // IDR slice\n if (t === 7) hasSps = true; // SPS — indicates keyframe boundary\n }\n return hasSps;\n}\n\n/**\n * Extract SPS and PPS from an H.264 Annex-B access unit.\n */\nexport function extractH264ParamSets(annexB: Buffer): {\n sps?: Buffer;\n pps?: Buffer;\n profileLevelId?: string;\n} {\n const nals = splitAnnexBToNals(annexB);\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n let profileLevelId: string | undefined;\n\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const nalType = nal[0]! & 0x1f;\n if (nalType === 7) {\n sps = nal;\n if (nal.length >= 4) {\n profileLevelId = Buffer.from([nal[1]!, nal[2]!, nal[3]!]).toString(\n \"hex\",\n );\n }\n } else if (nalType === 8) {\n pps = nal;\n }\n }\n\n const out: { sps?: Buffer; pps?: Buffer; profileLevelId?: string } = {};\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n if (profileLevelId) out.profileLevelId = profileLevelId;\n return out;\n}\n\n/**\n * H.264 RTP depacketizer (RFC 6184).\n * Handles single NAL units, STAP-A aggregation, and FU-A/FU-B fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H264RtpDepacketizer {\n private fuNalHeader: number | null = null;\n private fuParts: Buffer[] = [];\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuNalHeader = null;\n this.fuParts = [];\n }\n\n push(payload: Buffer): Buffer[] {\n if (payload.length === 0) return [];\n\n const rtpPayload = H264RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n if (hasStartCodes(payload)) return [payload];\n\n const b0 = payload[0]!;\n if ((b0 & 0x80) !== 0) return [];\n const nalType = b0 & 0x1f;\n\n // Single NAL unit\n if (nalType >= 1 && nalType <= 23) {\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n\n // STAP-A (24)\n if (nalType === 24) {\n if (payload.length < 1 + 2) return [];\n let off = 1;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length < 1) return [];\n if ((nal[0]! & 0x80) !== 0) return [];\n const t = nal[0]! & 0x1f;\n if (t === 0 || t >= 24) return [];\n out.push(Buffer.concat([NAL_START_CODE_4B, nal]));\n }\n return out;\n }\n\n // FU-A (28) / FU-B (29)\n if (nalType === 28 || nalType === 29) {\n if (payload.length < 2) return [];\n const fuIndicator = payload[0]!;\n const fuHeader = payload[1]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x1f;\n const reconstructedHeader = (fuIndicator & 0xe0) | origType;\n\n let off = 2;\n if (nalType === 29) {\n if (payload.length < off + 2) return [];\n off += 2;\n }\n const frag = payload.subarray(off);\n\n if (start) {\n this.fuNalHeader = reconstructedHeader;\n this.fuParts = [frag];\n } else if (this.fuNalHeader != null) {\n this.fuParts.push(frag);\n } else {\n return [];\n }\n\n if (end && this.fuNalHeader != null) {\n const nal = Buffer.concat([\n Buffer.from([this.fuNalHeader]),\n ...this.fuParts,\n ]);\n this.reset();\n return [Buffer.concat([NAL_START_CODE_4B, nal])];\n }\n return [];\n }\n\n return [];\n }\n}\n","/**\n * H.265/HEVC utilities.\n * HVCC to Annex-B conversion, VPS/SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- HVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH265Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 2) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = (b0 >> 1) & 0x3f;\n return nalType <= 40;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.265 data from length-prefixed (HVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH265ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try HVCC → AnnexB conversion\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n if (looksLikeSingleH265Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n return data;\n}\n\n/**\n * Get H.265 NAL unit type from a NAL payload (without start code).\n */\nexport function getH265NalType(nalPayload: Buffer): number | null {\n if (nalPayload.length < 1) return null;\n const b0 = nalPayload[0];\n if (b0 === undefined) return null;\n if ((b0 & 0x80) !== 0) return null;\n return (b0 >> 1) & 0x3f;\n}\n\n/**\n * Check if an H.265 NAL unit type is an IRAP (Intra Random Access Point) picture.\n * IRAP types: BLA (16-18), IDR (19-20), CRA (21).\n */\nexport function isH265Irap(nalType: number): boolean {\n return nalType >= 16 && nalType <= 23;\n}\n\n/**\n * Check if an H.265 Annex-B access unit is a keyframe (IRAP + VPS + SPS + PPS).\n */\nexport function isH265KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasVps = false;\n let hasSps = false;\n let hasPps = false;\n let hasIrap = false;\n\n for (const nal of nals) {\n const nalType = getH265NalType(nal);\n if (nalType === null) continue;\n if (nalType === 32) hasVps = true;\n if (nalType === 33) hasSps = true;\n if (nalType === 34) hasPps = true;\n if (isH265Irap(nalType)) hasIrap = true;\n }\n\n return hasIrap && hasVps && hasSps && hasPps;\n}\n\n/**\n * Check if an H.265 Annex-B access unit contains an IRAP picture.\n */\nexport function isH265IrapAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const b0 = nal[0];\n if (b0 === undefined) continue;\n if ((b0 & 0x80) !== 0) continue;\n const nalType = (b0 >> 1) & 0x3f;\n if (isH265Irap(nalType)) return true;\n }\n return false;\n}\n\n/**\n * Extract VPS, SPS, and PPS from an H.265 Annex-B access unit.\n */\nexport function extractH265ParamSets(annexB: Buffer): {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n} {\n const nals = splitAnnexBToNals(annexB);\n let vps: Buffer | undefined;\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const nalType = (nal[0]! >> 1) & 0x3f;\n if (nalType === 32) vps = nal;\n else if (nalType === 33) sps = nal;\n else if (nalType === 34) pps = nal;\n }\n\n const out: { vps?: Buffer; sps?: Buffer; pps?: Buffer } = {};\n if (vps) out.vps = vps;\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n return out;\n}\n\n/**\n * H.265 RTP depacketizer (RFC 7798).\n * Handles single NAL units, AP aggregation, and FU fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H265RtpDepacketizer {\n private fuParts: Buffer[] | null = null;\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuParts = null;\n }\n\n push(payload: Buffer): Buffer[] {\n if (!payload || payload.length < 2) return [];\n\n const rtpPayload = H265RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n const h0 = payload[0]!;\n const h1 = payload[1]!;\n if ((h0 & 0x80) !== 0) return [];\n const nalType = (h0 >> 1) & 0x3f;\n\n // AP (48): aggregation packet\n if (nalType === 48) {\n let off = 2;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length) out.push(NAL_START_CODE_4B, nal);\n }\n return out.length ? [Buffer.concat(out)] : [];\n }\n\n // FU (49): fragmentation unit\n if (nalType === 49) {\n if (payload.length < 3) return [];\n const fuHeader = payload[2]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x3f;\n const orig0 = (h0 & 0x81) | ((origType & 0x3f) << 1);\n const orig1 = h1;\n const frag = payload.subarray(3);\n\n if (start) {\n this.fuParts = [NAL_START_CODE_4B, Buffer.from([orig0, orig1]), frag];\n } else {\n if (!this.fuParts) return [];\n this.fuParts.push(frag);\n }\n\n if (end) {\n if (!this.fuParts) return [];\n const out = Buffer.concat(this.fuParts);\n this.fuParts = null;\n return [out];\n }\n\n return [];\n }\n\n // Single NAL unit\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n}\n","import { lookup as dnsLookup } from 'node:dns/promises'\nimport { errMsg } from '@camstack/types'\nimport type { Logger } from './types.js'\n\n/**\n * Rewrite mDNS (`*.local`) candidates in an SDP by resolving each\n * hostname to an IP via the OS resolver.\n *\n * Chrome emits anonymised `<uuid>.local` host candidates by default\n * for privacy; werift's ICE agent has no mDNS resolver, so those\n * candidates never pair and the session sits at \"checking\" forever.\n *\n * On macOS the OS resolver delegates `.local` to Bonjour (built in); on\n * Linux it usually requires Avahi's NSS module. We parallelise the\n * lookups and silently drop candidates that don't resolve — a failed\n * lookup is the same as \"werift didn't use it\", so we're not making\n * the worst case worse. The original line is replaced in place, keeping\n * the rest of the candidate fields (priority, ufrag, type, …) intact.\n *\n * Used by both `AdaptiveSession` and `SharedSession` — pulled out into\n * its own module so the two paths stay identical.\n */\nexport async function resolveMdnsCandidatesInSdp(\n sdp: string,\n logger: Logger,\n sessionTag: string,\n): Promise<string> {\n const mdnsHostRe = /([0-9a-fA-F-]+\\.local)/g\n const hosts = new Set<string>()\n for (const match of sdp.matchAll(mdnsHostRe)) hosts.add(match[1]!)\n if (hosts.size === 0) return sdp\n\n const replacements = await Promise.all(\n [...hosts].map(async (host) => {\n try {\n const { address } = await dnsLookup(host, { family: 4 })\n logger.info('mDNS resolve succeeded', { meta: { sessionTag, host, address } })\n return [host, address] as const\n } catch (err) {\n logger.warn('mDNS resolve failed', { meta: { sessionTag, host, error: errMsg(err) } })\n return [host, null] as const\n }\n }),\n )\n\n let rewritten = sdp\n for (const [host, address] of replacements) {\n if (!address) continue\n const escaped = host.replace(/\\./g, '\\\\.')\n rewritten = rewritten.replace(new RegExp(escaped, 'g'), address)\n }\n return rewritten\n}\n","/**\n * Ported from Scrypted (BSD-2-Clause):\n * https://github.com/koush/scrypted/blob/main/plugins/homekit/src/types/camera/jitter-buffer.ts\n *\n * Used by H265Repacketizer to reorder out-of-order RTP packets within a\n * small jitter window before re-fragmenting. Reordering matters for FU\n * fragments arriving across an unreliable transport — over UDP, a late\n * FU_middle could otherwise break the running re-fragmentation state.\n *\n * No semantic changes — only types narrowed for our codebase.\n */\n\n/**\n * This is a subset of Werift's RtpPacket.\n */\nexport interface RtpPacket {\n payload: Buffer;\n header: {\n padding: boolean;\n marker: boolean;\n timestamp: number;\n sequenceNumber: number;\n };\n clone(): RtpPacket;\n serialize(): Buffer;\n}\n\nexport function sequenceNumberDistance(s1: number, s2: number): number {\n if (s2 === s1)\n return 0;\n const distance = s2 - s1;\n let rolloverDistance: number;\n if (s2 > s1)\n rolloverDistance = s1 + 0x10000 - s2;\n else\n rolloverDistance = s2 + 0x10000 - s1;\n\n if (Math.abs(distance) < Math.abs(rolloverDistance))\n return distance;\n return rolloverDistance;\n}\n\nexport function nextSequenceNumber(current: number, increment = 1) {\n return (current + increment + 0x10000) % 0x10000;\n}\n\nconst maxRtpTimestamp = BigInt(0xFFFFFFFF);\nexport function addRtpTimestamp(current: number, adjust: number) {\n return Number(maxRtpTimestamp & (BigInt(current) + BigInt(adjust)));\n}\n\nexport function isNextSequenceNumber(current: number, next: number) {\n return nextSequenceNumber(current) === next;\n}\n\nexport class JitterBuffer {\n lastSequenceNumber: number | undefined;\n pending: (RtpPacket | undefined)[] = [];\n\n constructor(public console: Console, public jitterSize: number,) {\n }\n\n flushPending(afterSequenceNumber: number, ret: RtpPacket[]): RtpPacket[] {\n if (!this.pending)\n return ret;\n\n const start = nextSequenceNumber(afterSequenceNumber);\n\n for (let i = 0; i < this.jitterSize; i++) {\n const index = (start + i) % this.jitterSize;\n const packet = this.pending[index];\n if (!packet)\n continue;\n const { sequenceNumber } = packet.header;\n const sd = sequenceNumberDistance(this.lastSequenceNumber ?? sequenceNumber, sequenceNumber);\n // packet needs to be purged from the the buffer for being too old.\n if (sd <= 0) {\n this.console.log('jitter buffer purged packet:', sequenceNumber);\n this.pending[index] = undefined;\n ret.push(packet);\n }\n else if (sd === 1) {\n this.pending[index] = undefined;\n this.lastSequenceNumber = sequenceNumber;\n ret.push(packet);\n }\n else {\n // can't do anything with this packet yet.\n }\n }\n return ret;\n }\n\n queue(packet: RtpPacket): RtpPacket[] {\n if (this.lastSequenceNumber === undefined || isNextSequenceNumber(this.lastSequenceNumber, packet.header.sequenceNumber)) {\n this.lastSequenceNumber = packet.header.sequenceNumber;\n return this.flushPending(this.lastSequenceNumber, [packet]);\n }\n\n const { sequenceNumber } = packet.header;\n const packetDistance = sequenceNumberDistance(this.lastSequenceNumber, sequenceNumber);\n // late/duplicate packet\n if (packetDistance <= 0)\n return [];\n\n const ret: RtpPacket[] = [];\n\n // missed/late bunch of packets\n if (packetDistance > this.jitterSize) {\n // this.console.log('jitter buffer skipped packets:', packetDistance);\n const { lastSequenceNumber } = this;\n this.lastSequenceNumber = sequenceNumber - this.jitterSize;\n // use the previous sequence number to flush any packets that are too old compared\n // to the new sequence number.\n this.flushPending(lastSequenceNumber, ret);\n }\n\n this.pending[packet.header.sequenceNumber % this.jitterSize] = packet;\n return this.flushPending(this.lastSequenceNumber, ret);\n }\n}\n","/**\n * Ported from Scrypted (BSD-2-Clause):\n * https://github.com/koush/scrypted/blob/main/plugins/webrtc/src/h265-packetizer.ts\n *\n * Re-fragments RTP packets coming straight off the camera so they fit\n * the WebRTC peer's MTU (~1200 bytes) WITHOUT re-encoding or rebuilding\n * the RTP header from scratch. Source SSRC/seq/timestamp are preserved\n * (only adjusted by werift's `replaceRTP` offset machinery on send).\n *\n * Forwarding source RTP through this repacketizer fixes the\n * `framesAssembledFromMultiplePackets = 0` issue our prior\n * \"depacketize → AnnexB → re-packetize from scratch\" path produced\n * for H.265 over WebRTC. Original RTP header layout is what Chrome's\n * HEVC depacketizer expects.\n */\nimport { isNextSequenceNumber, JitterBuffer, RtpPacket } from \"./jitter-buffer.js\";\n\n// H.265 NAL unit types\n// https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/hevc/hevc.h\nconst NAL_TYPE_TRAIL_N = 0;\nconst _UNUSED_NAL_TYPE_TRAIL_R = 1;\nconst _UNUSED_NAL_TYPE_TSA_N = 2;\nconst _UNUSED_NAL_TYPE_TSA_R = 3;\nconst _UNUSED_NAL_TYPE_STSA_N = 4;\nconst _UNUSED_NAL_TYPE_STSA_R = 5;\nconst _UNUSED_NAL_TYPE_RADL_N = 6;\nconst _UNUSED_NAL_TYPE_RADL_R = 7;\nconst _UNUSED_NAL_TYPE_RASL_N = 8;\nconst _UNUSED_NAL_TYPE_RASL_R = 9;\nconst _UNUSED_NAL_TYPE_VCL_N10 = 10;\nconst _UNUSED_NAL_TYPE_VCL_R11 = 11;\nconst _UNUSED_NAL_TYPE_VCL_N12 = 12;\nconst _UNUSED_NAL_TYPE_VCL_R13 = 13;\nconst _UNUSED_NAL_TYPE_VCL_N14 = 14;\nconst _UNUSED_NAL_TYPE_VCL_R15 = 15;\nconst NAL_TYPE_BLA_W_LP = 16;\nconst NAL_TYPE_BLA_W_RADL = 17;\nconst NAL_TYPE_BLA_N_LP = 18;\nconst NAL_TYPE_IDR_W_RADL = 19;\nconst NAL_TYPE_IDR_N_LP = 20;\nconst NAL_TYPE_CRA_NUT = 21;\nconst _UNUSED_NAL_TYPE_RSV_IRAP_VCL22 = 22;\nconst NAL_TYPE_RSV_IRAP_VCL23 = 23;\nconst _UNUSED_NAL_TYPE_RSV_VCL24 = 24;\nconst _UNUSED_NAL_TYPE_RSV_VCL25 = 25;\nconst _UNUSED_NAL_TYPE_RSV_VCL26 = 26;\nconst _UNUSED_NAL_TYPE_RSV_VCL27 = 27;\nconst _UNUSED_NAL_TYPE_RSV_VCL28 = 28;\nconst _UNUSED_NAL_TYPE_RSV_VCL29 = 29;\nconst _UNUSED_NAL_TYPE_RSV_VCL30 = 30;\nconst _UNUSED_NAL_TYPE_RSV_VCL31 = 31;\nconst NAL_TYPE_VPS = 32;\nconst NAL_TYPE_SPS = 33;\nconst NAL_TYPE_PPS = 34;\nconst NAL_TYPE_AUD = 35;\nconst _UNUSED_NAL_TYPE_EOS_NUT = 36;\nconst _UNUSED_NAL_TYPE_EOB_NUT = 37;\nconst _UNUSED_NAL_TYPE_FD_NUT = 38;\nconst NAL_TYPE_SEI_PREFIX = 39;\nconst NAL_TYPE_SEI_SUFFIX = 40;\nconst _UNUSED_NAL_TYPE_RSV_NVCL41 = 41;\nconst _UNUSED_NAL_TYPE_RSV_NVCL42 = 42;\nconst _UNUSED_NAL_TYPE_RSV_NVCL43 = 43;\nconst _UNUSED_NAL_TYPE_RSV_NVCL44 = 44;\nconst _UNUSED_NAL_TYPE_RSV_NVCL45 = 45;\nconst _UNUSED_NAL_TYPE_RSV_NVCL46 = 46;\nconst _UNUSED_NAL_TYPE_RSV_NVCL47 = 47;\n// RTP payload format for H.265 defines these special types\nconst NAL_TYPE_AP = 48; // Aggregation Packet\nconst NAL_TYPE_FU = 49; // Fragmentation Unit\nconst _UNUSED_NAL_TYPE_UNSPEC50 = 50;\nconst _UNUSED_NAL_TYPE_UNSPEC51 = 51;\nconst _UNUSED_NAL_TYPE_UNSPEC52 = 52;\nconst _UNUSED_NAL_TYPE_UNSPEC53 = 53;\nconst _UNUSED_NAL_TYPE_UNSPEC54 = 54;\nconst _UNUSED_NAL_TYPE_UNSPEC55 = 55;\nconst _UNUSED_NAL_TYPE_UNSPEC56 = 56;\nconst _UNUSED_NAL_TYPE_UNSPEC57 = 57;\nconst _UNUSED_NAL_TYPE_UNSPEC58 = 58;\nconst _UNUSED_NAL_TYPE_UNSPEC59 = 59;\nconst _UNUSED_NAL_TYPE_UNSPEC60 = 60;\nconst _UNUSED_NAL_TYPE_UNSPEC61 = 61;\nconst _UNUSED_NAL_TYPE_UNSPEC62 = 62;\nconst _UNUSED_NAL_TYPE_UNSPEC63 = 63;\n\n\nconst NAL_HEADER_SIZE = 2; // H265 has 2-byte NAL header\nconst FU_HEADER_SIZE = 3; // 2-byte NAL header + 1-byte FU header\nconst LENGTH_FIELD_SIZE = 2;\nconst AP_HEADER_SIZE = NAL_HEADER_SIZE + LENGTH_FIELD_SIZE;\n\n// Function to extract NAL unit type from H.265 NAL header\nfunction getNalType(data: Buffer): number {\n return (data[0]! & 0x7E) >> 1; // 6 bits starting from bit 1\n}\n\nfunction isKeyFrame(nalType: number): boolean {\n // For IDR frames, send codec info first\n if (nalType === NAL_TYPE_IDR_W_RADL || nalType === NAL_TYPE_IDR_N_LP ||\n nalType === NAL_TYPE_BLA_W_LP || nalType === NAL_TYPE_BLA_W_RADL ||\n nalType === NAL_TYPE_BLA_N_LP || nalType === NAL_TYPE_CRA_NUT) {\n return true;\n }\n return false;\n}\n\n// Function to depacketize Aggregation Packets (similar to STAP-A in H.264)\nexport function depacketizeAP(data: Buffer): Buffer[] {\n const ret: Buffer[] = [];\n let lastPos: number | undefined;\n let pos = NAL_HEADER_SIZE;\n while (pos < data.length) {\n if (lastPos !== undefined)\n ret.push(data.subarray(lastPos, pos));\n const naluSize = data.readUInt16BE(pos);\n pos += LENGTH_FIELD_SIZE;\n lastPos = pos;\n pos += naluSize;\n }\n if (lastPos !== undefined) ret.push(data.subarray(lastPos));\n return ret;\n}\n\nexport function splitH265NaluStartCode(data: Buffer) {\n const ret: Buffer[] = [];\n let previous = 0;\n let offset = 0;\n const maybeAddSlice = () => {\n const slice = data.subarray(previous, offset);\n if (slice.length)\n ret.push(slice);\n offset += 4;\n previous = offset;\n }\n\n while (offset < data.length - 4) {\n const startCode = data.readUInt32BE(offset);\n if (startCode === 1) {\n maybeAddSlice();\n }\n else {\n offset++;\n }\n }\n offset = data.length;\n maybeAddSlice();\n\n return ret;\n}\n\nexport interface H265CodecInfo {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n seiPrefix?: Buffer;\n seiSuffix?: Buffer;\n}\n\nexport class H265Repacketizer {\n extraPackets = 0;\n fuMax!: number;\n pendingFU: RtpPacket[] | undefined;\n // the AP packet that will be sent before an IDR frame.\n ap: RtpPacket | undefined;\n fuMin!: number;\n // h265 can send multiple IDR and TRAIL_R frames per RTP timestamp.\n // the repurposed h264-packetizer code sends codec information before every\n // IDR frame.\n // the IDR burst frames are interleaved with codec information which causes decode failure.\n // The interleaved codec issues does not cause issues with Safari, but does cause\n // Chrome to send repeated Picture Loss Indication (PLI) requests.\n // the fix is to send the codec information before the first IDR frame,\n // then wait for the marker bit, which denotes the last packet for a given rtp\n // timestamp, to reset the flag.\n // expected result:\n // AP codec info -> IDR frame 1 -> IDR frame 2 -> ... -> IDR frame N with marker bit set\n sentMarker = true;\n\n constructor(public console: Console, private maxPacketSize: number, public codecInfo?: H265CodecInfo, public jitterBuffer = new JitterBuffer(console, 4)) {\n this.setMaxPacketSize(maxPacketSize);\n }\n\n setMaxPacketSize(maxPacketSize: number) {\n this.maxPacketSize = maxPacketSize;\n // 12 is the rtp/srtp header size.\n this.fuMax = maxPacketSize - FU_HEADER_SIZE;\n this.fuMin = Math.round(maxPacketSize * .8);\n }\n\n ensureCodecInfo(): H265CodecInfo {\n if (!this.codecInfo) {\n this.codecInfo = {};\n }\n return this.codecInfo;\n }\n\n updateVps(vps: Buffer): void {\n this.ensureCodecInfo().vps = vps;\n }\n\n updateSps(sps: Buffer): void {\n this.ensureCodecInfo().sps = sps;\n }\n\n updatePps(pps: Buffer): void {\n this.ensureCodecInfo().pps = pps;\n }\n\n updateSeiPrefix(sei: Buffer): void {\n this.ensureCodecInfo().seiPrefix = sei;\n }\n\n updateSeiSuffix(sei: Buffer): void {\n this.ensureCodecInfo().seiSuffix = sei;\n }\n\n shouldFilter(_nalType: number): boolean {\n // Currently nothing is filtered, but this could be customized\n return false;\n }\n\n // Fragmentation Unit (FU) for H.265\n // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3\n packetizeFU(data: Buffer, noStart?: boolean, noEnd?: boolean): Buffer[] {\n // Handle both normal packets and FU packets.\n const initialNalType = getNalType(data);\n\n // Check if the data is already a fragmentation unit\n if (initialNalType === NAL_TYPE_FU) {\n // Extract original NAL header information\n const originalNalType = data[2]! & 0x3F; // 6 bits\n const isFuStart = !!(data[2]! & 0x80);\n const isFuEnd = !!(data[2]! & 0x40);\n const isFuMiddle = !isFuStart && !isFuEnd;\n\n // Reconstruct the original NAL header\n const layerId = ((data[0]! & 0x01) << 5) | ((data[1]! & 0xF8) >> 3);\n const tid = data[1]! & 0x07;\n\n const originalNalHeader = Buffer.alloc(2);\n originalNalHeader[0] = (originalNalType << 1) | (layerId >> 5);\n originalNalHeader[1] = ((layerId & 0x1F) << 3) | tid;\n\n data = Buffer.concat([originalNalHeader, data.subarray(FU_HEADER_SIZE)]);\n\n if (isFuStart) {\n noEnd = true;\n }\n else if (isFuEnd) {\n noStart = true;\n }\n else if (isFuMiddle) {\n noStart = true;\n noEnd = true;\n }\n }\n\n // Extract information from the NAL header\n const nalType = getNalType(data);\n const layerId = ((data[0]! & 0x01) << 5) | ((data[1]! & 0xF8) >> 3);\n const tid = data[1]! & 0x07;\n\n // Construct the FU NAL header\n const fuNalHeader = Buffer.alloc(2);\n fuNalHeader[0] = (NAL_TYPE_FU << 1) | (layerId >> 5);\n fuNalHeader[1] = ((layerId & 0x1F) << 3) | tid;\n\n // Construct FU headers for different positions\n const fuHeaderMiddle = Buffer.from([...fuNalHeader, nalType]);\n const fuHeaderStart = noStart ? fuHeaderMiddle : Buffer.from([...fuNalHeader, nalType | 0x80]);\n const fuHeaderEnd = noEnd ? fuHeaderMiddle : Buffer.from([...fuNalHeader, nalType | 0x40]);\n let fuHeader = fuHeaderStart;\n\n const packages: Buffer[] = [];\n let offset = NAL_HEADER_SIZE;\n\n while (offset < data.length) {\n let payload: Buffer;\n const packageSize = Math.min(this.fuMax, data.length - offset);\n payload = data.subarray(offset, offset + packageSize);\n offset += packageSize;\n\n if (offset === data.length) {\n fuHeader = fuHeaderEnd;\n }\n\n packages.push(Buffer.concat([fuHeader, payload]));\n\n fuHeader = fuHeaderMiddle;\n }\n\n return packages;\n }\n\n // Aggregation Packet (AP) for H.265 (similar to STAP-A in H.264)\n // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2\n packetizeOneAP(datas: Buffer[]): Buffer {\n if (!datas.length)\n throw new Error('packetizeOneAP requires at least one NAL');\n\n let counter = 0;\n let availableSize = this.maxPacketSize - AP_HEADER_SIZE;\n\n // In H.265, AP uses a fixed header with NAL type 48\n const apHeader = Buffer.alloc(2);\n apHeader[0] = NAL_TYPE_AP << 1; // Type 48, no layer ID in first byte\n apHeader[1] = 0x01; // Default temporal ID = 1, no layer ID\n\n const payload: Buffer[] = [apHeader];\n\n while (datas.length && datas[0]!.length + LENGTH_FIELD_SIZE <= availableSize && counter < 9) {\n const nalu = datas.shift()!;\n availableSize -= LENGTH_FIELD_SIZE + nalu.length;\n counter += 1;\n const lengthField = Buffer.alloc(2);\n lengthField.writeUInt16BE(nalu.length, 0);\n payload.push(lengthField, nalu);\n }\n\n // If no NALUs fit, return the first one for FU packetization\n if (counter === 0)\n return datas.shift()!;\n\n // A single NALU AP is unnecessary, return the NALU itself\n if (counter === 1) {\n return payload[2]!; // Skip header and length field\n }\n\n return Buffer.concat(payload);\n }\n\n packetizeAP(datas: Buffer[]) {\n const ret: Buffer[] = [];\n while (datas.length) {\n const nalu = this.packetizeOneAP(datas);\n if (nalu.length < this.maxPacketSize) {\n ret.push(nalu);\n continue;\n }\n const fus = this.packetizeFU(nalu);\n ret.push(...fus);\n }\n return ret;\n }\n\n createPacket(rtp: RtpPacket, data: Buffer, marker: boolean) {\n const ret = rtp.clone();\n ret.header.sequenceNumber = (rtp.header.sequenceNumber + this.extraPackets + 0x10000) % 0x10000;\n ret.header.marker = marker;\n ret.header.padding = false;\n ret.payload = data;\n if (data.length > this.maxPacketSize)\n this.console.warn('packet exceeded max packet size. this may be a bug.');\n this.sentMarker = ret.header.marker;\n return ret;\n }\n\n flushPendingFU(ret: RtpPacket[]) {\n const pending = this.pendingFU;\n if (!pending || pending.length === 0)\n return;\n\n // Defragmenting assumes packets are sorted by sequence number,\n // and are all available, which is guaranteed over rtsp/tcp, but not over rtp/udp.\n const first = pending[0]!;\n const last = pending[pending.length - 1]!;\n const originalNalType = first.payload[2]! & 0x3F;\n const hasFuStart = !!(first.payload[2]! & 0x80);\n const hasFuEnd = !!(last.payload[2]! & 0x40);\n\n // Extract layerId and tid from FU header\n const layerId = ((first.payload[0]! & 0x01) << 5) | ((first.payload[1]! & 0xF8) >> 3);\n const tid = first.payload[1]! & 0x07;\n\n // Reconstruct original NAL header\n const originalNalHeader = Buffer.alloc(2);\n originalNalHeader[0] = (originalNalType << 1) | (layerId >> 5);\n originalNalHeader[1] = ((layerId & 0x1F) << 3) | tid;\n\n const getDefragmentedPendingFu = (): Buffer => {\n const originalFragments = pending.map(packet => packet.payload.subarray(FU_HEADER_SIZE));\n originalFragments.unshift(originalNalHeader);\n return Buffer.concat(originalFragments);\n }\n\n // Handle special case for VPS/SPS/PPS in FU (not standard but seen in some implementations)\n if (originalNalType === NAL_TYPE_VPS || originalNalType === NAL_TYPE_SPS) {\n const defragmented = getDefragmentedPendingFu();\n const splits = splitH265NaluStartCode(defragmented);\n\n while (splits.length) {\n const split = splits.shift()!;\n const splitNaluType = getNalType(split);\n\n if (splitNaluType === NAL_TYPE_VPS) {\n this.updateVps(split);\n }\n else if (splitNaluType === NAL_TYPE_SPS) {\n this.updateSps(split);\n }\n else if (splitNaluType === NAL_TYPE_PPS) {\n this.updatePps(split);\n }\n else {\n // For IDR frames, send codec info first\n if (isKeyFrame(splitNaluType)) {\n this.maybeSendAPCodecInfo(first, ret);\n }\n\n this.fragment(first, ret, {\n payload: split,\n noStart: !hasFuStart,\n noEnd: !hasFuEnd,\n marker: last.header.marker,\n });\n }\n }\n }\n else {\n // Process regular fragmentation units\n while (pending.length) {\n const fu = pending[0]!;\n if (fu.payload.length > this.maxPacketSize || fu.payload.length < this.fuMin)\n break;\n pending.shift();\n ret.push(this.createPacket(fu, fu.payload, fu.header.marker));\n }\n\n if (!pending.length) {\n this.pendingFU = undefined;\n return;\n }\n\n // Re-fragment remaining FU packets\n const refragFirst = pending[0]!;\n const refragLast = pending[pending.length - 1]!;\n const refragHasFuStart = !!(refragFirst.payload[2]! & 0x80);\n const refragHasFuEnd = !!(refragLast.payload[2]! & 0x40);\n\n const defragmented = getDefragmentedPendingFu();\n\n this.fragment(refragFirst, ret, {\n payload: defragmented,\n noStart: !refragHasFuStart,\n noEnd: !refragHasFuEnd,\n marker: refragLast.header.marker\n });\n }\n\n this.extraPackets -= pending.length - 1;\n this.pendingFU = undefined;\n }\n\n createRtpPackets(packet: RtpPacket, nalus: Buffer[], ret: RtpPacket[], hadMarker = packet.header.marker) {\n nalus.forEach((packetized, index) => {\n if (index !== 0)\n this.extraPackets++;\n const marker = hadMarker && index === nalus.length - 1;\n ret.push(this.createPacket(packet, packetized, marker));\n });\n }\n\n maybeSendAPCodecInfo(packet: RtpPacket, ret: RtpPacket[]) {\n if (this.ap) {\n // AP with codec information was sent recently, no need to send codec info.\n this.ap = undefined;\n return;\n }\n\n // can not send codec info if in the middle of sending packets for a specific rtp timestamp.\n if (!this.sentMarker)\n return;\n\n // vps is not required.\n if (!this.codecInfo?.sps || !this.codecInfo?.pps)\n return;\n\n const agg = [this.codecInfo.sps, this.codecInfo.pps];\n if (this.codecInfo.vps)\n agg.unshift(this.codecInfo.vps);\n if (this.codecInfo?.seiPrefix)\n agg.push(this.codecInfo.seiPrefix);\n if (this.codecInfo?.seiSuffix)\n agg.push(this.codecInfo.seiSuffix);\n\n const aggregates = this.packetizeAP(agg);\n if (aggregates.length !== 1) {\n this.console.error('expected only 1 packet for vps/sps/pps AP');\n return;\n }\n // This AP only contains codec info (and no frame data), thus the marker bit should not be set.\n this.createRtpPackets(packet, aggregates, ret, false);\n this.extraPackets++;\n }\n\n // Fragment payload into multiple packets as needed\n fragment(packet: RtpPacket, ret: RtpPacket[], fuOptions: {\n payload: Buffer;\n noStart: boolean;\n noEnd: boolean;\n marker: boolean;\n } = {\n payload: packet.payload,\n noStart: false,\n noEnd: false,\n marker: packet.header.marker\n }) {\n const { payload, noStart, noEnd, marker } = fuOptions;\n if (payload.length > this.maxPacketSize || noStart || noEnd) {\n const fragments = this.packetizeFU(payload, noStart, noEnd);\n this.createRtpPackets(packet, fragments, ret, marker);\n }\n else {\n // Can send this packet as is\n ret.push(this.createPacket(packet, payload, marker));\n }\n }\n\n repacketize<T extends RtpPacket>(packet: T): T[] {\n const ret: T[] = [];\n for (const dejittered of this.jitterBuffer.queue(packet)) {\n this.repacketizeOne(dejittered, ret);\n }\n return ret;\n }\n\n repacketizeOne(packet: RtpPacket, ret: RtpPacket[]) {\n // Filter empty packets\n if (!packet.payload.length) {\n this.flushPendingFU(ret);\n this.extraPackets--;\n return;\n }\n\n const nalType = getNalType(packet.payload);\n\n // Fragmented packets must share a timestamp\n if (this.pendingFU && this.pendingFU[0]!.header.timestamp !== packet.header.timestamp) {\n this.flushPendingFU(ret);\n }\n\n if (nalType === NAL_TYPE_FU) {\n // Handle Fragmentation Units\n const data = packet.payload;\n const originalNalType = data[2]! & 0x3F;\n\n if (this.shouldFilter(originalNalType)) {\n this.extraPackets--;\n return;\n }\n\n const isFuStart = !!(data[2]! & 0x80);\n const isFuEnd = !!(data[2]! & 0x40);\n\n if (isFuStart) {\n if (this.pendingFU)\n this.console.error('FU restarted. skipping refragmentation of previous FU.', originalNalType);\n\n this.pendingFU = undefined;\n\n // If this is an IDR frame, but no codec info has been sent via an AP, send it\n if (originalNalType === NAL_TYPE_IDR_W_RADL || originalNalType === NAL_TYPE_IDR_N_LP) {\n this.maybeSendAPCodecInfo(packet, ret);\n }\n }\n else {\n if (this.pendingFU) {\n // Check if packets were missing from the previously queued FU packets\n const last = this.pendingFU[this.pendingFU.length - 1]!;\n if (!isNextSequenceNumber(last.header.sequenceNumber, packet.header.sequenceNumber)) {\n this.console.error('FU packet missing. skipping refragmentation.', originalNalType);\n return;\n }\n }\n }\n\n if (!this.pendingFU)\n this.pendingFU = [];\n\n this.pendingFU.push(packet);\n\n if (isFuEnd) {\n this.flushPendingFU(ret);\n }\n else if (this.pendingFU.reduce((p, c) => p + c.payload.length - FU_HEADER_SIZE, NAL_HEADER_SIZE) > this.maxPacketSize) {\n // Refragment FU packets as they are received\n const last = this.pendingFU[this.pendingFU.length - 1]!.clone();\n const partial: RtpPacket[] = [];\n this.flushPendingFU(partial);\n // Retain a FU packet to validate subsequent FU packets\n const retain = partial.pop()!;\n last.payload = retain.payload;\n this.pendingFU = [last];\n ret.push(...partial);\n this.sentMarker = false;\n }\n }\n else if (nalType === NAL_TYPE_AP) {\n this.flushPendingFU(ret);\n\n let hasVps = false;\n let hasSps = false;\n let hasPps = false;\n\n // Process Aggregation Packets\n const depacketized = depacketizeAP(packet.payload);\n depacketized.forEach(payload => {\n const nalType = getNalType(payload);\n if (nalType === NAL_TYPE_VPS) {\n hasVps = true;\n this.updateVps(payload);\n }\n else if (nalType === NAL_TYPE_SPS) {\n hasSps = true;\n this.updateSps(payload);\n }\n else if (nalType === NAL_TYPE_PPS) {\n hasPps = true;\n this.updatePps(payload);\n }\n else if (nalType === NAL_TYPE_SEI_PREFIX) {\n this.updateSeiPrefix(payload);\n }\n else if (nalType === NAL_TYPE_SEI_SUFFIX) {\n this.updateSeiSuffix(payload);\n }\n else if (nalType === NAL_TYPE_AUD) {\n // Access Unit Delimiter - typically a no-op\n }\n else if (nalType >= NAL_TYPE_TRAIL_N && nalType <= 9 /* NAL_TYPE_RASL_R */) {\n // Various slice types - typically VCL NAL units\n }\n else if (nalType === NAL_TYPE_IDR_W_RADL) {\n // IDR\n }\n else if (nalType === 0) {\n // NAL delimiter or something. usually empty.\n }\n else {\n this.console.warn('Skipped an AP type.', nalType);\n }\n });\n\n // Log that an AP with codec info was sent\n if (hasVps && hasSps && hasPps && packet.payload.length <= this.maxPacketSize) {\n this.ap = packet;\n\n const ap = this.packetizeAP(depacketized);\n this.createRtpPackets(packet, ap, ret);\n }\n else {\n // h265 can send multiple IDR or TRAIL_R per rtp timestamp\n // so this can result in a large aggregation packet\n // if the MTU is large (like 64k on localhost udp).\n // the aggregation packet must be depacketized and potentially\n // fragmented.\n const fus: Buffer[] = [];\n while (depacketized.length) {\n const next = depacketized.shift()!;\n if (next.length <= this.maxPacketSize) {\n fus.push(next);\n continue;\n }\n fus.push(...this.packetizeFU(next));\n }\n this.createRtpPackets(packet, fus, ret);\n }\n }\n else if (nalType <= NAL_TYPE_RSV_IRAP_VCL23 || (nalType >= NAL_TYPE_VPS && nalType <= NAL_TYPE_SEI_SUFFIX)) {\n this.flushPendingFU(ret);\n\n if (this.shouldFilter(nalType)) {\n this.extraPackets--;\n return;\n }\n\n // Handle codec information\n if (nalType === NAL_TYPE_VPS) {\n this.extraPackets--;\n this.updateVps(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_SPS) {\n this.extraPackets--;\n this.updateSps(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_PPS) {\n this.extraPackets--;\n this.updatePps(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_SEI_PREFIX) {\n this.extraPackets--;\n this.updateSeiPrefix(packet.payload);\n return;\n }\n else if (nalType === NAL_TYPE_SEI_SUFFIX) {\n this.extraPackets--;\n this.updateSeiSuffix(packet.payload);\n return;\n }\n\n if (this.shouldFilter(nalType)) {\n this.extraPackets--;\n return;\n }\n\n // For IDR frames, send codec info first\n if (isKeyFrame(nalType)) {\n this.maybeSendAPCodecInfo(packet, ret);\n }\n\n this.fragment(packet, ret);\n }\n else {\n this.console.error('unknown NAL unit type ' + nalType);\n this.extraPackets--;\n }\n\n return;\n }\n}\n// Keep unused NAL_TYPE_ constants alive — they document the full RFC\n// 7798 NAL-type table even when only a subset is referenced by the\n// packetizer logic.\nvoid [_UNUSED_NAL_TYPE_TRAIL_R,_UNUSED_NAL_TYPE_TSA_N,_UNUSED_NAL_TYPE_TSA_R,_UNUSED_NAL_TYPE_STSA_N,_UNUSED_NAL_TYPE_STSA_R,_UNUSED_NAL_TYPE_RADL_N,_UNUSED_NAL_TYPE_RADL_R,_UNUSED_NAL_TYPE_RASL_N,_UNUSED_NAL_TYPE_RASL_R,_UNUSED_NAL_TYPE_VCL_N10,_UNUSED_NAL_TYPE_VCL_R11,_UNUSED_NAL_TYPE_VCL_N12,_UNUSED_NAL_TYPE_VCL_R13,_UNUSED_NAL_TYPE_VCL_N14,_UNUSED_NAL_TYPE_VCL_R15,_UNUSED_NAL_TYPE_RSV_IRAP_VCL22,_UNUSED_NAL_TYPE_RSV_VCL24,_UNUSED_NAL_TYPE_RSV_VCL25,_UNUSED_NAL_TYPE_RSV_VCL26,_UNUSED_NAL_TYPE_RSV_VCL27,_UNUSED_NAL_TYPE_RSV_VCL28,_UNUSED_NAL_TYPE_RSV_VCL29,_UNUSED_NAL_TYPE_RSV_VCL30,_UNUSED_NAL_TYPE_RSV_VCL31,_UNUSED_NAL_TYPE_EOS_NUT,_UNUSED_NAL_TYPE_EOB_NUT,_UNUSED_NAL_TYPE_FD_NUT,_UNUSED_NAL_TYPE_RSV_NVCL41,_UNUSED_NAL_TYPE_RSV_NVCL42,_UNUSED_NAL_TYPE_RSV_NVCL43,_UNUSED_NAL_TYPE_RSV_NVCL44,_UNUSED_NAL_TYPE_RSV_NVCL45,_UNUSED_NAL_TYPE_RSV_NVCL46,_UNUSED_NAL_TYPE_RSV_NVCL47,_UNUSED_NAL_TYPE_UNSPEC50,_UNUSED_NAL_TYPE_UNSPEC51,_UNUSED_NAL_TYPE_UNSPEC52,_UNUSED_NAL_TYPE_UNSPEC53,_UNUSED_NAL_TYPE_UNSPEC54,_UNUSED_NAL_TYPE_UNSPEC55,_UNUSED_NAL_TYPE_UNSPEC56,_UNUSED_NAL_TYPE_UNSPEC57,_UNUSED_NAL_TYPE_UNSPEC58,_UNUSED_NAL_TYPE_UNSPEC59,_UNUSED_NAL_TYPE_UNSPEC60,_UNUSED_NAL_TYPE_UNSPEC61,_UNUSED_NAL_TYPE_UNSPEC62,_UNUSED_NAL_TYPE_UNSPEC63]\n","/**\n * Adaptive WebRTC session — extends the base session pattern with:\n * - RTCP Receiver Report monitoring (packet loss, jitter, RTT)\n * - Source replacement (replaceTrack for seamless quality switching)\n * - Stats emission for the AdaptiveController\n *\n * Uses werift (optional peer dependency) for server-side WebRTC.\n */\n\nimport type {\n FrameSource,\n Logger,\n VideoCodec,\n AudioCodec,\n} from \"./types.js\";\nimport { splitAnnexBToNals } from \"./nal-utils.js\";\nimport { convertH264ToAnnexB, isH264IdrAccessUnit } from \"./h264-utils.js\";\nimport { convertH265ToAnnexB, isH265IrapAccessUnit } from \"./h265-utils.js\";\nimport { resolveMdnsCandidatesInSdp } from \"./mdns-resolve.js\";\nimport { H265Repacketizer, type H265CodecInfo } from \"./h265-repacketizer.js\";\nimport type {\n WeriftModule,\n WeriftPeerConnection,\n WeriftMediaStreamTrack,\n WeriftRtpSender,\n WeriftTransceiver,\n WeriftPcOptions,\n WeriftIceServer,\n} from \"./werift-types.js\";\nimport { errMsg } from '@camstack/types'\n\n// ---------------------------------------------------------------------------\n// Werift type aliases (lazy-loaded optional peer dep)\n// ---------------------------------------------------------------------------\n\nlet _werift: WeriftModule | undefined;\n\nasync function loadWerift(): Promise<WeriftModule> {\n if (_werift) return _werift;\n try {\n const moduleName = \"werift\";\n // Dynamic import via Function to avoid bundler static analysis\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- dynamic import via Function to avoid bundler static analysis\n _werift = await (Function(\"m\", \"return import(m)\")(moduleName) as Promise<WeriftModule>);\n return _werift!;\n } catch {\n throw new Error(\n \"The 'werift' package is required for WebRTC support but is not installed. \" +\n \"Install it with: npm install werift\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Unified ICE server entry — RTCIceServer-compatible. Covers both STUN\n * (no credentials) and TURN (username + credential). We keep a single\n * list instead of splitting stun/turn because the downstream werift API\n * takes a flat `iceServers` array and the split-only added ceremony.\n */\nexport interface IceServerEntry {\n urls: string | string[];\n username?: string;\n credential?: string;\n}\n\nexport interface AdaptiveSessionOptions {\n sessionId: string;\n source: FrameSource;\n intercom?: {\n onAudioReceived: (data: Buffer, format: AudioCodec) => void | Promise<void>;\n };\n iceConfig?: {\n /** Flat RTCIceServer-compatible list (STUN + TURN merged). */\n iceServers?: readonly IceServerEntry[];\n portRange?: [number, number];\n additionalHostAddresses?: readonly string[];\n };\n /** Callback for RTCP stats (called every ~3s). */\n onStats?: (stats: SessionStats) => void;\n /** Enable verbose frame/RTP logging. */\n debug?: boolean;\n /** Source video codec. Determines SDP negotiation + RTP packetization. */\n sourceCodec?: 'H264' | 'H265';\n logger: Logger;\n}\n\nexport interface SessionStats {\n sessionId: string;\n /** Fraction of packets lost (0.0–1.0). */\n packetLoss: number;\n /** Interarrival jitter in ms. */\n jitterMs: number;\n /** Round-trip time in ms (from RTCP SR/RR). */\n rttMs: number;\n /** Total packets received. */\n packetsReceived: number;\n /** Total packets lost. */\n packetsLost: number;\n /** Timestamp. */\n timestamp: number;\n}\n\nexport interface SessionInfo {\n sessionId: string;\n state: \"new\" | \"connecting\" | \"connected\" | \"disconnected\" | \"closed\";\n createdAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveSession\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveSession {\n private readonly sessionId: string;\n private source: FrameSource;\n private readonly logger: Logger;\n private readonly intercom: AdaptiveSessionOptions[\"intercom\"];\n private readonly iceConfig: AdaptiveSessionOptions[\"iceConfig\"];\n private readonly onStats: AdaptiveSessionOptions[\"onStats\"];\n debug: boolean;\n private readonly sourceCodec: 'H264' | 'H265';\n /** Codec actually negotiated with the browser after SDP answer. */\n private negotiatedCodec: 'H264' | 'H265' = 'H264';\n /** True when source is H.265 but browser negotiated H.264 — needs transcode. */\n get needsTranscode(): boolean { return this.sourceCodec === 'H265' && this.negotiatedCodec === 'H264' }\n private _firstKeyFrame: number | undefined;\n /**\n * Last seen SPS and PPS NALs. Many cameras send SPS/PPS only once\n * at stream start (not inline with every IDR). We cache them so\n * PLI-triggered keyframe re-sends include the parameter sets the\n * decoder needs to re-initialise.\n */\n private lastSps: Buffer | null = null;\n private lastPps: Buffer | null = null;\n /** H.265 VPS (Video Parameter Set) — required before every IRAP for decoder init. */\n private lastVps: Buffer | null = null;\n /** Whether replaceRTP has been called on the video sender to sync SSRC/seq. */\n private videoRtpSynced = false;\n private readonly createdAt: number;\n\n private state: SessionInfo[\"state\"] = \"new\";\n private pc: WeriftPeerConnection | null = null;\n private videoTrack: WeriftMediaStreamTrack | null = null;\n private audioTrack: WeriftMediaStreamTrack | null = null;\n /** Transceiver senders for direct sendRtp (more reliable than track.writeRtp) */\n private videoSender: WeriftRtpSender | null = null;\n private audioSender: WeriftRtpSender | null = null;\n private feedAbort: AbortController | null = null;\n private closed = false;\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n\n /**\n * Notification hook invoked exactly once when `close()` finishes. The\n * adaptive server wires this up after `createSession()` so the ICE\n * disconnect/failed path (see the `iceConnectionStateChange` handler\n * that calls `this.close()` on its own) reaches `cam.sessions.delete`\n * + `scheduleCameraAutoStop`. Without this callback the server kept\n * stale entries in `cam.sessions`, the auto-stop guard (`size > 0`)\n * never fired, ffmpeg stayed up, and the RTSP client toward the\n * broker leaked forever (`rtspClients: 2` on idle brokers).\n */\n onClosed: (() => void) | null = null;\n\n /** RTP sequence number counter (must increment per packet). */\n private videoSeqNum = 0;\n private audioSeqNum = 0;\n /**\n * Cached last keyframe NALs (SPS + PPS + IDR slices) in Annex-B format,\n * split and ready for RTP packetization. When the browser sends a PLI\n * (Picture Loss Indication) because it lost reference frames, we\n * immediately re-send this stored keyframe so the decoder can recover\n * without waiting for the next natural keyframe from the encoder.\n */\n private lastKeyframeNals: Buffer[] | null = null;\n private lastKeyframeRtpTs = 0;\n /** Throttle: minimum interval between PLI-triggered keyframe re-sends (ms). */\n private static readonly PLI_RESEND_COOLDOWN_MS = 500;\n private lastPliResendAt = 0;\n\n /**\n * Per-session H.265 RTP repacketizer (lazy-init). The H.265 path\n * forwards source RTP from the broker through this repacketizer so\n * Chrome's HEVC depacketizer sees an RTP shape it actually accepts —\n * the prior depacketize → AnnexB → re-packetize-from-scratch path\n * produced `framesAssembledFromMultiplePackets = 0` regardless of\n * codec metadata being correct. See `forwardSourceRtpVideo`.\n */\n private h265Repacketizer: H265Repacketizer | null = null;\n /** RTP MTU for the repacketizer — leave headroom for SRTP auth tag. */\n private static readonly H265_REPACKETIZER_MTU = 1180;\n /** Source SSRC — captured on first source RTP, used to populate the\n * outbound packets so werift's send pipeline doesn't reject them. */\n private sourceVideoSsrc: number | null = null;\n\n constructor(options: AdaptiveSessionOptions) {\n this.sessionId = options.sessionId;\n this.source = options.source;\n this.logger = options.logger;\n this.intercom = options.intercom;\n this.iceConfig = options.iceConfig;\n this.onStats = options.onStats;\n this.debug = options.debug ?? false;\n this.sourceCodec = options.sourceCodec ?? 'H264';\n this.createdAt = Date.now();\n }\n\n /** Build PeerConnection options including H.264 codec config. */\n private async buildPcOptions(): Promise<{ werift: WeriftModule; pcOptions: WeriftPcOptions }> {\n const werift = await loadWerift();\n\n // See `shared-session.ts` — werift's WeriftIceServer carries one URL\n // per entry, so array-shaped `entry.urls` expands into N entries that\n // share the same credentials.\n const iceServers: WeriftIceServer[] = [];\n for (const entry of this.iceConfig?.iceServers ?? []) {\n const urlList = Array.isArray(entry.urls) ? entry.urls : [entry.urls];\n for (const url of urlList) {\n iceServers.push({\n urls: url,\n ...(entry.username !== undefined ? { username: entry.username } : {}),\n ...(entry.credential !== undefined ? { credential: entry.credential } : {}),\n });\n }\n }\n\n const rtcpFeedback = [\n { type: \"transport-cc\" },\n { type: \"ccm\", parameter: \"fir\" },\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ];\n\n // Offer both codecs when source is H.265: browser picks H.265 if\n // supported, falls back to H.264. When source is H.264, offer only H.264.\n // After SDP negotiation, `negotiatedCodec` tells us which one won.\n const h264Codec = new werift.RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n payloadType: 96,\n parameters: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\",\n rtcpFeedback,\n });\n // H.265 fmtp — Scrypted ships the H.264-shaped fmtp string for H.265\n // and it works. Chrome rewrites the fmtp to proper H.265 syntax in\n // the answer regardless. Switching the offer to RFC 7798 syntax did\n // NOT change `framesDecoded=0` so the offer fmtp isn't the dominant\n // factor — keep the value Scrypted uses for parity with a known-\n // good implementation.\n const h265Codec = new werift.RTCRtpCodecParameters({\n mimeType: \"video/H265\",\n clockRate: 90000,\n payloadType: 97,\n parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',\n rtcpFeedback,\n });\n\n const videoCodecs = this.sourceCodec === 'H265'\n ? [h265Codec, h264Codec] // prefer H.265, fallback H.264\n : [h264Codec]; // H.264 only\n\n const pcOptions: WeriftPcOptions = {\n codecs: {\n video: videoCodecs,\n audio: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"audio/PCMU\",\n clockRate: 8000,\n payloadType: 0,\n channels: 1,\n parameters: \"\",\n }),\n ],\n },\n // RTP header extensions required for BUNDLE demuxing and congestion control.\n // Without sdes:mid, browsers cannot demux incoming RTP on a BUNDLE'd connection.\n headerExtensions: {\n video: [\n { uri: \"urn:ietf:params:rtp-hdrext:sdes:mid\" },\n { uri: \"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\" },\n { uri: \"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\" },\n ],\n audio: [\n { uri: \"urn:ietf:params:rtp-hdrext:sdes:mid\" },\n ],\n },\n };\n if (iceServers.length > 0) pcOptions.iceServers = iceServers;\n if (this.iceConfig?.portRange) pcOptions.icePortRange = this.iceConfig.portRange;\n if (this.iceConfig?.additionalHostAddresses?.length) {\n pcOptions.iceAdditionalHostAddresses = [...this.iceConfig.additionalHostAddresses];\n }\n\n return { werift, pcOptions };\n }\n\n /** Create offer SDP (server → client). */\n async createOffer(): Promise<{ sdp: string; type: \"offer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n // ICE state monitoring — promoted to info so the full lifecycle\n // (new → checking → connected/failed) is visible without flipping a\n // debug flag. Sessions stuck at \"checking\" are the canonical symptom\n // of ICE candidate mismatch; hiding the transitions behind `debug`\n // meant we couldn't tell \"stuck checking\" from \"never even started\".\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.info('ICE state changed', { meta: { phase: 'session', sessionId: this.sessionId, state } });\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Gathering state — surfaces whether the server is actually producing\n // ICE candidates and how long it takes. \"gathering\" without a\n // subsequent \"complete\" points at a local-network / binding issue.\n this.pc.iceGatheringStateChange.subscribe((state: string) => {\n this.logger.info('ICE gathering state changed', { meta: { phase: 'session', sessionId: this.sessionId, state } });\n });\n\n // Video track (sendonly) — save transceiver sender for sendRtp\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n const videoTransceiver = this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n this.videoSender = videoTransceiver.sender;\n\n // PLI/FIR listener — when the browser reports picture loss (decoder\n // can't decode P-frames because it missed the reference), re-send\n // the last cached keyframe immediately. This is the fix for the\n // \"stale frame with diffs\" symptom: without it, the viewer is stuck\n // on an old I-frame until the encoder naturally emits the next one.\n this.setupPliListener();\n\n // Audio track (sendrecv if intercom, sendonly otherwise)\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n const audioDir = this.intercom ? \"sendrecv\" : \"sendonly\";\n const audioTransceiver = this.pc.addTransceiver(this.audioTrack, { direction: audioDir });\n this.audioSender = audioTransceiver.sender;\n\n // Intercom: listen for incoming audio\n if (this.intercom) {\n const cb = this.intercom.onAudioReceived;\n audioTransceiver.onTrack.subscribe((track) => {\n track.onReceiveRtp.subscribe((pkt) => {\n try {\n const payload = pkt.payload;\n if (payload?.length > 0) void cb(payload, \"Opus\");\n } catch (err) {\n this.logger.error('Intercom error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n });\n });\n }\n\n const offer = await this.pc.createOffer();\n await this.pc.setLocalDescription(offer);\n\n // Wait for ICE gathering to complete so the offer includes candidates.\n // Without this, the offer has no candidates and the client can't connect.\n await new Promise<void>((resolve) => {\n if (this.pc?.iceGatheringState === \"complete\") { resolve(); return; }\n this.pc?.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") resolve();\n });\n // Timeout: don't wait forever if STUN is unreachable\n setTimeout(resolve, 5000);\n });\n\n // Use the local description which now includes gathered ICE candidates.\n // Force a=setup:actpass (let browser choose role) — werift defaults may cause DTLS issues.\n let finalSdp = this.pc.localDescription?.sdp ?? offer.sdp;\n // Ensure actpass so browser can be either active or passive\n finalSdp = finalSdp.replace(/a=setup:active\\r?\\n/g, \"a=setup:actpass\\r\\n\");\n this.state = \"connecting\";\n\n // Surface the ICE candidates the server ended up advertising. Printed\n // at info level because this is the first thing to inspect when a\n // session gets stuck at \"checking\" — if we only see a single\n // `127.0.0.1 host` line, the browser may well be unable to pair from\n // anywhere except a same-machine loopback, and anonymised mDNS\n // candidates from Chrome won't bind.\n const gatheredCandidates = finalSdp\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.startsWith('a=candidate:'))\n this.logger.info('Offer gathered ICE candidates', {\n meta: { phase: 'session', sessionId: this.sessionId, count: gatheredCandidates.length, candidates: gatheredCandidates },\n })\n\n // Log SDP video codec lines for debugging\n const videoCodecLines = finalSdp.split('\\n').filter((l: string) => l.includes('H264') || l.includes('rtpmap:96') || l.includes('fmtp:96') || l.includes('profile-level-id')).map((l: string) => l.trim());\n if (this.debug) this.logger.info('Offer created — video SDP', {\n meta: { phase: 'session', sessionId: this.sessionId, videoCodecLines },\n });\n return { sdp: finalSdp, type: \"offer\" };\n }\n\n /** Handle WHEP answer: client sends SDP answer, we set remote description and start feeding. */\n async handleAnswer(answer: { sdp: string; type: \"answer\" }): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n\n // Surface the browser's ICE candidates BEFORE setRemoteDescription so\n // we can see them even if werift rejects one of them. Chrome's mDNS\n // privacy layer emits candidates like `abcdef.local` on loopback; a\n // Node.js ICE agent without mDNS resolution can't pair them, and the\n // session stays at \"checking\" forever. If the only candidates shown\n // here are `*.local`, that's the smoking gun.\n const remoteCandidates = answer.sdp\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.startsWith('a=candidate:'))\n this.logger.info('Answer received ICE candidates', {\n meta: { phase: 'session', sessionId: this.sessionId, count: remoteCandidates.length, candidates: remoteCandidates },\n })\n\n // Rewrite Chrome-anonymised `*.local` candidates into their resolved IPs\n // before werift sees them; werift's ICE agent doesn't run mDNS itself\n // and would otherwise stall at \"checking\" forever.\n const resolvedSdp = await resolveMdnsCandidatesInSdp(answer.sdp, this.logger, `session:${this.sessionId}`)\n const desc = new werift.RTCSessionDescription(resolvedSdp, answer.type);\n await this.pc.setRemoteDescription(desc);\n\n // Detect which video codec the browser selected from the answer SDP.\n // If source is H.265 but browser only supports H.264, needsTranscode\n // will be true and the feed loop will spawn ffmpeg.\n const answerVideoCodec = resolvedSdp.match(/a=rtpmap:\\d+ (H264|H265)\\/90000/i)?.[1]?.toUpperCase()\n if (answerVideoCodec === 'H264' || answerVideoCodec === 'H265') {\n this.negotiatedCodec = answerVideoCodec as 'H264' | 'H265'\n }\n this.logger.info('Codec negotiated', {\n meta: { phase: 'session', sessionId: this.sessionId, source: this.sourceCodec, negotiated: this.negotiatedCodec, needsTranscode: this.needsTranscode },\n })\n\n // Wait for DTLS to connect before feeding — sendRtp silently drops if DTLS not ready\n const dtlsTransport = this.videoSender?.dtlsTransport;\n if (dtlsTransport && dtlsTransport.state !== \"connected\") {\n this.logger.debug('Waiting for DTLS...', {\n meta: { phase: 'session', sessionId: this.sessionId, current: dtlsTransport.state },\n });\n const deadline = Date.now() + 10_000;\n while (dtlsTransport.state !== \"connected\" && Date.now() < deadline) {\n await new Promise<void>((r) => setTimeout(r, 100));\n }\n this.logger.debug('DTLS wait complete', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n state: dtlsTransport.state,\n waitedSec: (10000 - (deadline - Date.now())) / 1000,\n },\n });\n }\n\n this.logger.debug('Answer set, feeding started', {\n meta: { phase: 'session', sessionId: this.sessionId, dtls: dtlsTransport?.state ?? 'unknown' },\n });\n this.startFeedingFrames();\n }\n\n /**\n * Handle WHEP offer: client sends SDP offer, we create answer.\n *\n * Uses the server-creates-offer pattern internally: we create our own offer\n * with sendonly tracks, then use the client's offer codecs to build a\n * compatible answer. This avoids werift transceiver direction issues.\n */\n async handleOffer(clientOffer: { sdp: string; type: \"offer\" }): Promise<{ sdp: string; type: \"answer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug('ICE state', { meta: { phase: 'session', sessionId: this.sessionId, state } });\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Set the client's offer as remote description. mDNS-rewriting the\n // offer for the same reason as `handleAnswer` — Chrome sends\n // anonymised `*.local` host candidates even in the offer path when\n // issuing WHEP-style requests.\n const resolvedOfferSdp = await resolveMdnsCandidatesInSdp(clientOffer.sdp, this.logger, `session:${this.sessionId}`)\n const remoteDesc = new werift.RTCSessionDescription(resolvedOfferSdp, clientOffer.type);\n await this.pc.setRemoteDescription(remoteDesc);\n\n // Find the transceivers werift created from the offer and attach our tracks.\n const transceivers: WeriftTransceiver[] = this.pc.getTransceivers();\n for (const t of transceivers) {\n const kind = t.receiver?.track?.kind ?? t.kind;\n if (kind === \"video\" && !this.videoTrack) {\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n await t.sender.replaceTrack(this.videoTrack);\n } else if (kind === \"audio\" && !this.audioTrack) {\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n await t.sender.replaceTrack(this.audioTrack);\n }\n }\n\n // Fallback: if no transceivers matched (shouldn't happen with valid offer)\n if (!this.videoTrack) {\n this.logger.warn('No video transceiver found in offer, adding one', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n }\n if (!this.audioTrack) {\n this.logger.warn('No audio transceiver found in offer, adding one', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n this.pc.addTransceiver(this.audioTrack, { direction: \"sendonly\" });\n }\n\n const answerDesc = await this.pc.createAnswer();\n await this.pc.setLocalDescription(answerDesc);\n this.state = \"connecting\";\n this.logger.info('WHEP answer created', { meta: { phase: 'session', sessionId: this.sessionId } });\n\n this.startFeedingFrames();\n\n return { sdp: answerDesc.sdp, type: \"answer\" };\n }\n\n /** Add ICE candidate. */\n async addIceCandidate(candidate: unknown): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n await this.pc.addIceCandidate(new werift.RTCIceCandidate(candidate));\n }\n\n /**\n * Detach the frame source (for connection pooling).\n * The session stays alive (ICE/DTLS connected) but stops feeding frames.\n * Call replaceSource() later to reattach a camera.\n */\n detachSource(): void {\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.logger.debug('Source detached (idle)', { meta: { phase: 'session', sessionId: this.sessionId } });\n }\n\n /** Whether the session has an active feed (vs idle/pooled). */\n get isFeeding(): boolean {\n return this.feedAbort !== null && !this.feedAbort.signal.aborted;\n }\n\n /**\n * Replace the frame source (for seamless source switching).\n * The new source will take effect at the next keyframe.\n */\n replaceSource(newSource: FrameSource): void {\n this.source = newSource;\n // Abort old feed — the feeding loop will restart with new source\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.startFeedingFrames();\n }\n\n getInfo(): SessionInfo {\n return { sessionId: this.sessionId, state: this.state, createdAt: this.createdAt };\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.state = \"closed\";\n this.logger.info('Closing', { meta: { phase: 'session', sessionId: this.sessionId } });\n\n if (this.statsTimer) {\n clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n try { await this.source.return(undefined); } catch { /* */ }\n if (this.pc) {\n try { await this.pc.close(); } catch { /* */ }\n this.pc = null;\n }\n this.videoTrack = null;\n this.audioTrack = null;\n\n // Notify the adaptive server so it can drop us from cam.sessions and\n // trigger scheduleCameraAutoStop. Guarded null-call so the server is\n // free to not wire it (tests, pooled sessions that manage cleanup\n // themselves).\n const hook = this.onClosed;\n this.onClosed = null;\n try { hook?.(); } catch { /* server-side bookkeeping shouldn't break close */ }\n }\n\n // -----------------------------------------------------------------------\n // H.265 source-RTP forwarding (packet-level repacketization)\n // -----------------------------------------------------------------------\n\n /**\n * Seed the H.265 repacketizer's codec info with VPS/SPS/PPS harvested\n * from the camera SDP. Cameras that publish parameter sets only via\n * SDP (e.g. Reolink high-profile streams) leave the wire bare of\n * VPS/SPS/PPS — without this seed the repacketizer would never\n * generate the AP codec packet that initialises the browser decoder.\n */\n seedH265CodecInfoFromSdp(parameterSets: ReadonlyArray<Buffer>): void {\n if (this.negotiatedCodec !== 'H265') return;\n if (!parameterSets.length) return;\n this.ensureH265Repacketizer();\n const rep = this.h265Repacketizer!;\n for (const ps of parameterSets) {\n const nalType = (ps[0]! & 0x7e) >> 1;\n if (nalType === 32) rep.updateVps(Buffer.from(ps));\n else if (nalType === 33) rep.updateSps(Buffer.from(ps));\n else if (nalType === 34) rep.updatePps(Buffer.from(ps));\n }\n if (this.debug) {\n this.logger.info('seeded H.265 repacketizer codecInfo from SDP', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n psCount: parameterSets.length,\n hasVps: !!rep.codecInfo?.vps,\n hasSps: !!rep.codecInfo?.sps,\n hasPps: !!rep.codecInfo?.pps,\n },\n });\n }\n }\n\n private ensureH265Repacketizer(): void {\n if (this.h265Repacketizer) return;\n const codecInfo: H265CodecInfo | undefined = undefined;\n this.h265Repacketizer = new H265Repacketizer(\n console,\n AdaptiveSession.H265_REPACKETIZER_MTU,\n codecInfo,\n );\n }\n\n /**\n * Forward a source RTP video packet (raw on-wire bytes) through the\n * H.265 repacketizer to the browser. Used by the broker's H.265\n * direct-RTP subscription — bypasses the AnnexB→writeVideoNals\n * path entirely.\n *\n * Drops everything until the SDP answer has been negotiated\n * (`negotiatedCodec`/`videoSender` populated) and silently no-ops on\n * non-H.265 sessions.\n */\n forwardSourceRtpVideo(rtpData: Buffer): void {\n if (this.closed) return;\n if (this.negotiatedCodec !== 'H265') return;\n if (!this.videoSender || !_werift) return;\n const werift = _werift;\n this.ensureH265Repacketizer();\n const rep = this.h265Repacketizer!;\n\n let srcPkt;\n try {\n srcPkt = werift.RtpPacket.deSerialize(rtpData);\n } catch (err) {\n this.logger.warn('H265 RTP deserialize failed', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err), len: rtpData.length },\n });\n return;\n }\n\n if (this.sourceVideoSsrc === null) {\n this.sourceVideoSsrc = srcPkt.header.ssrc;\n }\n\n const senderCodec = this.videoSender.codec;\n const pt = senderCodec?.payloadType ?? 97;\n\n let outPkts;\n try {\n outPkts = rep.repacketize(srcPkt);\n } catch (err) {\n this.logger.warn('H265 repacketize failed', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) },\n });\n return;\n }\n\n for (const pkt of outPkts) {\n pkt.header.payloadType = pt;\n // Sync werift's sender SSRC/seq to our outbound stream on first\n // packet — same fix the writeVideoNals path uses.\n if (!this.videoRtpSynced) {\n this.videoRtpSynced = true;\n try {\n this.videoSender.replaceRTP(\n { sequenceNumber: pkt.header.sequenceNumber, timestamp: pkt.header.timestamp },\n true,\n );\n } catch (e) {\n this.logger.warn('replaceRTP failed (non-fatal)', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(e) },\n });\n }\n }\n try {\n this.videoSender.sendRtp(pkt);\n this.rtpPacketsSent++;\n if (this.debug && (this.rtpPacketsSent === 1 || this.rtpPacketsSent % 500 === 0)) {\n this.logger.info('H265 source-RTP forwarded', {\n meta: { phase: 'session', sessionId: this.sessionId, count: this.rtpPacketsSent },\n });\n }\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error('sendRtp (h265 forward) error', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) },\n });\n }\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // PLI handling — resend cached keyframe on picture loss\n // -----------------------------------------------------------------------\n\n private setupPliListener(): void {\n const sender = this.videoSender;\n if (!sender) return;\n\n // werift exposes PLI via onPictureLossIndication (preferred) or the\n // generic onRtcpFeedback. Not all builds surface these — degrade\n // gracefully if neither is available.\n const onPli = () => this.handlePli();\n\n if (sender.onPictureLossIndication) {\n sender.onPictureLossIndication.subscribe(onPli);\n this.logger.debug('PLI listener attached (onPictureLossIndication)', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n } else if (sender.onRtcpFeedback) {\n sender.onRtcpFeedback.subscribe((fb) => {\n if (fb.type === 'nack' && fb.parameter === 'pli') onPli();\n if (fb.type === 'ccm' && fb.parameter === 'fir') onPli();\n });\n this.logger.debug('PLI listener attached (onRtcpFeedback)', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n } else {\n this.logger.debug('PLI listener not available — werift version may not expose RTCP feedback', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n }\n }\n\n private handlePli(): void {\n // Sessions running the H.265 repacketizer forward source RTP\n // through it — the upstream camera will respond to PLI by emitting\n // a fresh IDR access unit on its own. Re-sending a cached one\n // through writeVideoNals would collide with the repacketizer's\n // outbound RTP stream. Push-mode H.265 (no repacketizer) keeps the\n // cached-keyframe re-send path.\n if (this.h265Repacketizer) return;\n\n const now = Date.now();\n if (now - this.lastPliResendAt < AdaptiveSession.PLI_RESEND_COOLDOWN_MS) return;\n this.lastPliResendAt = now;\n\n if (!this.lastKeyframeNals || this.lastKeyframeNals.length === 0) {\n this.logger.debug('PLI received but no cached keyframe yet', {\n meta: { phase: 'session', sessionId: this.sessionId },\n });\n return;\n }\n\n this.logger.info('PLI received — re-sending cached keyframe', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n nals: this.lastKeyframeNals.length,\n totalBytes: this.lastKeyframeNals.reduce((s, n) => s + n.length, 0),\n },\n });\n // Re-send all NALs via writeVideoNals — same path the feed loop\n // uses for keyframes (single-NAL packets for VPS/SPS/PPS, FU\n // fragmentation for IDR slice). All share the same RTP timestamp;\n // marker bit set only on the last FU of the last NAL.\n this.writeVideoNals(this.lastKeyframeNals, this.lastKeyframeRtpTs, this.negotiatedCodec);\n }\n\n // -----------------------------------------------------------------------\n // Frame feeding\n // -----------------------------------------------------------------------\n\n private startFeedingFrames(): void {\n this.feedAbort = new AbortController();\n const { signal } = this.feedAbort;\n\n this.logger.info('Feed loop starting', {\n meta: { phase: 'session', sessionId: this.sessionId, dtls: this.videoSender?.dtlsTransport?.state ?? 'unknown', needsTranscode: this.needsTranscode },\n });\n\n if (this.needsTranscode) {\n this.startTranscodeFeed(signal);\n } else {\n this.startDirectFeed(signal);\n }\n }\n\n /**\n * Direct feed — source codec matches negotiated codec. Zero transcode.\n * H.264 and H.265 both go through this path with codec-specific RTP\n * packetization handled by writeVideoNals.\n */\n private startDirectFeed(signal: AbortSignal): void {\n\n void (async () => {\n let gotKeyframe = false;\n let videoTimestampBase: number | null = null;\n let audioTimestampBase: number | null = null;\n let frameCount = 0;\n\n try {\n for await (const mediaFrame of this.source) {\n if (signal.aborted || this.closed) break;\n frameCount++;\n if (frameCount <= 5 || frameCount % 100 === 0) {\n this.logger.debug('Frame received', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n frameCount,\n type: mediaFrame.type,\n size: mediaFrame.frame.data.length,\n isKeyframe: mediaFrame.type === 'video' && 'isKeyframe' in mediaFrame.frame\n ? mediaFrame.frame.isKeyframe\n : undefined,\n },\n });\n }\n\n if (mediaFrame.type === \"video\") {\n const frame = mediaFrame.frame;\n const annexB = frame.codec === \"H264\"\n ? convertH264ToAnnexB(frame.data)\n : convertH265ToAnnexB(frame.data);\n\n // Cache parameter sets BEFORE the keyframe gate. Cameras\n // that ship VPS/SPS/PPS in their own non-keyframe access\n // unit (or via the broker's synthetic SDP-replay packet —\n // see StreamBroker.emitSdpParamSetsTo) deliver them in a\n // frame whose `isKeyframe` is false. The previous code\n // skipped such frames entirely with `continue`, so\n // `lastVps/Sps/Pps` were never populated and HEVC decoding\n // failed on the first IDR.\n const __nalsForCaching = splitAnnexBToNals(annexB);\n if (frame.codec === 'H265') {\n for (const n of __nalsForCaching) {\n const nalType = (n[0]! & 0x7e) >> 1;\n if (nalType === 32) this.lastVps = Buffer.from(n);\n if (nalType === 33) this.lastSps = Buffer.from(n);\n if (nalType === 34) this.lastPps = Buffer.from(n);\n }\n } else {\n for (const n of __nalsForCaching) {\n const nalType = n[0]! & 0x1f;\n if (nalType === 7) this.lastSps = Buffer.from(n);\n if (nalType === 8) this.lastPps = Buffer.from(n);\n }\n }\n\n if (!gotKeyframe) {\n const isKey = frame.codec === \"H264\"\n ? isH264IdrAccessUnit(annexB)\n : isH265IrapAccessUnit(annexB);\n if (!isKey) {\n if (frameCount <= 3) {\n this.logger.debug('Skipping frame (waiting for keyframe)', {\n meta: { phase: 'session', sessionId: this.sessionId, frameCount, size: annexB.length },\n });\n }\n continue;\n }\n gotKeyframe = true;\n const iceState = this.pc?.iceConnectionState ?? \"unknown\";\n this.logger.info('First keyframe', {\n meta: { phase: 'session', sessionId: this.sessionId, frameCount, size: annexB.length, ice: iceState },\n });\n }\n\n if (videoTimestampBase === null) videoTimestampBase = frame.timestampMicros;\n const rtpTs = Math.floor(\n ((frame.timestampMicros - videoTimestampBase) * 90000) / 1_000_000,\n ) >>> 0;\n\n // Filter out AUD and SEI — not needed in RTP.\n // H.264: AUD=9, SEI=6 (mask 0x1f). H.265: AUD=35, SEI_PREFIX=39, SEI_SUFFIX=40 (mask (b>>1)&0x3f).\n const isH265 = frame.codec === 'H265';\n const allNals = splitAnnexBToNals(annexB);\n const nals = allNals.filter((n: Buffer) => {\n if (isH265) {\n const t = (n[0]! & 0x7e) >> 1;\n return t !== 35 && t !== 39 && t !== 40; // AUD, SEI_PREFIX, SEI_SUFFIX\n }\n const t = n[0]! & 0x1f;\n return t !== 9 && t !== 6; // AUD, SEI\n });\n\n // Debug: dump NAL info for first few frames (only when debug enabled)\n if (this.debug && (frameCount <= 5 || (gotKeyframe && frameCount <= (this._firstKeyFrame ?? 0) + 2))) {\n const nalInfo = allNals.map((n: Buffer) => {\n if (isH265) {\n const t = (n[0]! & 0x7e) >> 1;\n const names: Record<number, string> = {0:'TRAIL_N',1:'TRAIL_R',19:'IDR_W_RADL',20:'IDR_N_LP',21:'CRA',32:'VPS',33:'SPS',34:'PPS',35:'AUD',39:'SEI_P',40:'SEI_S'};\n return `${names[t] ?? `t${t}`}(${n.length})`;\n }\n const t = n[0]! & 0x1f;\n const names: Record<number, string> = {1:'SLICE',5:'IDR',6:'SEI',7:'SPS',8:'PPS',9:'AUD'};\n return `${names[t] ?? `t${t}`}(${n.length})`;\n }).join(' + ');\n this.logger.info('Frame NAL breakdown', {\n meta: { phase: 'session', sessionId: this.sessionId, frameCount, codec: frame.codec, nals: nalInfo, sending: nals.length },\n });\n if (gotKeyframe && !this._firstKeyFrame) this._firstKeyFrame = frameCount;\n }\n\n if (nals.length > 0 && this.videoTrack) {\n // For H.265 IRAP access units, send VPS+SPS+PPS as a\n // single Aggregation Packet (RFC 7798 §4.4.2) BEFORE the\n // IDR fragments. Scrypted does the same and Chrome's HEVC\n // depacketizer relies on this shape to initialise\n // VideoToolbox properly. Sending the param sets as 3\n // separate single-NAL packets caused `framesDecoded` to\n // stay at 0 with PLI flooding even with valid SDP fmtp.\n if (isH265) {\n // ── Param set tracking + AP+IDR for keyframes ──\n for (const n of nals) {\n const nalType = (n[0]! & 0x7e) >> 1;\n if (nalType === 32) this.lastVps = Buffer.from(n);\n if (nalType === 33) this.lastSps = Buffer.from(n);\n if (nalType === 34) this.lastPps = Buffer.from(n);\n }\n\n if (isH265IrapAccessUnit(annexB)) {\n // Resolve param sets — prefer inline, fall back to cache.\n const inlineVps = nals.find((n) => ((n[0]! & 0x7e) >> 1) === 32) ?? this.lastVps\n const inlineSps = nals.find((n) => ((n[0]! & 0x7e) >> 1) === 33) ?? this.lastSps\n const inlinePps = nals.find((n) => ((n[0]! & 0x7e) >> 1) === 34) ?? this.lastPps\n // VCL NALs (IRAP slices) — everything that isn't a param set.\n const vclNals = nals.filter((n) => {\n const t = (n[0]! & 0x7e) >> 1\n return t !== 32 && t !== 33 && t !== 34\n })\n if (inlineVps && inlineSps && inlinePps && vclNals.length > 0) {\n // Send VPS+SPS+PPS+IDR all via writeVideoNals — same\n // shape as the H.264 path that Chrome decodes\n // correctly. The previous Aggregation Packet (AP,\n // RFC 7798 §4.4.2) bundling produced a packet shape\n // that Chrome's HEVC depacketizer never accepted —\n // `framesAssembledFromMultiplePackets` stayed 0 and\n // `framesDecoded` stayed 0 even with valid SDP and\n // fmtp. Single-NAL packets for the param sets\n // followed by FU-fragmented IDR is what the H.264\n // path does for SPS/PPS/IDR and it works.\n const auNals = [inlineVps, inlineSps, inlinePps, ...vclNals]\n this.writeVideoNals(auNals, rtpTs, frame.codec)\n this.lastKeyframeNals = auNals.map((n) => Buffer.from(n))\n this.lastKeyframeRtpTs = rtpTs\n } else {\n // Missing param sets — fall back to \"send as-is\".\n this.writeVideoNals(nals, rtpTs, frame.codec)\n }\n } else {\n // Non-IRAP frames (TRAIL_R / RADL_R / etc.) — send as-is.\n this.writeVideoNals(nals, rtpTs, frame.codec)\n }\n } else {\n // ── H.264 param set tracking ──\n let auNals: Buffer[] = nals\n for (const n of nals) {\n const nalType = n[0]! & 0x1f;\n if (nalType === 7) this.lastSps = Buffer.from(n);\n if (nalType === 8) this.lastPps = Buffer.from(n);\n }\n if (isH264IdrAccessUnit(annexB)) {\n const hasInlineSps = nals.some((n: Buffer) => (n[0]! & 0x1f) === 7);\n if (!hasInlineSps && this.lastSps && this.lastPps) {\n auNals = [this.lastSps, this.lastPps, ...nals]\n }\n this.lastKeyframeNals = auNals.map((n) => Buffer.from(n))\n this.lastKeyframeRtpTs = rtpTs;\n }\n this.writeVideoNals(auNals, rtpTs, frame.codec);\n }\n if (this.debug && frameCount % 250 === 0) {\n this.logger.info('Feed progress', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n frames: frameCount,\n rtpPackets: this.rtpPacketsSent,\n ice: this.pc?.iceConnectionState ?? \"?\",\n conn: this.pc?.connectionState ?? \"?\",\n },\n });\n }\n }\n } else if (mediaFrame.type === \"audio\") {\n const frame = mediaFrame.frame;\n if (!this.audioSender) continue;\n\n // For G.711 (PCMU/PCMA): timestamp increments by frame size (160 samples per 20ms)\n // Using a simple counter is more reliable than wall-clock based timestamps\n if (audioTimestampBase === null) audioTimestampBase = 0;\n audioTimestampBase = ((audioTimestampBase as number) + frame.data.length) >>> 0;\n const rtpTs = audioTimestampBase as number;\n\n this.writeAudio(frame.data, rtpTs, frame.codec);\n }\n }\n } catch (err) {\n if (!signal.aborted && !this.closed) {\n this.logger.error('Feed error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n } finally {\n if (!this.closed) {\n if (this.debug) this.logger.info('Feed ended', { meta: { phase: 'session', sessionId: this.sessionId } });\n void this.close();\n }\n }\n })();\n }\n\n /** Max RTP payload size (MTU 1200 to stay under typical network MTU). */\n /**\n * Transcode feed — source is H.265 but browser only supports H.264.\n * Pipes raw H.265 Annex-B to ffmpeg stdin, reads H.264 from stdout.\n */\n private startTranscodeFeed(signal: AbortSignal): void {\n const { spawn } = require('node:child_process') as typeof import('node:child_process')\n\n this.logger.info('Starting H.265→H.264 transcode feed', {\n meta: { phase: 'session', sessionId: this.sessionId },\n })\n\n const ff = spawn('ffmpeg', [\n '-hide_banner', '-loglevel', 'error',\n '-f', 'hevc', '-i', 'pipe:0',\n '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency',\n '-profile:v', 'baseline', '-g', '30', '-bf', '0',\n '-an', '-f', 'h264', 'pipe:1',\n ], { stdio: ['pipe', 'pipe', 'pipe'] })\n\n signal.addEventListener('abort', () => {\n if (!ff.killed) ff.kill('SIGTERM')\n })\n\n ff.on('exit', (code) => {\n this.logger.info('Transcode ffmpeg exited', { meta: { phase: 'session', sessionId: this.sessionId, code } })\n })\n ff.stderr.on('data', (chunk: Buffer) => {\n const msg = chunk.toString().trim()\n if (msg.length > 0) this.logger.warn('Transcode ffmpeg stderr', { meta: { sessionId: this.sessionId, msg } })\n })\n\n // Parse H.264 output from ffmpeg and send via RTP\n let pendingBuf = Buffer.alloc(0)\n let rtpTs = 0\n const FRAME_INTERVAL_90K = 3000 // 30fps → 90kHz ticks\n\n ff.stdout.on('data', (chunk: Buffer) => {\n pendingBuf = Buffer.concat([pendingBuf, chunk])\n // Look for keyframe boundaries to emit complete access units\n const lastSc = findLastStartCode(pendingBuf)\n if (lastSc <= 0) return\n const complete = pendingBuf.subarray(0, lastSc)\n pendingBuf = Buffer.from(pendingBuf.subarray(lastSc))\n\n const completedNals = splitAnnexBToNals(complete)\n if (completedNals.length === 0) return\n\n rtpTs = (rtpTs + FRAME_INTERVAL_90K) >>> 0\n this.writeVideoNals(completedNals, rtpTs, 'H264')\n })\n\n // Feed source H.265 data to ffmpeg stdin\n void (async () => {\n try {\n for await (const mediaFrame of this.source) {\n if (signal.aborted || this.closed) break\n if (mediaFrame.type === 'video') {\n if (!ff.stdin.destroyed) {\n ff.stdin.write(mediaFrame.frame.data)\n }\n } else if (mediaFrame.type === 'audio') {\n // Audio passthrough (no transcode needed)\n const frame = mediaFrame.frame\n this.writeAudio(frame.data, frame.timestampMicros, frame.codec)\n }\n }\n } catch (err) {\n if (!signal.aborted && !this.closed) {\n this.logger.error('Transcode feed error', { meta: { sessionId: this.sessionId, error: errMsg(err) } })\n }\n } finally {\n if (!ff.stdin.destroyed) ff.stdin.end()\n if (!this.closed) void this.close()\n }\n })()\n }\n\n private static readonly MAX_RTP_PAYLOAD = 1200;\n\n private rtpPacketsSent = 0;\n private rtpLogCounter = 0;\n\n private writeVideoNals(nals: Buffer[], rtpTs: number, codec: VideoCodec): void {\n if (!this.videoSender || !_werift) {\n if (this.rtpLogCounter === 0) {\n this.logger.warn('writeVideoNals: no sender', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n hasVideoSender: !!this.videoSender,\n hasWerift: !!_werift,\n },\n });\n this.rtpLogCounter++;\n }\n return;\n }\n const werift = _werift;\n // Use the payload type from the negotiated codec on the sender,\n // not a hardcoded value. After SDP negotiation, werift assigns the\n // correct PT to the sender's codec. Using a different PT causes\n // the browser to ignore the packets.\n const senderCodec = this.videoSender.codec;\n const pt = senderCodec?.payloadType ?? (codec === \"H264\" ? 96 : 97);\n\n const sendPkt = (payload: Buffer, marker: boolean) => {\n try {\n const header = new werift.RtpHeader();\n header.payloadType = pt;\n header.timestamp = rtpTs;\n header.marker = marker;\n header.sequenceNumber = (this.videoSeqNum = (this.videoSeqNum + 1) & 0xffff);\n const pkt = new werift.RtpPacket(header, payload);\n\n // FIX: Sync SSRC/seq on first packet (like Scrypted's replaceRTP).\n // Without this, werift's internal SSRC and the RTP header SSRC\n // diverge, and the browser ignores the packets.\n if (!this.videoRtpSynced) {\n this.videoRtpSynced = true;\n try {\n this.videoSender!.replaceRTP(header, true);\n } catch (e) {\n this.logger.warn('replaceRTP failed (non-fatal)', {\n meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(e) },\n });\n }\n }\n\n // Debug: check sender state for first few packets\n if (this.debug && this.rtpPacketsSent < 3) {\n const dtls = this.videoSender?.dtlsTransport;\n const senderCodec = this.videoSender?.codec;\n const ssrc = this.videoSender?.ssrc;\n this.logger.info('sendRtp debug', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n packetIndex: this.rtpPacketsSent,\n dtls: dtls?.state ?? 'none',\n codec: senderCodec?.mimeType ?? 'NULL',\n payloadType: senderCodec?.payloadType ?? '?',\n ssrc: String(ssrc),\n payloadSize: payload.length,\n },\n });\n }\n this.videoSender!.sendRtp(pkt);\n\n this.rtpPacketsSent++;\n if (this.debug && (this.rtpPacketsSent === 1 || this.rtpPacketsSent % 500 === 0)) {\n this.logger.info('RTP packets sent', {\n meta: { phase: 'session', sessionId: this.sessionId, count: this.rtpPacketsSent, payload: payload.length },\n });\n }\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error('sendRtp error', {\n meta: { phase: 'session', sessionId: this.sessionId, packetIndex: this.rtpPacketsSent, error: errMsg(err) },\n });\n }\n }\n };\n\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n\n if (nal.length <= AdaptiveSession.MAX_RTP_PAYLOAD) {\n sendPkt(nal, isLastNal);\n } else if (codec === 'H265') {\n // H.265 FU fragmentation (RFC 7798 §4.4.3)\n // NAL header is 2 bytes: [F|type(6)|layerId_hi(1)] [layerId_lo(5)|tid(3)]\n const nalType = (nal[0]! & 0x7e) >> 1;\n const layerId = ((nal[0]! & 0x01) << 5) | ((nal[1]! & 0xf8) >> 3);\n const tid = nal[1]! & 0x07;\n // FU NAL header: type=49 (FU), keep layerId+tid\n const fuNalHeader0 = (49 << 1) | (layerId >> 5);\n const fuNalHeader1 = ((layerId & 0x1f) << 3) | tid;\n const nalBody = nal.subarray(2); // skip 2-byte NAL header\n\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + AdaptiveSession.MAX_RTP_PAYLOAD - 3, nalBody.length);\n const isLast = end >= nalBody.length;\n\n let fuHeader = nalType & 0x3f;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n\n const fragment = Buffer.alloc(3 + (end - offset));\n fragment[0] = fuNalHeader0;\n fragment[1] = fuNalHeader1;\n fragment[2] = fuHeader;\n nalBody.copy(fragment, 3, offset, end);\n\n sendPkt(fragment, isLastNal && isLast);\n offset = end;\n isFirst = false;\n }\n } else {\n // H.264 FU-A fragmentation (RFC 6184 §5.8)\n const nalHeader = nal[0]!;\n const fnri = nalHeader & 0xe0;\n const nalType = nalHeader & 0x1f;\n const fuIndicator = fnri | 28;\n const nalBody = nal.subarray(1);\n\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + AdaptiveSession.MAX_RTP_PAYLOAD - 2, nalBody.length);\n const isLast = end >= nalBody.length;\n\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n\n const fragment = Buffer.alloc(2 + (end - offset));\n fragment[0] = fuIndicator;\n fragment[1] = fuHeader;\n nalBody.copy(fragment, 2, offset, end);\n\n sendPkt(fragment, isLastNal && isLast);\n offset = end;\n isFirst = false;\n }\n }\n }\n }\n\n private writeAudio(data: Buffer, rtpTs: number, codec?: string): void {\n if (!this.audioSender || !_werift) return;\n const werift = _werift;\n // Use the payload type from the negotiated codec on the sender —\n // SAME pattern as `writeVideoNals`. werift assigns the negotiated\n // codec to the sender after SDP exchange completes; reading\n // `senderCodec.payloadType` keeps us in sync with whatever the\n // browser actually accepted in its answer. Fallback to the\n // static OFFER-side PT only if werift hasn't bound the codec yet\n // (timing race during the very first packet).\n // NOTE: NOT calling `audioSender.replaceRTP(header, true)`. Video\n // does it (and works), but audio's SSRC binding is more brittle\n // in werift — overriding the sender's SSRC with our header's\n // default (zero) leaves the SRTP context out of sync with the\n // SSRC the browser expects from the answer SDP, so the browser\n // gets RTP with the right PT but can't decrypt → silent drop.\n // werift assigns the audio sender's SSRC during transceiver\n // setup; let `sendRtp` use it directly.\n const senderCodec = this.audioSender.codec;\n const fallbackPt = codec === \"Pcmu\" || codec === \"Pcma\" ? 0 : 111;\n const pt = senderCodec?.payloadType ?? fallbackPt;\n try {\n const header = new werift.RtpHeader();\n header.payloadType = pt;\n header.timestamp = rtpTs;\n // marker=true on every audio packet — browsers tolerate it on\n // G.711 codecs, and some receive paths stall the jitter buffer\n // waiting for marker=true to start playback. Setting marker\n // on the first packet only (RFC 3551 talkspurt convention)\n // produced silent receivers in practice.\n header.marker = true;\n header.sequenceNumber = (this.audioSeqNum = (this.audioSeqNum + 1) & 0xffff);\n const pkt = new werift.RtpPacket(header, data);\n this.audioSender.sendRtp(pkt);\n } catch (err) {\n this.logger.debug('Audio write error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n }\n\n // -----------------------------------------------------------------------\n // RTCP stats collection\n // -----------------------------------------------------------------------\n\n private startStatsCollection(): void {\n if (this.statsTimer || !this.onStats) return;\n\n this.statsTimer = setInterval(() => {\n if (!this.pc || this.closed) return;\n this.collectStats();\n }, 3_000);\n }\n\n private collectStats(): void {\n if (!this.pc || !this.onStats) return;\n\n try {\n // werift exposes getReceivers() and getSenders() on RTCPeerConnection.\n // For video sender stats, we check RTCP RR feedback from the receiver.\n const senders = this.pc.getSenders?.() ?? [];\n\n for (const sender of senders) {\n const track = sender.track;\n if (!track || track.kind !== \"video\") continue;\n\n // werift tracks RTCP receiver reports via sender.rtcpReceiveReport\n // or via the transport's RTCP event. The exact API depends on werift version.\n // Fallback: use werift's built-in stats if available.\n const report = sender.lastReceiverReport ?? sender.rtcpReport;\n if (!report) continue;\n\n const fractionLost = report.fractionLost ?? 0;\n const packetsLost = report.packetsLost ?? report.cumulativeLost ?? 0;\n const jitter = report.jitter ?? 0;\n const rtt = report.roundTripTime ?? report.rtt ?? 0;\n\n const packetLoss = fractionLost / 256; // Fraction lost is 0–255\n\n this.onStats({\n sessionId: this.sessionId,\n packetLoss,\n jitterMs: jitter,\n rttMs: rtt * 1000, // seconds → ms\n packetsReceived: 0, // Not available from sender side\n packetsLost,\n timestamp: Date.now(),\n });\n return; // One video sender is enough\n }\n\n // Fallback: no RTCP report available yet — emit zeros\n // (Client-reported stats will supplement this)\n } catch {\n // RTCP not yet available — normal during early connection\n }\n }\n}\n\n\n/** Find the byte offset of the last 0x00000001 start code in a buffer. */\nfunction findLastStartCode(buf: Buffer): number {\n for (let i = buf.length - 4; i >= 0; i--) {\n if (buf[i] === 0 && buf[i + 1] === 0 && buf[i + 2] === 0 && buf[i + 3] === 1) return i\n }\n return -1\n}\n","/**\n * Broker-integrated WebRTC server.\n *\n * Unlike the old `addon-webrtc-adaptive` which spawned ffmpeg to re-encode\n * the broker's RTSP restream (Camera → Broker → RTSP → ffmpeg libx264 → RTP),\n * this server subscribes directly to `IStreamBroker.onEncodedData()` and\n * forward the raw H.264 packets to WebRTC sessions as-is — zero transcode,\n * zero extra RTSP connection, zero latency from re-encoding.\n *\n * Flow: Camera → RTSP → Broker → EncodedPacket → RTP packetization → WebRTC\n */\n\nimport crypto from 'node:crypto'\nimport type { EncodedPacket, IStreamBroker, IScopedLogger, Unsubscribe } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport { AdaptiveSession, type IceServerEntry } from './session.js'\nimport type { FrameSource, MediaFrame } from './types.js'\nimport { convertH264ToAnnexB } from './h264-utils.js'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface BrokerWebrtcServerOptions {\n readonly logger: IScopedLogger\n readonly getIceServers?: () => Promise<readonly IceServerEntry[]>\n readonly iceServers?: readonly IceServerEntry[]\n readonly icePortRange?: [number, number]\n readonly iceAdditionalHostAddresses?: readonly string[]\n}\n\ninterface SessionEntry {\n readonly session: AdaptiveSession\n readonly brokerId: string\n /** Unsubscribe from broker's onEncodedData. */\n readonly unsubBroker: Unsubscribe\n /**\n * Unsubscribe from broker's `onVideoRtp` for H.265 sessions that\n * forward source RTP through `H265Repacketizer`. `null` for H.264\n * sessions (which use the AnnexB feed-loop path).\n */\n readonly unsubRtp: Unsubscribe | null\n /**\n * Unsubscribe from broker's `onSdpParameterSets` for H.265 sessions\n * that need late delivery of VPS/SPS/PPS. Sessions created during a\n * battery-cam wake-up window register this so the repacketizer is\n * seeded as soon as the broker's first `onVideoTrack` fires —\n * without it the AP codec packet is never emitted and Chrome's HEVC\n * decoder never initialises. `null` for H.264 sessions and for H.265\n * sessions that found the SDP params already populated at create\n * time.\n */\n readonly unsubSdpParams: Unsubscribe | null\n /** Close the push source. */\n readonly closeSource: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Adaptive source selection\n// ---------------------------------------------------------------------------\n\n/** Fallback resolution/bitrate per label when the broker hasn't reported stats yet. */\nconst LABEL_DEFAULTS: Readonly<Record<string, { pixels: number; bitrateKbps: number }>> = {\n high: { pixels: 1920 * 1080, bitrateKbps: 4000 },\n mid: { pixels: 1280 * 720, bitrateKbps: 1200 },\n low: { pixels: 640 * 360, bitrateKbps: 400 },\n}\nconst TIER_PREFERENCE: readonly string[] = ['high', 'mid', 'low']\n\ninterface ClientHints {\n readonly viewportWidth?: number\n readonly viewportHeight?: number\n readonly devicePixelRatio?: number\n readonly downlinkMbps?: number\n readonly prefersTier?: string\n}\n\nfunction scoreBroker(\n tier: 'high' | 'mid' | 'low' | null,\n broker: IStreamBroker,\n hints: ClientHints,\n): number {\n const stats = broker.getStats()\n const fallback = tier ? LABEL_DEFAULTS[tier] : undefined\n const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : (fallback?.bitrateKbps ?? 2000)\n // Approximate pixel count from tier defaults (broker doesn't track resolution)\n const srcPixels = fallback?.pixels ?? 1920 * 1080\n\n const dpr = hints.devicePixelRatio ?? 1\n const targetW = (hints.viewportWidth ?? 1920) * dpr\n const targetH = (hints.viewportHeight ?? 1080) * dpr\n const targetPixels = targetW * targetH\n\n const pixelDistance = Math.abs(srcPixels - targetPixels) / 1_000_000\n\n let bandwidthPenalty = 0\n if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {\n const availableKbps = hints.downlinkMbps * 1000 * 0.6\n if (bitrateKbps > availableKbps) {\n bandwidthPenalty = ((bitrateKbps - availableKbps) / availableKbps) * 100\n }\n }\n return pixelDistance + bandwidthPenalty\n}\n\n// ---------------------------------------------------------------------------\n// BrokerWebrtcServer\n// ---------------------------------------------------------------------------\n\nexport class BrokerWebrtcServer {\n private readonly logger: IScopedLogger\n private readonly getIceServersFn: BrokerWebrtcServerOptions['getIceServers']\n private readonly staticIceServers: readonly IceServerEntry[] | undefined\n private readonly icePortRange: [number, number] | undefined\n private readonly iceAdditionalHostAddresses: readonly string[] | undefined\n\n /** brokerId → IStreamBroker reference. Set by the broker manager on registration. */\n private readonly brokers = new Map<string, IStreamBroker>()\n /**\n * Resolves a brokerId to its current profile tier (`'high' | 'mid' | 'low'`)\n * via the manager's `assignments` map, or `null` for brokers that\n * are only kept alive by a manual activation. Injected by the\n * StreamBrokerManager since this server doesn't own the assignment\n * map directly. Defaults to a no-op resolver — adaptive bitrate logic\n * still works (falls back to \"mid\" defaults) but tier-explicit\n * `prefersTier` requests always pick the first matching candidate.\n */\n private profileTierResolver: ((brokerId: string) => 'high' | 'mid' | 'low' | null) | null = null\n /** sessionId → session entry. */\n private readonly sessions = new Map<string, SessionEntry>()\n private stopped = false\n\n constructor(options: BrokerWebrtcServerOptions) {\n this.logger = options.logger\n this.getIceServersFn = options.getIceServers\n this.staticIceServers = options.iceServers\n this.icePortRange = options.icePortRange\n this.iceAdditionalHostAddresses = options.iceAdditionalHostAddresses\n }\n\n // ── Broker registration (called by StreamBrokerManager) ─────────────\n\n registerBroker(brokerId: string, broker: IStreamBroker): void {\n this.brokers.set(brokerId, broker)\n this.logger.info('WebRTC broker registered', { meta: { brokerId } })\n }\n\n /**\n * Provide a resolver that maps brokerId → current profile tier. The\n * manager injects one wired to its `assignments` map. Without it the\n * server still serves brokers but can't honour client `prefersTier`\n * hints precisely (it falls through to the order-based pick).\n */\n setProfileTierResolver(\n resolver: (brokerId: string) => 'high' | 'mid' | 'low' | null,\n ): void {\n this.profileTierResolver = resolver\n }\n\n unregisterBroker(brokerId: string): void {\n this.brokers.delete(brokerId)\n // Close all sessions on this broker\n for (const [sid, entry] of this.sessions) {\n if (entry.brokerId === brokerId) {\n this.cleanupSession(sid)\n void entry.session.close().catch(() => {})\n }\n }\n this.logger.info('WebRTC broker unregistered', { meta: { brokerId } })\n }\n\n supportsStream(streamId: string): boolean {\n // streamId is brokerId (e.g. \"6/high\") or bare deviceKey\n // Accept if any registered broker matches\n if (this.brokers.has(streamId)) return true\n // Check for adaptive alias: \"6/adaptive\" → any broker for device \"6\"\n const slash = streamId.lastIndexOf('/')\n if (slash > 0) {\n const tail = streamId.slice(slash + 1)\n if (tail === 'adaptive') {\n const deviceKey = streamId.slice(0, slash)\n for (const bid of this.brokers.keys()) {\n if (bid.startsWith(`${deviceKey}/`)) return true\n }\n }\n }\n return false\n }\n\n // ── Session lifecycle ───────────────────────────────────────────────\n\n async createSession(\n streamId: string,\n hints: ClientHints = {},\n opts: { streamingDebug?: boolean } = {},\n ): Promise<{ sessionId: string; sdpOffer: string }> {\n const setup = await this.setupSessionForBroker(streamId, hints, opts)\n try {\n const offer = await setup.session.createOffer()\n setup.sessionLogger.info('WebRTC session created', {\n meta: {\n sessionId: setup.sessionId,\n brokerId: setup.brokerId,\n iceServers: setup.iceServerCount,\n codec: setup.sessionCodec,\n useH265Repacketizer: setup.useH265Repacketizer,\n },\n })\n return { sessionId: setup.sessionId, sdpOffer: offer.sdp }\n } catch (err) {\n this.cleanupSession(setup.sessionId)\n await setup.session.close().catch(() => {})\n throw err\n }\n }\n\n /**\n * WHEP-style client-offer signaling: caller has already constructed\n * an SDP offer (Alexa / WHEP / mobile clients that do\n * `createOffer()` locally) and posts it here. We build the same\n * broker subscription + AdaptiveSession plumbing as `createSession`,\n * then call `session.handleOffer(clientOffer)` to produce the\n * server's SDP answer.\n *\n * The session lifecycle is identical to `createSession` — broker\n * unsubscribe and `closeSession(sessionId)` still apply — so the\n * RTC controller / Alexa handler closes the session the same way.\n */\n async handleOffer(\n streamId: string,\n clientOfferSdp: string,\n hints: ClientHints = {},\n opts: { streamingDebug?: boolean; sessionId?: string } = {},\n ): Promise<{ sessionId: string; sdpAnswer: string }> {\n const setup = await this.setupSessionForBroker(streamId, hints, opts)\n try {\n const answer = await setup.session.handleOffer({ sdp: clientOfferSdp, type: 'offer' })\n setup.sessionLogger.info('WebRTC session created (client-offer)', {\n meta: {\n sessionId: setup.sessionId,\n brokerId: setup.brokerId,\n iceServers: setup.iceServerCount,\n codec: setup.sessionCodec,\n useH265Repacketizer: setup.useH265Repacketizer,\n },\n })\n return { sessionId: setup.sessionId, sdpAnswer: answer.sdp }\n } catch (err) {\n this.cleanupSession(setup.sessionId)\n await setup.session.close().catch(() => {})\n throw err\n }\n }\n\n /**\n * Shared session bring-up: resolve broker → subscribe to encoded\n * data + RTP → flush pre-buffer → seed H.265 codec info → register\n * the SessionEntry. Returns the `AdaptiveSession` ready for SDP\n * negotiation (either `createOffer()` for the server-offer flow or\n * `handleOffer(clientOffer)` for the WHEP / Alexa flow).\n *\n * The returned session has its `onClosed` hook already wired to\n * cleanup, and the SessionEntry is already in `this.sessions` keyed\n * by `sessionId` — callers MUST cleanup + close on negotiation\n * failure (both `createSession` and `handleOffer` do this in their\n * catch blocks).\n */\n private async setupSessionForBroker(\n streamId: string,\n hints: ClientHints,\n opts: { streamingDebug?: boolean; sessionId?: string },\n ): Promise<{\n sessionId: string\n session: AdaptiveSession\n sessionLogger: IScopedLogger\n brokerId: string\n sessionCodec: 'H264' | 'H265'\n useH265Repacketizer: boolean\n iceServerCount: number\n }> {\n if (this.stopped) throw new Error('Server stopped')\n\n const brokerId = this.resolveBrokerId(streamId, hints)\n const broker = this.brokers.get(brokerId)\n if (!broker) throw new Error(`No broker for stream \"${streamId}\" (resolved: \"${brokerId}\")`)\n\n // Parse the brokerId so every session log gets `deviceId` /\n // `camStreamId` tags. Without these tags, operator log filters\n // scoped to a single device (the common debugging shape) drop\n // every WebRTC session lifecycle line — and we end up flying\n // blind on whether the session was created, on which codec path,\n // and whether the H.265 SDP seed / first-RTP forward fired.\n const slashIdx = brokerId.indexOf('/')\n const deviceTags = slashIdx > 0\n ? {\n deviceId: Number.parseInt(brokerId.slice(0, slashIdx), 10),\n camStreamId: brokerId.slice(slashIdx + 1),\n }\n : { deviceId: -1, camStreamId: brokerId }\n const sessionLogger = this.logger.withTags(deviceTags)\n\n const brokerCodec = broker.getStats().codec ?? 'h264'\n const isHevc = brokerCodec === 'h265' || brokerCodec === 'hevc'\n const sessionCodec = isHevc ? 'H265' : 'H264' as const\n // RTP-bearing brokers expose source RTP via `onVideoRtp` — that's\n // RTSP (pull) and `push-rtp` (provider-synthesised RTP). AnnexB-\n // only push brokers (Reolink Baichuan today, Frigate event streams)\n // and the future RTMP path have no source RTP and fall back to\n // AnnexB → writeVideoNals even on H.265.\n const sourceType = broker.getSourceType()\n const isRtp = broker.isRtpSource()\n const useH265Repacketizer = sessionCodec === 'H265' && isRtp\n // Surface the codec/path resolution in the message itself so it\n // shows up in operator log filters that strip the structured meta\n // — without this, \"WebRTC session created\" alone makes it\n // impossible to tell whether a session went down the H.265 RTP\n // repacketizer path (where the late-seed fix matters) or the\n // AnnexB fallback (where it doesn't), which is the first\n // discriminator any wake-up debugging needs.\n sessionLogger.info(\n `WebRTC session: codec=${sessionCodec} brokerCodec=${brokerCodec} sourceType=${sourceType ?? 'null'} isRtp=${isRtp} repacketizer=${useH265Repacketizer}`,\n { meta: { brokerId, sessionCodec, brokerCodec, sourceType, isRtp, useH265Repacketizer } },\n )\n\n // Create push-based FrameSource from broker's encoded data\n const { source, pushFrame, close: closeSource } = createPushFrameSource()\n\n // Subscribe to broker's encoded packets. Both H.264 and H.265 use\n // the same direct path — the session handles codec-specific RTP\n // packetization (FU-A for H.264, FU for H.265).\n //\n // Parameter-set NALs (SPS/PPS for H.264, VPS/SPS/PPS for H.265)\n // arrive as separate EncodedPackets from the broker. We buffer them\n // and prepend to the next VCL NAL so the decoder gets a complete\n // access unit.\n const pendingParamNals: Buffer[] = []\n /**\n * Sticky SDP-derived VPS/SPS/PPS Annex-B blob. Cameras that publish\n * parameter sets only in SDP (Reolink high-profile streams) emit\n * raw IDR access units on the wire — without this sticky prepend,\n * every keyframe after the first one would arrive at the WebRTC\n * session as a bare IDR and Chrome's HEVC decoder never advances\n * past zero.\n *\n * Captured the first time `pendingParamNals` is populated by a\n * paramSet packet (the synthetic SDP-replay emitted by\n * `StreamBroker.emitSdpParamSetsTo` when this subscriber registered).\n * After that, every keyframe missing inline VPS/SPS/PPS gets the\n * sticky blob prepended.\n */\n let stickyParamSets: Buffer | null = null\n /**\n * Whether we've seen a non-placeholder video packet on this\n * session yet. The placeholder pump emits a 1280×720 SPS/PPS+IDR\n * blob at 1Hz while the broker waits for real RTP. If those\n * param sets land in `stickyParamSets`, the camera's first real\n * keyframe (a different resolution) would get the placeholder\n * SPS prepended and Chrome's decoder would freeze on the last\n * placeholder frame until the session is recreated. Flipping\n * `seenRealPacket` on the first real video packet flushes the\n * placeholder-derived sticky state.\n */\n let seenRealPacket = false\n const unsubBroker = broker.onEncodedData((packet: EncodedPacket) => {\n if (packet.type === 'video') {\n // RTSP+H.265 sessions forward source RTP directly through the\n // repacketizer — the AnnexB → writeVideoNals path is\n // bypassed entirely. The repacketizer is seeded at\n // session-create time with the camera's VPS/SPS/PPS via\n // `seedH265CodecInfoFromSdp`. Pushing placeholder frames\n // through writeVideoNals here would re-configure Chrome's\n // HEVC decoder with the placeholder's parameter sets\n // (1280×720) and the transition back to the camera's real\n // SPS through the repacketizer's AP codec packet doesn't\n // recover cleanly — the viewer ends up frozen on the\n // placeholder. So H.265 sessions show no labelled\n // placeholder during wake-up; H.264 paths still get them.\n if (useH265Repacketizer) return\n\n // First real packet after a placeholder window — drop any\n // sticky state captured from placeholder param sets. From\n // this point forward `pendingParamNals` / `stickyParamSets`\n // track the live camera's actual SPS/PPS only.\n if (!packet.isPlaceholder && !seenRealPacket) {\n seenRealPacket = true\n pendingParamNals.length = 0\n stickyParamSets = null\n }\n\n const annexB = convertH264ToAnnexB(packet.data)\n\n // Detect first NAL type\n const nalTypeInfo = detectFirstNalType(annexB, isHevc)\n if (nalTypeInfo.isParamSet) {\n // Placeholder packets are self-contained access units —\n // VPS+SPS+PPS+IDR concatenated in a single buffer. The\n // first NAL is a parameter set, but the buffer also\n // contains the IDR slice that produces the visible frame.\n // `detectFirstNalType` only inspects the leading NAL, so\n // the live-stream branch (which assumes \"param sets arrive\n // separately, then a VCL\") would discard the IDR portion\n // and the operator never sees the labelled placeholder.\n // Push the whole bundle as a keyframe and skip the sticky-\n // param caching (corrupts the camera's real keyframe later\n // — different resolution/profile).\n if (packet.isPlaceholder) {\n pushFrame({\n type: 'video',\n frame: {\n data: annexB,\n codec: sessionCodec,\n isKeyframe: true,\n timestampMicros: packet.pts,\n },\n })\n return\n }\n pendingParamNals.push(Buffer.from(annexB))\n // Keep a sticky copy so future keyframes that ship without\n // inline param sets still get configured decoder.\n stickyParamSets = Buffer.concat(pendingParamNals)\n return\n }\n\n // VCL NAL — prepend any buffered parameter sets, OR the sticky\n // SDP-derived blob if the keyframe lacks them inline.\n let finalAnnexB = annexB\n if (pendingParamNals.length > 0) {\n pendingParamNals.push(annexB)\n finalAnnexB = Buffer.concat(pendingParamNals)\n pendingParamNals.length = 0\n } else if (nalTypeInfo.isKeyframe && stickyParamSets) {\n finalAnnexB = Buffer.concat([stickyParamSets, annexB])\n }\n\n pushFrame({\n type: 'video',\n frame: {\n data: finalAnnexB,\n codec: sessionCodec,\n isKeyframe: packet.keyframe || nalTypeInfo.isKeyframe,\n timestampMicros: packet.pts,\n },\n })\n } else if (packet.type === 'audio') {\n pushFrame({\n type: 'audio',\n frame: {\n data: packet.data,\n codec: packet.codec?.toLowerCase() === 'pcma' ? 'Pcma' : 'Pcmu',\n sampleRate: 8000,\n channels: 1,\n timestampMicros: packet.pts,\n },\n })\n }\n })\n\n // Flush pre-buffer. Seed `preParamNals` with the SDP-derived\n // VPS/SPS/PPS captured in the live callback's `stickyParamSets`\n // closure variable above. Without this seed, pre-buffer flush\n // starts with empty param sets and the first VCL flushed is a\n // bare IDR — Chrome's HEVC decoder fails on cameras that ship\n // param sets only via SDP (Reolink high-profile streams).\n const preBuffer = broker.getPreBuffer()\n const preParamNals: Buffer[] = stickyParamSets\n ? [Buffer.from(stickyParamSets)]\n : pendingParamNals.length > 0\n ? pendingParamNals.map((b) => Buffer.from(b))\n : []\n for (const pkt of preBuffer) {\n if (pkt.type === 'video') {\n // RTSP+H.265 pre-buffer holds AnnexB packets — feeding them\n // into the writeVideoNals path conflicts with the\n // repacketizer's outbound RTP stream. Skip pre-buffer for\n // video on the repacketizer path and rely on PLI to trigger\n // a fresh keyframe from the camera. Push-mode H.265 keeps\n // pre-buffer flush.\n if (useH265Repacketizer) continue\n\n const annexB = convertH264ToAnnexB(pkt.data)\n const nalTypeInfo = detectFirstNalType(annexB, isHevc)\n if (nalTypeInfo.isParamSet) { preParamNals.push(Buffer.from(annexB)); continue }\n\n let finalAnnexB = annexB\n if (preParamNals.length > 0) {\n preParamNals.push(annexB)\n finalAnnexB = Buffer.concat(preParamNals)\n preParamNals.length = 0\n } else if (nalTypeInfo.isKeyframe && stickyParamSets) {\n finalAnnexB = Buffer.concat([stickyParamSets, annexB])\n }\n pushFrame({\n type: 'video',\n frame: {\n data: finalAnnexB,\n codec: sessionCodec,\n isKeyframe: pkt.keyframe || nalTypeInfo.isKeyframe,\n timestampMicros: pkt.pts,\n },\n })\n }\n }\n\n // Allow callers (WHEP / Alexa) to override the server-generated\n // sessionId so the protocol response can echo the directive's\n // sessionId verbatim. Collide-guard against a stale session\n // already keyed on the same id — the caller should `closeSession`\n // first if it really wants to reuse the id.\n const requestedSessionId = opts.sessionId\n if (requestedSessionId !== undefined && this.sessions.has(requestedSessionId)) {\n throw new Error(`Session id already in use: ${requestedSessionId}`)\n }\n const sessionId = requestedSessionId ?? crypto.randomUUID()\n const iceServers = await this.resolveIceServers()\n\n const session = new AdaptiveSession({\n sessionId,\n source,\n sourceCodec: sessionCodec,\n iceConfig: {\n iceServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n },\n logger: this.logger,\n debug: opts.streamingDebug === true,\n })\n\n // For RTSP+H.265 sessions: subscribe to source-RTP and forward\n // through the H265Repacketizer in the session. Also seed the\n // repacketizer codec info from the SDP-derived VPS/SPS/PPS so the\n // AP codec packet can be sent ahead of the first IDR even when\n // the camera doesn't emit param sets in-band (Reolink RTSP\n // high-profile streams). Push-mode H.265 (Reolink Baichuan, etc.)\n // skips this and uses the AnnexB feed path.\n let unsubRtp: Unsubscribe | null = null\n let unsubSdpParams: Unsubscribe | null = null\n if (useH265Repacketizer) {\n const sdpPs = broker.getSdpParameterSets()\n if (sdpPs && sdpPs.length > 0) {\n session.seedH265CodecInfoFromSdp(sdpPs)\n sessionLogger.info('H.265 session seeded from SDP at create-time', {\n meta: { sessionId, brokerId, psCount: sdpPs.length },\n })\n } else {\n // Battery-cam wake-up: broker is still dialing rfc4571, so\n // `getSdpParameterSets()` is null at this point. Subscribe so\n // the seed lands the moment `onVideoTrack` fires — before\n // that, the repacketizer can't emit its AP codec packet, so\n // Chrome receives RTP it can't initialise a decoder against.\n // `onSdpParameterSets` delivers synchronously when params are\n // already known (no-op for the wake-up case), and on every\n // future reader rebuild.\n sessionLogger.info('H.265 session deferred: SDP params not ready, subscribing for late delivery', {\n meta: { sessionId, brokerId },\n })\n unsubSdpParams = broker.onSdpParameterSets((ps) => {\n try {\n session.seedH265CodecInfoFromSdp(ps)\n sessionLogger.info('H.265 session seeded from SDP late delivery', {\n meta: { sessionId, brokerId, psCount: ps.length },\n })\n } catch (err) {\n sessionLogger.warn('seedH265CodecInfoFromSdp threw', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n })\n }\n let firstRtpForwarded = false\n unsubRtp = broker.onVideoRtp((rtpData) => {\n if (!firstRtpForwarded) {\n firstRtpForwarded = true\n sessionLogger.info('H.265 session: first source RTP forwarded to repacketizer', {\n meta: { sessionId, brokerId, bytes: rtpData.length },\n })\n }\n try {\n session.forwardSourceRtpVideo(rtpData)\n } catch (err) {\n sessionLogger.warn('forwardSourceRtpVideo threw', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n })\n }\n\n const entry: SessionEntry = { session, brokerId, unsubBroker, unsubRtp, unsubSdpParams, closeSource }\n this.sessions.set(sessionId, entry)\n\n session.onClosed = () => this.cleanupSession(sessionId)\n\n return {\n sessionId,\n session,\n sessionLogger,\n brokerId,\n sessionCodec,\n useH265Repacketizer,\n iceServerCount: iceServers.length,\n }\n }\n\n async handleAnswer(sessionId: string, sdpAnswer: string): Promise<void> {\n const entry = this.sessions.get(sessionId)\n if (!entry) throw new Error(`Session not found: ${sessionId}`)\n await entry.session.handleAnswer({ sdp: sdpAnswer, type: 'answer' })\n }\n\n async closeSession(sessionId: string): Promise<void> {\n const entry = this.sessions.get(sessionId)\n if (!entry) return\n this.cleanupSession(sessionId)\n await entry.session.close().catch(() => {})\n this.logger.info('WebRTC session closed', { meta: { sessionId } })\n }\n\n async stop(): Promise<void> {\n if (this.stopped) return\n this.stopped = true\n const closes: Promise<void>[] = []\n for (const [sid, entry] of this.sessions) {\n this.cleanupSession(sid)\n closes.push(entry.session.close().catch(() => {}))\n }\n await Promise.all(closes)\n this.brokers.clear()\n this.logger.info('WebRTC server stopped')\n }\n\n getSessionCount(): number {\n return this.sessions.size\n }\n\n // ── Private ─────────────────────────────────────────────────────────\n\n private cleanupSession(sessionId: string): void {\n const entry = this.sessions.get(sessionId)\n if (!entry) return\n this.sessions.delete(sessionId)\n entry.unsubBroker()\n entry.unsubRtp?.()\n entry.unsubSdpParams?.()\n entry.closeSource()\n }\n\n private resolveBrokerId(streamId: string, hints: ClientHints = {}): string {\n // Direct match — `webrtc-session.createSession` resolves the\n // discriminated `WebrtcStreamTarget` to a canonical brokerId\n // (`${deviceId}/${camStreamId}`) before invoking the server, so\n // every legitimate client request hits this branch.\n if (this.brokers.has(streamId)) return streamId\n\n // Adaptive fallthrough — `${deviceId}/adaptive` is a sentinel\n // that means \"pick the best broker for the device\". Bare\n // deviceKey (no slash) is treated the same way for legacy\n // callers that only know the device id.\n const slash = streamId.indexOf('/')\n if (slash > 0) {\n const tail = streamId.slice(slash + 1)\n if (tail === 'adaptive') {\n return this.selectBestBroker(streamId.slice(0, slash), hints)\n }\n } else {\n return this.selectBestBroker(streamId, hints)\n }\n // Unknown shape — let the lookup fail in the caller so the\n // 'No broker for stream' message points at the actual input.\n return streamId\n }\n\n /**\n * Adaptive source selection: score registered brokers for a device\n * against client hints (viewport, downlink). Returns the broker ID\n * with the best fit. Falls back to tier preference when no hints.\n */\n private selectBestBroker(deviceKey: string, hints: ClientHints): string {\n // Collect all brokers for this device, attaching the resolver-\n // derived tier so candidates can be ranked irrespective of brokerId\n // shape (post-refactor brokerId carries the camStreamId, not the\n // tier — tiers are looked up in the assignments map at request\n // time via `profileTierResolver`).\n type Candidate = {\n brokerId: string\n tier: 'high' | 'mid' | 'low' | null\n broker: IStreamBroker\n }\n const candidates: Candidate[] = []\n for (const [bid, broker] of this.brokers) {\n if (!bid.startsWith(`${deviceKey}/`)) continue\n const tier = this.profileTierResolver?.(bid) ?? null\n candidates.push({ brokerId: bid, tier, broker })\n }\n if (candidates.length === 0) return `${deviceKey}/high` // will fail at broker lookup\n if (candidates.length === 1) return candidates[0]!.brokerId\n\n // Explicit tier override from client\n if (hints.prefersTier === 'high' || hints.prefersTier === 'mid' || hints.prefersTier === 'low') {\n const explicit = candidates.find((c) => c.tier === hints.prefersTier)\n if (explicit) return explicit.brokerId\n }\n\n // No hints — use tier preference order\n const hasHints = hints.viewportWidth !== undefined\n || hints.viewportHeight !== undefined\n || hints.downlinkMbps !== undefined\n if (!hasHints) {\n for (const tier of TIER_PREFERENCE) {\n const c = candidates.find((c) => c.tier === tier)\n if (c) return c.brokerId\n }\n return candidates[0]!.brokerId\n }\n\n // Score each candidate\n let bestId = candidates[0]!.brokerId\n let bestTier: 'high' | 'mid' | 'low' | null = candidates[0]!.tier\n let bestScore = Infinity\n for (const c of candidates) {\n const score = scoreBroker(c.tier, c.broker, hints)\n const cRank = c.tier ? TIER_PREFERENCE.indexOf(c.tier) : TIER_PREFERENCE.length\n const bestRank = bestTier ? TIER_PREFERENCE.indexOf(bestTier) : TIER_PREFERENCE.length\n if (score < bestScore || (score === bestScore && cRank < bestRank)) {\n bestId = c.brokerId\n bestTier = c.tier\n bestScore = score\n }\n }\n\n this.logger.debug('Adaptive source selected', {\n meta: { deviceKey, selected: bestId, tier: bestTier, score: Math.round(bestScore * 100) / 100, hints },\n })\n return bestId\n }\n\n private async resolveIceServers(): Promise<readonly IceServerEntry[]> {\n if (this.getIceServersFn) {\n try { return await this.getIceServersFn() } catch (err) {\n this.logger.warn('getIceServers failed', { meta: { error: errMsg(err) } })\n }\n }\n return this.staticIceServers ?? []\n }\n}\n\n// ---------------------------------------------------------------------------\n// Push-based FrameSource factory\n// ---------------------------------------------------------------------------\n// NAL type detection (codec-agnostic)\n// ---------------------------------------------------------------------------\n\ninterface NalTypeInfo {\n readonly isParamSet: boolean\n readonly isKeyframe: boolean\n}\n\n/**\n * Detect the type of the first NAL in an Annex-B buffer.\n * Works for both H.264 (1-byte NAL header) and H.265 (2-byte NAL header).\n */\nfunction detectFirstNalType(annexB: Buffer, isHevc: boolean): NalTypeInfo {\n for (let i = 0; i < annexB.length - 4; i++) {\n if (annexB[i] === 0 && annexB[i + 1] === 0 && annexB[i + 2] === 0 && annexB[i + 3] === 1 && i + 4 < annexB.length) {\n if (isHevc) {\n const nalType = (annexB[i + 4]! & 0x7e) >> 1\n const isParamSet = nalType === 32 || nalType === 33 || nalType === 34\n const isKeyframe = isParamSet || (nalType >= 16 && nalType <= 21)\n return { isParamSet, isKeyframe }\n } else {\n const nalType = annexB[i + 4]! & 0x1f\n const isParamSet = nalType === 7 || nalType === 8\n const isKeyframe = nalType === 5 || isParamSet\n return { isParamSet, isKeyframe }\n }\n }\n }\n return { isParamSet: false, isKeyframe: false }\n}\n\n// ---------------------------------------------------------------------------\n// Push-based FrameSource factory\n// ---------------------------------------------------------------------------\n\nfunction createPushFrameSource(): {\n source: FrameSource\n pushFrame: (frame: MediaFrame) => void\n close: () => void\n} {\n const queue: MediaFrame[] = []\n let resolve: ((value: IteratorResult<MediaFrame>) => void) | null = null\n let done = false\n\n const pushFrame = (mf: MediaFrame): void => {\n if (done) return\n if (resolve) {\n const r = resolve\n resolve = null\n r({ value: mf, done: false })\n } else {\n queue.push(mf)\n // Back-pressure: keep last 60 frames if queue overflows\n if (queue.length > 120) queue.splice(0, queue.length - 60)\n }\n }\n\n const close = (): void => {\n done = true\n if (resolve) {\n const r = resolve\n resolve = null\n r({ value: undefined as never, done: true })\n }\n }\n\n const source: FrameSource = (async function* () {\n try {\n while (true) {\n const item = queue.shift()\n if (item) { yield item; continue }\n if (done) return\n const result = await new Promise<IteratorResult<MediaFrame>>((r) => {\n resolve = r\n })\n if (result.done) return\n yield result.value\n }\n } finally {\n done = true\n }\n })()\n\n return { source, pushFrame, close }\n}\n\n// ---------------------------------------------------------------------------\n","import type {\n CameraStream, ProfileSlot, CamProfile,\n RtspRestreamEntry,\n WebrtcClientHints, WebrtcStreamChoice, WebrtcStreamTarget,\n} from '@camstack/types'\nimport type { StreamBrokerManager } from './stream-broker/stream-broker-manager.js'\n\n/**\n * Device-scoped facade over the broker's cam-stream registry.\n * Implements `cameraStreamsCapability` (reads only — every mutation\n * lives on the system-scope `stream-broker` cap).\n */\nexport class CameraStreamsProvider {\n constructor(private readonly manager: StreamBrokerManager) {}\n\n async getCameraStreams(input: { deviceId: number }): Promise<readonly CameraStream[]> {\n return this.manager.getCameraStreamsForDevice(input.deviceId)\n }\n\n async getBrokerStreams(input: { deviceId: number }): Promise<readonly ProfileSlot[]> {\n return this.manager.getProfileSlotsForDevice(input.deviceId)\n }\n\n async getRtspEntries(\n input: { deviceId: number; hostname?: string },\n ): Promise<readonly RtspRestreamEntry[]> {\n return this.manager.getRtspEntriesForDevice(input.deviceId, input.hostname)\n }\n}\n\n/**\n * Device-scoped WebRTC session facade.\n *\n * Implements `webrtcSessionCapability`. Resolves `{deviceId, profile}` to\n * a broker-integrated WebRTC session — no external webrtc addon needed.\n * The `BrokerWebrtcServer` subscribes directly to `IStreamBroker.onEncodedData()`\n * so frames flow Camera → Broker → RTP → WebRTC with zero re-encoding.\n */\nexport { BrokerWebrtcServer } from './webrtc/broker-webrtc-server.js'\nimport type { BrokerWebrtcServer } from './webrtc/broker-webrtc-server.js'\n\nexport class WebrtcSessionProvider {\n constructor(\n private readonly webrtcServer: BrokerWebrtcServer,\n private readonly manager: StreamBrokerManager,\n ) {}\n\n private static readonly PROFILE_LABELS: Record<CamProfile, string> = {\n high: 'High',\n mid: 'Mid',\n low: 'Low',\n }\n\n /**\n * Resolve a structured `WebrtcStreamTarget` to the brokerId the\n * WebRTC server understands. `'adaptive'` yields the magic\n * `${deviceId}/adaptive` sentinel that triggers `selectBestBroker`\n * server-side; the other two kinds resolve to the canonical\n * cam-stream-keyed brokerId.\n */\n private resolveTargetToStreamId(deviceId: number, target: WebrtcStreamTarget): string {\n switch (target.kind) {\n case 'adaptive':\n return `${deviceId}/adaptive`\n case 'profile': {\n const slots = this.manager.getProfileSlotsForDevice(deviceId)\n const slot = slots.find((s) => s.profile === target.profile)\n if (!slot?.sourceCamStreamId) {\n throw new Error(`webrtc-session: profile \"${target.profile}\" has no source assigned for device ${deviceId}`)\n }\n return `${deviceId}/${slot.sourceCamStreamId}`\n }\n case 'cam-stream':\n return `${deviceId}/${target.camStreamId}`\n }\n }\n\n async listStreams(input: { deviceId: number }): Promise<readonly WebrtcStreamChoice[]> {\n const { deviceId } = input\n const slots = this.manager.getProfileSlotsForDevice(deviceId)\n const assignedSlots = slots.filter((s) => s.sourceCamStreamId !== null)\n const camStreams = this.manager.getCameraStreamsForDevice(deviceId)\n const camStreamById = new Map<string, CameraStream>()\n for (const c of camStreams) camStreamById.set(c.camStreamId, c)\n\n const choices: WebrtcStreamChoice[] = []\n\n if (assignedSlots.length > 0) {\n choices.push({\n id: 'adaptive',\n label: 'Adaptive',\n target: { kind: 'adaptive' },\n codec: null,\n resolution: null,\n status: null,\n inputFps: null,\n decodeFps: null,\n bitrateKbps: null,\n })\n }\n\n // Profile-tier choices: pin a session to high / mid / low. Source\n // can change as the operator reassigns the slot — the session\n // follows.\n for (const slot of assignedSlots) {\n const profile = slot.profile as CamProfile\n const camStreamId = slot.sourceCamStreamId\n if (!camStreamId) continue\n const broker = await this.manager.getBroker({ brokerId: `${deviceId}/${camStreamId}` })\n const stats = broker?.getStats()\n const cam = camStreamById.get(camStreamId)\n choices.push({\n id: `profile:${profile}`,\n label: WebrtcSessionProvider.PROFILE_LABELS[profile] ?? profile,\n target: { kind: 'profile', profile },\n codec: stats?.codec ?? cam?.codec ?? slot.codec ?? null,\n resolution: cam?.resolution ?? slot.resolution ?? null,\n status: stats?.status ?? null,\n inputFps: stats?.inputFps ?? null,\n decodeFps: stats?.decodeFps ?? null,\n bitrateKbps: stats?.bitrateKbps ?? null,\n })\n }\n\n // Cam-stream-direct choices: enumerate EVERY published camStream.\n // Each published stream has a live broker (broker lifecycle is 1:1\n // with `publishCameraStream` / `retractCameraStream`).\n for (const cam of camStreams) {\n const camStreamId = cam.camStreamId\n const broker = await this.manager.getBroker({ brokerId: `${deviceId}/${camStreamId}` })\n const stats = broker?.getStats()\n choices.push({\n id: `stream:${camStreamId}`,\n label: cam.label ?? camStreamId,\n target: { kind: 'cam-stream', camStreamId },\n codec: stats?.codec ?? cam.codec ?? null,\n resolution: cam.resolution ?? null,\n status: stats?.status ?? null,\n inputFps: stats?.inputFps ?? null,\n decodeFps: stats?.decodeFps ?? null,\n bitrateKbps: stats?.bitrateKbps ?? null,\n })\n }\n return choices\n }\n\n async createSession(input: {\n deviceId: number\n target: WebrtcStreamTarget\n hints?: WebrtcClientHints\n }): Promise<{ sessionId: string; sdpOffer: string }> {\n const streamId = this.resolveTargetToStreamId(input.deviceId, input.target)\n const streamingDebug = typeof this.manager.isStreamingDebug === 'function'\n ? this.manager.isStreamingDebug(input.deviceId)\n : false\n return this.webrtcServer.createSession(streamId, input.hints, { streamingDebug })\n }\n\n async handleOffer(input: {\n deviceId: number\n target?: WebrtcStreamTarget\n sdpOffer: string\n sessionId?: string\n }): Promise<{ sessionId: string; sdpAnswer: string }> {\n // Default to adaptive: client-offer callers (Alexa, WHEP) don't\n // know about our profile slot model; they just want \"give me a\n // working video session for this device\". The same adaptive\n // selection logic the UI uses applies cleanly.\n const target: WebrtcStreamTarget = input.target ?? { kind: 'adaptive' }\n const streamId = this.resolveTargetToStreamId(input.deviceId, target)\n const streamingDebug = typeof this.manager.isStreamingDebug === 'function'\n ? this.manager.isStreamingDebug(input.deviceId)\n : false\n return this.webrtcServer.handleOffer(streamId, input.sdpOffer, undefined, {\n streamingDebug,\n sessionId: input.sessionId,\n })\n }\n\n async handleAnswer(input: {\n deviceId: number\n sessionId: string\n sdpAnswer: string\n }): Promise<void> {\n await this.webrtcServer.handleAnswer(input.sessionId, input.sdpAnswer)\n }\n\n async closeSession(input: { deviceId: number; sessionId: string }): Promise<void> {\n await this.webrtcServer.closeSession(input.sessionId)\n }\n\n async hasAdaptiveBitrate(_input: {\n deviceId: number\n profile: CamProfile\n }): Promise<boolean> {\n // Broker-direct mode: no adaptive bitrate switching (single source per profile).\n // The broker serves the camera's native stream at its native bitrate.\n return false\n }\n}\n","import type {\n ProviderRegistration,\n} from '@camstack/types'\nimport {\n BaseAddon, asJsonObject, EventCategory, createEvent, streamBrokerCapability,\n cameraStreamsCapability, webrtcSessionCapability, errMsg,\n addonWidgetsSourceCapability,\n type IAddonWidgetsSourceProvider,\n} from '@camstack/types'\nimport { StreamBrokerManager, type ProfileMapPersister } from './stream-broker/stream-broker-manager'\nimport { CameraStreamsProvider, WebrtcSessionProvider } from './device-scoped-providers'\nimport { BrokerWebrtcServer } from './webrtc/broker-webrtc-server'\nimport type { CamProfile } from '@camstack/types'\n\ninterface StreamBrokerConfig {\n readonly defaultPreBufferSec: number\n readonly rtspPort: number\n readonly maxDecodeFps: number\n readonly initialReconnectDelayMs: number\n readonly maxReconnectDelayMs: number\n}\n\ninterface PersistedAssignment {\n readonly map: Partial<Record<CamProfile, string>>\n readonly auto: boolean\n}\n\nconst PROFILE_KEYS: readonly CamProfile[] = ['high', 'mid', 'low']\n\n/**\n * Cadence for the per-broker metrics-snapshot bus event. ~1 Hz keeps\n * UI dashboards (StreamBrokerPanel, broker stats overlays) live without\n * needing to poll `getBrokerStats` every 3s — admin-ui subscribes to\n * the snapshot category and updates state from the event payload.\n */\nconst BROKER_METRICS_SNAPSHOT_INTERVAL_MS = 1_000\n\n/**\n * Build a dedup-key for a snapshot by serialising the payload with\n * monotonic counter fields stripped. Without this, fields like\n * `uptimeMs` / `totalBytes` / `packetCount` on broker stats — which\n * tick up every second regardless of state — make every JSON-equal\n * check fail and the defer never fires. Stripping happens in the\n * key only; the full payload still rides the bus when an emit\n * fires.\n */\nfunction stableSnapshotKey<T>(payload: T, dropKeys: readonly string[]): string {\n const dropSet = new Set(dropKeys)\n const strip = (v: unknown): unknown => {\n if (Array.isArray(v)) return v.map(strip)\n if (v && typeof v === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (dropSet.has(k)) continue\n out[k] = strip(val)\n }\n return out\n }\n return v\n }\n return JSON.stringify(strip(payload))\n}\n/**\n * Force a broker-stats emit at least every 30s even when the\n * payload hasn't changed — gives consumers a heartbeat without\n * the per-tick spam an unconditional emit produces. Picks up\n * idle brokers (no encoded subscribers, status frozen at\n * `streaming` with stale fps/bitrate).\n */\nconst BROKER_METRICS_HEARTBEAT_MS = 30_000\n\nexport class StreamBrokerAddon extends BaseAddon<StreamBrokerConfig> {\n private brokerManager: StreamBrokerManager | null = null\n private metricsSnapshotTimer: ReturnType<typeof setInterval> | null = null\n /**\n * Snapshot-equality cache for the broker-metrics emit. Each\n * broker emits a stats snapshot every BROKER_METRICS_SNAPSHOT_-\n * INTERVAL_MS; for an idle broker (status=streaming, fps=0,\n * subscribers=0, bitrate frozen) the per-tick payload is\n * bytewise-identical and floods the bus to no end. We skip the\n * emit when the JSON-serialised payload matches the last one\n * and reset the heartbeat clock so a quiet broker still emits\n * every BROKER_METRICS_HEARTBEAT_MS.\n *\n * Keyed by broker.deviceId (the brokerId — `<deviceId>/<profile>`\n * string). Cleared when the broker manager is destroyed.\n */\n private readonly lastEmittedBrokerSnapshot = new Map<string, { json: string; emittedAt: number }>()\n\n constructor() {\n super({\n defaultPreBufferSec: 10,\n rtspPort: 8554,\n maxDecodeFps: 5,\n initialReconnectDelayMs: 1000,\n maxReconnectDelayMs: 30000,\n })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.brokerManager = new StreamBrokerManager(undefined, this.ctx.logger)\n this.brokerManager.setDefaultPreBufferSec(this.config.defaultPreBufferSec)\n this.brokerManager.setEventBus(this.ctx.eventBus)\n // Wire the tRPC api proxy so the broker can reach `decoder` (and\n // any future cross-cap consumer) via `localProviderLink` /\n // `brokerTransportLink` regardless of where the decoder addon\n // physically runs. Replaces the previous direct `CapabilitiesAccess`\n // lookup which only worked on the hub.\n this.brokerManager.setApiAccess(this.ctx.api)\n if (this.capabilities) {\n this.brokerManager.setCapabilitiesAccess(this.capabilities)\n }\n\n // Decoder placement resolver removed: the `decoder` cap is now a\n // singleton (phase 2g+). The capability registry resolves the one\n // active provider per node; no per-device decoder-node routing.\n const rtspProvider = this.brokerManager.getRtspRestreamProvider()\n\n // Load persisted RTSP state from addon settings store\n try {\n const addonStore = (await this.ctx.settings?.readAddonStore()) ?? {}\n const tokenData = asJsonObject(addonStore['rtspTokens'])\n if (tokenData) {\n const tokens = new Map<string, string>()\n for (const [k, v] of Object.entries(tokenData)) {\n if (typeof v === 'string') tokens.set(k, v)\n }\n this.brokerManager.loadPersistedTokens(tokens)\n this.ctx.logger.info('Loaded persisted RTSP tokens', { meta: { tokenCount: tokens.size } })\n }\n const enabledData = asJsonObject(addonStore['rtspEnabled'])\n if (enabledData) {\n const states = new Map<string, boolean>()\n for (const [k, v] of Object.entries(enabledData)) {\n states.set(k, Boolean(v))\n }\n rtspProvider.loadPersistedEnabled(states)\n this.ctx.logger.info('Loaded persisted RTSP enabled states', { meta: { stateCount: states.size } })\n }\n } catch { /* first boot — no persisted state yet */ }\n\n rtspProvider.setTokenPersister((tokens) => {\n const obj: Record<string, string> = {}\n for (const [k, v] of tokens) { obj[k] = v }\n this.ctx.settings?.writeAddonStore({ rtspTokens: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist RTSP tokens', { meta: { error: errMsg(err) } })\n })\n })\n\n rtspProvider.setEnabledPersister((states) => {\n const obj: Record<string, boolean> = {}\n for (const [k, v] of states) { obj[k] = v }\n this.ctx.settings?.writeAddonStore({ rtspEnabled: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist RTSP enabled states', { meta: { error: errMsg(err) } })\n })\n })\n\n // Device overrides (per-profile pre-buffer) persisted as numeric\n // device ids. JSON serialises numeric Map keys as strings, so on\n // read we reparse and drop anything that isn't a valid number.\n try {\n const addonStore = (await this.ctx.settings?.readAddonStore()) ?? {}\n const overrideData = asJsonObject(addonStore['deviceOverrides'])\n if (overrideData) {\n const overrides = new Map<number, {\n readonly preBufferSecOverride?: number\n readonly preBuffer?: Record<string, { enabled: boolean; seconds: number }>\n readonly streamingDebug?: boolean\n }>()\n for (const [rawKey, raw] of Object.entries(overrideData)) {\n const numericKey = Number(rawKey)\n if (!Number.isFinite(numericKey) || !Number.isInteger(numericKey)) continue\n const o = asJsonObject(raw)\n if (!o) continue\n const entry: Record<string, unknown> = {}\n if (typeof o['preBufferSecOverride'] === 'number') {\n entry['preBufferSecOverride'] = o['preBufferSecOverride']\n }\n if (typeof o['streamingDebug'] === 'boolean') {\n entry['streamingDebug'] = o['streamingDebug']\n }\n const pbRaw = asJsonObject(o['preBuffer'])\n if (pbRaw) {\n const preBuffer: Record<string, { enabled: boolean; seconds: number }> = {}\n for (const [profile, v] of Object.entries(pbRaw)) {\n const sv = asJsonObject(v)\n if (sv && typeof sv['seconds'] === 'number') {\n preBuffer[profile] = { enabled: sv['enabled'] !== false, seconds: sv['seconds'] }\n }\n }\n entry['preBuffer'] = preBuffer\n }\n overrides.set(numericKey, entry)\n }\n this.brokerManager.loadPersistedDeviceOverrides(overrides)\n this.ctx.logger.info('Loaded persisted device overrides', { meta: { overrideCount: overrides.size } })\n }\n } catch { /* first boot — no persisted state yet */ }\n\n this.brokerManager.setDeviceOverridePersister((overrides) => {\n const obj: Record<string, Record<string, unknown>> = {}\n for (const [deviceId, ov] of overrides) {\n const entry: Record<string, unknown> = {}\n if (ov.preBufferSecOverride !== undefined) entry['preBufferSecOverride'] = ov.preBufferSecOverride\n if (ov.streamingDebug !== undefined) entry['streamingDebug'] = ov.streamingDebug\n if (ov.preBuffer) entry['preBuffer'] = ov.preBuffer\n obj[`${deviceId}`] = entry\n }\n this.ctx.settings?.writeAddonStore({ deviceOverrides: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist device overrides', { meta: { error: errMsg(err) } })\n })\n })\n\n // Profile-map persistence: load on boot, persist on every mutation.\n try {\n const addonStore = (await this.ctx.settings?.readAddonStore()) ?? {}\n const raw = asJsonObject(addonStore['profileMap'])\n if (raw) {\n const loaded = new Map<number, PersistedAssignment>()\n for (const [rawKey, rawVal] of Object.entries(raw)) {\n const deviceId = Number(rawKey)\n if (!Number.isFinite(deviceId) || !Number.isInteger(deviceId)) continue\n const val = asJsonObject(rawVal)\n if (!val) continue\n const mapObj = asJsonObject(val['map']) ?? {}\n const map: Partial<Record<CamProfile, string>> = {}\n for (const profile of PROFILE_KEYS) {\n const v = mapObj[profile]\n if (typeof v === 'string' && v.length > 0) map[profile] = v\n }\n const auto = val['auto'] !== false\n loaded.set(deviceId, { map, auto })\n }\n this.brokerManager.loadPersistedProfileMap(loaded)\n this.ctx.logger.info('Loaded persisted profile map', { meta: { deviceCount: loaded.size } })\n }\n } catch { /* first boot — no persisted state yet */ }\n\n const profileMapPersister: ProfileMapPersister = (assignments) => {\n const obj: Record<string, PersistedAssignment> = {}\n for (const [deviceId, assignment] of assignments) {\n obj[`${deviceId}`] = { map: { ...assignment.map }, auto: assignment.auto }\n }\n this.ctx.settings?.writeAddonStore({ profileMap: obj }).catch((err: unknown) => {\n this.ctx.logger.warn('Failed to persist profile map', { meta: { error: errMsg(err) } })\n })\n }\n this.brokerManager.setProfileMapPersister(profileMapPersister)\n\n // Start RTSP restream server\n await this.brokerManager.startRtspServer(this.config.rtspPort)\n\n // Per-agent hwaccel override changed — force-rotate any active shared\n // decoder session whose owning node matches.\n this.subscribe(\n { category: EventCategory.PipelineAgentHwaccelChanged },\n (event) => {\n const { agentNodeId, reason } = event.data\n if (typeof agentNodeId !== 'string' || agentNodeId.length === 0) return\n const manager = this.brokerManager\n if (!manager) return\n const reasonLabel = typeof reason === 'string' ? reason : 'hwaccel-changed'\n void manager.rotateDecodersOnNode(agentNodeId, reasonLabel).then((rotated) => {\n if (rotated > 0) {\n this.ctx.logger.info('decoders rotated for hwaccel change', {\n tags: { nodeId: agentNodeId },\n meta: { rotated, reason: reasonLabel },\n })\n }\n }).catch((err: unknown) => {\n this.ctx.logger.warn('hwaccel-driven decoder rotation failed', {\n tags: { nodeId: agentNodeId },\n meta: { error: errMsg(err) },\n })\n })\n },\n )\n\n // Decoder down → rotate active shared sessions to a healthy provider.\n this.watchCapability(['decoder'], {\n onDown: (nodeId, capName) => {\n if (capName !== 'decoder') return\n const manager = this.brokerManager\n if (!manager) return\n const reasonLabel = `decoder-down:${nodeId}`\n void manager.rotateDecodersOnNode(nodeId, reasonLabel).then((rotated) => {\n if (rotated > 0) {\n this.ctx.logger.info('decoders rotated after provider went down', {\n tags: { nodeId },\n meta: { rotated, reason: reasonLabel },\n })\n }\n }).catch((err: unknown) => {\n this.ctx.logger.warn('decoder-down rotation failed', {\n tags: { nodeId },\n meta: { error: errMsg(err) },\n })\n })\n },\n })\n\n const cameraStreamsProvider = new CameraStreamsProvider(this.brokerManager)\n\n // Broker-integrated WebRTC server — subscribes directly to each\n // StreamBroker's onEncodedData() callback, zero ffmpeg re-encoding.\n //\n // `getIceServers` is the single per-session entry-point into the\n // `turn-orchestrator` cap (singleton). The orchestrator aggregates\n // ICE servers from every enabled `turn-provider` (cloudflare-turn,\n // future coturn / Twilio, …) — there's NO per-device round-trip;\n // the orchestrator caches at the provider layer (cloudflare-turn\n // refreshes ICE credentials in the background ~12 h before the\n // 24 h TTL), so the per-session call costs a single cap dispatch +\n // an in-process memory read. Without TURN configured the call\n // returns `[]` and WebRTC falls back to host/srflx candidates.\n const turnApi = this.ctx.api as unknown as {\n turnOrchestrator?: {\n getAllServers: { query: (input: object) => Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]> }\n }\n }\n const webrtcServer = new BrokerWebrtcServer({\n logger: this.ctx.logger.child('webrtc'),\n getIceServers: async () => {\n if (!turnApi.turnOrchestrator) return []\n try {\n const servers = await turnApi.turnOrchestrator.getAllServers.query({})\n // The cap returns readonly entries; normalise to mutable shape\n // the werift session expects + drop entries with empty url lists.\n const out: { urls: string | string[]; username?: string; credential?: string }[] = []\n for (const s of servers) {\n if (!s.urls || (Array.isArray(s.urls) && s.urls.length === 0)) continue\n out.push({\n urls: Array.isArray(s.urls) ? [...s.urls] : s.urls,\n ...(s.username ? { username: s.username } : {}),\n ...(s.credential ? { credential: s.credential } : {}),\n })\n }\n return out\n } catch (err) {\n // Non-fatal — log + return empty so the session still gets\n // built (LAN-only / public-IP scenarios don't need TURN).\n this.ctx.logger.warn('turnOrchestrator.getAllServers failed — session will start without TURN', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return []\n }\n },\n })\n // Wire the server into the broker manager so it registers/unregisters\n // brokers as camera profiles come and go.\n this.brokerManager.setWebrtcServer(webrtcServer)\n\n const webrtcSessionProvider = new WebrtcSessionProvider(webrtcServer, this.brokerManager)\n\n // Per-broker metrics snapshot emission (~1 Hz). UI dashboards\n // subscribe to `stream-broker.metrics-snapshot` and drive their\n // overlays from event payloads instead of polling the cap.\n this.metricsSnapshotTimer = setInterval(\n () => this.emitBrokerMetricsSnapshot(),\n BROKER_METRICS_SNAPSHOT_INTERVAL_MS,\n )\n\n // Widget bundle contribution — declares the per-camera\n // stream-broker panel as an addon-shipped widget. Admin-ui's\n // <WidgetRegistryProvider> registers the Module Federation remote\n // at /api/addon-widgets/stream-broker/remoteEntry.js?v=<mtime>,\n // then loads the exposed `./widgets` module; the form-builder /\n // dashboard / device-tab dispatcher mounts each widget via\n // <WidgetSlot widgetId=\"stream-broker/stream-broker-panel\" deviceId=…/>.\n const widgetsProvider: IAddonWidgetsSourceProvider = {\n listWidgets: async () => [\n {\n stableId: 'stream-broker-panel',\n label: 'Stream Brokers',\n description: 'Per-camera stream broker panel with adaptive controls.',\n icon: 'radio',\n remoteName: 'addon_stream_broker_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'lg' as const,\n allowedSizes: ['md', 'lg', 'xl'] as const,\n defaultColumns: 8,\n defaultRows: 2,\n },\n ],\n }\n\n this.ctx.logger.info('Stream broker manager initialized')\n const registrations: ProviderRegistration[] = [\n { capability: streamBrokerCapability, provider: this.brokerManager },\n {\n capability: cameraStreamsCapability,\n provider: cameraStreamsProvider,\n kind: 'wrapper',\n defaultActive: true,\n },\n {\n capability: webrtcSessionCapability,\n provider: webrtcSessionProvider,\n kind: 'wrapper',\n defaultActive: true,\n },\n { capability: addonWidgetsSourceCapability, provider: widgetsProvider },\n ]\n return registrations\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.metricsSnapshotTimer) {\n clearInterval(this.metricsSnapshotTimer)\n this.metricsSnapshotTimer = null\n }\n await this.brokerManager?.destroyAll()\n this.brokerManager = null\n }\n\n /**\n * Emit one `stream-broker.metrics-snapshot` event per active broker.\n * BrokerStats already aggregates everything UI consumers need\n * (status, fps, bitrate, codec, subscriber count). Skipped when no\n * brokers are registered so quiet dev runs don't emit noise.\n */\n private emitBrokerMetricsSnapshot(): void {\n const manager = this.brokerManager\n const eventBus = this.ctx.eventBus\n if (!manager || !eventBus) return\n const brokers = manager.getBrokerInstances()\n if (brokers.length === 0) return\n const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id\n const nodeId = rawNodeId.includes('/') ? rawNodeId.split('/')[0]! : rawNodeId\n const timestamp = Date.now()\n const seenBrokerIds = new Set<string>()\n for (const broker of brokers) {\n const stats = broker.getStats()\n // brokerId encoded as `<deviceId>/<profile>` (StreamBroker.deviceId\n // field is overloaded to mean brokerId — see broker constructor).\n const brokerId = broker.deviceId\n seenBrokerIds.add(brokerId)\n const slash = brokerId.indexOf('/')\n const deviceId = slash > 0 ? Number(brokerId.slice(0, slash)) : Number(brokerId)\n const profile = slash > 0 ? brokerId.slice(slash + 1) : ''\n if (!Number.isFinite(deviceId)) continue\n // Snapshot-equality skip — same pattern as pipeline-runner's\n // emitMetricsSnapshot. Idle broker → bytewise-identical payload\n // → no emit. Quiet brokers still emit every\n // BROKER_METRICS_HEARTBEAT_MS so the UI's \"broker reachable\"\n // chip doesn't go stale.\n //\n // Strip monotonic counters before equality — `uptimeMs` ticks\n // every second on every broker, `totalBytes` / `packetCount`\n // grow continuously while streaming, so the raw JSON of stats\n // always differs and the defer never fires. We only want\n // observable-state changes (status, fps, subscribers, codec,\n // bitrate). The full payload still goes on the bus when an\n // emit fires; the strip only affects the dedup key.\n const json = stableSnapshotKey(stats, ['uptimeMs', 'totalBytes', 'packetCount'])\n const prev = this.lastEmittedBrokerSnapshot.get(brokerId)\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= BROKER_METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) continue\n this.lastEmittedBrokerSnapshot.set(brokerId, { json, emittedAt: timestamp })\n eventBus.emit(createEvent(\n EventCategory.StreamBrokerMetricsSnapshot,\n { type: 'device', id: deviceId, nodeId },\n { brokerId, deviceId, profile, nodeId, stats, timestamp },\n ))\n }\n // Drop cache entries for brokers that no longer exist (camera\n // detached, stream profile unassigned). Cheap because brokers\n // is the canonical truth and seenBrokerIds is a Set.\n if (this.lastEmittedBrokerSnapshot.size > seenBrokerIds.size) {\n for (const cachedId of this.lastEmittedBrokerSnapshot.keys()) {\n if (!seenBrokerIds.has(cachedId)) this.lastEmittedBrokerSnapshot.delete(cachedId)\n }\n }\n }\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'stream-broker-settings',\n title: 'Stream Broker',\n columns: 3,\n fields: [\n {\n type: 'number',\n key: 'defaultPreBufferSec',\n label: 'Pre-buffer Duration',\n description: 'Seconds of video to keep buffered before a detection event',\n min: 0,\n max: 30,\n step: 1,\n default: 10,\n unit: 's',\n },\n {\n type: 'number',\n key: 'rtspPort',\n label: 'RTSP Restream Port',\n description: 'Port for the RTSP restream server. Requires restart.',\n min: 1024,\n max: 65535,\n step: 1,\n default: 8554,\n },\n {\n type: 'number',\n key: 'maxDecodeFps',\n label: 'Max Decode FPS',\n description: 'Maximum frames per second for decoded stream subscribers',\n min: 1,\n max: 30,\n step: 1,\n default: 5,\n },\n ],\n },\n {\n id: 'stream-broker-reconnect',\n title: 'Reconnection',\n description: 'Controls how quickly the broker retries a failed RTSP connection (exponential backoff).',\n columns: 2,\n fields: [\n {\n type: 'number',\n key: 'initialReconnectDelayMs',\n label: 'Initial Reconnect Delay',\n description: 'First retry delay after a stream disconnect',\n min: 100,\n max: 10000,\n step: 100,\n default: 1000,\n unit: 'ms',\n },\n {\n type: 'number',\n key: 'maxReconnectDelayMs',\n label: 'Max Reconnect Delay',\n description: 'Upper bound for exponential backoff between retries',\n min: 1000,\n max: 120000,\n step: 1000,\n default: 30000,\n unit: 'ms',\n },\n ],\n },\n ],\n })\n }\n}\n\nexport default StreamBrokerAddon\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type { IDecoderSession, DecoderSessionConfig, DecoderStats, EncodedPacket, DecodedFrame, Unsubscribe, IScopedLogger } from '@camstack/types'\nimport { FrameDropper } from './frame-dropper'\n\n/** Minimal no-op logger for default parameter */\nconst noopLogger: IScopedLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n child() { return noopLogger },\n withTags() { return noopLogger },\n}\n\nconst SOI = Buffer.from([0xff, 0xd8])\nconst EOI = Buffer.from([0xff, 0xd9])\n\nfunction codecToInputFormat(codec: string): string {\n switch (codec) {\n case 'h265':\n case 'hevc':\n return 'hevc'\n case 'mjpeg':\n return 'mjpeg'\n case 'h264':\n default:\n return 'h264'\n }\n}\n\nfunction buildFfmpegArgs(config: DecoderSessionConfig): string[] {\n const inputFormat = codecToInputFormat(config.codec)\n const args = [\n '-hide_banner', '-loglevel', 'error',\n // Low-latency flags: skip stream probing, disable buffering, flush immediately\n '-fflags', '+nobuffer+flush_packets',\n '-flags', 'low_delay',\n '-probesize', '32',\n '-analyzeduration', '0',\n '-f', inputFormat, '-i', 'pipe:0',\n ]\n\n if (config.scale > 1) {\n args.push('-vf', `scale=iw/${config.scale}:ih/${config.scale}`)\n }\n\n args.push('-f', 'image2pipe', '-vcodec', 'mjpeg', '-q:v', '3', '-threads', '1', 'pipe:1')\n return args\n}\n\nexport class FfmpegDecoderSession implements IDecoderSession {\n private config: DecoderSessionConfig\n private frameDropper: FrameDropper\n private process: ChildProcess | null = null\n private frameCallbacks = new Set<(frame: DecodedFrame) => void>()\n private outputBuffer = Buffer.alloc(0)\n private destroyed = false\n private readonly logger: IScopedLogger\n\n // Cached dimensions — won't change between frames from the same FFmpeg session\n private cachedWidth = 0\n private cachedHeight = 0\n\n // Stats tracking\n private inputPackets = 0\n private outputFrames = 0\n private droppedFrames = 0\n private totalDecodeTimeMs = 0\n private decodeCount = 0\n private startTime = Date.now()\n\n constructor(config: DecoderSessionConfig, logger: IScopedLogger = noopLogger) {\n this.config = { ...config }\n this.logger = logger\n this.frameDropper = new FrameDropper(config.maxFps)\n this.spawnFfmpeg()\n }\n\n private spawnFfmpeg(): void {\n if (this.destroyed) return\n\n this.killFfmpeg()\n this.outputBuffer = Buffer.alloc(0)\n this.cachedWidth = 0\n this.cachedHeight = 0\n\n const args = buildFfmpegArgs(this.config)\n this.process = spawn('ffmpeg', args)\n\n // Swallow EPIPE errors on stdin during shutdown\n this.process.stdin?.on('error', () => {})\n\n this.process.stdout?.on('data', (chunk: Buffer) => {\n this.handleOutputData(chunk)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const line = data.toString().trim()\n if (line) this.logger.debug('ffmpeg stderr', { meta: { line } })\n })\n\n this.process.on('error', (err: Error) => {\n this.logger.error('FFmpeg decoder spawn error', { meta: { error: err.message } })\n })\n\n this.process.on('close', (_code, _signal) => {\n if (!this.destroyed) {\n this.process = null\n }\n })\n }\n\n private killFfmpeg(): void {\n if (this.process) {\n try {\n this.process.kill('SIGKILL')\n } catch {\n // already dead\n }\n this.process = null\n }\n }\n\n private handleOutputData(chunk: Buffer): void {\n this.outputBuffer = Buffer.concat([this.outputBuffer, chunk])\n\n // Extract complete JPEG frames from the buffer\n let searchFrom = 0\n while (true) {\n const soiIndex = this.outputBuffer.indexOf(SOI, searchFrom)\n if (soiIndex === -1) break\n\n const eoiIndex = this.outputBuffer.indexOf(EOI, soiIndex + 2)\n if (eoiIndex === -1) break\n\n const frameEnd = eoiIndex + 2\n const jpegData = this.outputBuffer.subarray(soiIndex, frameEnd)\n\n // Advance past the consumed frame\n searchFrom = frameEnd\n\n this.emitFrame(Buffer.from(jpegData))\n }\n\n // Keep only unprocessed tail\n if (searchFrom > 0) {\n this.outputBuffer = Buffer.from(this.outputBuffer.subarray(searchFrom))\n }\n }\n\n private emitFrame(data: Buffer): void {\n const decodeStart = Date.now()\n\n if (!this.frameDropper.shouldKeep()) {\n this.droppedFrames++\n return\n }\n\n const decodeTime = Date.now() - decodeStart\n this.totalDecodeTimeMs += decodeTime\n this.decodeCount++\n this.outputFrames++\n\n // Only parse dimensions on first frame or after FFmpeg restart (cached=0)\n if (this.cachedWidth === 0) {\n const dims = parseJpegDimensions(data)\n this.cachedWidth = dims.width\n this.cachedHeight = dims.height\n }\n\n const frame: DecodedFrame = {\n data,\n width: this.cachedWidth,\n height: this.cachedHeight,\n format: 'jpeg',\n timestamp: Date.now(),\n }\n\n for (const cb of this.frameCallbacks) {\n cb(frame)\n }\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (this.destroyed || !this.process?.stdin) return\n this.inputPackets++\n try {\n this.process.stdin.write(packet.data)\n } catch {\n // stdin may be closed if ffmpeg crashed\n }\n }\n\n onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe {\n this.frameCallbacks.add(callback)\n return () => {\n this.frameCallbacks.delete(callback)\n }\n }\n\n updateConfig(update: Partial<DecoderSessionConfig>): void {\n const needsRestart =\n (update.scale !== undefined && update.scale !== this.config.scale) ||\n (update.outputFormat !== undefined && update.outputFormat !== this.config.outputFormat) ||\n (update.codec !== undefined && update.codec !== this.config.codec)\n\n this.config = { ...this.config, ...update }\n\n if (update.maxFps !== undefined) {\n this.frameDropper.setMaxFps(update.maxFps)\n }\n\n if (needsRestart) {\n this.spawnFfmpeg()\n }\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n this.killFfmpeg()\n this.frameCallbacks.clear()\n }\n\n getStats(): DecoderStats {\n const uptimeSec = Math.max((Date.now() - this.startTime) / 1000, 1)\n return {\n inputFps: this.inputPackets / uptimeSec,\n outputFps: this.outputFrames / uptimeSec,\n avgDecodeTimeMs: this.decodeCount > 0 ? this.totalDecodeTimeMs / this.decodeCount : 0,\n droppedFrames: this.droppedFrames,\n }\n }\n}\n\n/**\n * Parse JPEG SOF0/SOF2 marker to extract image dimensions.\n * Scans for 0xFF 0xC0 (baseline) or 0xFF 0xC2 (progressive) markers.\n * Returns {width: 0, height: 0} if not found.\n */\nfunction parseJpegDimensions(data: Buffer): { width: number; height: number } {\n for (let i = 0; i < data.length - 8; i++) {\n if (data[i] === 0xFF && (data[i + 1] === 0xC0 || data[i + 1] === 0xC2)) {\n const height = (data[i + 5]! << 8) | data[i + 6]!\n const width = (data[i + 7]! << 8) | data[i + 8]!\n return { width, height }\n }\n }\n return { width: 0, height: 0 }\n}\n","import type { IDecoderProvider, DecoderSessionConfig, IDecoderSession } from '@camstack/types'\nimport { FfmpegDecoderSession } from './ffmpeg-decoder-session'\n\nconst SUPPORTED_CODECS = new Set(['h264', 'h265', 'hevc', 'mjpeg'])\n\nexport class FfmpegDecoderProvider implements IDecoderProvider {\n readonly id = 'ffmpeg'\n readonly name = 'FFmpeg Decoder'\n /** Software decoder — used as fallback when hardware decoders are unavailable. */\n readonly priority = 50\n\n async supportsCodec(input: { codec: string }): Promise<boolean> {\n return SUPPORTED_CODECS.has(input.codec)\n }\n\n async createSession(config: DecoderSessionConfig): Promise<IDecoderSession> {\n return new FfmpegDecoderSession(config)\n }\n}\n","/**\n * Generic length-prefix codec.\n *\n * Wire format: [4 bytes length (BE uint32)] [payload bytes]\n *\n * The length field is the size of the payload only (not including the\n * 4-byte prefix itself). Maximum payload size: 2^32 - 1 bytes. Single\n * frames larger than this will throw at encode time.\n *\n * Decode is stateful — callers feed raw bytes via `push()` and receive\n * 0+ complete payloads. Bytes straddling frame boundaries are buffered\n * internally until the next `push()` completes the frame.\n *\n * This codec is transport-agnostic. It's used on top of any stream-\n * oriented transport (TCP, UDS, in-process). It does NOT work for\n * datagram transports (UDP) — those need a different framing strategy\n * because the transport itself provides message boundaries.\n */\nimport type { FrameCodec, FrameDecoder } from '../types.js'\n\nconst LENGTH_PREFIX_BYTES = 4\n/** Hard cap — 2^31-1 to stay safely within uint32 BE range and V8 Buffer limits. */\nconst MAX_PAYLOAD_BYTES = 0x7fffffff\n\nexport interface LengthPrefixCodecOptions<TMessage> {\n /** Encode a message to its payload bytes (without any length prefix). */\n readonly serialize: (msg: TMessage) => Uint8Array\n /** Decode a single complete payload back to a message. */\n readonly deserialize: (bytes: Uint8Array) => TMessage\n /** Codec identifier for diagnostics/metrics. */\n readonly kind?: string\n}\n\nexport class LengthPrefixCodec<TMessage> implements FrameCodec<TMessage> {\n readonly kind: string\n private readonly serialize: (msg: TMessage) => Uint8Array\n private readonly deserialize: (bytes: Uint8Array) => TMessage\n\n constructor(options: LengthPrefixCodecOptions<TMessage>) {\n this.serialize = options.serialize\n this.deserialize = options.deserialize\n this.kind = options.kind ?? 'length-prefix'\n }\n\n encode(msg: TMessage): Uint8Array {\n const payload = this.serialize(msg)\n if (payload.length > MAX_PAYLOAD_BYTES) {\n throw new Error(`LengthPrefixCodec.encode: payload too large (${payload.length} bytes, max ${MAX_PAYLOAD_BYTES})`)\n }\n const buf = Buffer.allocUnsafe(LENGTH_PREFIX_BYTES + payload.length)\n buf.writeUInt32BE(payload.length, 0)\n buf.set(payload, LENGTH_PREFIX_BYTES)\n return buf\n }\n\n createDecoder(): FrameDecoder<TMessage> {\n return new LengthPrefixDecoder<TMessage>(this.deserialize)\n }\n}\n\nclass LengthPrefixDecoder<TMessage> implements FrameDecoder<TMessage> {\n /** Accumulates bytes until at least one full frame is available. */\n private buffer = Buffer.alloc(0)\n private readonly deserialize: (bytes: Uint8Array) => TMessage\n\n constructor(deserialize: (bytes: Uint8Array) => TMessage) {\n this.deserialize = deserialize\n }\n\n push(chunk: Uint8Array): readonly TMessage[] {\n // Concat the new chunk onto the pending buffer. Allocation-light for the\n // common case where chunk already contains one or more complete frames.\n this.buffer = this.buffer.length === 0\n ? Buffer.from(chunk)\n : Buffer.concat([this.buffer, Buffer.from(chunk)])\n\n const out: TMessage[] = []\n let offset = 0\n\n while (true) {\n // Need at least the length prefix.\n if (this.buffer.length - offset < LENGTH_PREFIX_BYTES) break\n\n const payloadLen = this.buffer.readUInt32BE(offset)\n if (payloadLen > MAX_PAYLOAD_BYTES) {\n // Corrupted stream — reset to avoid cascading failures.\n this.buffer = Buffer.alloc(0)\n throw new Error(`LengthPrefixDecoder: declared payload length ${payloadLen} exceeds max ${MAX_PAYLOAD_BYTES}`)\n }\n\n const totalFrameBytes = LENGTH_PREFIX_BYTES + payloadLen\n if (this.buffer.length - offset < totalFrameBytes) {\n // Frame is incomplete — wait for more bytes.\n break\n }\n\n const payloadStart = offset + LENGTH_PREFIX_BYTES\n const payloadEnd = payloadStart + payloadLen\n const payload = this.buffer.subarray(payloadStart, payloadEnd)\n out.push(this.deserialize(payload))\n offset = payloadEnd\n }\n\n // Trim the consumed prefix to free memory once per call (vs. per-frame).\n if (offset > 0) {\n this.buffer = this.buffer.subarray(offset)\n }\n\n return out\n }\n\n reset(): void {\n this.buffer = Buffer.alloc(0)\n }\n}\n","/**\n * Codec specialized for `DecodedFrame` values.\n *\n * Wraps a fixed-size binary header + raw payload inside a\n * `LengthPrefixCodec` so the transport layer doesn't need to know the\n * shape. Using a bespoke binary layout (instead of JSON via superjson)\n * keeps per-frame overhead low — critical on the hot path where a broker\n * fans out a frame to many consumers at 30+ fps.\n *\n * Wire format (after the length prefix added by LengthPrefixCodec):\n *\n * Offset Size Field\n * ────── ──── ─────────────────────────────────────────────────\n * 0 8 timestamp (BE bigint64, ms epoch)\n * 8 4 width (BE uint32)\n * 12 4 height (BE uint32)\n * 16 1 format enum (0=jpeg, 1=rgb, 2=yuv420, 3=gray, 4=bgr)\n * 17 3 reserved (zero-padded for 4-byte alignment)\n * 20 N frame data\n *\n * Fixed header: 20 bytes. All subsequent bytes are the raw frame payload.\n * The outer `LengthPrefixCodec` adds the 4-byte length prefix, so the\n * total on-wire size per frame is `4 + 20 + data.length` = 24 + data.\n *\n * Enum values are append-only: existing values must never be reassigned\n * — a worker built before a new format is added would still send the\n * old code over the wire.\n */\nimport type { DecodedFrame } from '@camstack/types'\nimport { LengthPrefixCodec } from './length-prefix-codec.js'\n\nconst HEADER_BYTES = 20\nconst FORMAT_MAP = {\n jpeg: 0,\n rgb: 1,\n yuv420: 2,\n gray: 3,\n bgr: 4,\n} as const satisfies Record<DecodedFrame['format'], number>\n\ntype FormatNumber = typeof FORMAT_MAP[keyof typeof FORMAT_MAP]\n\nconst FORMAT_REVERSE: Readonly<Record<FormatNumber, DecodedFrame['format']>> = {\n 0: 'jpeg',\n 1: 'rgb',\n 2: 'yuv420',\n 3: 'gray',\n 4: 'bgr',\n}\n\nfunction isKnownFormatNumber(n: number): n is FormatNumber {\n return n === 0 || n === 1 || n === 2 || n === 3 || n === 4\n}\n\nexport function encodeDecodedFrame(frame: DecodedFrame): Uint8Array {\n const payload = Buffer.allocUnsafe(HEADER_BYTES + frame.data.length)\n payload.writeBigInt64BE(BigInt(frame.timestamp), 0)\n payload.writeUInt32BE(frame.width, 8)\n payload.writeUInt32BE(frame.height, 12)\n payload.writeUInt8(FORMAT_MAP[frame.format], 16)\n // Zero-fill the 3 reserved bytes (17..19)\n payload.writeUInt8(0, 17)\n payload.writeUInt8(0, 18)\n payload.writeUInt8(0, 19)\n payload.set(frame.data, HEADER_BYTES)\n return payload\n}\n\nexport function decodeDecodedFrame(bytes: Uint8Array): DecodedFrame {\n if (bytes.length < HEADER_BYTES) {\n throw new Error(`decodeDecodedFrame: payload too short (${bytes.length} < ${HEADER_BYTES})`)\n }\n const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n const timestampBig = buf.readBigInt64BE(0)\n // Safe because timestamps in ms fit in Number.MAX_SAFE_INTEGER until year 287396.\n const timestamp = Number(timestampBig)\n const width = buf.readUInt32BE(8)\n const height = buf.readUInt32BE(12)\n const formatNum = buf.readUInt8(16)\n if (!isKnownFormatNumber(formatNum)) {\n throw new Error(`decodeDecodedFrame: unknown format enum ${formatNum}`)\n }\n const format = FORMAT_REVERSE[formatNum]\n const data = Buffer.from(buf.subarray(HEADER_BYTES))\n return { data, width, height, format, timestamp }\n}\n\n/**\n * Pre-built codec instance for `DecodedFrame`.\n * Composes `LengthPrefixCodec` with the DecodedFrame serializers above.\n */\nexport const decodedFrameCodec = new LengthPrefixCodec<DecodedFrame>({\n kind: 'decoded-frame',\n serialize: encodeDecodedFrame,\n deserialize: decodeDecodedFrame,\n})\n","/**\n * TCP frame transport.\n *\n * Wire:\n * - URL scheme: `tcp://host:port/`\n * - Producer calls `listen({host, port})` → server bound to a TCP port\n * - Consumer calls `connect(\"tcp://host:port/\")` → opens a socket\n * - Bidirectional byte stream via `net.Socket`\n *\n * The transport is framing-agnostic. The codec stacked on top (usually\n * `LengthPrefixCodec`) handles message boundaries.\n *\n * Backpressure: `net.Socket.write()` returns false when the internal\n * buffer is full. We expose `bufferedBytes` (from `socket.writableLength`)\n * so the FrameServer's drop policy can react. We do NOT wait for 'drain'\n * inside `send()` — the caller (FrameServer) decides whether to drop.\n *\n * Max outbound buffer is set via `socket.setDefaultEncoding`-style config;\n * Node's default high-water mark is 16 KB which is too small for video\n * frames. We bump the socket's writable high-water mark at connection time.\n */\nimport net from 'node:net'\nimport { randomUUID } from 'node:crypto'\nimport type {\n FrameTransport,\n TransportEndpoint,\n TransportConnection,\n TransportListenOptions,\n TransportConnectOptions,\n} from '../types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nconst URL_SCHEME = 'tcp://'\n\nexport class TcpTransport implements FrameTransport {\n readonly kind = 'tcp' as const\n\n async listen(options?: TransportListenOptions): Promise<TransportEndpoint> {\n const host = options?.host ?? '127.0.0.1'\n const port = options?.port ?? 0\n return new Promise<TransportEndpoint>((resolve, reject) => {\n const connectionHandlers = new Set<(conn: TransportConnection) => void>()\n const connections = new Set<TcpConnection>()\n\n const server = net.createServer((socket) => {\n socket.setNoDelay(true)\n const conn = new TcpConnection(socket, () => {\n connections.delete(conn)\n })\n connections.add(conn)\n for (const handler of connectionHandlers) handler(conn)\n })\n\n server.once('error', (err) => reject(err))\n\n server.listen(port, host, () => {\n server.removeAllListeners('error')\n const address = server.address()\n if (address === null || typeof address === 'string') {\n server.close()\n reject(new Error('TcpTransport: server.address() returned unexpected value'))\n return\n }\n // Use the actual bound address/port (host may be 0.0.0.0, port may be 0 → OS assigned).\n const boundHost = address.address === '::' ? '0.0.0.0' : address.address\n const url = `${URL_SCHEME}${boundHost}:${address.port}/`\n const namespace = options?.namespace\n const finalUrl = namespace !== undefined ? `${url}${namespace}` : url\n\n const endpoint: TransportEndpoint = {\n kind: 'tcp',\n url: finalUrl,\n get activeConnections() { return connections.size },\n onConnection(handler) {\n connectionHandlers.add(handler)\n return () => connectionHandlers.delete(handler)\n },\n async close() {\n for (const conn of [...connections]) {\n conn.close('endpoint-closed')\n }\n await new Promise<void>((res) => {\n server.close(() => res())\n })\n },\n }\n resolve(endpoint)\n })\n })\n }\n\n async connect(url: string, options?: TransportConnectOptions): Promise<TransportConnection> {\n const parsed = parseTcpUrl(url)\n if (!parsed) {\n throw new Error(`TcpTransport: invalid URL \"${url}\"`)\n }\n\n const timeoutMs = options?.timeoutMs ?? 10_000\n\n return new Promise<TransportConnection>((resolve, reject) => {\n const socket = new net.Socket()\n socket.setNoDelay(true)\n\n let settled = false\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n socket.destroy()\n reject(new Error(`TcpTransport: connect timeout after ${timeoutMs}ms (${url})`))\n }, timeoutMs)\n\n socket.once('error', (err) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(err)\n })\n\n socket.once('connect', () => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n socket.removeAllListeners('error')\n resolve(new TcpConnection(socket, () => { /* client-side dispose noop */ }))\n })\n\n socket.connect(parsed.port, parsed.host)\n })\n }\n}\n\nfunction parseTcpUrl(url: string): { host: string; port: number } | null {\n if (!url.startsWith(URL_SCHEME)) return null\n const rest = url.slice(URL_SCHEME.length)\n // Strip trailing path/namespace — only host:port matters for routing.\n const authority = rest.split('/')[0] ?? ''\n const lastColon = authority.lastIndexOf(':')\n if (lastColon === -1) return null\n const host = authority.slice(0, lastColon)\n const port = Number(authority.slice(lastColon + 1))\n if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) return null\n return { host, port }\n}\n\nclass TcpConnection implements TransportConnection {\n readonly id = randomUUID()\n readonly kind = 'tcp' as const\n readonly remoteAddress: string\n\n private readonly dataHandlers = new Set<(chunk: Uint8Array) => void>()\n private readonly closeHandlers = new Set<(reason?: string) => void>()\n private closed = false\n\n constructor(\n private readonly socket: net.Socket,\n private readonly onDispose: () => void,\n ) {\n this.remoteAddress = socket.remoteAddress !== undefined && socket.remotePort !== undefined\n ? `${socket.remoteAddress}:${socket.remotePort}`\n : 'unknown'\n\n socket.on('data', (chunk: Buffer) => {\n if (this.closed) return\n for (const handler of this.dataHandlers) handler(chunk)\n })\n\n const emitClose = (reason?: string) => {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.onDispose()\n }\n socket.on('close', () => emitClose('socket-closed'))\n socket.on('error', (err) => emitClose(err.message))\n socket.on('end', () => emitClose('peer-ended'))\n }\n\n send(chunk: Uint8Array): void {\n if (this.closed) return\n // We ignore the boolean returned by write() — the FrameServer's drop\n // policy uses `bufferedBytes` to decide when to back off.\n this.socket.write(chunk)\n }\n\n onData(handler: (chunk: Uint8Array) => void): Unsubscribe {\n this.dataHandlers.add(handler)\n return () => this.dataHandlers.delete(handler)\n }\n\n onClose(handler: (reason?: string) => void): Unsubscribe {\n this.closeHandlers.add(handler)\n return () => this.closeHandlers.delete(handler)\n }\n\n close(reason?: string): void {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.socket.destroy()\n this.onDispose()\n }\n\n get bufferedBytes(): number {\n return this.socket.writableLength\n }\n}\n","/**\n * Unix Domain Socket frame transport.\n *\n * Same-host IPC with lower overhead than TCP loopback (no TCP/IP stack\n * traversal, no three-way handshake). Best choice when producer and\n * consumer are on the same machine — roughly 2-3× the throughput of TCP\n * loopback for large binary payloads like decoded frames.\n *\n * Wire:\n * - URL scheme: `unix:///absolute/path/to/socket.sock`\n * - Producer calls `listen({socketPath})` → creates the socket file\n * - Consumer calls `connect(\"unix:///.../socket.sock\")` → opens AF_UNIX\n * - Bidirectional stream via `net.Socket` (Node treats AF_UNIX as a\n * regular socket behind the same API)\n *\n * The socket file is removed on `close()` and at server startup if stale.\n * Windows note: Node supports AF_UNIX on Windows 10+ but named pipes may\n * be preferable. This transport uses `net.createServer` which handles\n * AF_UNIX natively on all platforms Node targets.\n */\nimport net from 'node:net'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport * as os from 'node:os'\nimport { randomUUID } from 'node:crypto'\nimport type {\n FrameTransport,\n TransportEndpoint,\n TransportConnection,\n TransportListenOptions,\n TransportConnectOptions,\n} from '../types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nconst URL_SCHEME = 'unix://'\n\nexport class UdsTransport implements FrameTransport {\n readonly kind = 'uds' as const\n\n async listen(options?: TransportListenOptions): Promise<TransportEndpoint> {\n const socketPath = options?.socketPath ?? path.join(os.tmpdir(), `camstack-frame-${randomUUID()}.sock`)\n\n // Remove stale socket file if it exists (common after crash).\n try {\n fs.unlinkSync(socketPath)\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code !== 'ENOENT') throw err\n }\n\n return new Promise<TransportEndpoint>((resolve, reject) => {\n const connectionHandlers = new Set<(conn: TransportConnection) => void>()\n const connections = new Set<UdsConnection>()\n\n const server = net.createServer((socket) => {\n const conn = new UdsConnection(socket, () => {\n connections.delete(conn)\n })\n connections.add(conn)\n for (const handler of connectionHandlers) handler(conn)\n })\n\n server.once('error', (err) => reject(err))\n\n server.listen(socketPath, () => {\n server.removeAllListeners('error')\n const url = `${URL_SCHEME}${socketPath}`\n\n const endpoint: TransportEndpoint = {\n kind: 'uds',\n url,\n get activeConnections() { return connections.size },\n onConnection(handler) {\n connectionHandlers.add(handler)\n return () => connectionHandlers.delete(handler)\n },\n async close() {\n for (const conn of [...connections]) {\n conn.close('endpoint-closed')\n }\n await new Promise<void>((res) => {\n server.close(() => res())\n })\n try {\n fs.unlinkSync(socketPath)\n } catch {\n // File may already be gone — ignore.\n }\n },\n }\n resolve(endpoint)\n })\n })\n }\n\n async connect(url: string, options?: TransportConnectOptions): Promise<TransportConnection> {\n const socketPath = parseUdsUrl(url)\n if (socketPath === null) {\n throw new Error(`UdsTransport: invalid URL \"${url}\"`)\n }\n\n const timeoutMs = options?.timeoutMs ?? 10_000\n\n return new Promise<TransportConnection>((resolve, reject) => {\n const socket = new net.Socket()\n\n let settled = false\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n socket.destroy()\n reject(new Error(`UdsTransport: connect timeout after ${timeoutMs}ms (${url})`))\n }, timeoutMs)\n\n socket.once('error', (err) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(err)\n })\n\n socket.once('connect', () => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n socket.removeAllListeners('error')\n resolve(new UdsConnection(socket, () => { /* client-side dispose noop */ }))\n })\n\n socket.connect(socketPath)\n })\n }\n}\n\nfunction parseUdsUrl(url: string): string | null {\n if (!url.startsWith(URL_SCHEME)) return null\n const rest = url.slice(URL_SCHEME.length)\n // Expected form: `unix:///abs/path.sock` → rest starts with `/`\n if (!rest.startsWith('/')) return null\n return rest\n}\n\nclass UdsConnection implements TransportConnection {\n readonly id = randomUUID()\n readonly kind = 'uds' as const\n readonly remoteAddress: string\n\n private readonly dataHandlers = new Set<(chunk: Uint8Array) => void>()\n private readonly closeHandlers = new Set<(reason?: string) => void>()\n private closed = false\n\n constructor(\n private readonly socket: net.Socket,\n private readonly onDispose: () => void,\n ) {\n this.remoteAddress = 'unix-peer'\n\n socket.on('data', (chunk: Buffer) => {\n if (this.closed) return\n for (const handler of this.dataHandlers) handler(chunk)\n })\n\n const emitClose = (reason?: string) => {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.onDispose()\n }\n socket.on('close', () => emitClose('socket-closed'))\n socket.on('error', (err) => emitClose(err.message))\n socket.on('end', () => emitClose('peer-ended'))\n }\n\n send(chunk: Uint8Array): void {\n if (this.closed) return\n this.socket.write(chunk)\n }\n\n onData(handler: (chunk: Uint8Array) => void): Unsubscribe {\n this.dataHandlers.add(handler)\n return () => this.dataHandlers.delete(handler)\n }\n\n onClose(handler: (reason?: string) => void): Unsubscribe {\n this.closeHandlers.add(handler)\n return () => this.closeHandlers.delete(handler)\n }\n\n close(reason?: string): void {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.socket.destroy()\n this.onDispose()\n }\n\n get bufferedBytes(): number {\n return this.socket.writableLength\n }\n}\n","/**\n * In-process transport — connects producer and consumer via EventEmitter\n * pairs, no real I/O. Used for:\n *\n * - Unit tests of FrameServer/FrameClient without TCP/UDS setup\n * - Same-process fast path (broker and consumer in the same Node process,\n * e.g. hub-local addons consuming their own decoder)\n *\n * The URL scheme `inprocess://<namespace>` registers the endpoint in a\n * module-local registry; connects look it up by namespace. Each endpoint\n * is isolated by namespace so multiple in-process transports can coexist.\n *\n * No auth token enforcement at the transport level (the FrameServer still\n * runs its `authenticate` callback). In-process callers are assumed trusted.\n */\nimport { EventEmitter } from 'node:events'\nimport { randomUUID } from 'node:crypto'\nimport type {\n FrameTransport,\n TransportEndpoint,\n TransportConnection,\n TransportListenOptions,\n TransportConnectOptions,\n} from '../types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nconst URL_SCHEME = 'inprocess://'\n\n/** Module-level registry: namespace → endpoint. Allows `connect()` to locate `listen()`. */\nconst registry = new Map<string, InProcessEndpoint>()\n\nexport class InProcessTransport implements FrameTransport {\n readonly kind = 'inprocess' as const\n\n async listen(options?: TransportListenOptions): Promise<TransportEndpoint> {\n const namespace = options?.namespace ?? `ns-${randomUUID()}`\n if (registry.has(namespace)) {\n throw new Error(`InProcessTransport: namespace \"${namespace}\" already in use`)\n }\n const endpoint = new InProcessEndpoint(namespace)\n registry.set(namespace, endpoint)\n return endpoint\n }\n\n async connect(url: string, _options?: TransportConnectOptions): Promise<TransportConnection> {\n if (!url.startsWith(URL_SCHEME)) {\n throw new Error(`InProcessTransport: unsupported URL \"${url}\"`)\n }\n const namespace = url.slice(URL_SCHEME.length)\n const endpoint = registry.get(namespace)\n if (!endpoint) {\n throw new Error(`InProcessTransport: no endpoint for namespace \"${namespace}\"`)\n }\n return endpoint.acceptNewConsumer()\n }\n}\n\nclass InProcessEndpoint implements TransportEndpoint {\n readonly kind = 'inprocess' as const\n readonly url: string\n private readonly connectionHandlers = new Set<(conn: TransportConnection) => void>()\n private readonly connections = new Set<InProcessConnection>()\n private closed = false\n\n constructor(private readonly namespace: string) {\n this.url = `${URL_SCHEME}${namespace}`\n }\n\n get activeConnections(): number {\n return this.connections.size\n }\n\n onConnection(handler: (conn: TransportConnection) => void): Unsubscribe {\n this.connectionHandlers.add(handler)\n return () => this.connectionHandlers.delete(handler)\n }\n\n async close(): Promise<void> {\n if (this.closed) return\n this.closed = true\n for (const conn of [...this.connections]) {\n conn.close('endpoint-closed')\n }\n registry.delete(this.namespace)\n }\n\n /** Internal — called by the transport's `connect()` to wire a new pair. */\n acceptNewConsumer(): InProcessConnection {\n if (this.closed) {\n throw new Error(`InProcessEndpoint: \"${this.namespace}\" is closed`)\n }\n // Two linked connections — one for the server side (what handlers see),\n // one for the client side (what `connect()` returns). Each writes into\n // the peer's incoming emitter.\n const serverEmitter = new EventEmitter()\n const clientEmitter = new EventEmitter()\n const serverConn = new InProcessConnection(clientEmitter, serverEmitter, () => {\n this.connections.delete(serverConn)\n })\n const clientConn = new InProcessConnection(serverEmitter, clientEmitter, () => {\n // Closing the client side also tears down the server side.\n serverConn.close('peer-closed')\n })\n this.connections.add(serverConn)\n for (const handler of this.connectionHandlers) {\n // Fire synchronously so the server can register `onData` before the\n // client starts writing. Order of registration is the handler's\n // responsibility, but this matches real TCP semantics well enough.\n handler(serverConn)\n }\n return clientConn\n }\n}\n\nclass InProcessConnection implements TransportConnection {\n readonly id = randomUUID()\n readonly kind = 'inprocess' as const\n readonly remoteAddress = 'inprocess'\n\n private readonly dataHandlers = new Set<(chunk: Uint8Array) => void>()\n private readonly closeHandlers = new Set<(reason?: string) => void>()\n private closed = false\n\n constructor(\n /** The peer's emitter — we write to this one via `.emit('data', ...)`. */\n private readonly outbound: EventEmitter,\n /** Our own emitter — we listen to this one for incoming bytes. */\n private readonly inbound: EventEmitter,\n private readonly onDispose: () => void,\n ) {\n this.inbound.on('data', (chunk: Uint8Array) => {\n if (this.closed) return\n for (const handler of this.dataHandlers) handler(chunk)\n })\n this.inbound.on('close', (reason?: string) => {\n if (this.closed) return\n this.closed = true\n for (const handler of this.closeHandlers) handler(reason)\n this.onDispose()\n })\n }\n\n send(chunk: Uint8Array): void {\n if (this.closed) return\n this.outbound.emit('data', chunk)\n }\n\n onData(handler: (chunk: Uint8Array) => void): Unsubscribe {\n this.dataHandlers.add(handler)\n return () => this.dataHandlers.delete(handler)\n }\n\n onClose(handler: (reason?: string) => void): Unsubscribe {\n this.closeHandlers.add(handler)\n return () => this.closeHandlers.delete(handler)\n }\n\n close(reason?: string): void {\n if (this.closed) return\n // Notify both sides. The peer listens to its own inbound, which is our\n // outbound, so emitting 'close' there tears down the peer too.\n this.outbound.emit('close', reason)\n this.inbound.emit('close', reason)\n }\n\n get bufferedBytes(): number {\n // No real buffer — delivery is synchronous via EventEmitter.\n return 0\n }\n}\n","/**\n * FrameServer — producer-side high-level API.\n *\n * Responsibilities (everything a consumer of the frame-transport layer\n * wants \"just to work\"):\n *\n * 1. Open a transport endpoint.\n * 2. On each incoming consumer connection:\n * a. Wait for a handshake message carrying the auth token.\n * b. Run the configured `authenticate` callback — reject on failure.\n * c. Register the connection as an active consumer.\n * 3. On `broadcast(msg)` / `sendTo(id, msg)`:\n * a. Encode the message via the codec.\n * b. For each target consumer, check backpressure.\n * c. If over threshold, apply drop policy (default drop-newest).\n * d. Otherwise `connection.send(bytes)`.\n * 4. Expose metrics (framesSent, framesDropped, activeConsumers).\n *\n * Handshake protocol (first bytes sent by the client):\n * [4 bytes length BE] [N bytes JSON: { \"authToken\": \"...\" }]\n *\n * The handshake reuses the length-prefix framing. The payload is a small\n * JSON blob (not the frame codec) so the server can read it without\n * needing the full codec chain. Subsequent bytes from the client are\n * currently unused but reserved for future control messages (pause,\n * resume, rate changes).\n */\nimport type {\n FrameServer,\n FrameServerOptions,\n FrameServerStats,\n TransportConnection,\n TransportEndpoint,\n} from './types.js'\nimport type { Unsubscribe } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\n\n/** Pending state while waiting for the handshake. */\ninterface PendingConsumer {\n readonly conn: TransportConnection\n readonly unsubscribeData: Unsubscribe\n readonly unsubscribeClose: Unsubscribe\n /** Bytes received before the handshake completes. Capped to avoid memory abuse. */\n buffer: Buffer\n readonly timer: ReturnType<typeof setTimeout>\n}\n\n/** An authenticated consumer ready to receive frames. */\ninterface ActiveConsumer {\n readonly conn: TransportConnection\n readonly unsubscribeClose: Unsubscribe\n}\n\nconst HANDSHAKE_MAX_BYTES = 4096\nconst HANDSHAKE_TIMEOUT_MS = 5_000\nconst DEFAULT_BACKPRESSURE_THRESHOLD = 4 * 1024 * 1024\n\nexport async function createFrameServer<TMessage>(\n options: FrameServerOptions<TMessage>,\n): Promise<FrameServer<TMessage>> {\n const endpoint = await options.transport.listen(options.listen)\n const server = new FrameServerImpl<TMessage>(endpoint, options)\n return server\n}\n\nclass FrameServerImpl<TMessage> implements FrameServer<TMessage> {\n private readonly pending = new Map<string, PendingConsumer>()\n private readonly active = new Map<string, ActiveConsumer>()\n private readonly connectHandlers = new Set<(id: string) => void>()\n private readonly disconnectHandlers = new Set<(id: string, reason?: string) => void>()\n private readonly startedAt = Date.now()\n private framesSent = 0\n private framesDropped = 0\n private bytesSent = 0\n private stopped = false\n\n private readonly backpressureThreshold: number\n\n constructor(\n private readonly endpoint: TransportEndpoint,\n private readonly options: FrameServerOptions<TMessage>,\n ) {\n this.backpressureThreshold = options.backpressureThresholdBytes ?? DEFAULT_BACKPRESSURE_THRESHOLD\n endpoint.onConnection((conn) => this.handleIncomingConnection(conn))\n }\n\n get url(): string {\n return this.endpoint.url\n }\n\n get activeConsumers(): number {\n return this.active.size\n }\n\n broadcast(msg: TMessage): void {\n if (this.stopped || this.active.size === 0) return\n const bytes = this.options.codec.encode(msg)\n for (const consumer of this.active.values()) {\n this.sendTo_unsafe(consumer, bytes)\n }\n }\n\n sendTo(connectionId: string, msg: TMessage): void {\n if (this.stopped) return\n const consumer = this.active.get(connectionId)\n if (!consumer) return\n const bytes = this.options.codec.encode(msg)\n this.sendTo_unsafe(consumer, bytes)\n }\n\n onConsumerConnect(handler: (id: string) => void): Unsubscribe {\n this.connectHandlers.add(handler)\n return () => this.connectHandlers.delete(handler)\n }\n\n onConsumerDisconnect(handler: (id: string, reason?: string) => void): Unsubscribe {\n this.disconnectHandlers.add(handler)\n return () => this.disconnectHandlers.delete(handler)\n }\n\n getStats(): FrameServerStats {\n return {\n activeConsumers: this.active.size,\n framesSent: this.framesSent,\n framesDropped: this.framesDropped,\n bytesSent: this.bytesSent,\n uptimeMs: Date.now() - this.startedAt,\n }\n }\n\n async stop(): Promise<void> {\n if (this.stopped) return\n this.stopped = true\n for (const pending of this.pending.values()) {\n clearTimeout(pending.timer)\n pending.unsubscribeData()\n pending.unsubscribeClose()\n pending.conn.close('server-stopped')\n }\n this.pending.clear()\n for (const consumer of this.active.values()) {\n consumer.unsubscribeClose()\n consumer.conn.close('server-stopped')\n }\n this.active.clear()\n await this.endpoint.close()\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private handleIncomingConnection(conn: TransportConnection): void {\n const timer = setTimeout(() => {\n const p = this.pending.get(conn.id)\n if (!p) return\n this.pending.delete(conn.id)\n p.unsubscribeData()\n p.unsubscribeClose()\n conn.close('handshake-timeout')\n }, HANDSHAKE_TIMEOUT_MS)\n\n const unsubscribeData = conn.onData((chunk) => {\n const pending = this.pending.get(conn.id)\n if (!pending) return\n pending.buffer = pending.buffer.length === 0\n ? Buffer.from(chunk)\n : Buffer.concat([pending.buffer, Buffer.from(chunk)])\n if (pending.buffer.length > HANDSHAKE_MAX_BYTES) {\n this.rejectPending(conn.id, 'handshake-too-large')\n return\n }\n this.tryCompleteHandshake(conn.id)\n })\n\n const unsubscribeClose = conn.onClose((reason) => {\n const p = this.pending.get(conn.id)\n if (p) {\n clearTimeout(p.timer)\n p.unsubscribeData()\n this.pending.delete(conn.id)\n }\n const a = this.active.get(conn.id)\n if (a) {\n this.active.delete(conn.id)\n for (const h of this.disconnectHandlers) h(conn.id, reason)\n }\n })\n\n this.pending.set(conn.id, {\n conn,\n buffer: Buffer.alloc(0),\n timer,\n unsubscribeData,\n unsubscribeClose,\n })\n }\n\n private tryCompleteHandshake(connectionId: string): void {\n const pending = this.pending.get(connectionId)\n if (!pending) return\n if (pending.buffer.length < 4) return\n const len = pending.buffer.readUInt32BE(0)\n if (pending.buffer.length < 4 + len) return\n\n const handshakeBytes = pending.buffer.subarray(4, 4 + len)\n let token: string | null = null\n try {\n const json: unknown = JSON.parse(handshakeBytes.toString('utf-8'))\n if (json !== null && typeof json === 'object' && 'authToken' in json) {\n const t = (json as { authToken: unknown }).authToken\n if (typeof t === 'string') token = t\n }\n } catch {\n this.rejectPending(connectionId, 'handshake-invalid-json')\n return\n }\n\n // Run the authenticate callback (if any). Async-safe.\n const runAuth = async () => {\n if (this.options.authenticate) {\n await this.options.authenticate(token, pending.conn)\n }\n }\n\n runAuth().then(\n () => {\n // Promote to active.\n const p = this.pending.get(connectionId)\n if (!p) return // already gone (e.g. closed mid-handshake)\n clearTimeout(p.timer)\n p.unsubscribeData()\n this.pending.delete(connectionId)\n\n this.active.set(connectionId, {\n conn: p.conn,\n unsubscribeClose: p.unsubscribeClose,\n })\n\n for (const h of this.connectHandlers) h(connectionId)\n },\n (err: unknown) => {\n const msg = errMsg(err)\n this.rejectPending(connectionId, `auth-failed:${msg}`)\n },\n )\n }\n\n private rejectPending(connectionId: string, reason: string): void {\n const p = this.pending.get(connectionId)\n if (!p) return\n clearTimeout(p.timer)\n p.unsubscribeData()\n p.unsubscribeClose()\n this.pending.delete(connectionId)\n p.conn.close(reason)\n }\n\n private sendTo_unsafe(consumer: ActiveConsumer, bytes: Uint8Array): void {\n const policy = this.options.dropPolicy ?? 'drop-newest'\n\n if (consumer.conn.bufferedBytes >= this.backpressureThreshold) {\n if (policy === 'drop-newest') {\n this.framesDropped++\n return\n }\n if (policy === 'drop-oldest') {\n // Best-effort for stream transports: we can't pop already-sent bytes\n // out of the socket buffer. Fall back to drop-newest semantics.\n this.framesDropped++\n return\n }\n // policy === 'block' → send anyway, let Node's internal buffer grow.\n }\n\n consumer.conn.send(bytes)\n this.framesSent++\n this.bytesSent += bytes.length\n }\n}\n","/**\n * FrameClient — consumer-side high-level API.\n *\n * Responsibilities:\n * 1. Connect the transport to the given URL.\n * 2. Send the handshake (length-prefixed JSON with authToken).\n * 3. Stream incoming bytes through the codec's incremental decoder.\n * 4. Fan out decoded messages to both:\n * - callback handlers (via `onMessage(fn)`)\n * - AsyncIterable (`for await (const msg of client.messages)`)\n * 5. Cleanly disconnect on `disconnect()` or remote close.\n *\n * The AsyncIterable is implemented with a small unbounded queue. If no\n * `for await` is iterating, messages are buffered; if the buffer grows\n * indefinitely this is a consumer-side memory leak. Callers who care\n * about backpressure should prefer `onMessage()` + explicit pacing, or\n * iterate promptly.\n */\nimport type {\n FrameClient,\n FrameClientOptions,\n FrameClientStats,\n FrameDecoder,\n TransportConnection,\n} from './types.js'\nimport type { Unsubscribe } from '@camstack/types'\n\nexport function createFrameClient<TMessage>(\n options: FrameClientOptions<TMessage>,\n): FrameClient<TMessage> {\n return new FrameClientImpl<TMessage>(options)\n}\n\ninterface IterableState<TMessage> {\n /** Messages waiting for a `for await` puller. */\n buffer: TMessage[]\n /** Pending puller's resolve function — called when a new message arrives. */\n waiter: ((value: IteratorResult<TMessage>) => void) | null\n done: boolean\n}\n\nclass FrameClientImpl<TMessage> implements FrameClient<TMessage> {\n private connection: TransportConnection | null = null\n private decoder: FrameDecoder<TMessage> | null = null\n private readonly messageHandlers = new Set<(msg: TMessage) => void>()\n private readonly disconnectHandlers = new Set<(reason?: string) => void>()\n private readonly iterState: IterableState<TMessage> = {\n buffer: [],\n waiter: null,\n done: false,\n }\n\n // Stats\n private framesReceived = 0\n private bytesReceived = 0\n private connectedAtMs: number | null = null\n\n constructor(private readonly options: FrameClientOptions<TMessage>) {}\n\n async connect(url: string): Promise<void> {\n if (this.connection !== null) {\n throw new Error('FrameClient: already connected')\n }\n\n const conn = await this.options.transport.connect(url, this.options.connect)\n this.connection = conn\n this.decoder = this.options.codec.createDecoder()\n this.connectedAtMs = Date.now()\n\n conn.onData((chunk) => this.handleIncomingChunk(chunk))\n conn.onClose((reason) => this.handleDisconnect(reason))\n\n // Send the handshake. Format matches FrameServerImpl.tryCompleteHandshake:\n // [4 bytes BE length][JSON payload]\n const payload = Buffer.from(JSON.stringify({\n authToken: this.options.connect?.authToken ?? null,\n }), 'utf-8')\n const header = Buffer.allocUnsafe(4)\n header.writeUInt32BE(payload.length, 0)\n conn.send(Buffer.concat([header, payload]))\n }\n\n get messages(): AsyncIterable<TMessage> {\n const state = this.iterState\n return {\n [Symbol.asyncIterator](): AsyncIterator<TMessage> {\n return {\n next(): Promise<IteratorResult<TMessage>> {\n if (state.buffer.length > 0) {\n const value = state.buffer.shift() as TMessage\n return Promise.resolve({ value, done: false })\n }\n if (state.done) {\n return Promise.resolve({ value: undefined, done: true })\n }\n return new Promise((resolve) => {\n state.waiter = resolve\n })\n },\n return(): Promise<IteratorResult<TMessage>> {\n state.done = true\n state.buffer = []\n if (state.waiter) {\n state.waiter({ value: undefined, done: true })\n state.waiter = null\n }\n return Promise.resolve({ value: undefined, done: true })\n },\n }\n },\n }\n }\n\n onMessage(handler: (msg: TMessage) => void): Unsubscribe {\n this.messageHandlers.add(handler)\n return () => this.messageHandlers.delete(handler)\n }\n\n onDisconnect(handler: (reason?: string) => void): Unsubscribe {\n this.disconnectHandlers.add(handler)\n return () => this.disconnectHandlers.delete(handler)\n }\n\n getStats(): FrameClientStats {\n return {\n framesReceived: this.framesReceived,\n bytesReceived: this.bytesReceived,\n connected: this.connection !== null,\n connectedAtMs: this.connectedAtMs,\n }\n }\n\n async disconnect(): Promise<void> {\n const conn = this.connection\n if (conn === null) return\n conn.close('client-disconnect')\n // handleDisconnect will be invoked by the onClose handler.\n }\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private handleIncomingChunk(chunk: Uint8Array): void {\n if (this.decoder === null) return\n this.bytesReceived += chunk.length\n let messages: readonly TMessage[]\n try {\n messages = this.decoder.push(chunk)\n } catch (err) {\n // Framing error — tear down the connection. The decoder's internal\n // buffer has been reset already by its own error handling.\n const reason = err instanceof Error ? `decoder-error:${err.message}` : 'decoder-error'\n this.handleDisconnect(reason)\n if (this.connection) this.connection.close(reason)\n return\n }\n\n for (const msg of messages) {\n this.framesReceived++\n\n // Fan out to callbacks.\n for (const handler of this.messageHandlers) handler(msg)\n\n // Push to AsyncIterable buffer or resolve the pending waiter.\n if (this.iterState.done) continue\n if (this.iterState.waiter) {\n const w = this.iterState.waiter\n this.iterState.waiter = null\n w({ value: msg, done: false })\n } else {\n this.iterState.buffer.push(msg)\n }\n }\n }\n\n private handleDisconnect(reason?: string): void {\n if (this.connection === null) return\n this.connection = null\n this.decoder?.reset()\n this.decoder = null\n\n for (const handler of this.disconnectHandlers) handler(reason)\n\n // Drain AsyncIterable state.\n this.iterState.done = true\n if (this.iterState.waiter) {\n const w = this.iterState.waiter\n this.iterState.waiter = null\n w({ value: undefined, done: true })\n }\n }\n}\n","/**\n * HMAC-based auth tokens for FrameServer subscriptions.\n *\n * Flow:\n * 1. Server issues `createAuthToken(sessionId, expiryMs)` and returns the\n * token to the consumer via tRPC.\n * 2. Consumer sends the token in the first handshake message after\n * connecting the transport.\n * 3. Server validates via `verifyAuthToken(token, expectedSessionId)`\n * before accepting frame broadcasts.\n *\n * Tokens are self-contained: `${sessionId}.${expiryEpochMs}.${signature}`.\n * The signature is HMAC-SHA256 of the payload using the server's secret.\n * Base64url encoding — URL-safe, no padding.\n *\n * The secret is per-FrameServer instance and is generated at construction\n * time via `crypto.randomBytes(32)`. It never leaves the server process.\n * Rotating the secret invalidates all existing tokens.\n */\nimport * as crypto from 'node:crypto'\n\n/** Issue a signed token for a single subscription. */\nexport interface AuthSigner {\n sign(sessionId: string, expiryEpochMs: number): string\n}\n\n/** Verify a token against expected sessionId and current wall-clock. */\nexport interface AuthVerifier {\n verify(token: string, expectedSessionId: string): AuthVerifyResult\n}\n\nexport type AuthVerifyResult =\n | { readonly ok: true; readonly sessionId: string; readonly expiresAt: number }\n | { readonly ok: false; readonly reason: 'malformed' | 'bad-signature' | 'expired' | 'wrong-session' }\n\n/** Create a matched signer + verifier pair sharing the same secret. */\nexport function createAuthPair(): { signer: AuthSigner; verifier: AuthVerifier } {\n const secret = crypto.randomBytes(32)\n return {\n signer: {\n sign: (sessionId, expiryEpochMs) => signToken(secret, sessionId, expiryEpochMs),\n },\n verifier: {\n verify: (token, expectedSessionId) => verifyToken(secret, token, expectedSessionId),\n },\n }\n}\n\n// ── Internal helpers ────────────────────────────────────────────────────────\n\nconst SEPARATOR = '.'\n\nfunction signToken(secret: Buffer, sessionId: string, expiryEpochMs: number): string {\n if (sessionId.includes(SEPARATOR)) {\n throw new Error(`sessionId must not contain \"${SEPARATOR}\"`)\n }\n const payload = `${sessionId}${SEPARATOR}${expiryEpochMs}`\n const sig = crypto.createHmac('sha256', secret).update(payload).digest()\n return `${payload}${SEPARATOR}${toBase64Url(sig)}`\n}\n\nfunction verifyToken(secret: Buffer, token: string, expectedSessionId: string): AuthVerifyResult {\n const parts = token.split(SEPARATOR)\n if (parts.length !== 3) {\n return { ok: false, reason: 'malformed' }\n }\n const [sessionId, expiryStr, signatureB64] = parts as [string, string, string]\n\n const expiryEpochMs = Number(expiryStr)\n if (!Number.isFinite(expiryEpochMs) || expiryEpochMs <= 0) {\n return { ok: false, reason: 'malformed' }\n }\n\n if (sessionId !== expectedSessionId) {\n return { ok: false, reason: 'wrong-session' }\n }\n\n const expectedSig = crypto.createHmac('sha256', secret).update(`${sessionId}${SEPARATOR}${expiryStr}`).digest()\n const providedSig = fromBase64Url(signatureB64)\n\n if (providedSig === null || providedSig.length !== expectedSig.length) {\n return { ok: false, reason: 'bad-signature' }\n }\n if (!crypto.timingSafeEqual(providedSig, expectedSig)) {\n return { ok: false, reason: 'bad-signature' }\n }\n\n if (Date.now() > expiryEpochMs) {\n return { ok: false, reason: 'expired' }\n }\n\n return { ok: true, sessionId, expiresAt: expiryEpochMs }\n}\n\nfunction toBase64Url(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(s: string): Buffer | null {\n const normalized = s.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4)\n try {\n return Buffer.from(padded, 'base64')\n } catch {\n return null\n }\n}\n"],"names":["parseAudioSpecificConfig","hexToBytes","errMsg","type","parameterSets","net","randomUUID","createHash","AudioSoundFormat","Socket","messageTypeId","once","maskUrlCredentials","cache","startCode","randomBytes","key","spawn","CAM_PROFILE_ORDER","RingBuffer","EventCategory","DeviceFeature","i","tryConvertWithLengthReader","tryConvertWithLengthReader16","tryConvertWithLengthReader24","dnsLookup","console","layerId","tid","nalType","t","names","senderCodec","c","BaseAddon","asJsonObject","streamBrokerCapability","cameraStreamsCapability","webrtcSessionCapability","addonWidgetsSourceCapability","createEvent","URL_SCHEME","path","os","fs","EventEmitter","crypto"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcO,MAAM,oBAAoB;AAAA,EAG/B,YACmB,KACR,WACT;AAFiB,SAAA,MAAA;AACR,SAAA,YAAA;AAAA,EACR;AAAA,EALK,UAAU;AAAA,EAOlB,MAAM,WAAW,QAAsC;AACrD,UAAM,KAAK,IAAI,WAAW,EAAE,WAAW,KAAK,WAAW,QAAQ;AAAA,EACjE;AAAA,EAEA,MAAM,WAAW,KAA4B;AAC3C,UAAM,KAAK,IAAI,WAAW,EAAE,WAAW,KAAK,WAAW,KAAK;AAAA,EAC9D;AAAA,EAEA,MAAM,aAAa,SAAuD;AACxE,SAAK,UAAU;AACf,WAAO,KAAK,SAAS;AACnB,YAAM,SAAS,MAAM,KAAK,IAAI,WAAW,EAAE,WAAW,KAAK,WAAW,UAAU,EAAA,CAAG;AACnF,iBAAW,SAAS,OAAQ,SAAQ,KAAK;AACzC,UAAI,OAAO,WAAW,EAAG,OAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAAA,IACpE;AAAA,EACF;AAAA,EAEA,cAAoB;AAClB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAM,aAAa,QAAsD;AACvE,UAAM,KAAK,IAAI,aAAa,EAAE,WAAW,KAAK,WAAW,QAAQ;AAAA,EACnE;AAAA,EAEA,MAAM,WAAkC;AACtC,WAAO,KAAK,IAAI,SAAS,EAAE,WAAW,KAAK,WAAW;AAAA,EACxD;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,YAAA;AACL,UAAM,KAAK,IAAI,eAAe,EAAE,WAAW,KAAK,WAAW;AAAA,EAC7D;AACF;ACpCA,MAAM,mBAAsC;AAAA,EAC1C;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACjD;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAO;AAC/B;AAWA,MAAM,UAAU;AAAA,EAEd,YAA6B,KAAiB;AAAjB,SAAA,MAAA;AAAA,EAAkB;AAAA,EADvC,SAAS;AAAA,EAEjB,KAAK,GAAmB;AACtB,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,UAAU,KAAK,UAAU;AAC/B,YAAM,MAAM,KAAK,KAAK,SAAS;AAC/B,YAAM,IAAI,UAAU,KAAK,IAAI,SAAS,KAAK,IAAI,OAAO,IAAK;AAC3D,UAAK,KAAK,IAAO,KAAK,MAAO;AAC7B,WAAK;AAAA,IACP;AACA,WAAO;AAAA,EACT;AAAA,EACA,gBAAwB;AACtB,WAAO,KAAK,IAAI,SAAS,IAAI,KAAK;AAAA,EACpC;AACF;AAWO,SAASA,2BAAyB,OAAiD;AACxF,QAAM,MAAM,OAAO,UAAU,WAAWC,aAAW,KAAK,IAAI;AAC5D,MAAI,IAAI,SAAS,GAAG;AAClB,UAAM,IAAI,MAAM,+CAA+C,IAAI,MAAM,SAAS;AAAA,EACpF;AACA,QAAM,SAAS,IAAI,UAAU,GAAG;AAChC,MAAI,aAAa,OAAO,KAAK,CAAC;AAC9B,MAAI,eAAe,IAAI;AACrB,iBAAa,KAAK,OAAO,KAAK,CAAC;AAAA,EACjC;AACA,QAAM,kBAAkB,OAAO,KAAK,CAAC;AACrC,MAAI;AACJ,MAAI,oBAAoB,IAAK;AAC3B,iBAAa,OAAO,KAAK,EAAE;AAAA,EAC7B,OAAO;AACL,UAAM,IAAI,iBAAiB,eAAe;AAC1C,QAAI,CAAC,EAAG,OAAM,IAAI,MAAM,+CAA+C,eAAe,EAAE;AACxF,iBAAa;AAAA,EACf;AACA,QAAM,gBAAgB,OAAO,KAAK,CAAC;AACnC,SAAO,EAAE,YAAY,YAAY,cAAA;AACnC;AAEA,SAASA,aAAW,KAAyB;AAC3C,QAAM,UAAU,IAAI,QAAQ,QAAQ,EAAE;AACtC,MAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B,UAAM,IAAI,MAAM,2CAA2C,QAAQ,MAAM,GAAG;AAAA,EAC9E;AACA,QAAM,MAAM,IAAI,WAAW,QAAQ,SAAS,CAAC;AAC7C,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,OAAO,SAAS,QAAQ,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AACzD,QAAI,OAAO,MAAM,IAAI,GAAG;AACtB,YAAM,IAAI,MAAM,sCAAsC,IAAI,CAAC,EAAE;AAAA,IAC/D;AACA,QAAI,CAAC,IAAI;AAAA,EACX;AACA,SAAO;AACT;AAsBO,SAAS,mBACd,SACA,OAAuB,EAAE,YAAY,IAAI,aAAa,KACxC;AACd,MAAI,QAAQ,SAAS,EAAG,QAAO,CAAA;AAC/B,QAAM,UAAU,KAAK;AACrB,QAAM,SAAS,KAAK;AACpB,QAAM,cAAc,KAAK,oBAAoB;AAC7C,QAAM,mBAAoB,QAAQ,CAAC,KAAM,IAAK,QAAQ,CAAC;AACvD,MAAI,qBAAqB,EAAG,QAAO,CAAA;AAEnC,QAAM,mBAAmB;AACzB,QAAM,oBAAqB,mBAAmB,KAAM;AACpD,QAAM,iBAAiB,mBAAmB;AAC1C,MAAI,iBAAiB,QAAQ,OAAQ,QAAO,CAAA;AAE5C,QAAM,eAAe,QAAQ,SAAS,kBAAkB,cAAc;AACtE,QAAM,SAAS,IAAI,UAAU,YAAY;AACzC,QAAM,QAAkB,CAAA;AACxB,MAAI,WAAW;AACf,MAAI,UAAU;AACd,SAAO,WAAW,kBAAkB;AAClC,QAAI,OAAO,kBAAkB,WAAW,UAAU,SAAS,aAAc;AACzE,UAAM,SAAS,OAAO,KAAK,OAAO;AAClC,WAAO,KAAK,UAAU,SAAS,WAAW;AAC1C,UAAM,KAAK,MAAM;AACjB,gBAAY,WAAW,UAAU,SAAS;AAC1C,cAAU;AAAA,EACZ;AAEA,QAAM,QAAsB,CAAA;AAC5B,MAAI,SAAS;AACb,aAAW,MAAM,OAAO;AACtB,QAAI,SAAS,KAAK,QAAQ,OAAQ;AAClC,UAAM,KAAK,QAAQ,SAAS,QAAQ,SAAS,EAAE,CAAC;AAChD,cAAU;AAAA,EACZ;AACA,SAAO;AACT;ACjIA,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;AAMzB,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAKlB,MAAM,uBAAuB;AAE7B,SAAS,cAAc,KAAuB;AAC5C,SAAO,qBAAqB,KAAKC,MAAAA,OAAO,GAAG,CAAC;AAC9C;AAEO,MAAM,kBAAkB;AAAA,EAyB7B,YACmB,KACA,KACA,MACA,QACjB;AAJiB,SAAA,MAAA;AACA,SAAA,MAAA;AACA,SAAA,OAAA;AACA,SAAA,SAAA;AAEjB,SAAK,mBACH,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,oBAAoB,kBAAkB,IAAK,CAAC,IACvE,KAAK,IAAI,GAAG,IAAI,cAAc,IAC9B;AAAA,EACJ;AAAA,EAlCQ,YAA2B;AAAA,EAC3B,gBAA+B;AAAA,EAC/B,WAA0C;AAAA,EAC1C,YAAmD;AAAA,EACnD,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,gCAAgB,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,aAAuB,CAAA;AAAA,EACvB,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACR;AAAA,EAcjB,cAAc,SAAuB;AACnC,QAAI,KAAK,OAAQ;AACjB,QAAI,QAAQ,UAAU,kBAAmB;AACzC,UAAM,UAAU,QAAQ,SAAS,iBAAiB;AAClD,SAAK;AAEL,SAAK,KAAK,cAAA,EAAgB,KAAK,CAAC,QAAQ;AACtC,UAAI,CAAC,OAAO,KAAK,OAAQ;AACzB,YAAM,SAAS,KAAK,iBAAiB;AACrC,YAAM,QAAQ,KAAK,IAAI,UACnB,mBAAmB,SAAS,KAAK,IAAI,OAAO,IAC5C,CAAC,WAAW,KAAK,OAAO,CAAC;AAC7B,iBAAW,QAAQ,OAAO;AACxB,aAAK,KAAK,IAAI,iBAAiB;AAAA,UAC7B,WAAW;AAAA,UACX,GAAI,SAAS,EAAE,OAAA,IAAW,CAAA;AAAA,UAC1B,MAAM;AAAA,QAAA,CACP,EAAE,MAAM,CAAC,QAAQ;AAChB,eAAK;AACL,eAAK,gBAAgB,wCAAwC,KAAK,GAAG;AAAA,QACvE,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAa,MAAwB;AACnC,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,WAAW,EAAG;AACvB,SAAK;AACL,SAAK,KAAK,cAAA,EAAgB,KAAK,CAAC,QAAQ;AACtC,UAAI,CAAC,OAAO,KAAK,OAAQ;AACzB,YAAM,SAAS,KAAK,iBAAiB;AACrC,WAAK,KAAK,IAAI,iBAAiB;AAAA,QAC7B,WAAW;AAAA,QACX,GAAI,SAAS,EAAE,OAAA,IAAW,CAAA;AAAA,QAC1B,MAAM;AAAA,MAAA,CACP,EAAE,MAAM,CAAC,QAAQ;AAChB,aAAK;AACL,aAAK,gBAAgB,8CAA8C,KAAK,GAAG;AAAA,MAC7E,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,WAAoF;AAClF,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,gBAAgB,KAAK;AAAA,MACrB,WAAW,KAAK;AAAA,IAAA;AAAA,EAEpB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,QAAI,KAAK,WAAW;AAClB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IACnB;AAIA,SAAK,aAAA;AACL,QAAI,KAAK,WAAW;AAClB,YAAM,MAAM,KAAK;AACjB,YAAM,SAAS,KAAK,iBAAiB;AACrC,UAAI;AAAE,cAAM,KAAK,IAAI,aAAa,EAAE,WAAW,KAAK,GAAI,SAAS,EAAE,OAAA,IAAW,CAAA,GAAK;AAAA,MAAE,SAC9E,KAAK;AACV,aAAK,QAAQ,KAAK,oCAAoC;AAAA,UACpD,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5B;AAAA,MACH;AACA,WAAK,YAAY;AACjB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAc,gBAAwC;AACpD,QAAI,KAAK,UAAW,QAAO,KAAK;AAChC,QAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,SAAK,YAAY,YAAY;AAC3B,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,IAAI,oBAAoB;AAAA,UAC7C,OAAO,KAAK,IAAI;AAAA,UAChB,kBAAkB,KAAK,IAAI;AAAA,UAC3B,gBAAgB,KAAK,IAAI;AAAA,UACzB,GAAI,KAAK,IAAI,YAAY,EAAE,WAAW,KAAK,IAAI,UAAA,IAAc,CAAA;AAAA,UAC7D,kBAAkB,KAAK,IAAI;AAAA,UAC3B,gBAAgB,KAAK,IAAI;AAAA,UACzB,cAAc;AAAA,UACd,GAAI,KAAK,IAAI,MAAM,EAAE,KAAK,KAAK,IAAI,QAAQ,CAAA;AAAA,QAAC,CAC7C;AACD,aAAK,YAAY,IAAI;AACrB,aAAK,gBAAgB,IAAI;AACzB,aAAK,cAAA;AACL,aAAK,QAAQ,KAAK,qCAAqC;AAAA,UACrD,MAAM;AAAA,YACJ,WAAW,IAAI;AAAA,YACf,QAAQ,IAAI;AAAA,YACZ,OAAO,KAAK,IAAI;AAAA,YAChB,QAAQ,GAAG,KAAK,IAAI,gBAAgB,MAAM,KAAK,IAAI,cAAc;AAAA,UAAA;AAAA,QACnE,CACD;AACD,eAAO,IAAI;AAAA,MACb,SAAS,KAAK;AACZ,aAAK,QAAQ,MAAM,2CAA2C;AAAA,UAC5D,MAAM,EAAE,OAAO,KAAK,IAAI,OAAO,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CACnD;AACD,eAAO;AAAA,MACT,UAAA;AACE,aAAK,WAAW;AAAA,MAClB;AAAA,IACF,GAAA;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY,YAAY,MAAM;AACjC,WAAK,KAAK,SAAA;AAAA,IACZ,GAAG,gBAAgB;AACnB,QAAI,OAAO,KAAK,UAAU,UAAU,WAAY,MAAK,UAAU,MAAA;AAAA,EACjE;AAAA,EAEA,MAAc,WAA0B;AACtC,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,OAAO,KAAK,OAAQ;AACzB,UAAM,SAAS,KAAK,iBAAiB;AACrC,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,IAAI,QAAQ;AAAA,QAClC,WAAW;AAAA,QACX,GAAI,SAAS,EAAE,OAAA,IAAW,CAAA;AAAA,QAC1B,UAAU;AAAA,MAAA,CACX;AACD,iBAAW,OAAO,MAAM;AACtB,aAAK,UAAU,GAAG;AAAA,MACpB;AAAA,IACF,SAAS,KAAK;AAKZ,UAAI,cAAc,GAAG,GAAG;AACtB,aAAK,kBAAkB,GAAG;AAC1B;AAAA,MACF;AACA,WAAK,cAAc,+BAA+BA,MAAAA,OAAO,GAAG,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,UAAU,KAAoF;AACpG,UAAM,WAAW,IAAI,KAAK;AAC1B,QAAI,aAAa,EAAG;AAGpB,QACE,KAAK,eAAe,MACnB,IAAI,eAAe,KAAK,qBACvB,IAAI,aAAa,KAAK,kBACxB;AACA,WAAK,aAAA;AAAA,IACP;AAEA,QAAI,KAAK,iBAAiB,GAAG;AAC3B,WAAK,oBAAoB,IAAI;AAC7B,WAAK,kBAAkB,IAAI;AAQ3B,WAAK,iBAAiB,KAAK,IAAA;AAAA,IAC7B;AAGA,SAAK,WAAW,KAAK,OAAO,KAAK,IAAI,IAAI,CAAC;AAC1C,SAAK,gBAAgB;AAErB,QAAI,KAAK,gBAAgB,KAAK,kBAAkB;AAC9C,WAAK,aAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI,KAAK,iBAAiB,EAAG;AAC7B,UAAM,SAAS,KAAK,WAAW,WAAW,IACtC,KAAK,WAAW,CAAC,IACjB,OAAO,OAAO,KAAK,YAAY,KAAK,YAAY;AACpD,UAAM,aAAa,KAAK;AACxB,UAAM,WAAW,KAAK;AACtB,UAAM,YAAY,KAAK;AACvB,SAAK,aAAa,CAAA;AAClB,SAAK,eAAe;AACpB,SAAK,oBAAoB;AACzB,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;AACtB,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,kBAAkB,UAAwB;AAChD,QAAI,KAAK,cAAc,SAAU;AACjC,SAAK,YAAY;AACjB,SAAK,gBAAgB;AACrB,QAAI,KAAK,WAAW;AAClB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IACnB;AAIA,SAAK,aAAA;AACL,SAAK;AAAA,MACH;AAAA,MACA,kBAAkB,QAAQ;AAAA,IAAA;AAAA,EAE9B;AAAA,EAEQ,gBAAgB,OAAe,KAAa,KAAoB;AACtE,QAAI,cAAc,GAAG,GAAG;AACtB,WAAK,kBAAkB,GAAG;AAC1B;AAAA,IACF;AACA,SAAK,cAAc,OAAOA,MAAAA,OAAO,GAAG,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,cAAc,OAAe,QAAsB;AACzD,UAAM,MAAM,GAAG,KAAK,IAAI,MAAM;AAC9B,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,OAAO,KAAK,UAAU,IAAI,GAAG;AACnC,QAAI,QAAQ,MAAM,KAAK,eAAe,kBAAkB;AACtD,WAAK;AACL;AAAA,IACF;AACA,UAAM,aAAa,MAAM,cAAc;AACvC,SAAK,UAAU,IAAI,KAAK,EAAE,cAAc,KAAK,YAAY,GAAG;AAC5D,SAAK,QAAQ,KAAK,OAAO;AAAA,MACvB,MAAM;AAAA,QACJ,OAAO,KAAK,IAAI;AAAA,QAChB,OAAO;AAAA,QACP,GAAI,aAAa,IAAI,EAAE,wBAAwB,WAAA,IAAe,CAAA;AAAA,MAAC;AAAA,IACjE,CACD;AAAA,EACH;AACF;ACrXO,MAAM,aAAa;AAAA,EAChB;AAAA,EACA,eAAe;AAAA,EAEvB,YAAY,QAAgB;AAC1B,SAAK,aAAa,SAAS,IAAI,MAAO,SAAS;AAAA,EACjD;AAAA,EAEA,aAAsB;AACpB,QAAI,KAAK,eAAe,EAAG,QAAO;AAElC,UAAM,MAAM,KAAK,IAAA;AACjB,QAAI,MAAM,KAAK,gBAAgB,KAAK,YAAY;AAC9C,WAAK,eAAe;AACpB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAsB;AAC9B,SAAK,aAAa,SAAS,IAAI,MAAO,SAAS;AAAA,EACjD;AACF;ACTA,MAAM,yBAAyB,IAAI,OAAO;AAEnC,MAAM,iBAAiB;AAAA,EACX;AAAA,EACA,8BAA+B,IAAA;AAAA,EACxC,OAAO;AAAA,EACP,UAAU;AAAA,EACD;AAAA,EACT,wBAA6C;AAAA;AAAA,EAGrD,qBAAqB,IAAsB;AAAE,SAAK,wBAAwB;AAAA,EAAG;AAAA,EAE7E,YAAY,QAAwB;AAClC,SAAK,SAAS;AACd,SAAK,SAAS,IAAI,aAAa,CAAC,WAAW;AACzC,WAAK,iBAAiB,MAAM;AAAA,IAC9B,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,QAAQ,MAAM,0BAA0B,EAAE,MAAM,EAAE,OAAO,IAAI,QAAA,GAAW;AAAA,IAC/E,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB;AAAA,IACF;AAEA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,OAAO,KAAK,SAAS,MAAM;AAChC,WAAK,OAAO,OAAO,GAAG,aAAa,MAAM;AACvC,aAAK,OAAO,eAAe,SAAS,MAAM;AAC1C,cAAM,UAAU,KAAK,OAAO,QAAA;AAC5B,aAAK,OAAO,QAAQ;AACpB,aAAK,UAAU;AACf,aAAK,QAAQ,MAAM,8BAA8B,EAAE,MAAM,EAAE,MAAM,KAAK,KAAA,GAAQ;AAC9E,gBAAA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,MAAoB;AAC5B,eAAW,UAAU,KAAK,SAAS;AACjC,UAAI,OAAO,WAAW;AACpB,aAAK,QAAQ,OAAO,MAAM;AAC1B;AAAA,MACF;AAGA,UAAI,OAAO,iBAAiB,wBAAwB;AAClD,aAAK,QAAQ;AAAA,UACX;AAAA,UACA,EAAE,MAAM,EAAE,UAAU,QAAQ,OAAO,iBAAiB,OAAO,MAAM,QAAQ,CAAC,CAAC,IAAE;AAAA,QAAE;AAEjF,aAAK,aAAa,MAAM;AACxB;AAAA,MACF;AAEA,aAAO,MAAM,MAAM,CAAC,QAAQ;AAC1B,YAAI,KAAK;AACP,eAAK,QAAQ;AAAA,YACX;AAAA,YACA,EAAE,MAAM,EAAE,OAAO,IAAI,UAAQ;AAAA,UAAE;AAEjC,eAAK,aAAa,MAAM;AAAA,QAC1B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,UAAkB;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SAAiB;AACf,WAAO,mBAAmB,KAAK,IAAI;AAAA,EACrC;AAAA,EAEA,iBAAyB;AACvB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,SAAK,UAAU;AACf,SAAK,wBAAwB;AAE7B,eAAW,UAAU,KAAK,SAAS;AACjC,aAAO,QAAA;AAAA,IACT;AACA,SAAK,QAAQ,MAAA;AAEb,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,WAAK,OAAO,MAAM,MAAM;AACtB,aAAK,QAAQ,MAAM,0BAA0B;AAC7C,gBAAA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,QAA0B;AACjD,SAAK,QAAQ,IAAI,MAAM;AACvB,SAAK,QAAQ;AAAA,MACX;AAAA,MACA,EAAE,MAAM,EAAE,OAAO,KAAK,QAAQ,OAAK;AAAA,IAAE;AAEvC,SAAK,wBAAA;AAEL,WAAO,GAAG,SAAS,MAAM;AACvB,WAAK,aAAa,MAAM;AAAA,IAC1B,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AACvB,WAAK,aAAa,MAAM;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,QAA0B;AAC7C,UAAM,UAAU,KAAK,QAAQ,OAAO,MAAM;AAC1C,QAAI,SAAS;AACX,WAAK,QAAQ;AAAA,QACX;AAAA,QACA,EAAE,MAAM,EAAE,OAAO,KAAK,QAAQ,OAAK;AAAA,MAAE;AAEvC,WAAK,wBAAA;AAAA,IACP;AACA,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO,QAAA;AAAA,IACT;AAAA,EACF;AACF;AC/IO,MAAM,kBAAkB;AAAA,EACZ,UAA2B,CAAA;AAAA,EACpC;AAAA,EAER,YAAY,iBAAyB,IAAI;AACvC,SAAK,gBAAgB,iBAAiB;AAAA,EACxC;AAAA;AAAA,EAGA,YAAY,SAAuB;AACjC,SAAK,gBAAgB,UAAU;AAC/B,SAAK,MAAA;AAAA,EACP;AAAA;AAAA,EAGA,cAAsB;AACpB,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA,EAGA,KAAK,QAA6B;AAChC,SAAK,QAAQ,KAAK,MAAM;AACxB,SAAK,MAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAuC;AAErC,QAAI,kBAAkB;AACtB,aAAS,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AACjD,UAAI,KAAK,QAAQ,CAAC,EAAG,YAAY,KAAK,QAAQ,CAAC,EAAG,SAAS,SAAS;AAClE,0BAAkB;AAClB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,kBAAkB,EAAG,QAAO,CAAA;AAEhC,WAAO,KAAK,QAAQ,MAAM,eAAe;AAAA,EAC3C;AAAA;AAAA,EAGA,wBAAgC;AAC9B,QAAI,KAAK,QAAQ,SAAS,EAAG,QAAO;AACpC,UAAM,QAAQ,KAAK,QAAQ,CAAC;AAC5B,UAAM,OAAO,KAAK,QAAQ,KAAK,QAAQ,SAAS,CAAC;AACjD,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B;AAAA;AAAA,EAGA,iBAAyB;AACvB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ,SAAS;AAAA,EACxB;AAAA;AAAA,EAGQ,QAAc;AACpB,QAAI,KAAK,QAAQ,WAAW,EAAG;AAE/B,UAAM,SAAS,KAAK,QAAQ,KAAK,QAAQ,SAAS,CAAC,EAAG,MAAM,KAAK;AACjE,QAAI,cAAc;AAElB,WAAO,cAAc,KAAK,QAAQ,UAAU,KAAK,QAAQ,WAAW,EAAG,MAAM,QAAQ;AACnF;AAAA,IACF;AAEA,QAAI,cAAc,GAAG;AACnB,WAAK,QAAQ,OAAO,GAAG,WAAW;AAAA,IACpC;AAAA,EACF;AACF;ACjDO,MAAM,eAAe,CAAC,WAAW,YAAY,SAAS,QAAQ,YAAY,eAAe;AAGzF,MAAM,cAAc;AACpB,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAGxB,MAAM,qBAAqB;AAC3B,MAAM,eAAe;AAGrB,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,cAAc;AAGpB,MAAM,oBAAoB;AAG1B,MAAM,mBAAmB,IAAI,OAAO;ACjDpC,MAAM,cAAc;AAAA,EAIzB,YACmB,OACA,aACjB;AAFiB,SAAA,QAAA;AACA,SAAA,cAAA;AAEjB,SAAK,OAAO,KAAK,MAAM,KAAK,OAAA,IAAW,UAAU;AAAA,EACnD;AAAA,EARQ,iBAAiB;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAajB,OAAO,YAAY,MAAqC;AACtD,UAAM,OAAiB,CAAA;AACvB,QAAI,QAAQ;AAEZ,aAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACxC,YAAM,eAAe,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM;AAC3E,YAAM,eAAe,IAAI,IAAI,KAAK,UAChC,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM;AAE7E,UAAI,gBAAgB,cAAc;AAChC,YAAI,SAAS,GAAG;AAGd,cAAI,MAAM;AACV,iBAAO,MAAM,SAAS,KAAK,MAAM,CAAC,MAAM,EAAG;AAC3C,cAAI,MAAM,MAAO,MAAK,KAAK,KAAK,SAAS,OAAO,GAAG,CAAC;AAAA,QACtD;AACA,gBAAQ,eAAe,IAAI,IAAI,IAAI;AACnC,YAAI,aAAc,MAAK;AAAA,YAClB,MAAK;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,SAAS,KAAK,QAAQ,KAAK,QAAQ;AACrC,WAAK,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,IAChC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAU,KAAa,WAAmB,QAA2C;AACnF,QAAI,KAAK,UAAU,QAAQ;AACzB,aAAO,KAAK,cAAc,KAAK,WAAW,MAAM;AAAA,IAClD;AACA,WAAO,KAAK,cAAc,KAAK,WAAW,MAAM;AAAA,EAClD;AAAA,EAEQ,cAAc,KAAa,WAAmB,QAA2C;AAC/F,QAAI,IAAI,UAAU,iBAAiB;AACjC,aAAO,CAAC,KAAK,eAAe,KAAK,WAAW,MAAM,CAAC;AAAA,IACrD;AACA,WAAO,KAAK,gBAAgB,KAAK,WAAW,MAAM;AAAA,EACpD;AAAA,EAEQ,cAAc,KAAa,WAAmB,QAA2C;AAC/F,QAAI,IAAI,UAAU,iBAAiB;AACjC,aAAO,CAAC,KAAK,eAAe,KAAK,WAAW,MAAM,CAAC;AAAA,IACrD;AACA,WAAO,KAAK,eAAe,KAAK,WAAW,MAAM;AAAA,EACnD;AAAA;AAAA,EAGQ,gBAAgB,KAAa,WAAmB,QAA2C;AACjG,UAAM,YAAY,IAAI,CAAC;AACvB,UAAM,OAAO,YAAY;AACzB,UAAM,UAAU,YAAY;AAC5B,UAAM,UAAU,IAAI,SAAS,CAAC;AAE9B,UAAM,cAAc,kBAAkB;AACtC,UAAM,UAAuB,CAAA;AAC7B,QAAI,SAAS;AAEb,WAAO,SAAS,QAAQ,QAAQ;AAC9B,YAAM,MAAM,KAAK,IAAI,SAAS,aAAa,QAAQ,MAAM;AACzD,YAAM,UAAU,WAAW;AAC3B,YAAM,SAAS,QAAQ,QAAQ;AAE/B,YAAM,cAAc,OAAO;AAC3B,YAAM,YAAY,UAAU,MAAO,MAAM,SAAS,KAAO,KAAK;AAE9D,YAAM,WAAW,OAAO,YAAY,KAAK,MAAM,OAAO;AACtD,eAAS,CAAC,IAAI;AACd,eAAS,CAAC,IAAI;AACd,cAAQ,KAAK,UAAU,GAAG,QAAQ,GAAG;AAErC,cAAQ,KAAK,KAAK,eAAe,UAAU,WAAW,UAAU,MAAM,CAAC;AACvE,eAAS;AAAA,IACX;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,eAAe,KAAa,WAAmB,QAA2C;AAChG,UAAM,UAAW,IAAI,CAAC,KAAM,sBAAuB;AACnD,UAAM,aAAa,IAAI,CAAC;AACxB,UAAM,UAAU,IAAI,SAAS,CAAC;AAE9B,UAAM,cAAc,kBAAkB;AACtC,UAAM,UAAuB,CAAA;AAC7B,QAAI,SAAS;AAEb,WAAO,SAAS,QAAQ,QAAQ;AAC9B,YAAM,MAAM,KAAK,IAAI,SAAS,aAAa,QAAQ,MAAM;AACzD,YAAM,UAAU,WAAW;AAC3B,YAAM,SAAS,QAAQ,QAAQ;AAE/B,YAAM,cAAe,eAAe,sBAAwB,IAAI,CAAC,IAAK;AACtE,YAAM,cAAc;AACpB,YAAM,YAAY,UAAU,MAAO,MAAM,SAAS,KAAO,KAAK;AAE9D,YAAM,WAAW,OAAO,YAAY,KAAK,MAAM,OAAO;AACtD,eAAS,CAAC,IAAI;AACd,eAAS,CAAC,IAAI;AACd,eAAS,CAAC,IAAI;AACd,cAAQ,KAAK,UAAU,GAAG,QAAQ,GAAG;AAErC,cAAQ,KAAK,KAAK,eAAe,UAAU,WAAW,UAAU,MAAM,CAAC;AACvE,eAAS;AAAA,IACX;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,SAAiB,WAAmB,QAA4B;AACrF,UAAM,SAAS,OAAO,YAAY,eAAe;AACjD,WAAO,CAAC,IAAI,eAAe;AAC3B,WAAO,CAAC,KAAK,SAAS,MAAO,KAAM,KAAK,cAAc;AACtD,WAAO,cAAc,KAAK,iBAAiB,OAAQ,CAAC;AACpD,SAAK,iBAAkB,KAAK,iBAAiB,IAAK;AAClD,WAAO,cAAc,cAAc,GAAG,CAAC;AACvC,WAAO,cAAc,KAAK,SAAS,GAAG,CAAC;AAEvC,WAAO;AAAA,MACL,MAAM,OAAO,OAAO,CAAC,QAAQ,OAAO,CAAC;AAAA,MACrC;AAAA,IAAA;AAAA,EAEJ;AACF;ACtJO,MAAM,eAAe;AAAA,EAI1B,YACE,OACiB,OACjB;AADiB,SAAA,QAAA;AAEjB,SAAK,QAAQ;AAAA,EACf;AAAA,EARQ,SAAkC,OAAO,MAAM,CAAC;AAAA,EACvC;AAAA;AAAA,EAUjB,KAAK,OAAqB;AACxB,SAAK,SAAS,KAAK,OAAO,SAAS,IAC/B,OAAO,OAAO,CAAC,KAAK,QAAQ,KAAK,CAAC,IAClC;AAEJ,SAAK,MAAA;AAAA,EACP;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,OAAO,SAAS,GAAG;AAC1B,YAAM,MAAM,KAAK,sBAAsB,KAAK,MAAM;AAClD,UAAI,IAAI,SAAS,GAAG;AAClB,aAAK,MAAM,KAAK,KAAK,cAAc,GAAG,CAAC;AAAA,MACzC;AACA,WAAK,SAAS,OAAO,MAAM,CAAC;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA,EAGQ,QAAc;AACpB,WAAO,MAAM;AAEX,YAAM,UAAU,KAAK,cAAc,CAAC;AACpC,UAAI,UAAU,GAAG;AAEf;AAAA,MACF;AAGA,YAAM,WAAW,WAAW,KAAK,aAAa,OAAO,IAAI,IAAI;AAC7D,YAAM,SAAS,KAAK,cAAc,QAAQ;AAE1C,UAAI,SAAS,GAAG;AAGd,YAAI,UAAU,GAAG;AACf,eAAK,SAAS,OAAO,KAAK,KAAK,OAAO,SAAS,OAAO,CAAC;AAAA,QACzD;AACA;AAAA,MACF;AAGA,YAAM,MAAM,KAAK,OAAO,SAAS,UAAU,MAAM;AACjD,UAAI,IAAI,SAAS,GAAG;AAClB,aAAK,MAAM,KAAK,KAAK,cAAc,GAAG,CAAC;AAAA,MACzC;AAGA,WAAK,SAAS,OAAO,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAGQ,cAAc,MAAsB;AAC1C,UAAM,MAAM,KAAK;AACjB,UAAM,MAAM,IAAI,SAAS;AACzB,aAAS,IAAI,MAAM,IAAI,KAAK,KAAK;AAE/B,UAAI,IAAI,IAAI,CAAC,IAAK,GAAG;AACnB,aAAK;AACL;AAAA,MACF;AACA,UAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG;AACpC,YAAI,IAAI,IAAI,CAAC,MAAM,EAAG,QAAO;AAC7B,YAAI,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,EAAG,QAAO;AAAA,MACzE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,aAAa,KAAsB;AACzC,WAAO,MAAM,IAAI,KAAK,OAAO,UAC3B,KAAK,OAAO,GAAG,MAAM,KACrB,KAAK,OAAO,MAAM,CAAC,MAAM,KACzB,KAAK,OAAO,MAAM,CAAC,MAAM,KACzB,KAAK,OAAO,MAAM,CAAC,MAAM;AAAA,EAC7B;AAAA;AAAA,EAGQ,sBAAsB,KAAqB;AACjD,QAAI,IAAI,UAAU,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,GAAG;AACnF,aAAO,IAAI,SAAS,CAAC;AAAA,IACvB;AACA,QAAI,IAAI,UAAU,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,GAAG;AACnE,aAAO,IAAI,SAAS,CAAC;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,cAAc,KAAsB;AAC1C,QAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,QAAI,KAAK,UAAU,QAAQ;AACzB,YAAMC,QAAO,IAAI,CAAC,IAAK;AACvB,aAAOA,UAAS;AAAA,IAClB;AACA,UAAM,OAAQ,IAAI,CAAC,KAAM,IAAK;AAC9B,WAAO,QAAQ,MAAM,QAAQ;AAAA,EAC/B;AACF;ACtHO,SAAS,SAAS,SAA6B;AACpD,QAAM,EAAE,OAAO,eAAe,WAAA,IAAe;AAC7C,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA,OAAO,KAAK,IAAA,CAAK;AAAA,IACjB,KAAK,UAAU;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,MAAI,UAAU,QAAQ;AACpB,UAAM,KAAK,wBAAwB;AACnC,UAAM,MAAM,cAAc,CAAC;AAC3B,UAAM,YAAY,cAAc,IAAI,CAAA,OAAM,GAAG,SAAS,QAAQ,CAAC,EAAE,KAAK,GAAG;AACzE,UAAM,iBAAiB,OAAO,IAAI,UAAU,IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAI,IAAI,CAAC,GAAI,IAAI,CAAC,CAAE,CAAC,EAAE,SAAS,KAAK,IACvD;AACJ,UAAM,KAAK,mDAAmD,cAAc,yBAAyB,SAAS,EAAE;AAAA,EAClH,OAAO;AACL,UAAM,KAAK,wBAAwB;AACnC,UAAM,YAAY,cAAc,IAAI,CAAC,IAAI,MAAM;AAC7C,YAAM,SAAS,MAAM,IAAI,cAAc,MAAM,IAAI,cAAc;AAC/D,aAAO,GAAG,MAAM,IAAI,GAAG,SAAS,QAAQ,CAAC;AAAA,IAC3C,CAAC;AACD,UAAM,KAAK,aAAa,UAAU,KAAK,GAAG,CAAC,EAAE;AAAA,EAC/C;AAEA,QAAM,KAAK,qBAAqB;AAChC,QAAM,KAAK,EAAE;AACb,SAAO,MAAM,KAAK,MAAM;AAC1B;AClBO,MAAM,eAAe;AAAA,EA8B1B,YAA6B,YAAoB;AAApB,SAAA,aAAA;AAAA,EAAqB;AAAA,EA7BjC,+BAAe,IAAA;AAAA;AAAA,EAEf,sCAAsB,IAAA;AAAA,EAC/B,aAAmC;AAAA,EACnC,cAAkC;AAAA,EAClC,MAAqB;AAAA,EACrB,WAA0B;AAAA,EAC1B,WAAkC;AAAA,EAClC,gBAAwC;AAAA;AAAA,EAGxC,mBAA6B,CAAA;AAAA,EAC7B,wBAAwB;AAAA;AAAA,EAExB,qBAAqB;AAAA;AAAA,EAGrB,mBAAmB;AAAA;AAAA,EAGnB,yBAAsC,CAAA;AAAA;AAAA,EAEtC,sBAAsB;AAAA;AAAA,EAEtB,wBAAwB;AAAA,EAExB,SAA+B;AAAA,EAC/B,yBAA8C;AAAA;AAAA,EAKtD,IAAI,qBAA8B;AAAE,WAAO,KAAK;AAAA,EAAoB;AAAA;AAAA,EAGpE,sBAAsB,IAAsB;AAAE,SAAK,yBAAyB;AAAA,EAAG;AAAA,EAE/E,UAAU,QAA6B;AACrC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAyB;AACvB,SAAK,mBAAmB,CAAA;AACxB,SAAK,wBAAwB;AAC7B,SAAK,qBAAqB;AAC1B,SAAK,mBAAmB;AACxB,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,MAAM;AACX,SAAK,WAAW;AAChB,SAAK,WAAW;AAChB,SAAK,gBAAgB;AACrB,SAAK,yBAAyB,CAAA;AAC9B,SAAK,sBAAsB;AAC3B,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA,EAGA,UAAmB;AAAE,WAAO,KAAK,gBAAgB,QAAQ,KAAK,eAAe;AAAA,EAAK;AAAA,EAElF,iBAAqC;AAAE,WAAO,KAAK;AAAA,EAAY;AAAA,EAC/D,SAAwB;AAAE,WAAO,KAAK;AAAA,EAAI;AAAA,EAC1C,cAA6B;AAAE,WAAO,KAAK,YAAY,KAAK;AAAA,EAAI;AAAA,EAChE,kBAA0B;AAAE,WAAO,KAAK,SAAS;AAAA,EAAK;AAAA;AAAA,EAGtD,mBAAkE;AAChE,WAAO,CAAC,GAAG,KAAK,SAAS,OAAA,CAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,EAC3D;AAAA,EAEA,WAAW,SAA4B;AACrC,SAAK,SAAS,IAAI,QAAQ,aAAA,GAAgB,OAAO;AACjD,SAAK,gBAAgB,IAAI,QAAQ,aAAA,CAAc;AAC/C,SAAK,QAAQ,KAAK,yBAAyB;AAAA,MACzC,MAAM,EAAE,YAAY,KAAK,YAAY,OAAO,KAAK,SAAS,KAAA;AAAA,IAAK,CAChE;AACD,SAAK,yBAAA;AAEL,QAAI,KAAK,qBAAqB;AAE5B,UAAI,KAAK,uBAAuB,SAAS,GAAG;AAC1C,mBAAW,MAAM,KAAK,wBAAA,GAA2B,CAAC;AAAA,MACpD;AAAA,IACF,OAAO;AAEL,UAAI,KAAK,iBAAiB,SAAS,KAAK,KAAK,YAAY;AACvD,mBAAW,MAAM,KAAK,qBAAA,GAAwB,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,SAAiB,OAAwB,aAAuC;AAC3F,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB;AACrB,SAAK,cAAc;AACnB,SAAK,MAAM;AAEX,SAAK,WAAW,kBAAkB,OAAO;AACzC,SAAK,QAAQ,KAAK,kCAAkC;AAAA,MAClD,MAAM,EAAE,YAAY,KAAK,YAAY,MAAA;AAAA,IAAM,CAC5C;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,mBAAmB,SAAiB,YAAqB,gBAA+B;AACtF,QAAI,KAAK,SAAS,SAAS,KAAK,CAAC,cAAc,CAAC,eAAgB;AAEhE,UAAM,YAAuB,EAAE,MAAM,SAAS,SAAS,QAAQ,CAAC,IAAK,SAAU,EAAA;AAG/E,QAAI,cAAc,gBAAgB;AAChC,UAAI,CAAC,KAAK,uBAAuB;AAC/B,aAAK,wBAAwB;AAC7B,aAAK,yBAAyB,CAAA;AAAA,MAChC;AAAA,IACF;AAEA,QAAI,KAAK,uBAAuB;AAC9B,WAAK,uBAAuB,KAAK,EAAE,MAAM,OAAO,KAAK,OAAO,GAAG,QAAQ,UAAU,OAAA,CAAQ;AACzF,UAAI,CAAC,cAAc,CAAC,gBAAgB;AAClC,aAAK,wBAAwB;AAAA,MAC/B;AAAA,IACF;AAEA,QAAI,KAAK,SAAS,SAAS,EAAG;AAG9B,SAAK,wBAAA;AAGL,eAAW,WAAW,KAAK,SAAS,OAAA,GAAU;AAC5C,UAAI,KAAK,gBAAgB,IAAI,QAAQ,aAAA,CAAc,EAAG;AACtD,UAAI,QAAQ,aAAa;AACvB,gBAAQ,QAAQ,SAAS;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,0BAAgC;AACtC,QAAI,KAAK,gBAAgB,SAAS,EAAG;AACrC,QAAI,KAAK,uBAAuB,WAAW,EAAG;AAE9C,UAAM,QAAkB,CAAA;AACxB,eAAW,aAAa,KAAK,iBAAiB;AAC5C,YAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,UAAI,SAAS,UAAA,EAAa,OAAM,KAAK,SAAS;AAAA,IAChD;AAEA,QAAI,MAAM,WAAW,EAAG;AAExB,eAAW,aAAa,KAAK,wBAAwB;AACnD,iBAAW,aAAa,OAAO;AAC7B,cAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,YAAI,SAAS,UAAA,EAAa,SAAQ,QAAQ,SAAS;AAAA,MACrD;AAAA,IACF;AAEA,eAAW,MAAM,MAAO,MAAK,gBAAgB,OAAO,EAAE;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAAwB,SAAuB;AAC7C,QAAI,KAAK,SAAS,SAAS,EAAG;AAE9B,UAAM,YAAuB,EAAE,MAAM,SAAS,SAAS,QAAQ,CAAC,IAAK,SAAU,EAAA;AAE/E,eAAW,WAAW,KAAK,SAAS,OAAA,GAAU;AAC5C,UAAI,QAAQ,UAAW;AACvB,UAAI,KAAK,gBAAgB,IAAI,QAAQ,aAAA,CAAc,EAAG;AACtD,UAAI,QAAQ,aAAa;AACvB,gBAAQ,aAAa,SAAS;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc,WAAyB;AACrC,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,gBAAgB,OAAO,SAAS;AACrC,SAAK,QAAQ,KAAK,4BAA4B;AAAA,MAC5C,MAAM,EAAE,YAAY,KAAK,YAAY,OAAO,KAAK,SAAS,KAAA;AAAA,IAAK,CAChE;AACD,SAAK,yBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,WAA4B;AACtC,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI;AAAE,cAAQ,MAAA;AAAA,IAAQ,QAAQ;AAAA,IAA0B;AACxD,SAAK,cAAc,SAAS;AAC5B,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,QAA6B;AACtC,QAAI,OAAO,SAAS,QAAS;AAE7B,SAAK,mBAAmB,KAAK,MAAM,OAAO,MAAM,EAAE;AAGlD,QAAI,CAAC,KAAK,UAAU;AAClB,WAAK,gBAAiB,OAAO,UAAU,UAAU,OAAO,UAAU,SAAU,SAAS;AACrF,WAAK,WAAW,IAAI,eAAe,KAAK,eAAe,CAAC,KAAK,eAAe;AAC1E,aAAK,UAAU,KAAK,UAAU;AAAA,MAChC,CAAC;AAAA,IACH;AAOA,SAAK,SAAS,KAAK,OAAO,IAAI;AAAA,EAChC;AAAA,EAEA,UAAgB;AACd,SAAK,yBAAyB;AAC9B,SAAK,UAAU,MAAA;AACf,eAAW,WAAW,KAAK,SAAS,OAAA,GAAU;AAC5C,cAAQ,MAAA;AAAA,IACV;AACA,SAAK,SAAS,MAAA;AACd,SAAK,aAAa;AAClB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGQ,UAAU,KAAa,YAA2B;AAExD,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,aAAa,GAAG;AAAA,IACvB;AAMA,QAAI,cAAc,KAAK,kBAAkB,GAAG,GAAG;AAC7C,UAAI,CAAC,KAAK,oBAAoB;AAC5B,aAAK,qBAAqB;AAC1B,aAAK,mBAAmB,CAAA;AACxB,aAAK,wBAAwB,KAAK;AAAA,MACpC;AAAA,IACF;AAGA,QAAI,KAAK,oBAAoB;AAC3B,WAAK,iBAAiB,KAAK,OAAO,KAAK,GAAG,CAAC;AAG3C,UAAI,CAAC,cAAc,CAAC,KAAK,kBAAkB,GAAG,GAAG;AAC/C,aAAK,qBAAqB;AAAA,MAC5B;AAAA,IACF;AAGA,QAAI,KAAK,SAAS,SAAS,EAAG;AAC9B,QAAI,CAAC,KAAK,WAAY;AAGtB,SAAK,qBAAA;AAGL,UAAM,aAAa,KAAK,WAAW,UAAU,KAAK,KAAK,kBAAkB,IAAI;AAC7E,eAAW,OAAO,YAAY;AAC5B,iBAAW,WAAW,KAAK,SAAS,OAAA,GAAU;AAC5C,YAAI,KAAK,gBAAgB,IAAI,QAAQ,aAAA,CAAc,EAAG;AACtD,YAAI,QAAQ,aAAa;AACvB,kBAAQ,QAAQ,GAAG;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,uBAA6B;AACnC,QAAI,KAAK,gBAAgB,SAAS,EAAG;AAErC,UAAM,QAAkB,CAAA;AACxB,eAAW,aAAa,KAAK,iBAAiB;AAC5C,YAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,UAAI,SAAS,UAAA,EAAa,OAAM,KAAK,SAAS;AAAA,IAChD;AAEA,QAAI,MAAM,WAAW,EAAG;AACxB,QAAI,KAAK,iBAAiB,WAAW,KAAK,CAAC,KAAK,WAAY;AAG5D,eAAW,UAAU,KAAK,kBAAkB;AAC1C,YAAM,aAAa,KAAK,WAAW,UAAU,QAAQ,KAAK,uBAAuB,IAAI;AACrF,iBAAW,OAAO,YAAY;AAC5B,mBAAW,aAAa,OAAO;AAC7B,gBAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,cAAI,SAAS,UAAA,EAAa,SAAQ,QAAQ,GAAG;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAEA,eAAW,MAAM,MAAO,MAAK,gBAAgB,OAAO,EAAE;AAAA,EACxD;AAAA;AAAA,EAGQ,aAAa,KAAmB;AACtC,QAAI,CAAC,KAAK,kBAAkB,GAAG,EAAG;AAClC,QAAI,CAAC,KAAK,cAAe;AAGzB,QAAI,CAAC,KAAK,aAAa;AAErB,YAAM,WAAW,KAAK,iBAAiB,OAAO,OAAK,KAAK,kBAAkB,CAAC,CAAC;AAC5E,YAAM,MAAM,CAAC,GAAG,UAAU,GAAG;AAE7B,YAAM,QAAQ,KAAK;AACnB,YAAM,SAAS,UAAU,SAAS,IAAI;AACtC,UAAI,IAAI,UAAU,QAAQ;AACxB,aAAK,cAAc,EAAE,OAAO,eAAe,IAAI,MAAM,GAAG,MAAM,EAAA;AAC9D,aAAK,aAAa,IAAI,cAAc,OAAO,EAAE;AAC7C,aAAK,MAAM,SAAS,EAAE,OAAO,eAAe,IAAI,MAAM,GAAG,MAAM,GAAG,YAAY,KAAK,YAAY;AAC/F,aAAK,WAAW,SAAS,EAAE,OAAO,eAAe,IAAI,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,KAAK,UAAU,YAAY;AAAA,MACnH;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,kBAAkB,KAAsB;AAC9C,QAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,QAAI,KAAK,kBAAkB,QAAQ;AACjC,YAAMA,QAAO,IAAI,CAAC,IAAK;AACvB,aAAOA,UAAS,KAAKA,UAAS;AAAA,IAChC;AACA,UAAM,OAAQ,IAAI,CAAC,KAAM,IAAK;AAC9B,WAAO,SAAS,MAAM,SAAS,MAAM,SAAS;AAAA,EAChD;AACF;AASA,SAAS,kBAAkB,SAAyB;AAClD,QAAM,QAAQ,QAAQ,MAAM,OAAO;AACnC,QAAM,SAAmB,CAAA;AACzB,MAAI,iBAAiB;AAErB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,uBAAiB,KAAK,WAAW,SAAS;AAAA,IAC5C;AACA,QAAI,CAAC,gBAAgB;AACnB,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,MAAM;AAC3B;ACpUO,SAAS,SAAS,SAA4B;AACnD,QAAM,QAAQ,QAAQ,MAAM,OAAO,EAAE,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC;AAE7D,QAAM,eAAyB,CAAA;AAC/B,QAAM,WAAsD,CAAA;AAC5D,MAAI,iBAA4D;AAEhE,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,uBAAiB,EAAE,OAAO,MAAM,OAAO,CAAC,IAAI,EAAA;AAC5C,eAAS,KAAK,cAAc;AAAA,IAC9B,WAAW,gBAAgB;AACzB,qBAAe,MAAM,KAAK,IAAI;AAAA,IAChC,OAAO;AACL,mBAAa,KAAK,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,iBAAiB,iBAAiB,cAAc,SAAS;AAE/D,QAAM,gBAAgB,SAAS,IAAI,CAAA,MAAK,kBAAkB,EAAE,OAAO,EAAE,KAAK,CAAC;AAE3E,SAAO,EAAE,cAAc,gBAAgB,cAAA;AACzC;AAMO,SAAS,kBAAkB,QAAyC;AACzE,WAAS,IAAI,GAAG,IAAI,OAAO,cAAc,QAAQ,KAAK;AACpD,UAAM,UAAU,OAAO,cAAc,CAAC;AACtC,QAAI,QAAQ,SAAS,QAAS;AAE9B,UAAM,aAAa,QAAQ,OAAO,YAAA,KAAiB;AACnD,QAAI,eAAe,UAAU,eAAe,OAAQ;AAEpD,UAAM,QAAyB,eAAe,SAAS,SAAS;AAChE,UAAM,cAAc,QAAQ,aAAa,CAAC,KAAK;AAC/C,UAAM,YAAY,QAAQ,aAAa;AAEvC,UAAM,cAAc,mBAAmB,OAAO,QAAQ,IAAI;AAC1D,UAAM,iBAAiB,UAAU,SAC5B,QAAQ,KAAK,kBAAkB,KAAK,OACrC;AAEJ,WAAO;AAAA,MACL,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AACA,SAAO;AACT;AAMO,SAAS,kBAAkB,QAAyC;AACzE,WAAS,IAAI,GAAG,IAAI,OAAO,cAAc,QAAQ,KAAK;AACpD,UAAM,UAAU,OAAO,cAAc,CAAC;AACtC,QAAI,QAAQ,SAAS,QAAS;AAE9B,WAAO;AAAA,MACL,cAAc;AAAA,MACd,OAAO,QAAQ,SAAS;AAAA,MACxB,aAAa,QAAQ,aAAa,CAAC,KAAK;AAAA,MACxC,WAAW,QAAQ,aAAa;AAAA,MAChC,UAAU,QAAQ,YAAY;AAAA,MAC9B,SAAS,QAAQ;AAAA,MACjB,MAAM,QAAQ;AAAA,IAAA;AAAA,EAElB;AACA,SAAO;AACT;AAUO,SAAS,gBACd,SACA,gBACA,cACQ;AACR,MAAI,CAAC,aAAc,QAAO;AAG1B,MAAI,aAAa,WAAW,SAAS,KAAK,aAAa,WAAW,UAAU,GAAG;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,OAAQ,mBAAmB,eAAe,WAAW,SAAS,KAAK,eAAe,WAAW,UAAU,KACzG,iBACA;AAGJ,QAAM,YAAY,KAAK,SAAS,GAAG,IAAI,KAAK;AAC5C,SAAO,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY;AAC3C;AAIA,SAAS,kBAAkB,OAAe,OAA+C;AAEvF,QAAM,SAAS,MAAM,UAAU,CAAC,EAAE,MAAM,KAAK;AAC7C,QAAM,OAAO,OAAO,CAAC,KAAK;AAC1B,QAAM,OAAO,SAAS,OAAO,CAAC,KAAK,KAAK,EAAE;AAC1C,QAAM,WAAW,OAAO,CAAC,KAAK;AAC9B,QAAM,eAAe,OAAO,MAAM,CAAC,EAAE,IAAI,CAAA,MAAK,SAAS,GAAG,EAAE,CAAC,EAAE,OAAO,OAAK,CAAC,MAAM,CAAC,CAAC;AAEpF,QAAM,YAAY,aAAa,CAAC;AAGhC,MAAI,QAAuB;AAC3B,MAAI,YAA2B;AAC/B,MAAI,WAA0B;AAE9B,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,KAAK,MAAM,8CAA8C;AAC7E,QAAI,aAAa;AACf,YAAM,KAAK,SAAS,YAAY,CAAC,GAAI,EAAE;AACvC,UAAI,OAAO,WAAW;AACpB,gBAAQ,YAAY,CAAC;AACrB,oBAAY,SAAS,YAAY,CAAC,GAAI,EAAE;AACxC,mBAAW,YAAY,CAAC,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAGA,MAAI,CAAC,SAAS,cAAc,QAAW;AACrC,UAAM,cAAc,qBAAqB,IAAI,SAAS;AACtD,QAAI,aAAa;AACf,cAAQ,YAAY;AACpB,kBAAY,YAAY;AACxB,iBAAW,YAAY;AAAA,IACzB;AAAA,EACF;AAGA,QAAM,OAAO,UAAU,OAAO,SAAS;AAGvC,QAAM,UAAU,iBAAiB,OAAO,SAAS;AAEjD,SAAO,EAAE,MAAM,MAAM,UAAU,cAAc,OAAO,WAAW,UAAU,MAAM,SAAS,MAAA;AAC1F;AAEA,SAAS,UACP,OACA,aACwB;AACxB,QAAM,SAAiC,CAAA;AAEvC,aAAW,QAAQ,OAAO;AACxB,UAAM,YAAY,KAAK,MAAM,sBAAsB;AACnD,QAAI,CAAC,UAAW;AAChB,UAAM,KAAK,SAAS,UAAU,CAAC,GAAI,EAAE;AACrC,QAAI,gBAAgB,UAAa,OAAO,YAAa;AAErD,UAAM,YAAY,UAAU,CAAC;AAE7B,eAAW,QAAQ,UAAU,MAAM,MAAM,GAAG;AAC1C,YAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,UAAI,QAAQ,GAAG;AACb,cAAM,MAAM,KAAK,UAAU,GAAG,KAAK,EAAE,KAAA,EAAO,YAAA;AAC5C,cAAM,QAAQ,KAAK,UAAU,QAAQ,CAAC,EAAE,KAAA;AACxC,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAA8B,MAA6B;AACnF,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,KAAK,IAAI,GAAG,GAAG;AACjC,aAAO,KAAK,UAAU,KAAK,SAAS,CAAC,EAAE,KAAA;AAAA,IACzC;AAAA,EACF;AACA,SAAO;AACT;AAWA,SAAS,mBACP,OACA,MACoB;AACpB,MAAI,UAAU,QAAQ;AACpB,UAAM,YAAY,KAAK,sBAAsB;AAC7C,QAAI,CAAC,UAAW,QAAO;AAEvB,UAAM,QAAQ,UAAU,MAAM,GAAG,EAAE,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC;AAC3D,QAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,UAAMC,iBAAgB,MAAM,IAAI,CAAA,MAAK,OAAO,KAAK,GAAG,QAAQ,CAAC;AAC7D,WAAO,EAAE,OAAO,eAAAA,eAAAA;AAAAA,EAClB;AAGA,QAAM,SAAS,KAAK,WAAW;AAC/B,QAAM,SAAS,KAAK,WAAW;AAC/B,QAAM,SAAS,KAAK,WAAW;AAC/B,MAAI,CAAC,UAAU,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE1C,QAAM,gBAAgB;AAAA,IACpB,OAAO,KAAK,QAAQ,QAAQ;AAAA,IAC5B,OAAO,KAAK,QAAQ,QAAQ;AAAA,IAC5B,OAAO,KAAK,QAAQ,QAAQ;AAAA,EAAA;AAE9B,SAAO,EAAE,OAAO,cAAA;AAClB;AAGA,MAAM,2CAA2B,IAAoE;AAAA,EACnG,CAAC,GAAG,EAAE,OAAO,QAAQ,WAAW,KAAM,UAAU,GAAG;AAAA,EACnD,CAAC,GAAG,EAAE,OAAO,QAAQ,WAAW,KAAM,UAAU,EAAA,CAAG;AACrD,CAAC;AC1QD,MAAM,6BAA6B;AACnC,MAAM,gCAAgC;AACtC,MAAM,YAAY;AAoBX,MAAM,iBAAiB;AAAA,EACpB,QAAmB;AAAA,EACnB,SAA4B;AAAA,EAC5B,OAAO;AAAA,EACP,YAA2B;AAAA,EAElB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGT,YAA8B;AAAA,EAC9B,aAA4B;AAAA,EAC5B,aAAmC;AAAA,EACnC,aAAmC;AAAA;AAAA,EAGnC,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,mBAAmB;AAAA;AAAA,EAGnB,aAA0C;AAAA,EAC1C,kBAA0C;AAAA,EAC1C,WAAW;AAAA;AAAA,EAGX,aAAqB,OAAO,MAAM,CAAC;AAAA;AAAA,EAGnC,kBAA6G;AAAA;AAAA,EAG7G;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA,WAAW;AAAA,EAEnB,YAAY,SAA4B,WAAgC;AACtE,SAAK,YAAY;AACjB,SAAK,SAAS,QAAQ;AACtB,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,sBAAsB,QAAQ,uBAAuB;AAG1D,UAAM,SAAS,IAAI,IAAI,QAAQ,GAAG;AAClC,SAAK,MAAM,QAAQ;AACnB,SAAK,OAAO,OAAO;AACnB,SAAK,OAAO,OAAO,OAAO,SAAS,OAAO,MAAM,EAAE,IAAI;AACtD,SAAK,WAAW,QAAQ,YAAY,mBAAmB,OAAO,QAAQ;AACtE,SAAK,WAAW,QAAQ,YAAY,mBAAmB,OAAO,QAAQ;AAAA,EACxE;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAU,UAAU,KAAK,UAAU,UAAU;AACpD,YAAM,IAAI,MAAM,2BAA2B,KAAK,KAAK,EAAE;AAAA,IACzD;AAEA,SAAK,WAAW;AAChB,SAAK,QAAQ;AACb,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,WAAW;AAChB,SAAK,aAAa,OAAO,MAAM,CAAC;AAEhC,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,YAAM,SAASC,eAAI,iBAAiB,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM;AACxE,WAAK,SAAS;AAEd,WAAK,eAAe,WAAW,MAAM;AACnC,aAAK,eAAe;AACpB,eAAO,QAAQ,IAAI,MAAM,8BAA8B,KAAK,gBAAgB,IAAI,CAAC;AAAA,MACnF,GAAG,KAAK,gBAAgB;AAExB,aAAO,GAAG,WAAW,MAAM;AACzB,YAAI,KAAK,cAAc;AACrB,uBAAa,KAAK,YAAY;AAC9B,eAAK,eAAe;AAAA,QACtB;AACA,aAAK,QAAQ,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,KAAA,GAAQ;AAE7E,aAAK,UAAA,EACF,KAAK,MAAM,SAAS,EACpB,MAAM,CAAC,QAAQ;AACd,eAAK,QAAA;AACL,iBAAO,GAAG;AAAA,QACZ,CAAC;AAAA,MACL,CAAC;AAED,aAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,aAAK,OAAO,KAAK;AAAA,MACnB,CAAC;AAED,aAAO,GAAG,SAAS,MAAM;AACvB,YAAI,KAAK,UAAU,eAAe,CAAC,KAAK,UAAU;AAChD,eAAK,UAAU,UAAU,IAAI,MAAM,qCAAqC,CAAC;AAAA,QAC3E;AACA,aAAK,QAAA;AAAA,MACP,CAAC;AAED,aAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,YAAI,KAAK,cAAc;AACrB,uBAAa,KAAK,YAAY;AAC9B,eAAK,eAAe;AAAA,QACtB;AACA,YAAI,KAAK,UAAU,YAAY,KAAK,UAAU,YAAY;AACxD,eAAK,UAAU,UAAU,GAAG;AAAA,QAC9B;AACA,aAAK,QAAA;AACL,eAAO,GAAG;AAAA,MACZ,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,WAA0B;AAC9B,QAAI,KAAK,UAAU,YAAY,KAAK,UAAU,OAAQ;AACtD,SAAK,WAAW;AAChB,SAAK,QAAQ;AAEb,QAAI;AACF,UAAI,KAAK,UAAU,CAAC,KAAK,OAAO,WAAW;AACzC,cAAM,KAAK,YAAY,YAAY,KAAK,GAAG;AAAA,MAC7C;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,SAAK,QAAA;AACL,SAAK,UAAU,aAAA;AAAA,EACjB;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,WAAW;AAChB,SAAK,QAAA;AAAA,EACP;AAAA;AAAA,EAGA,aAA4B;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA;AAAA,EAGrD,eAAiC;AAAE,WAAO,KAAK;AAAA,EAAU;AAAA;AAAA,EAGzD,gBAAsC;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA;AAAA,EAG/D,WAAsB;AAAE,WAAO,KAAK;AAAA,EAAM;AAAA;AAAA,EAI1C,MAAc,YAA2B;AAEvC,SAAK,QAAQ;AACb,UAAM,KAAK,YAAY,WAAW,KAAK,GAAG;AAG1C,SAAK,QAAQ;AACb,UAAM,iBAAiB,MAAM,KAAK,YAAY,YAAY,KAAK,KAAK;AAAA,MAClE,UAAU;AAAA,IAAA,CACX;AAED,QAAI,eAAe,eAAe,KAAK;AAErC,WAAK,mBAAmB,eAAe,OAAO;AAC9C,YAAM,cAAc,MAAM,KAAK,YAAY,YAAY,KAAK,KAAK;AAAA,QAC/D,UAAU;AAAA,MAAA,CACX;AACD,UAAI,YAAY,eAAe,KAAK;AAClC,cAAM,IAAI,MAAM,+BAA+B,YAAY,UAAU,EAAE;AAAA,MACzE;AACA,WAAK,WAAW,YAAY,IAAI;AAAA,IAClC,WAAW,eAAe,eAAe,KAAK;AAC5C,WAAK,WAAW,eAAe,IAAI;AAAA,IACrC,OAAO;AACL,YAAM,IAAI,MAAM,oBAAoB,eAAe,UAAU,EAAE;AAAA,IACjE;AAEA,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAGA,SAAK,QAAQ;AACb,UAAM,kBAAkB;AAAA,MACtB,KAAK;AAAA,MACL,KAAK,WAAW,kBAAkB;AAAA,MAClC,KAAK,WAAW;AAAA,IAAA;AAGlB,UAAM,mBAAmB,MAAM,KAAK,YAAY,SAAS,iBAAiB;AAAA,MACxE,aAAa,mCAAmC,KAAK,eAAe,IAAI,KAAK,gBAAgB;AAAA,IAAA,CAC9F;AAED,QAAI,iBAAiB,eAAe,KAAK;AACvC,YAAM,IAAI,MAAM,uBAAuB,iBAAiB,UAAU,EAAE;AAAA,IACtE;AAGA,UAAM,gBAAgB,iBAAiB,QAAQ,IAAI,SAAS;AAC5D,QAAI,eAAe;AAEjB,WAAK,YAAY,cAAc,MAAM,GAAG,EAAE,CAAC,EAAG,KAAA;AAAA,IAChD;AAGA,QAAI,KAAK,YAAY;AACnB,WAAK,QAAQ;AACb,YAAM,kBAAkB;AAAA,QACtB,KAAK;AAAA,QACL,KAAK,WAAW,kBAAkB;AAAA,QAClC,KAAK,WAAW;AAAA,MAAA;AAGlB,YAAM,mBAAmB,MAAM,KAAK,YAAY,SAAS,iBAAiB;AAAA,QACxE,aAAa,mCAAmC,KAAK,eAAe,IAAI,KAAK,gBAAgB;AAAA,MAAA,CAC9F;AAED,UAAI,iBAAiB,eAAe,KAAK;AACvC,aAAK,QAAQ,KAAK,iDAAiD;AAAA,UACjE,MAAM,EAAE,YAAY,iBAAiB,WAAA;AAAA,QAAW,CACjD;AACD,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAGA,SAAK,QAAQ;AACb,UAAM,aAAa,MAAM,KAAK,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC1D,SAAS;AAAA,IAAA,CACV;AAED,QAAI,WAAW,eAAe,KAAK;AACjC,YAAM,IAAI,MAAM,gBAAgB,WAAW,UAAU,EAAE;AAAA,IACzD;AAEA,SAAK,QAAQ;AACb,SAAK,QAAQ,KAAK,kBAAkB,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,KAAA,GAAQ;AAGlF,SAAK,iBAAiB,YAAY,MAAM;AACtC,WAAK,cAAA,EAAgB,MAAM,CAAC,QAAQ;AAClC,aAAK,QAAQ,KAAK,oBAAoB;AAAA,UACpC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAA;AAAA,QAAE,CACjE;AAAA,MACH,CAAC;AAAA,IACH,GAAG,KAAK,mBAAmB;AAE3B,SAAK,UAAU,YAAA;AAAA,EACjB;AAAA,EAEQ,WAAW,SAAuB;AACxC,SAAK,aAAa;AAClB,SAAK,YAAY,SAAS,OAAO;AACjC,SAAK,aAAa,kBAAkB,KAAK,SAAS;AAClD,SAAK,aAAa,kBAAkB,KAAK,SAAS;AAElD,QAAI,KAAK,YAAY;AACnB,WAAK,UAAU,eAAe,KAAK,YAAY,OAAO;AACtD,WAAK,QAAQ,KAAK,eAAe;AAAA,QAC/B,MAAM;AAAA,UACJ,OAAO,KAAK,WAAW;AAAA,UACvB,aAAa,KAAK,WAAW;AAAA,UAC7B,WAAW,KAAK,WAAW;AAAA,QAAA;AAAA,MAC7B,CACD;AAAA,IACH;AAEA,QAAI,KAAK,YAAY;AACnB,WAAK,UAAU,eAAe,KAAK,UAAU;AAC7C,WAAK,QAAQ,KAAK,eAAe;AAAA,QAC/B,MAAM;AAAA,UACJ,OAAO,KAAK,WAAW;AAAA,UACvB,aAAa,KAAK,WAAW;AAAA,UAC7B,WAAW,KAAK,WAAW;AAAA,QAAA;AAAA,MAC7B,CACD;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,OAAO,OAAqB;AAClC,SAAK,aAAa,KAAK,WAAW,SAAS,IACvC,OAAO,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,IACtC;AAEJ,WAAO,KAAK,WAAW,SAAS,GAAG;AACjC,UAAI,KAAK,WAAW,CAAC,MAAM,mBAAmB;AAE5C,YAAI,KAAK,WAAW,SAAS,EAAG;AAEhC,cAAM,UAAU,KAAK,WAAW,CAAC;AACjC,cAAM,SAAS,KAAK,WAAW,aAAa,CAAC;AAE7C,YAAI,KAAK,WAAW,SAAS,IAAI,OAAQ;AAEzC,cAAM,UAAU,OAAO,KAAK,KAAK,WAAW,SAAS,GAAG,IAAI,MAAM,CAAC;AACnE,aAAK,aAAa,KAAK,WAAW,SAAS,IAAI,MAAM;AAErD,aAAK,wBAAwB,SAAS,OAAO;AAAA,MAC/C,OAAO;AAEL,cAAM,YAAY,KAAK,WAAW,QAAQ,UAAU;AACpD,YAAI,YAAY,EAAG;AAEnB,cAAM,aAAa,KAAK,WAAW,SAAS,GAAG,SAAS,EAAE,SAAS,OAAO;AAC1E,YAAI,YAAY,YAAY;AAG5B,cAAM,UAAU,WAAW,MAAM,0BAA0B;AAC3D,cAAM,gBAAgB,UAAU,SAAS,QAAQ,CAAC,GAAI,EAAE,IAAI;AAE5D,YAAI,KAAK,WAAW,SAAS,YAAY,cAAe;AAExD,cAAM,OAAO,gBAAgB,IACzB,KAAK,WAAW,SAAS,WAAW,YAAY,aAAa,EAAE,SAAS,OAAO,IAC/E;AAEJ,aAAK,aAAa,KAAK,WAAW,SAAS,YAAY,aAAa;AAEpE,aAAK,mBAAmB,YAAY,IAAI;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,wBAAwB,SAAiB,SAAuB;AACtE,QAAI,YAAY,KAAK,iBAAiB;AACpC,WAAK,UAAU,aAAa,SAAS,OAAO;AAAA,IAC9C,WAAW,YAAY,KAAK,iBAAiB;AAC3C,WAAK,UAAU,aAAa,SAAS,OAAO;AAAA,IAC9C;AAAA,EAEF;AAAA,EAEQ,mBAAmB,YAAoB,MAAoB;AACjE,UAAM,QAAQ,WAAW,MAAM,MAAM;AACrC,UAAM,aAAa,MAAM,CAAC,KAAK;AAC/B,UAAM,cAAc,WAAW,MAAM,qBAAqB;AAC1D,UAAM,aAAa,cAAc,SAAS,YAAY,CAAC,GAAI,EAAE,IAAI;AAEjE,UAAM,8BAAc,IAAA;AACpB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,WAAW,MAAM,CAAC,EAAG,QAAQ,GAAG;AACtC,UAAI,WAAW,EAAG;AAClB,YAAM,MAAM,MAAM,CAAC,EAAG,UAAU,GAAG,QAAQ,EAAE,KAAA,EAAO,YAAA;AACpD,YAAM,QAAQ,MAAM,CAAC,EAAG,UAAU,WAAW,CAAC,EAAE,KAAA;AAChD,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AAEA,QAAI,KAAK,iBAAiB;AACxB,YAAM,UAAU,KAAK;AACrB,WAAK,kBAAkB;AACvB,cAAQ,YAAY,SAAS,IAAI;AAAA,IACnC;AAAA,EACF;AAAA;AAAA,EAIQ,YACN,QACA,KACA,cACqF;AACrF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,UAAU,KAAK,OAAO,WAAW;AACzC,eAAO,IAAI,MAAM,sBAAsB,CAAC;AACxC;AAAA,MACF;AAEA,WAAK;AACL,YAAM,UAAkC;AAAA,QACtC,QAAQ,OAAO,KAAK,IAAI;AAAA,QACxB,cAAc;AAAA,QACd,GAAG;AAAA,MAAA;AAGL,UAAI,KAAK,WAAW;AAClB,gBAAQ,SAAS,IAAI,KAAK;AAAA,MAC5B;AAGA,YAAM,aAAa,KAAK,gBAAgB,QAAQ,GAAG;AACnD,UAAI,YAAY;AACd,gBAAQ,eAAe,IAAI;AAAA,MAC7B;AAEA,UAAI,UAAU,GAAG,MAAM,IAAI,GAAG;AAAA;AAC9B,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,mBAAW,GAAG,GAAG,KAAK,KAAK;AAAA;AAAA,MAC7B;AACA,iBAAW;AAEX,WAAK,kBAAkB,CAAC,YAAY,iBAAiB,SAAS;AAC5D,gBAAQ,EAAE,YAAY,SAAS,iBAAiB,MAAM;AAAA,MACxD;AAGA,YAAM,UAAU,WAAW,MAAM;AAC/B,YAAI,KAAK,iBAAiB;AACxB,eAAK,kBAAkB;AACvB,iBAAO,IAAI,MAAM,QAAQ,MAAM,mBAAmB,CAAC;AAAA,QACrD;AAAA,MACF,GAAG,KAAK,gBAAgB;AAExB,YAAM,kBAAkB,KAAK;AAC7B,WAAK,kBAAkB,CAAC,YAAY,iBAAiB,SAAS;AAC5D,qBAAa,OAAO;AACpB,wBAAgB,YAAY,iBAAiB,IAAI;AAAA,MACnD;AAEA,WAAK,OAAO,MAAM,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,gBAA+B;AAC3C,QAAI,KAAK,UAAU,eAAe,CAAC,KAAK,OAAQ;AAChD,UAAM,KAAK,YAAY,iBAAiB,KAAK,GAAG;AAAA,EAClD;AAAA;AAAA,EAIQ,mBAAmB,SAA4C;AACrE,UAAM,UAAU,QAAQ,IAAI,kBAAkB;AAC9C,QAAI,CAAC,QAAS;AAEd,QAAI,QAAQ,YAAA,EAAc,WAAW,QAAQ,GAAG;AAC9C,WAAK,aAAa;AAClB,YAAM,QAAQ,cAAc,SAAS,OAAO,KAAK;AACjD,YAAM,QAAQ,cAAc,SAAS,OAAO,KAAK;AACjD,YAAM,MAAM,cAAc,SAAS,KAAK;AACxC,YAAM,SAAS,cAAc,SAAS,QAAQ;AAC9C,WAAK,kBAAkB,EAAE,OAAO,OAAO,KAAK,OAAA;AAC5C,WAAK,WAAW;AAAA,IAClB,WAAW,QAAQ,YAAA,EAAc,WAAW,OAAO,GAAG;AACpD,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,gBAAgB,QAAgB,KAA4B;AAClE,QAAI,CAAC,KAAK,YAAY,CAAC,KAAK,SAAU,QAAO;AAE7C,QAAI,KAAK,eAAe,SAAS;AAC/B,YAAM,UAAU,OAAO,KAAK,GAAG,KAAK,QAAQ,IAAI,KAAK,QAAQ,EAAE,EAAE,SAAS,QAAQ;AAClF,aAAO,SAAS,OAAO;AAAA,IACzB;AAEA,QAAI,KAAK,eAAe,YAAY,KAAK,iBAAiB;AACxD,aAAO,KAAK,gBAAgB,QAAQ,GAAG;AAAA,IACzC;AAGA,QAAI,KAAK,YAAY,KAAK,UAAU;AAClC,YAAM,UAAU,OAAO,KAAK,GAAG,KAAK,QAAQ,IAAI,KAAK,QAAQ,EAAE,EAAE,SAAS,QAAQ;AAClF,aAAO,SAAS,OAAO;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,QAAgB,KAAqB;AAC3D,UAAM,EAAE,OAAO,OAAO,KAAK,OAAA,IAAW,KAAK;AAC3C,UAAM,MAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,QAAQ,EAAE;AAE5D,QAAI;AACJ,QAAI;AAEJ,QAAI,QAAQ,UAAU,KAAK,SAAS,MAAM,GAAG;AAC3C,WAAK;AACL,YAAM,KAAK,KAAK,SAAS,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACrD,YAAM,SAASC,oBAAa,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AACzD,YAAM,MAAM,IAAI,GAAG,MAAM,IAAI,GAAG,EAAE;AAClC,iBAAW,IAAI,GAAG,GAAG,IAAI,KAAK,IAAI,EAAE,IAAI,MAAM,SAAS,GAAG,EAAE;AAC5D,mBAAa,aAAa,KAAK,QAAQ,aAAa,KAAK,aAAa,KAAK,WAAW,GAAG,mBAAmB,EAAE,aAAa,MAAM,gBAAgB,QAAQ;AAAA,IAC3J,OAAO;AACL,YAAM,MAAM,IAAI,GAAG,MAAM,IAAI,GAAG,EAAE;AAClC,iBAAW,IAAI,GAAG,GAAG,IAAI,KAAK,IAAI,GAAG,EAAE;AACvC,mBAAa,aAAa,KAAK,QAAQ,aAAa,KAAK,aAAa,KAAK,WAAW,GAAG,gBAAgB,QAAQ;AAAA,IACnH;AAEA,QAAI,QAAQ;AACV,oBAAc,aAAa,MAAM;AAAA,IACnC;AAEA,WAAO,UAAU,UAAU;AAAA,EAC7B;AAAA;AAAA,EAIQ,UAAgB;AACtB,SAAK,QAAQ;AAEb,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AAEA,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAEA,QAAI,KAAK,QAAQ;AACf,UAAI;AAAE,aAAK,OAAO,QAAA;AAAA,MAAU,QAAQ;AAAA,MAAuB;AAC3D,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,kBAAkB;AAAA,EACzB;AACF;AAIA,SAAS,IAAI,OAAuB;AAClC,SAAOC,OAAAA,WAAW,KAAK,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AACrD;AAEA,SAAS,cAAc,QAAgB,KAAiC;AAEtE,QAAM,QAAQ,IAAI,OAAO,GAAG,GAAG,cAAc,GAAG;AAChD,QAAM,QAAQ,OAAO,MAAM,KAAK;AAChC,MAAI,MAAO,QAAO,MAAM,CAAC;AAGzB,QAAM,gBAAgB,IAAI,OAAO,GAAG,GAAG,eAAe,GAAG;AACzD,QAAM,gBAAgB,OAAO,MAAM,aAAa;AAChD,SAAO,gBAAgB,CAAC;AAC1B;ACtjBO,MAAM,cAAc;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,SAA4B;AAAA;AAAA,EAE5B,aAAqB,OAAO,MAAM,CAAC;AAAA,EACnC,YAA8B;AAAA,EAC9B,aAAmC;AAAA,EACnC,aAAmC;AAAA,EACnC,YAAY;AAAA,EAEpB,YAAY,SAA+B,WAAmC;AAC5E,SAAK,UAAU;AACf,SAAK,YAAY;AACjB,SAAK,SAAS,QAAQ;AACtB,UAAM,SAAS,IAAI,IAAI,QAAQ,GAAG;AAClC,SAAK,OAAO,OAAO;AACnB,SAAK,OAAO,SAAS,OAAO,QAAQ,KAAK,EAAE;AAC3C,QAAI,KAAK,SAAS,GAAG;AACnB,YAAM,IAAI,MAAM,iDAAiD,QAAQ,GAAG,GAAG;AAAA,IACjF;AAMA,QAAI,OAAO,YAAY,OAAO,UAAU;AACtC,YAAM,IAAI,mBAAmB,OAAO,QAAQ;AAC5C,YAAM,IAAI,mBAAmB,OAAO,QAAQ;AAC5C,WAAK,WAAW,OAAO,KAAK,GAAG,CAAC,IAAI,CAAC;AAAA,GAAM,MAAM;AAAA,IACnD,OAAO;AACL,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAI7B,SAAK,YAAY,SAAS,KAAK,QAAQ,GAAG;AAC1C,SAAK,aAAa,kBAAkB,KAAK,SAAS;AAClD,SAAK,aAAa,kBAAkB,KAAK,SAAS;AAElD,QAAI,KAAK,YAAY;AACnB,WAAK,UAAU,eAAe,KAAK,YAAY,KAAK,QAAQ,GAAG;AAAA,IACjE;AACA,QAAI,KAAK,YAAY;AACnB,WAAK,UAAU,eAAe,KAAK,UAAU;AAAA,IAC/C;AAEA,UAAM,KAAK,WAAA;AAAA,EACb;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,QAAI,KAAK,QAAQ;AACf,UAAI;AAAE,aAAK,OAAO,QAAA;AAAA,MAAU,QAAQ;AAAA,MAAe;AACnD,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA,EAEQ,aAA4B;AAClC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,UAAU;AACd,YAAM,SAASF,eAAI,iBAAiB,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,KAAA,GAAQ,MAAM;AAC9E,aAAK,QAAQ,KAAK,0BAA0B,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,KAAA,GAAQ;AAK1F,YAAI,KAAK,SAAU,QAAO,MAAM,KAAK,QAAQ;AAI7C,aAAK,UAAU,YAAA;AACf,kBAAU;AACV,gBAAA;AAAA,MACF,CAAC;AACD,WAAK,SAAS;AAGd,aAAO,WAAW,IAAI;AAEtB,aAAO,GAAG,QAAQ,CAAC,UAAU,KAAK,OAAO,KAAK,CAAC;AAC/C,aAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,aAAK,QAAQ,KAAK,sBAAsB,EAAE,MAAM,EAAE,OAAO,IAAI,QAAA,GAAW;AACxE,YAAI,CAAC,KAAK,WAAW;AACnB,eAAK,UAAU,UAAU,GAAG;AAAA,QAC9B;AACA,YAAI,CAAC,QAAS,QAAO,GAAG;AAAA,MAC1B,CAAC;AACD,aAAO,GAAG,SAAS,MAAM;AACvB,aAAK,QAAQ,KAAK,qBAAqB;AACvC,YAAI,CAAC,KAAK,WAAW;AACnB,eAAK,UAAU,aAAA;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,OAAO,OAAqB;AAClC,SAAK,aAAa,KAAK,WAAW,SAAS,IACvC,OAAO,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,IACtC;AAEJ,WAAO,KAAK,WAAW,UAAU,GAAG;AAClC,YAAM,SAAS,KAAK,WAAW,aAAa,CAAC;AAC7C,UAAI,KAAK,WAAW,SAAS,IAAI,OAAQ;AAEzC,YAAM,YAAY,OAAO,KAAK,KAAK,WAAW,SAAS,GAAG,IAAI,MAAM,CAAC;AACrE,WAAK,aAAa,KAAK,WAAW,SAAS,IAAI,MAAM;AAErD,WAAK,YAAY,SAAS;AAAA,IAC5B;AAAA,EACF;AAAA,EAEQ,YAAY,WAAyB;AAC3C,QAAI,UAAU,SAAS,EAAG;AAE1B,UAAM,cAAc,UAAU,CAAC,IAAK;AACpC,QAAI,KAAK,cAAc,gBAAgB,KAAK,WAAW,aAAa;AAClE,WAAK,UAAU,aAAa,WAAW,CAAC;AACxC;AAAA,IACF;AACA,QAAI,KAAK,cAAc,gBAAgB,KAAK,WAAW,aAAa;AAClE,WAAK,UAAU,aAAa,WAAW,CAAC;AACxC;AAAA,IACF;AAAA,EAGF;AACF;ACxIO,IAAK,qCAAAG,sBAAL;AACLA,oBAAAA,kBAAA,YAAS,CAAA,IAAT;AACAA,oBAAAA,kBAAA,WAAQ,CAAA,IAAR;AACAA,oBAAAA,kBAAA,SAAM,CAAA,IAAN;AACAA,oBAAAA,kBAAA,YAAS,CAAA,IAAT;AACAA,oBAAAA,kBAAA,oBAAiB,CAAA,IAAjB;AACAA,oBAAAA,kBAAA,mBAAgB,CAAA,IAAhB;AACAA,oBAAAA,kBAAA,gBAAa,CAAA,IAAb;AACAA,oBAAAA,kBAAA,YAAS,CAAA,IAAT;AACAA,oBAAAA,kBAAA,YAAS,CAAA,IAAT;AACAA,oBAAAA,kBAAA,SAAM,EAAA,IAAN;AACAA,oBAAAA,kBAAA,WAAQ,EAAA,IAAR;AACAA,oBAAAA,kBAAA,YAAS,EAAA,IAAT;AAZU,SAAAA;AAAA,GAAA,oBAAA,CAAA,CAAA;AA2HL,MAAM,sBAAsB,MAAM;AAAA,EACvC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,8BAA8B,cAAc;AAAA,EACvD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAIA,SAAS,mCAAmC,QAA+C;AACzF,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,cAAc,yCAAyC;AAAA,EACnE;AAEA,QAAM,uBAAuB,OAAO,UAAU,CAAC;AAC/C,QAAM,uBAAuB,OAAO,UAAU,CAAC;AAC/C,QAAM,uBAAuB,OAAO,UAAU,CAAC;AAC/C,QAAM,qBAAqB,OAAO,UAAU,CAAC;AAC7C,QAAM,qBAAqB,OAAO,UAAU,CAAC,IAAI;AAEjD,QAAM,SAAS,OAAO,UAAU,CAAC,IAAI;AACrC,MAAI,MAAM;AAEV,QAAM,MAAgB,CAAA;AACtB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,QAAI,MAAM,IAAI,OAAO,OAAQ,OAAM,IAAI,cAAc,4DAA4D;AACjH,UAAM,MAAM,OAAO,aAAa,GAAG;AACnC,WAAO;AACP,QAAI,MAAM,MAAM,OAAO,OAAQ,OAAM,IAAI,cAAc,kDAAkD;AACzG,QAAI,KAAK,OAAO,SAAS,KAAK,MAAM,GAAG,CAAC;AACxC,WAAO;AAAA,EACT;AAEA,QAAM,MAAgB,CAAA;AACtB,MAAI,MAAM,OAAO,QAAQ;AACvB,UAAM,SAAS,OAAO,UAAU,GAAG;AACnC,WAAO;AACP,aAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAI,MAAM,IAAI,OAAO,OAAQ,OAAM,IAAI,cAAc,4DAA4D;AACjH,YAAM,MAAM,OAAO,aAAa,GAAG;AACnC,aAAO;AACP,UAAI,MAAM,MAAM,OAAO,OAAQ,OAAM,IAAI,cAAc,kDAAkD;AACzG,UAAI,KAAK,OAAO,SAAS,KAAK,MAAM,GAAG,CAAC;AACxC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AA2BA,SAAS,oCAAoC,QAAgD;AAC3F,MAAI,OAAO,SAAS,IAAI;AACtB,UAAM,IAAI,cAAc,0CAA0C;AAAA,EACpE;AAEA,QAAM,uBAAuB,OAAO,UAAU,CAAC;AAC/C,QAAM,QAAQ,OAAO,UAAU,CAAC;AAChC,QAAM,sBAAuB,SAAS,IAAK;AAC3C,QAAM,kBAAmB,SAAS,IAAK;AACvC,QAAM,oBAAoB,QAAQ;AAClC,QAAM,mCAAmC,OAAO,aAAa,CAAC;AAE9D,QAAM,kBAAkB,OAAO,UAAU,EAAE;AAQ3C,QAAM,qBAAqB,OAAO,UAAU,EAAE,IAAI;AAElD,QAAM,cAAc,OAAO,UAAU,EAAE;AACvC,MAAI,MAAM;AAEV,QAAM,MAAgB,CAAA;AACtB,QAAM,MAAgB,CAAA;AACtB,QAAM,MAAgB,CAAA;AAEtB,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,QAAI,MAAM,IAAI,OAAO,OAAQ,OAAM,IAAI,cAAc,+DAA+D;AACpH,UAAM,cAAc,OAAO,UAAU,GAAG,IAAI;AAC5C,UAAM,WAAW,OAAO,aAAa,MAAM,CAAC;AAC5C,WAAO;AAEP,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,UAAI,MAAM,IAAI,OAAO,OAAQ,OAAM,IAAI,cAAc,8DAA8D;AACnH,YAAM,MAAM,OAAO,aAAa,GAAG;AACnC,aAAO;AACP,UAAI,MAAM,MAAM,OAAO,OAAQ,OAAM,IAAI,cAAc,oDAAoD;AAC3G,YAAM,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG;AAC1C,aAAO;AAEP,UAAI,gBAAgB,GAAI,KAAI,KAAK,GAAG;AAAA,eAC3B,gBAAgB,GAAI,KAAI,KAAK,GAAG;AAAA,eAChC,gBAAgB,GAAI,KAAI,KAAK,GAAG;AAAA,IAE3C;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,SAAS,yBAAyB,QAAqC;AACrE,MAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,+BAA+B;AAC9E,QAAM,KAAK,OAAO,UAAU,CAAC;AAC7B,QAAM,KAAK,OAAO,UAAU,CAAC;AAC7B,SAAO;AAAA,IACL,iBAAkB,MAAM,IAAK;AAAA,IAC7B,yBAA0B,KAAK,MAAS,IAAO,MAAM,IAAK;AAAA,IAC1D,sBAAuB,MAAM,IAAK;AAAA,EAAA;AAEtC;AAIA,SAAS,2BAA2B,SAAiB,YAA8B;AACjF,MAAI,aAAa,KAAK,aAAa,GAAG;AACpC,UAAM,IAAI,cAAc,6BAA6B,UAAU,EAAE;AAAA,EACnE;AACA,QAAM,MAAgB,CAAA;AACtB,MAAI,MAAM;AACV,SAAO,MAAM,cAAc,QAAQ,QAAQ;AACzC,UAAM,MAAM,QAAQ,WAAW,KAAK,UAAU;AAC9C,WAAO;AACP,QAAI,QAAQ,EAAG;AACf,QAAI,MAAM,MAAM,QAAQ,QAAQ;AAC9B,YAAM,IAAI,cAAc,+BAA+B,GAAG,QAAQ,GAAG,EAAE;AAAA,IACzE;AACA,QAAI,KAAK,QAAQ,SAAS,KAAK,MAAM,GAAG,CAAC;AACzC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAsDA,SAAS,4BACP,QACA,OACA,WACA,YACa;AACb,MAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,GAAG,MAAM,aAAa,6BAA6B;AAClG,QAAM,aAAa,OAAO,UAAU,CAAC;AACrC,QAAM,kBAAkB,OAAO,UAAU,GAAG,CAAC;AAE7C,MAAI,eAAe,GAA+B;AAChD,QAAI,UAAU,QAAQ;AACpB,YAAM,YAAY,mCAAmC,OAAO,SAAS,CAAC,CAAC;AACvE,aAAO,EAAE,MAAM,yBAAyB,OAAO,QAAQ,WAAW,iBAAiB,UAAA;AAAA,IACrF;AACA,UAAM,aAAa,oCAAoC,OAAO,SAAS,CAAC,CAAC;AACzE,WAAO,EAAE,MAAM,yBAAyB,OAAO,QAAQ,WAAW,iBAAiB,WAAA;AAAA,EACrF;AAEA,MAAI,eAAe,GAAoB;AACrC,UAAM,QAAQ,2BAA2B,OAAO,SAAS,CAAC,GAAG,UAAU;AACvE,WAAO,EAAE,MAAM,sBAAsB,OAAO,WAAW,iBAAiB,MAAA;AAAA,EAC1E;AAEA,MAAI,eAAe,GAA+B;AAChD,WAAO,EAAE,MAAM,sBAAsB,OAAO,WAAW,gBAAA;AAAA,EACzD;AAEA,QAAM,IAAI,cAAc,4BAA4B,UAAU,EAAE;AAClE;AAQO,SAAS,+BAA+B,QAAgB,YAAiC;AAC9F,MAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,iBAAiB;AAChE,QAAM,QAAQ,OAAO,UAAU,CAAC;AAChC,QAAM,cAAc,QAAQ,SAAU;AAEtC,MAAI,YAAY;AACd,WAAO,sBAAsB,QAAQ,UAAU;AAAA,EACjD;AAEA,QAAM,YAAa,SAAS;AAC5B,QAAM,UAAU,QAAQ;AACxB,QAAM,QACJ,YAAY,IAAoB,SAC5B,YAAY,KAA2B,SACrC;AACR,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI,sBAAsB,qBAAqB,OAAO,gBAAgB;AAAA,EAC9E;AACA,SAAO,4BAA4B,QAAQ,OAAO,WAAW,UAAU;AACzE;AAEA,SAAS,sBAAsB,QAAgB,aAAqB,GAAgB;AAElF,MAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,gDAAgD;AAC/F,QAAM,QAAQ,OAAO,UAAU,CAAC;AAChC,QAAM,aAAe,SAAS,IAAK;AACnC,QAAM,YAAa,QAAQ;AAE3B,QAAM,SAAS,OAAO,SAAS,GAAG,CAAC,EAAE,SAAS,OAAO;AACrD,QAAM,QAAQ,iBAAiB,MAAM;AAErC,MAAI,UAAU,QAAQ;AACpB,UAAM,IAAI,sBAAsB,wBAAwB,MAAM,kCAAkC;AAAA,EAClG;AAEA,MAAI,eAAe,GAAmC;AACpD,UAAM,aAAa,oCAAoC,OAAO,SAAS,CAAC,CAAC;AACzE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,IAAA;AAAA,EAEJ;AAEA,MAAI,eAAe,GAAiC;AAClD,QAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,qCAAqC;AACpF,UAAM,kBAAkB,OAAO,UAAU,GAAG,CAAC;AAC7C,UAAM,QAAQ,2BAA2B,OAAO,SAAS,CAAC,GAAG,UAAU;AACvE,WAAO,EAAE,MAAM,sBAAsB,OAAO,QAAQ,WAAW,iBAAiB,MAAA;AAAA,EAClF;AAEA,MAAI,eAAe,GAAmC;AACpD,UAAM,QAAQ,2BAA2B,OAAO,SAAS,CAAC,GAAG,UAAU;AACvE,WAAO,EAAE,MAAM,sBAAsB,OAAO,QAAQ,WAAW,iBAAiB,GAAG,MAAA;AAAA,EACrF;AAEA,MAAI,eAAe,GAAiC;AAClD,WAAO,EAAE,MAAM,sBAAsB,OAAO,QAAQ,WAAW,iBAAiB,EAAA;AAAA,EAClF;AAEA,QAAM,IAAI,cAAc,yCAAyC,UAAU,EAAE;AAC/E;AAEA,SAAS,iBAAiB,QAAmC;AAC3D,MAAI,WAAW,UAAU,WAAW,OAAQ,QAAO;AACnD,MAAI,WAAW,OAAQ,QAAO;AAC9B,SAAO;AACT;AAGO,SAAS,iBAAiB,QAA6B;AAC5D,MAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,iBAAiB;AAChE,QAAM,QAAQ,OAAO,UAAU,CAAC;AAChC,QAAM,cAAgB,SAAS,IAAK;AAEpC,MAAI,gBAAgB,IAAsB;AACxC,WAAO,EAAE,MAAM,aAAa,aAAa,MAAM,OAAO,SAAS,CAAC,EAAA;AAAA,EAClE;AAEA,MAAI,OAAO,SAAS,EAAG,OAAM,IAAI,cAAc,yBAAyB;AACxE,QAAM,gBAAgB,OAAO,UAAU,CAAC;AAExC,MAAI,kBAAkB,GAA+B;AACnD,UAAM,WAAW,OAAO,SAAS,CAAC;AAClC,UAAM,sBAAsB,yBAAyB,QAAQ;AAC7D,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,0BAA0B,OAAO,KAAK,QAAQ;AAAA,IAAA;AAAA,EAElD;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,MAAM,OAAO,SAAS,CAAC;AAAA,EAAA;AAE3B;AAKA,MAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AAMvD,SAAS,cAAc,OAAsC;AAClE,MAAI,MAAM,WAAW,EAAG,QAAO,OAAO,MAAM,CAAC;AAC7C,QAAM,QAAkB,CAAA;AACxB,aAAW,OAAO,OAAO;AACvB,UAAM,KAAK,mBAAmB,GAAG;AAAA,EACnC;AACA,SAAO,OAAO,OAAO,KAAK;AAC5B;AAMO,SAAS,yBAAyB,QAA+C;AACtF,SAAO,cAAc,CAAC,GAAG,OAAO,KAAK,GAAG,OAAO,GAAG,CAAC;AACrD;AAMO,SAAS,yBAAyB,QAAgD;AACvF,SAAO,cAAc,CAAC,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,GAAG,OAAO,GAAG,CAAC;AACpE;AAIA,MAAM,mBAA0C;AAAA,EAC9C;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACjD;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAM;AAC7B;AAOO,SAAS,qBAAqB,OAA8B;AACjE,SAAO,iBAAiB,KAAK,KAAK;AACpC;ACxkBA,MAAM,uBAAuB,MAAM;AAAA,EACjC,YAAY,OAAe;AACzB,UAAM,iBAAiB,KAAK,EAAE;AAC9B,SAAK,OAAO;AAAA,EACd;AACF;AAEA,eAAe,WAAW,UAAoB,QAAiC;AAC7E,MAAI,SAAS,iBAAiB,SAAS,WAAW;AAChD,UAAM,IAAI,eAAe,kBAAkB;AAAA,EAC7C;AACA,MAAI,CAAC,OAAQ,QAAO,OAAO,MAAM,CAAC;AAElC,QAAM,UAAU,SAAS,KAAK,MAAM;AACpC,MAAI,QAAS,QAAO;AAEpB,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,aAAa,MAAY;AAC7B,YAAM,QAAQ,SAAS,KAAK,MAAM;AAClC,UAAI,OAAO;AACT,gBAAA;AACA,gBAAQ,KAAK;AACb;AAAA,MACF;AACA,UAAI,SAAS,iBAAiB,SAAS,WAAW;AAChD,gBAAA;AACA,eAAO,IAAI,eAAe,qBAAqB,CAAC;AAAA,MAClD;AAAA,IACF;AACA,UAAM,QAAQ,MAAY;AACxB,cAAA;AACA,aAAO,IAAI,eAAe,gBAAgB,CAAC;AAAA,IAC7C;AACA,UAAM,UAAU,MAAY;AAC1B,eAAS,eAAe,YAAY,UAAU;AAC9C,eAAS,eAAe,OAAO,KAAK;AAAA,IACtC;AACA,aAAS,GAAG,YAAY,UAAU;AAClC,aAAS,GAAG,OAAO,KAAK;AAAA,EAC1B,CAAC;AACH;AAIA,MAAM,iBAAiB;AACvB,MAAM,eAAe;AACrB,MAAM,oBAAoB;AA2C1B,SAAS,WAAW,OAA0B;AAC5C,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,QAAI,CAAC,IAAI;AACT,QAAI,cAAc,OAAO,CAAC;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,MAAM,OAAO,MAAM,IAAI,MAAM,MAAM;AACzC,QAAI,CAAC,IAAI;AACT,QAAI,cAAc,MAAM,QAAQ,CAAC;AACjC,QAAI,MAAM,OAAO,GAAG,MAAM;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,WAAW;AAC9B,UAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,QAAI,CAAC,IAAI;AACT,QAAI,CAAC,IAAI,QAAQ,IAAI;AACrB,WAAO;AAAA,EACT;AACA,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO,OAAO,KAAK,CAAC,CAAI,CAAC;AAAA,EAC3B;AAEA,QAAM,QAAkB,CAAC,OAAO,KAAK,CAAC,CAAI,CAAC,CAAC;AAC5C,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC9C,UAAM,SAAS,OAAO,MAAM,IAAI,IAAI,MAAM;AAC1C,WAAO,cAAc,IAAI,QAAQ,CAAC;AAClC,WAAO,MAAM,KAAK,GAAG,MAAM;AAC3B,UAAM,KAAK,MAAM;AACjB,UAAM,KAAK,WAAW,GAAG,CAAC;AAAA,EAC5B;AACA,QAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,CAAI,CAAC,CAAC;AAC1C,SAAO,OAAO,OAAO,KAAK;AAC5B;AAEA,SAAS,kBACP,aACA,eACA,kBACG,MACK;AACR,QAAM,QAAkB;AAAA,IACtB,WAAW,WAAW;AAAA,IACtB,WAAW,aAAa;AAAA,IACxB,WAAW,aAAa;AAAA,EAAA;AAE1B,aAAW,OAAO,KAAM,OAAM,KAAK,WAAW,GAAG,CAAC;AAClD,SAAO,OAAO,OAAO,KAAK;AAC5B;AAEA,SAAS,cAAc,QAAgB,OAAe,QAAsB;AAC1E,SAAO,MAAM,IAAK,SAAS,KAAM;AACjC,SAAO,SAAS,CAAC,IAAK,SAAS,IAAK;AACpC,SAAO,SAAS,CAAC,IAAI,QAAQ;AAC/B;AAsBO,MAAM,WAAW;AAAA,EACb;AAAA,EACQ;AAAA,EACT;AAAA,EACA,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,2BAA2B;AAAA,EAC3B,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,mCAAkD,IAAA;AAAA,EAClD,YAAY;AAAA,EAEpB,YAAY,KAAa,QAAwB;AAC/C,SAAK,MAAM;AACX,SAAK,SAAS;AACd,SAAK,SAAS,IAAIC,aAAA;AAAA,EACpB;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,UAAM,KAAK,QAAA;AAEX,SAAK,QAAQ,MAAM,2BAA2B;AAC9C,UAAM,KAAK,YAAA;AAEX,WAAO,MAAM;AACX,YAAM,MAAM,MAAM,KAAK,YAAA;AACvB,YAAM,EAAE,eAAAC,eAAAA,IAAkB,IAAI;AAC9B,UAAIA,mBAAkB,EAA6C;AACnE,UAAIA,mBAAkB,EAAoC;AAC1D,UAAIA,mBAAkB,GAA4B;AAChD,aAAK,YAAY,IAAI,QAAQ,aAAa,CAAC;AAC3C,aAAK,QAAQ,MAAM,0BAA0B,EAAE,MAAM,EAAE,WAAW,KAAK,UAAA,GAAa;AACpF;AAAA,MACF;AACA,UAAIA,mBAAkB,IAA8B;AAClD,cAAM,cAAc,IAAI,QAAQ,SAAS,GAAG,EAAE,EAAE,SAAS,MAAM;AAC/D,YAAI,gBAAgB,WAAW;AAC7B,eAAK,QAAQ,MAAM,+BAA+B;AAClD;AAAA,QACF;AACA,cAAM,IAAI,MAAM,4BAA4B,WAAW,EAAE;AAAA,MAC3D;AACA,YAAM,IAAI,MAAM,iCAAiCA,cAAa,EAAE;AAAA,IAClE;AAEA,SAAK,kBAAkB,GAAS;AAEhC,SAAK,WAAW,MAAM,KAAK,iBAAA;AAC3B,UAAM,qBAAqB,MAAM,KAAK,YAAA;AACtC,UAAM,EAAE,kBAAkB,mBAAmB;AAC7C,QAAI,kBAAkB,IAA8B;AAClD,YAAM,IAAI,MAAM,oDAAoD,aAAa,EAAE;AAAA,IACrF;AACA,SAAK,QAAQ,MAAM,oCAAoC;AAEvD,UAAM,YAAY,IAAI,IAAI,KAAK,GAAG;AAClC,UAAM,QAAQ,UAAU,SAAS,MAAM,GAAG;AAC1C,UAAM,aAAa,MAAM,SAAS,IAAI,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;AACjE,UAAM,WAAW,aAAa,UAAU;AAExC,UAAM,sBAAsB,kBAAkB,mBAAmB,KAAK,iBAAiB,MAAM,QAAQ;AACrG,SAAK,YAAY,GAAG,GAAG,IAA8B,GAAG,mBAAmB;AAE3E,SAAK,SAAS,KAAK,UAAU,QAAQ;AACrC,SAAK,gBAAgB,KAAK,UAAU,GAAI;AAExC,SAAK,QAAQ,KAAK,kBAAkB,EAAE,MAAM,EAAE,SAAA,GAAY;AAAA,EAC5D;AAAA;AAAA,EAGA,OAAO,WAAwD;AAC7D,WAAO,CAAC,KAAK,WAAW;AACtB,YAAM,MAAM,MAAM,KAAK,YAAA;AACvB,YAAM,SAAS,IAAI,YAAY;AAC/B,UAAI,WAAW,GAAuB;AACpC,cAAM,EAAE,OAAO,SAAS,QAAQ,IAAI,SAAS,WAAW,IAAI,YAAY,UAAA;AAAA,MAC1E,WAAW,WAAW,GAAuB;AAC3C,cAAM,EAAE,OAAO,SAAS,QAAQ,IAAI,SAAS,WAAW,IAAI,YAAY,UAAA;AAAA,MAC1E,OAAO;AACL,aAAK,QAAQ,MAAM,yBAAyB,EAAE,MAAM,EAAE,eAAe,OAAA,GAAU;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,QAAI;AACF,WAAK,OAAO,QAAA;AAAA,IACd,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,UAAyB;AACrC,UAAM,YAAY,IAAI,IAAI,KAAK,GAAG;AAClC,UAAM,OAAO,UAAU;AACvB,UAAM,OAAO,SAAS,UAAU,QAAQ,GAAG,iBAAiB,IAAI,EAAE,KAAK;AAEvE,SAAK,QAAQ,MAAM,uBAAuB,EAAE,MAAM,EAAE,MAAM,KAAA,GAAQ;AAElE,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,QAAQ,KAAK,qBAAqB,EAAE,MAAM,EAAE,OAAO,IAAI,QAAA,GAAW;AAAA,IACzE,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,UAAU,CAAC,QAAqB,OAAO,GAAG;AAChD,WAAK,OAAO,KAAK,SAAS,OAAO;AACjC,YAAM,OAA0B,EAAE,MAAM,KAAA;AACxC,WAAK,OAAO,QAAQ,MAAM,MAAM;AAC9B,aAAK,OAAO,eAAe,SAAS,OAAO;AAC3C,gBAAA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,UAAM,KAAK,iBAAA;AAAA,EACb;AAAA,EAEA,MAAc,mBAAkC;AAE9C,UAAM,KAAK,OAAO,KAAK,CAAC,YAAY,CAAC;AAErC,UAAM,KAAK,OAAO,MAAM,cAAc;AACtC,OAAG,cAAc,KAAK,MAAM,KAAK,QAAQ,GAAI,GAAG,CAAC;AACjD,OAAG,cAAc,GAAG,CAAC;AACrB,SAAK,OAAO,MAAM,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAEzC,UAAM,KAAK,MAAM,WAAW,KAAK,QAAQ,CAAC;AAC1C,UAAM,gBAAgB,GAAG,UAAU,CAAC;AACpC,QAAI,kBAAkB,cAAc;AAClC,YAAM,IAAI,MAAM,oCAAoC,aAAa,EAAE;AAAA,IACrE;AACA,UAAM,KAAK,MAAM,WAAW,KAAK,QAAQ,cAAc;AACvD,UAAM,WAAW,KAAK,QAAQ,cAAc;AAG5C,SAAK,OAAO,MAAM,EAAE;AAAA,EACtB;AAAA;AAAA,EAIA,MAAc,cAA2E;AACvF,UAAM,SAAS,KAAK;AACpB,WAAO,MAAM;AACX,YAAM,cAAc,MAAM,WAAW,QAAQ,CAAC;AAC9C,YAAM,SAAS,YAAY,UAAU,CAAC;AACtC,YAAM,MAAO,UAAU,IAAK;AAC5B,UAAI,OAAO,SAAS;AAEpB,UAAI,SAAS,GAAG;AACd,cAAM,aAAa,MAAM,WAAW,QAAQ,CAAC;AAC7C,eAAO,WAAW,UAAU,CAAC,IAAI;AAAA,MACnC,WAAW,SAAS,GAAG;AACrB,cAAM,QAAQ,MAAM,WAAW,QAAQ,CAAC;AACxC,eAAQ,MAAM,UAAU,CAAC,KAAK,IAAM,MAAM,UAAU,CAAC,IAAI;AAAA,MAC3D;AAEA,UAAI,cAAc,KAAK,aAAa,IAAI,IAAI;AAC5C,UAAI,CAAC,aAAa;AAChB,sBAAc;AAAA,UACZ,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,eAAe;AAAA,UACf,eAAe;AAAA,UACf,WAAW;AAAA,UACX,aAAa,CAAA;AAAA,UACb,eAAe;AAAA,UACf,sBAAsB;AAAA,QAAA;AAExB,aAAK,aAAa,IAAI,MAAM,WAAW;AAAA,MACzC;AAEA,UAAI,uBAAuB;AAC3B,UAAI;AAEJ,UAAI,QAAQ,GAAoB;AAC9B,qBAAa;AACb,cAAM,SAAS,MAAM,WAAW,QAAQ,EAAE;AAC1C,cAAM,YAAY,OAAO,WAAW,GAAG,CAAC;AACxC,cAAM,gBAAgB,OAAO,WAAW,GAAG,CAAC;AAC5C,cAAM,gBAAgB,OAAO,UAAU,CAAC;AACxC,cAAM,kBAAkB,OAAO,aAAa,CAAC;AAE7C,oBAAY,kBAAkB;AAC9B,oBAAY,gBAAgB;AAC5B,oBAAY,gBAAgB;AAC5B,oBAAY,YAAY;AACxB,oBAAY,gBAAgB;AAC5B,oBAAY,cAAc,CAAA;AAE1B,YAAI,aAAa,UAAU;AACzB,iCAAuB;AACvB,sBAAY,uBAAuB;AAAA,QACrC;AAAA,MACF,WAAW,QAAQ,GAAoB;AACrC,qBAAa;AACb,cAAM,SAAS,MAAM,WAAW,QAAQ,CAAC;AACzC,cAAM,iBAAiB,OAAO,WAAW,GAAG,CAAC;AAC7C,cAAM,gBAAgB,OAAO,WAAW,GAAG,CAAC;AAC5C,cAAM,gBAAgB,OAAO,UAAU,CAAC;AACxC,oBAAY,gBAAgB;AAC5B,oBAAY,gBAAgB;AAC5B,oBAAY,aAAa;AACzB,oBAAY,gBAAgB;AAC5B,oBAAY,cAAc,CAAA;AAC1B,YAAI,kBAAkB,UAAU;AAC9B,iCAAuB;AACvB,sBAAY,uBAAuB;AAAA,QACrC;AAAA,MACF,WAAW,QAAQ,GAAoB;AACrC,qBAAa;AACb,cAAM,SAAS,MAAM,WAAW,QAAQ,CAAC;AACzC,cAAM,iBAAiB,OAAO,WAAW,GAAG,CAAC;AAC7C,oBAAY,aAAa;AACzB,oBAAY,gBAAgB;AAC5B,oBAAY,cAAc,CAAA;AAC1B,YAAI,kBAAkB,UAAU;AAC9B,iCAAuB;AACvB,sBAAY,uBAAuB;AAAA,QACrC;AAAA,MACF,OAAO;AACL,qBAAa;AACb,YAAI,YAAY,kBAAkB,GAAG;AACnC,gBAAM,IAAI,MAAM,8CAA8C;AAAA,QAChE;AAAA,MACF;AAEA,UAAI,wBAAwB,YAAY,sBAAsB;AAC5D,cAAM,QAAQ,MAAM,WAAW,QAAQ,CAAC;AACxC,cAAM,oBAAoB,MAAM,aAAa,CAAC;AAC9C,YAAI,QAAQ,GAAoB;AAC9B,sBAAY,YAAY;AAAA,QAC1B;AAAA,MACF;AAEA,YAAM,qBAAqB,YAAY,gBAAgB,YAAY;AACnE,YAAM,gBAAgB,KAAK,IAAI,KAAK,WAAW,kBAAkB;AAEjE,YAAM,iBAAiB,OAAO;AAC9B,UAAI,gBAAgB,gBAAgB;AAClC,cAAM,IAAI,MAAM,cAAc,aAAa,gBAAgB,cAAc,EAAE;AAAA,MAC7E;AAEA,YAAM,YAAY,MAAM,WAAW,QAAQ,aAAa;AACxD,kBAAY,YAAY,KAAK,SAAS;AACtC,kBAAY,iBAAiB;AAE7B,YAAM,mBAAoB,wBAAwB,YAAY,uBAAwB,IAAI;AAC1F,WAAK,sBAAsB,IAAI,aAAa,mBAAmB;AAC/D,WAAK,4BAAA;AAEL,UAAI,YAAY,iBAAiB,YAAY,eAAe;AAC1D,cAAM,UAAU,OAAO,OAAO,YAAY,WAAW;AACrD,oBAAY,cAAc,CAAA;AAC1B,oBAAY,gBAAgB;AAC5B,oBAAY,uBAAuB;AACnC,eAAO,EAAE,aAAa,QAAA;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,8BAAoC;AAC1C,UAAM,aAAa,KAAK,qBAAqB,KAAK;AAClD,QAAI,cAAc,KAAK,eAAe;AACpC,WAAK,2BAA2B,KAAK;AACrC,YAAM,OAAO,OAAO,MAAM,CAAC;AAC3B,WAAK,cAAc,KAAK,2BAA2B,YAAY,CAAC;AAChE,WAAK,YAAY,GAAG,GAAG,GAAiC,GAAG,IAAI;AAAA,IACjE;AAAA,EACF;AAAA;AAAA,EAIQ,YACN,eACA,iBACA,eACA,WACA,MACM;AACN,UAAM,SAAmB,CAAA;AACzB,QAAI,SAAS;AAEb,WAAO,SAAS,KAAK,QAAQ;AAC3B,YAAM,gBAAgB,KAAK,IAAI,KAAK,mBAAmB,KAAK,SAAS,MAAM;AAC3E,YAAM,UAAU,WAAW;AAC3B,YAAM,aAAa,UAAU,KAAK;AAClC,YAAM,SAAS,OAAO,MAAM,UAAU;AAEtC,UAAI,gBAAgB,IAAI;AACtB,eAAO,CAAC,KAAM,UAAU,IAAqB,MAAuB,IAAK;AAAA,MAC3E,OAAO;AACL,eAAO,CAAC,KAAM,UAAU,IAAqB,MAAuB,IAAK;AAAA,MAC3E;AAEA,UAAI,SAAS;AACX,sBAAc,QAAQ,WAAW,CAAC;AAClC,sBAAc,QAAQ,KAAK,QAAQ,CAAC;AACpC,eAAO,CAAC,IAAI;AACZ,eAAO,cAAc,iBAAiB,CAAC;AAAA,MACzC;AAEA,aAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ,SAAS,aAAa,CAAC;AACjE,gBAAU;AAAA,IACZ;AAEA,eAAW,SAAS,OAAQ,MAAK,OAAO,MAAM,KAAK;AAAA,EACrD;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,YAAY,IAAI,IAAI,KAAK,GAAG;AAClC,UAAM,QAAQ,GAAG,UAAU,QAAQ,KAAK,UAAU,IAAI,IAAI,UAAU,SAAS,MAAM,GAAG,EAAE,CAAC,CAAC;AAC1F,UAAM,gBAAgB;AAAA,MACpB,KAAK,UAAU,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,MACpC,UAAU;AAAA,MACV;AAAA,MACA,MAAM;AAAA,MACN,cAAc;AAAA,MACd,aAAa;AAAA,MACb,aAAa;AAAA,MACb,eAAe;AAAA,IAAA;AAEjB,UAAM,OAAO,kBAAkB,WAAW,KAAK,iBAAiB,aAAa;AAC7E,SAAK,YAAY,GAAG,GAAG,IAA8B,GAAG,IAAI;AAAA,EAC9D;AAAA,EAEA,MAAc,mBAAoC;AAChD,UAAM,OAAO,kBAAkB,gBAAgB,KAAK,iBAAiB,IAAI;AACzE,SAAK,YAAY,GAAG,GAAG,IAA8B,GAAG,IAAI;AAC5D,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,UAAkB,UAAwB;AACzD,UAAM,OAAO,kBAAkB,QAAQ,KAAK,iBAAiB,MAAM,UAAU,IAAK;AAClF,SAAK,YAAY,GAAG,UAAU,IAA8B,GAAG,IAAI;AAAA,EACrE;AAAA,EAEQ,gBAAgB,UAAkB,cAA4B;AACpE,UAAM,OAAO,OAAO,MAAM,EAAE;AAC5B,SAAK,cAAc,GAAG,CAAC;AACvB,SAAK,cAAc,UAAU,CAAC;AAC9B,SAAK,cAAc,cAAc,CAAC;AAClC,SAAK,YAAY,GAAG,GAAG,GAA8B,GAAG,IAAI;AAAA,EAC9D;AAAA,EAEQ,kBAAkB,YAA0B;AAClD,UAAM,OAAO,OAAO,MAAM,CAAC;AAC3B,SAAK,cAAc,YAAY,CAAC;AAChC,SAAK,YAAY,GAAG,GAAG,GAA6C,GAAG,IAAI;AAAA,EAC7E;AAAA;AAAA,EAGA,MAAM,eAA8B;AAClC,QAAI,KAAK,OAAO,UAAW;AAC3B,UAAMC,YAAK,KAAK,QAAQ,OAAO;AAAA,EACjC;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AACF;ACvfO,MAAM,WAAW;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACT,SAA4B;AAAA,EAC5B,YAAY;AAAA;AAAA,EAGZ,aAAqC;AAAA,EACrC,kBAAiC;AAAA,EACjC,kBAAiC;AAAA,EACjC,iBAAiB;AAAA,EAEjB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,gBAAgB;AAAA,EAExB,YAAY,KAAa,QAAmC,WAAgC;AAC1F,SAAK,MAAM;AACX,SAAK,SAAS;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,SAAS,IAAI,WAAW,KAAK,KAAK,KAAK,QAAQ,MAAM,aAAa,CAAC;AACzE,SAAK,SAAS;AAEd,QAAI;AACF,YAAM,OAAO,MAAA;AAAA,IACf,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,WAAK,QAAQ,KAAK,qBAAqB,EAAE,MAAM,EAAE,OAAO,MAAM,QAAA,GAAW;AACzE,WAAK,KAAK,KAAK;AACf;AAAA,IACF;AAEA,SAAK,KAAK,YAAY,MAAM;AAAA,EAC9B;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,SAAK,QAAQ,QAAA;AAAA,EACf;AAAA,EAEA,MAAc,YAAY,QAAmC;AAC3D,QAAI;AACF,uBAAiB,SAAS,OAAO,YAAY;AAC3C,YAAI,KAAK,UAAW;AAEpB,YAAI,MAAM,UAAU,SAAS;AAC3B,eAAK,kBAAkB,MAAM,QAAQ,MAAM,SAAS;AAAA,QACtD,OAAO;AACL,eAAK,kBAAkB,MAAM,QAAQ,MAAM,SAAS;AAAA,QACtD;AAEA,YAAI,CAAC,KAAK,sBAAsB;AAC9B,eAAK,uBAAuB;AAC5B,eAAK,UAAU,YAAA;AAAA,QACjB;AAAA,MACF;AAEA,WAAK,UAAU,aAAA;AAAA,IACjB,SAAS,KAAK;AACZ,UAAI,KAAK,WAAW;AAClB,aAAK,UAAU,aAAA;AACf;AAAA,MACF;AACA,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,WAAK,KAAK,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EAEQ,KAAK,OAAoB;AAC/B,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,SAAK,QAAQ,QAAA;AACb,SAAK,UAAU,QAAQ,KAAK;AAAA,EAC9B;AAAA;AAAA,EAIQ,kBAAkB,SAAiB,WAAyB;AAClE,QAAI;AACJ,QAAI;AACF,YAAM,+BAA+B,SAAS,KAAK,cAAc;AAAA,IACnE,SAAS,KAAK;AACZ,UAAI,eAAe,uBAAuB;AACxC,aAAK,KAAK,GAAG;AACb;AAAA,MACF;AACA,UAAI,eAAe,eAAe;AAChC,aAAK,QAAQ,KAAK,sCAAsC,EAAE,MAAM,EAAE,OAAO,IAAI,QAAA,GAAW;AACxF;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAEA,QAAI,IAAI,SAAS,yBAAyB;AACxC,WAAK,0BAA0B,IAAI,OAAO,GAAG;AAC7C;AAAA,IACF;AACA,QAAI,IAAI,SAAS,sBAAsB;AAErC,WAAK,QAAQ,KAAK,mCAAmC;AACrD;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,YAAY;AAGpB;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,sBAAsB,IAAI,OAAO,IAAI,OAAO,KAAK,oBAAoB,IAAI,SAAS,CAAC;AACvG,QAAI,OAAO,WAAW,EAAG;AAEzB,SAAK;AACL,UAAM,SAAwB;AAAA,MAC5B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK,YAAY,IAAI;AAAA,MACrB,KAAK;AAAA,MACL,UAAU,KAAK,oBAAoB,IAAI,SAAS;AAAA,MAChD,OAAO,IAAI;AAAA,IAAA;AAEb,SAAK,UAAU,gBAAgB,MAAM;AAAA,EACvC;AAAA,EAEQ,0BACN,OACA,KACM;AACN,QAAI,IAAI,SAAS,wBAAyB;AAE1C,QAAI,KAAK,eAAe,QAAQ,KAAK,eAAe,OAAO;AACzD,WAAK,KAAK,IAAI,MAAM,wCAAwC,KAAK,UAAU,MAAM,KAAK,EAAE,CAAC;AACzF;AAAA,IACF;AAEA,QAAI,UAAU,UAAU,IAAI,UAAU,QAAQ;AAC5C,WAAK,aAAa;AAClB,WAAK,iBAAiB,IAAI,UAAU,qBAAqB;AACzD,WAAK,kBAAkB,yBAAyB,IAAI,SAAS;AAC7D,WAAK,QAAQ,KAAK,+BAA+B;AAAA,QAC/C,MAAM;AAAA,UACJ,SAAS,IAAI,UAAU;AAAA,UACvB,OAAO,IAAI,UAAU;AAAA,UACrB,UAAU,IAAI,UAAU,IAAI;AAAA,UAC5B,UAAU,IAAI,UAAU,IAAI;AAAA,UAC5B,YAAY,KAAK;AAAA,QAAA;AAAA,MACnB,CACD;AACD,WAAK,UAAU,eAAe,MAAM;AACpC;AAAA,IACF;AAEA,QAAI,UAAU,UAAU,IAAI,UAAU,QAAQ;AAC5C,WAAK,aAAa;AAClB,WAAK,iBAAiB,IAAI,WAAW,qBAAqB;AAC1D,WAAK,kBAAkB,yBAAyB,IAAI,UAAU;AAC9D,WAAK,QAAQ,KAAK,+BAA+B;AAAA,QAC/C,MAAM;AAAA,UACJ,SAAS,IAAI,WAAW;AAAA,UACxB,OAAO,IAAI,WAAW;AAAA,UACtB,UAAU,IAAI,WAAW,IAAI;AAAA,UAC7B,UAAU,IAAI,WAAW,IAAI;AAAA,UAC7B,UAAU,IAAI,WAAW,IAAI;AAAA,UAC7B,YAAY,KAAK;AAAA,QAAA;AAAA,MACnB,CACD;AACD,WAAK,UAAU,eAAe,MAAM;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBACN,OACA,OACA,UACQ;AACR,QAAI,MAAM,WAAW,EAAG,QAAO,OAAO,MAAM,CAAC;AAC7C,UAAM,OAAO,cAAc,KAAK;AAChC,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,SAAS,UAAU,SAAS,KAAK,kBAAkB,KAAK;AAC9D,QAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAC3C,WAAO,OAAO,OAAO,CAAC,QAAQ,IAAI,CAAC;AAAA,EACrC;AAAA,EAEQ,oBAAoB,WAA4B;AAEtD,WAAO,cAAc,KAAK,cAAc;AAAA,EAC1C;AAAA;AAAA,EAIQ,kBAAkB,SAAiB,WAAyB;AAClE,QAAI;AACJ,QAAI;AACF,YAAM,iBAAiB,OAAO;AAAA,IAChC,SAAS,KAAK;AACZ,UAAI,eAAe,eAAe;AAChC,aAAK,QAAQ,KAAK,sCAAsC,EAAE,MAAM,EAAE,OAAO,IAAI,QAAA,GAAW;AACxF;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAEA,QAAI,IAAI,SAAS,yBAAyB;AACxC,UAAI,IAAI,gBAAgB,iBAAiB,KAAK;AAC5C,aAAK,QAAQ,KAAK,iDAAiD;AAAA,UACjE,MAAM,EAAE,aAAa,IAAI,YAAA;AAAA,QAAY,CACtC;AACD;AAAA,MACF;AACA,YAAM,aAAa,qBAAqB,IAAI,oBAAoB,sBAAsB;AACtF,YAAM,WAAW,IAAI,oBAAoB,yBAAyB,IAC9D,IACA,IAAI,oBAAoB;AAC5B,UAAI,eAAe,MAAM;AACvB,aAAK,QAAQ,KAAK,wDAAwD;AAAA,UACxE,MAAM,EAAE,wBAAwB,IAAI,oBAAoB,uBAAA;AAAA,QAAuB,CAChF;AACD;AAAA,MACF;AACA,WAAK,mBAAmB;AACxB,WAAK,UAAU,cAAc;AAAA,QAC3B,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,WAAW,IAAI,WAAW,IAAI,wBAAwB;AAAA,MAAA,CACvD;AACD,WAAK,QAAQ,KAAK,6BAA6B;AAAA,QAC7C,MAAM;AAAA,UACJ,iBAAiB,IAAI,oBAAoB;AAAA,UACzC;AAAA,UACA;AAAA,QAAA;AAAA,MACF,CACD;AACD;AAAA,IACF;AAEA,QAAI,IAAI,gBAAgB,iBAAiB,KAAK;AAG5C;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,kBAAkB;AAG1B;AAAA,IACF;AAEA,UAAM,SAAwB;AAAA,MAC5B,MAAM;AAAA,MACN,MAAM,IAAI;AAAA,MACV,KAAK;AAAA,MACL,KAAK;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,IAAA;AAET,SAAK,UAAU,gBAAgB,MAAM;AAAA,EACvC;AACF;AC/RA,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,kBAAkB;AAIxB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,cAAc;AAoBb,MAAM,gBAAgB;AAAA,EACV;AAAA,EACA;AAAA;AAAA,EAGT,WAAqB,CAAA;AAAA,EACrB,cAAc;AAAA;AAAA,EAEd,cAAc;AAAA;AAAA,EAEd,eAA8B;AAAA,EAEtC,YAAY,OAAwB,OAAoB;AACtD,SAAK,QAAQ;AACb,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,WAAW,CAAA;AAChB,SAAK,cAAc;AAEnB,SAAK,cAAc;AACnB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,SAAuB;AACnC,QAAI,QAAQ,SAAS,gBAAiB;AAEtC,UAAM,UAAU,QAAQ,CAAC,IAAK,SAAU;AACxC,UAAM,YAAY,QAAQ,aAAa,CAAC;AACxC,UAAM,UAAU,QAAQ,SAAS,eAAe;AAEhD,QAAI,QAAQ,WAAW,EAAG;AAE1B,QAAI,KAAK,UAAU,QAAQ;AACzB,WAAK,YAAY,SAAS,WAAW,MAAM;AAAA,IAC7C,OAAO;AACL,WAAK,YAAY,SAAS,WAAW,MAAM;AAAA,IAC7C;AAAA,EACF;AAAA;AAAA,EAIQ,YAAY,SAAiB,WAAmB,QAAuB;AAC7E,UAAM,YAAY,QAAQ,CAAC;AAC3B,UAAM,UAAU,YAAY;AAE5B,QAAI,WAAW,KAAK,WAAW,IAAI;AAEjC,WAAK,QAAQ,SAAS,WAAW,MAAM;AAAA,IACzC,WAAW,YAAY,iBAAiB;AACtC,WAAK,iBAAiB,SAAS,WAAW,MAAM;AAAA,IAClD,WAAW,YAAY,cAAc;AACnC,WAAK,eAAe,SAAS,WAAW,MAAM;AAAA,IAChD;AAAA,EAGF;AAAA;AAAA,EAGQ,iBAAiB,SAAiB,WAAmB,QAAuB;AAClF,QAAI,SAAS;AACb,WAAO,SAAS,KAAK,QAAQ,QAAQ;AACnC,YAAM,UAAU,QAAQ,aAAa,MAAM;AAC3C,gBAAU;AACV,UAAI,SAAS,UAAU,QAAQ,OAAQ;AAEvC,YAAM,MAAM,OAAO,KAAK,QAAQ,SAAS,QAAQ,SAAS,OAAO,CAAC;AAClE,gBAAU;AACV,WAAK,QAAQ,KAAK,WAAW,UAAU,UAAU,QAAQ,MAAM;AAAA,IACjE;AAAA,EACF;AAAA;AAAA,EAGQ,eAAe,SAAiB,WAAmB,QAAuB;AAChF,QAAI,QAAQ,SAAS,EAAG;AAExB,UAAM,cAAc,QAAQ,CAAC;AAC7B,UAAM,WAAW,QAAQ,CAAC;AAC1B,UAAM,WAAW,WAAW,SAAU;AACtC,UAAM,SAAS,WAAW,QAAU;AACpC,UAAM,UAAU,WAAW;AAE3B,QAAI,SAAS;AAEX,WAAK,WAAW,CAAC,QAAQ,SAAS,CAAC,CAAC;AACpC,WAAK,cAAc;AAEnB,WAAK,cAAe,cAAc,MAAQ;AAAA,IAC5C,WAAW,KAAK,SAAS,SAAS,KAAK,KAAK,gBAAgB,WAAW;AAErE,WAAK,SAAS,KAAK,QAAQ,SAAS,CAAC,CAAC;AAEtC,UAAI,OAAO;AAET,cAAM,YAAY,KAAK,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AACpE,cAAM,MAAM,OAAO,YAAY,IAAI,SAAS;AAC5C,YAAI,CAAC,IAAI,KAAK;AACd,YAAI,SAAS;AACb,mBAAW,YAAY,KAAK,UAAU;AACpC,mBAAS,KAAK,KAAK,MAAM;AACzB,oBAAU,SAAS;AAAA,QACrB;AACA,aAAK,WAAW,CAAA;AAChB,aAAK,QAAQ,KAAK,WAAW,MAAM;AAAA,MACrC;AAAA,IACF,OAAO;AAEL,WAAK,WAAW,CAAA;AAAA,IAClB;AAAA,EACF;AAAA;AAAA,EAIQ,YAAY,SAAiB,WAAmB,QAAuB;AAC7E,QAAI,QAAQ,SAAS,EAAG;AAExB,UAAM,UAAW,QAAQ,CAAC,KAAM,sBAAuB;AAEvD,QAAI,UAAU,IAAI;AAEhB,WAAK,QAAQ,SAAS,WAAW,MAAM;AAAA,IACzC,WAAW,YAAY,aAAa;AAClC,WAAK,cAAc,SAAS,WAAW,MAAM;AAAA,IAC/C,WAAW,YAAY,aAAa;AAClC,WAAK,cAAc,SAAS,WAAW,MAAM;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA,EAGQ,cAAc,SAAiB,WAAmB,QAAuB;AAC/E,QAAI,SAAS;AACb,WAAO,SAAS,KAAK,QAAQ,QAAQ;AACnC,YAAM,UAAU,QAAQ,aAAa,MAAM;AAC3C,gBAAU;AACV,UAAI,SAAS,UAAU,QAAQ,OAAQ;AAEvC,YAAM,MAAM,OAAO,KAAK,QAAQ,SAAS,QAAQ,SAAS,OAAO,CAAC;AAClE,gBAAU;AACV,WAAK,QAAQ,KAAK,WAAW,UAAU,UAAU,QAAQ,MAAM;AAAA,IACjE;AAAA,EACF;AAAA;AAAA,EAGQ,cAAc,SAAiB,WAAmB,QAAuB;AAC/E,QAAI,QAAQ,SAAS,EAAG;AAExB,UAAM,WAAW,QAAQ,CAAC;AAC1B,UAAM,WAAW,WAAW,SAAU;AACtC,UAAM,SAAS,WAAW,QAAU;AACpC,UAAM,UAAU,WAAW;AAE3B,QAAI,SAAS;AAEX,WAAK,WAAW,CAAC,QAAQ,SAAS,CAAC,CAAC;AACpC,WAAK,cAAc;AAGnB,YAAM,SAAS,OAAO,YAAY,CAAC;AACnC,aAAO,CAAC,IAAK,QAAQ,CAAC,IAAK,MAAS,WAAW;AAC/C,aAAO,CAAC,IAAI,QAAQ,CAAC;AACrB,WAAK,eAAe;AAAA,IACtB,WAAW,KAAK,SAAS,SAAS,KAAK,KAAK,gBAAgB,WAAW;AAErE,WAAK,SAAS,KAAK,QAAQ,SAAS,CAAC,CAAC;AAEtC,UAAI,SAAS,KAAK,cAAc;AAE9B,cAAM,YAAY,KAAK,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AACpE,cAAM,MAAM,OAAO,YAAY,IAAI,SAAS;AAC5C,aAAK,aAAa,KAAK,KAAK,CAAC;AAC7B,YAAI,SAAS;AACb,mBAAW,YAAY,KAAK,UAAU;AACpC,mBAAS,KAAK,KAAK,MAAM;AACzB,oBAAU,SAAS;AAAA,QACrB;AACA,aAAK,WAAW,CAAA;AAChB,aAAK,eAAe;AACpB,aAAK,QAAQ,KAAK,WAAW,MAAM;AAAA,MACrC;AAAA,IACF,OAAO;AAEL,WAAK,WAAW,CAAA;AAChB,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA,EAIQ,QAAQ,KAAa,WAAmB,QAAuB;AACrE,UAAM,WAAW,KAAK,UAAU,SAC5B,KAAK,eAAe,GAAG,IACvB,KAAK,eAAe,GAAG;AAE3B,UAAM,eAAe,KAAK,UAAU,SAChC,KAAK,mBAAmB,GAAG,IAC3B,KAAK,mBAAmB,GAAG;AAE/B,SAAK,MAAM,EAAE,KAAK,UAAU,cAAc,WAAW,QAAQ;AAAA,EAC/D;AAAA,EAEQ,eAAe,KAAsB;AAC3C,QAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,YAAQ,IAAI,CAAC,IAAK,wBAAwB;AAAA,EAC5C;AAAA,EAEQ,mBAAmB,KAAsB;AAC/C,QAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,UAAM,OAAO,IAAI,CAAC,IAAK;AACvB,WAAO,SAAS,gBAAgB,SAAS;AAAA,EAC3C;AAAA,EAEQ,eAAe,KAAsB;AAC3C,QAAI,IAAI,SAAS,EAAG,QAAO;AAC3B,UAAM,OAAQ,IAAI,CAAC,KAAM,sBAAuB;AAChD,WAAO,QAAQ,qBAAqB,QAAQ;AAAA,EAC9C;AAAA,EAEQ,mBAAmB,KAAsB;AAC/C,QAAI,IAAI,SAAS,EAAG,QAAO;AAC3B,UAAM,OAAQ,IAAI,CAAC,KAAM,sBAAuB;AAChD,WAAO,SAAS,gBAAgB,SAAS,gBAAgB,SAAS;AAAA,EACpE;AACF;AChRA,MAAM,qBAAqB;AAE3B,MAAM,oBAAoB;AAC1B,MAAM,gBAAiB,qBAAqB,oBAAqB;AAQ1D,MAAM,gBAAgB;AAAA,EACV;AAAA,EACA;AAAA,EACT;AAAA,EACA,eAAe;AAAA,EAEvB,YAAY,OAAmB,SAA6C;AAC1E,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,SAAS,IAAI,aAAa,aAAa;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,SAAuB;AAEnC,QAAI,QAAQ,UAAU,GAAI;AAE1B,UAAM,UAAU,QAAQ,SAAS,EAAE;AACnC,UAAM,SAAS,KAAK,UAAU,SAAS,aAAa;AAIpD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,UAAU,OAAO,QAAQ,CAAC,CAAE;AAClC,YAAM,OAAO,IAAI,IAAI,QAAQ,SAAS,OAAO,QAAQ,IAAI,CAAC,CAAE,IAAI;AAGhE,WAAK,WAAW,OAAO;AAEvB,WAAK,YAAY,UAAU,QAAQ,GAAG;AAAA,IACxC;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,iBAAiB,EAAG;AAE7B,UAAM,OAAO,OAAO;AAAA,MAClB,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ,KAAK,eAAe;AAAA,IAAA;AAGtB,SAAK,QAAQ;AAAA,MACX,MAAM,OAAO,KAAK,IAAI;AAAA;AAAA,MACtB,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAED,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,eAAe;AAAA,EACtB;AAAA,EAEQ,WAAW,QAAsB;AACvC,SAAK,OAAO,KAAK,cAAc,IAAI;AAEnC,QAAI,KAAK,gBAAgB,eAAe;AAEtC,YAAM,OAAO,OAAO;AAAA,QAClB,KAAK,OAAO;AAAA,QACZ,KAAK,OAAO;AAAA,QACZ,gBAAgB;AAAA,MAAA;AAGlB,WAAK,QAAQ;AAAA,QACX,MAAM,OAAO,KAAK,IAAI;AAAA;AAAA,QACtB,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAED,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AACF;AAKA,SAAS,WAAW,MAAsB;AACxC,SAAO,WAAW,IAAI;AACxB;AAGA,SAAS,WAAW,MAAsB;AACxC,SAAO,WAAW,IAAI;AACxB;AAOA,SAAS,iBAA+B;AACtC,QAAM,QAAQ,IAAI,aAAa,GAAG;AAClC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,eAAe,CAAC,IAAI;AAC1B,UAAM,QAAQ,eAAe,SAAU,IAAI,KAAK;AAChD,UAAM,WAAY,gBAAgB,IAAK;AACvC,UAAM,WAAW,eAAe;AAChC,UAAM,aAAc,IAAI,WAAW,MAAO,YAAY;AACtD,UAAM,CAAC,IAAK,OAAO,YAAa;AAAA,EAClC;AACA,SAAO;AACT;AAKA,SAAS,iBAA+B;AACtC,QAAM,QAAQ,IAAI,aAAa,GAAG;AAClC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,QAAQ,IAAI;AAClB,UAAM,QAAQ,QAAQ,SAAU,IAAI,KAAK;AACzC,UAAM,WAAY,SAAS,IAAK;AAChC,UAAM,WAAW,QAAQ;AACzB,QAAI;AACJ,QAAI,aAAa,GAAG;AAClB,kBAAa,IAAI,WAAW;AAAA,IAC9B,OAAO;AACL,kBAAa,IAAI,WAAW,MAAQ,WAAW;AAAA,IACjD;AACA,UAAM,CAAC,IAAK,OAAO,YAAa;AAAA,EAClC;AACA,SAAO;AACT;AAEA,MAAM,aAAa,eAAA;AACnB,MAAM,aAAa,eAAA;AC1JnB,MAAM,aAAa;AACnB,MAAM,aAAa;AAGnB,SAAS,cAAc,QAAwB;AAC7C,QAAM,OAAQ,UAAU,IAAK;AAC7B,MAAI,SAAS,EAAG,UAAS,CAAC;AAC1B,MAAI,SAAS,WAAY,UAAS;AAClC,WAAS,SAAS;AAElB,QAAM,WAAW,sBAAuB,UAAU,IAAK,GAAI;AAC3D,QAAM,WAAY,UAAW,WAAW,IAAM;AAC9C,QAAM,OAAO,EAAE,OAAQ,YAAY,IAAK,YAAY;AACpD,SAAO;AACT;AAGA,MAAM,wBAAwB,IAAI,WAAW,GAAG;AAAA,CAC9C,MAAM;AACN,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,QAAI,MAAM;AACV,QAAI,MAAM;AACV,YAAQ;AACR,WAAO,MAAM,KAAK,MAAM,GAAG;AAAE,cAAQ;AAAG;AAAA,IAAM;AAC9C,0BAAsB,CAAC,IAAI;AAAA,EAC7B;AACF,GAAA;AAGO,SAAS,WAAW,KAA6B;AACtD,QAAM,SAAS,IAAI,WAAW,IAAI,MAAM;AACxC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,WAAO,CAAC,IAAI,cAAc,IAAI,CAAC,CAAE;AAAA,EACnC;AACA,SAAO;AACT;AAOO,SAAS,eAAe,MAAkB,OAA2B;AAC1E,QAAM,SAAS,KAAK,MAAM,KAAK,SAAS,KAAK;AAC7C,QAAM,SAAS,IAAI,WAAW,MAAM;AACpC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,WAAO,CAAC,IAAI,KAAK,IAAI,KAAK;AAAA,EAC5B;AACA,SAAO;AACT;ACAA,MAAM,YAA2F;AAAA,EAC/F,MAAM;AAAA,IACJ,cAAc;AAAA,IACd,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,EAAA;AAAA,EAEV,MAAM;AAAA,IACJ,cAAc;AAAA,IACd,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,EAAA;AAEZ;AAEA,MAAM,QAAqF,CAAA;AAQpF,SAAS,oBAAoB,MAAuB,OAAiC;AAC1F,QAAM,aAAa,MAAM,KAAK,MAAM,MAAM,KAAK,IAAI;AACnD,QAAM,SAAS,WAAW,IAAI;AAC9B,MAAI,OAAQ,QAAO;AACnB,QAAM,MAAM,OAAO,KAAK,UAAU,KAAK,EAAE,IAAI,GAAG,QAAQ;AACxD,aAAW,IAAI,IAAI;AACnB,SAAO;AACT;ACpDA,IAAI,cAA8B;AAClC,eAAe,WAA6B;AAC1C,MAAI,YAAa,QAAO;AACxB,QAAM,MAAM,MAAM,OAAO,OAAO;AAChC,gBAAc,IAAI;AAClB,SAAO;AACT;AAQA,SAAS,YAAY,KAAqB;AACxC,QAAM,MAAM,OAAO,YAAY,IAAI,MAAM;AACzC,WAAS,IAAI,GAAG,IAAI,IAAI,IAAI,QAAQ,KAAK,GAAG;AAC1C,QAAI,CAAC,IAAI,IAAI,IAAI,CAAC;AAClB,QAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;AACtB,QAAI,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,EACpB;AACA,SAAO;AACT;AAEA,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AAEtB,MAAM,6BAA6B;AACnC,MAAM,yBAAyB;AAC/B,MAAM,4BAA4B;AAElC,MAAM,mBAAmB;AAEzB,MAAM,wBAAwB;AAuBvB,MAAM,aAAsC;AAAA,EACxC;AAAA,EAED,UAAwB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAAqD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASrD,uBAA4C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW5C,uBAAuB;AAAA;AAAA,EAEvB;AAAA,EACA,eAA2C;AAAA;AAAA,EAE3C,sBAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ1C,gBAA+B;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAoB,KAAK,IAAA;AAAA,EAElC,eAAwC;AAAA,EACxC,gBAAsC;AAAA,EACtC,aAAgC;AAAA,EAChC,kBAA0C;AAAA,EAC1C,kBAA0C;AAAA,EAC1C,oBAA8C;AAAA,EAC9C,eAAe;AAAA;AAAA,EAEf,aAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM5B,iBAAqG;AAAA,EACrG;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUR,OAAwB,yBAAyB;AAAA,EACzC,aAAa;AAAA,EACb,WAAW;AAAA,EACX;AAAA,EAEA,cAAsC,CAAA;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EAEA,uCAAuB,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvB,wCAAwB,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAejC,mBAAiD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjD,iBAAiB;AAAA,EACR,yCAAyB,IAAA;AAAA,EACzB,uCAAuB,IAAA;AAAA;AAAA,EAEhC;AAAA;AAAA,EAEA,sBAA2C;AAAA,EAC3C,wBAAwB;AAAA;AAAA;AAAA;AAAA,EAIxB;AAAA;AAAA,EAEA,yBAAyB;AAAA;AAAA,EAGzB;AAAA;AAAA,EAEA,oBAAoB;AAAA;AAAA,EAEpB;AAAA;AAAA,EAEA,aAAa;AAAA;AAAA,EAGb,mBAAmB;AAAA,EACnB,oBAAoB;AAAA;AAAA;AAAA;AAAA,EAIpB,eAAe;AAAA;AAAA,EAEf;AAAA;AAAA,EAGA,mBAAmB;AAAA,EACnB,mBAAmB;AAAA;AAAA,EAGnB,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gBAAgB,KAAK,IAAA;AAAA,EACrB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd,eAAe;AAAA;AAAA,EAGvB,OAAgB,yBAAyB;AAAA;AAAA,EAEzC,OAAgB,qBAAqB;AAAA,EAErC,YACE,UACA,YACA,QACA,eACA;AACA,SAAK,WAAW;AAChB,SAAK,aAAa;AAClB,SAAK,gBAAgB,iBAAiB;AACtC,SAAK,SAAS;AACd,SAAK,aAAa,IAAI,iBAAiB,QAAQ,MAAM,aAAa,CAAC;AACnE,SAAK,iBAAiB,IAAI,eAAe,QAAQ;AACjD,QAAI,OAAQ,MAAK,eAAe,UAAU,OAAO,MAAM,MAAM,CAAC;AAC9D,SAAK,YAAY,IAAI,kBAAkB,aAAa,sBAAsB;AAG1E,SAAK,eAAe,sBAAsB,MAAM,KAAK,aAAa;AAClE,SAAK,WAAW,qBAAqB,MAAM,KAAK,aAAa;AAAA,EAC/D;AAAA,EAEA,IAAI,SAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAA0B;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,sBAA8B;AAC5B,WAAO,KAAK,QAAQ,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,mBAIN;AACA,UAAM,MAAM,KAAK;AACjB,UAAM,MAAM,OAAO,IAAI,SAAS,iBAAiB,IAAI,SAAS,UAAU,IAAI,SAAS,aACjFC,MAAAA,mBAAmB,IAAI,OAAO,EAAE,IAChC;AACJ,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf,YAAY,KAAK,QAAQ;AAAA,MACzB;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,eAAe,aAA2C;AACxD,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAAkB,UAA2C;AAC3D,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,wBAAwB,QAA0B;AAChD,SAAK,uBAAuB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBAA4B;AAClC,QAAI,CAAC,KAAK,qBAAsB;AAChC,UAAM,MAAM,KAAK,IAAA;AACjB,QAAI,MAAM,KAAK,uBAAuB,IAAK;AAC3C,SAAK,uBAAuB;AAC5B,SAAK,qBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,gBAAsB;AACpB,QAAI,KAAK,cAAc,KAAK,YAAY,CAAC,KAAK,OAAQ;AACtD,QAAI,CAAC,KAAK,YAAa;AACvB,QAAI,CAAC,KAAK,eAAgB;AAC1B,iBAAa,KAAK,cAAc;AAChC,SAAK,iBAAiB;AACtB,SAAK,mBAAmB;AACxB,SAAK,uBAAuB;AAC5B,SAAK,QAAQ,KAAK,+CAA+C;AACjE,SAAK,kBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,uBAA4C;AAClD,QAAI,KAAK,gBAAgB;AACvB,YAAM,QAAQ,KAAK,eAAA;AACnB,UAAI,OAAO;AACT,aAAK,SAAS;AACd,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAIA,MAAM,MAAM,QAAqC;AAC/C,SAAK,SAAS;AACd,SAAK,aAAa;AAClB,SAAK,WAAW;AAYhB,QAAI,OAAO,YAAY;AACrB,WAAK,gBAAgB,OAAO;AAAA,IAC9B;AAEA,SAAK,QAAQ,KAAK,mBAAmB;AAAA,MACnC,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,GAAI,OAAO,SAAS,gBAAgB,EAAE,KAAKA,MAAAA,mBAAmB,OAAO,GAAG,EAAA,IAAM,CAAA;AAAA,QAC9E,GAAI,KAAK,gBAAgB,EAAE,OAAO,KAAK,cAAA,IAAkB,CAAA;AAAA,MAAC;AAAA,IAC5D,CACD;AAED,UAAM,KAAK,WAAW,MAAA;AACtB,SAAK,QAAQ;AAAA,MACX;AAAA,MACA,EAAE,MAAM,EAAE,KAAK,KAAK,WAAW,OAAA,IAAS;AAAA,IAAE;AAoB5C,UAAM,mBACJ,OAAO,SAAS,UACb,OAAO,SAAS,UAChB,OAAO,SAAS;AACrB,QAAI,oBAAoB,CAAC,KAAK,aAAa;AACzC,WAAK,aAAa;AAClB,WAAK,UAAU;AACf,WAAK,QAAQ,KAAK,0DAA0D;AAC5E;AAAA,IACF;AAEA,SAAK,UAAU;AAEf,QAAI,OAAO,SAAS,QAAQ;AAC1B,WAAK,gBAAgB,MAAM;AAAA,IAC7B,WAAW,OAAO,SAAS,WAAW;AACpC,WAAK,mBAAmB,MAAM;AAAA,IAChC,WAAW,OAAO,SAAS,eAAe;AACxC,WAAK,uBAAuB,MAAM;AAAA,IACpC,WAAW,OAAO,SAAS,UAAU,OAAO,SAAS,YAAY;AAM/D,WAAK,UAAU;AACf,WAAK,QAAQ,KAAK,0CAA0C;AAAA,QAC1D,MAAM,EAAE,YAAY,OAAO,MAAM,OAAO,KAAK,iBAAiB,KAAA;AAAA,MAAK,CACpE;AACD,WAAK,oBAAA;AAAA,IACP,WAAW,OAAO,SAAS,QAAQ;AACjC,WAAK,gBAAgB,MAAM;AAAA,IAC7B,OAAO;AAEL,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,QAAQ,KAAK,iBAAiB;AACnC,SAAK,WAAW;AAChB,SAAK,aAAa;AAClB,SAAK,UAAU;AAEf,SAAK,oBAAA;AACL,SAAK,kBAAA;AACL,SAAK,qBAAA;AACL,SAAK,yBAAA;AAEL,QAAI,KAAK,kBAAkB;AACzB,oBAAc,KAAK,gBAAgB;AACnC,WAAK,mBAAmB;AAAA,IAC1B;AAEA,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AAEA,QAAI,KAAK,sBAAsB;AAC7B,mBAAa,KAAK,oBAAoB;AACtC,WAAK,uBAAuB;AAAA,IAC9B;AAEA,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAEA,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AAEA,QAAI,KAAK,cAAc;AACrB,YAAM,KAAK,eAAA;AAAA,IACb;AAEA,UAAM,KAAK,WAAW,KAAA;AACtB,SAAK,eAAe,QAAA;AACpB,SAAK,UAAU,MAAA;AAEf,SAAK,mBAAmB,CAAA;AACxB,SAAK,sBAAsB;AAC3B,SAAK,wBAAwB;AAC7B,SAAK,iBAAiB,MAAA;AACtB,SAAK,mBAAmB,MAAA;AACxB,SAAK,iBAAiB,MAAA;AAAA,EACxB;AAAA;AAAA;AAAA,EAKA,IAAI,YAAqB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA;AAAA,EAGlD,oBAAoB,SAAwB;AAC1C,SAAK,oBAAoB;AACzB,SAAK,YAAA;AAAA,EACP;AAAA;AAAA,EAGQ,YAAqB;AAC3B,QAAI,KAAK,iBAAiB,OAAO,EAAG,QAAO;AAC3C,QAAI,KAAK,kBAAkB,OAAO,EAAG,QAAO;AAC5C,QAAI,KAAK,mBAAmB,OAAO,EAAG,QAAO;AAQ7C,QAAI,KAAK,iBAAiB,OAAO,EAAG,QAAO;AAC3C,QAAI,KAAK,eAAe,gBAAA,IAAoB,EAAG,QAAO;AACtD,QAAI,KAAK,WAAW,eAAA,IAAmB,EAAG,QAAO;AACjD,QAAI,KAAK,qBAAqB,KAAK,UAAU,YAAA,IAAgB,EAAG,QAAO;AACvE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBQ,uBAAoC;AAC1C,QAAI,KAAK,mBAAmB,SAAS,EAAG,QAAO;AAC/C,QAAI,UAAU;AACd,QAAI,UAAU;AACd,eAAW,OAAO,KAAK,mBAAmB,OAAA,GAAU;AAClD,UAAI,IAAI,WAAW,OAAQ,WAAU;AACrC,UAAI,IAAI,WAAW,OAAQ,WAAU;AAAA,IACvC;AACA,QAAI,QAAS,QAAO;AACpB,QAAI,QAAS,QAAO;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,4BAAkC;AACxC,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,oBAAqB;AACrD,UAAM,SAAS,KAAK,qBAAA;AACpB,QAAI,WAAW,KAAK,oBAAqB;AACzC,SAAK,QAAQ,KAAK,mCAAmC;AAAA,MACnD,MAAM,EAAE,MAAM,KAAK,qBAAqB,IAAI,OAAA;AAAA,IAAO,CACpD;AACD,SAAK,sBAAsB;AAC3B,SAAK,aAAa,aAAa,EAAE,cAAc,QAAQ,EAAE,MAAM,CAAC,QAAiB;AAC/E,WAAK,QAAQ,KAAK,iCAAiC,EAAE,MAAM,EAAE,OAAOV,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,IACrF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBQ,mBAAmB,OAA2B;AACpD,UAAM,qCAAqB,IAAA;AAC3B,mBAAe,IAAI,MAAM,QAAQ,KAAK;AACtC,eAAW,cAAc,KAAK,mBAAmB,OAAA,GAAU;AACzD,UAAI,CAAC,WAAW,aAAa,cAAc;AACzC,mBAAW;AACX;AAAA,MACF;AACA,WAAK,sBAAsB,YAAY,OAAO,cAAc;AAAA,IAC9D;AAAA,EACF;AAAA,EAEQ,sBACN,YACA,OACAW,QACM;AACN,QAAI,WAAW,WAAW,MAAM,QAAQ;AACtC,iBAAW,SAAS,KAAK;AACzB,iBAAW;AACX;AAAA,IACF;AACA,UAAM,SAASA,OAAM,IAAI,WAAW,MAAM;AAC1C,QAAI,UAAU,EAAE,kBAAkB,UAAU;AAC1C,iBAAW,SAAS,MAAM;AAC1B,iBAAW;AACX;AAAA,IACF;AACA,QAAI,kBAAkB,SAAS;AAC7B,aAAO,KAAK,CAAC,cAAc;AACzB,mBAAW,SAAS,SAAS;AAC7B,mBAAW;AAAA,MACb,CAAC,EAAE,MAAM,CAAC,QAAiB;AACzB,mBAAW;AACX,aAAK,QAAQ,KAAK,2BAA2B;AAAA,UAC3C,MAAM,EAAE,KAAK,WAAW,KAAK,QAAQ,WAAW,QAAQ,OAAOX,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5E;AAAA,MACH,CAAC;AACD;AAAA,IACF;AACA,UAAM,aAAa,KAAK,aAAa,OAAO,WAAW,MAAM;AAC7D,IAAAW,OAAM,IAAI,WAAW,QAAQ,UAAU;AACvC,eAAW,KAAK,CAAC,cAAc;AAC7B,MAAAA,OAAM,IAAI,WAAW,QAAQ,SAAS;AACtC,iBAAW,SAAS,SAAS;AAC7B,iBAAW;AAAA,IACb,CAAC,EAAE,MAAM,CAAC,QAAiB;AACzB,iBAAW;AACX,WAAK,QAAQ,KAAK,2BAA2B;AAAA,QAC3C,MAAM,EAAE,KAAK,WAAW,KAAK,QAAQ,WAAW,QAAQ,OAAOX,MAAAA,OAAO,GAAG,EAAA;AAAA,MAAE,CAC5E;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,aAAa,QAAsB,QAA4C;AAC3F,QAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,UAAM,QAAQ,MAAM,SAAA;AACpB,QAAI,OAAO,WAAW,SAAS,OAAO,WAAW,QAAQ;AACvD,YAAM,WAAW,OAAO,WAAW,SAAS,IAAI;AAChD,YAAM,MAAM,EAAE,OAAO,OAAO,OAAO,QAAQ,OAAO,QAAQ,SAAA;AAC1D,YAAM,WAAW,MAAM,OAAO,MAAM,EAAE,KAAK;AAC3C,UAAI,WAAW,QAAQ;AACrB,cAAM,OAAO,MAAM,SAAS,KAAK,EAAE,SAAS,IAAI,SAAS,OAAO,EAAE,SAAA;AAClE,eAAO,EAAE,GAAG,QAAQ,MAAM,QAAQ,OAAA;AAAA,MACpC;AACA,UAAI,WAAW,OAAO;AAGpB,YAAI,OAAO,WAAW,OAAO;AAC3B,gBAAM,IAAI,MAAM,2CAA2C,OAAO,MAAM,EAAE;AAAA,QAC5E;AACA,cAAM,OAAO,YAAY,OAAO,IAAI;AACpC,eAAO,EAAE,GAAG,QAAQ,MAAM,QAAQ,MAAA;AAAA,MACpC;AACA,UAAI,WAAW,QAAQ;AACrB,cAAM,OAAO,MAAM,SAAS,aAAa,KAAK,EAAE,IAAA,EAAM,SAAA;AACtD,eAAO,EAAE,GAAG,QAAQ,MAAM,QAAQ,OAAA;AAAA,MACpC;AACA,UAAI,WAAW,OAAO;AAGpB,cAAM,OAAO,MAAM,SAAS,aAAa,MAAM,EAAE,IAAA,EAAM,SAAA;AACvD,eAAO,EAAE,GAAG,QAAQ,MAAM,QAAQ,MAAA;AAAA,MACpC;AACA,UAAI,WAAW,UAAU;AACvB,cAAM,OAAO,MAAM,SAAS,aAAa,KAAK,EAAE,IAAA,EAAM,SAAA;AACtD,eAAO,EAAE,GAAG,QAAQ,MAAM,QAAQ,SAAA;AAAA,MACpC;AAAA,IACF;AACA,QAAI,OAAO,WAAW,QAAQ;AAI5B,WAAK,QAAQ,KAAK,yDAAyD;AAAA,QACzE,MAAM,EAAE,OAAA;AAAA,MAAO,CAChB;AACD,aAAO;AAAA,IACT;AACA,UAAM,IAAI,MAAM,0BAA0B,OAAO,MAAM,MAAM,MAAM,EAAE;AAAA,EACvE;AAAA,EAEQ,cAAoB;AAC1B,QAAI,KAAK,cAAc,KAAK,SAAU;AAEtC,QAAI,KAAK,aAAa;AAEpB,UAAI,KAAK,cAAc;AACrB,qBAAa,KAAK,YAAY;AAC9B,aAAK,eAAe;AAAA,MACtB;AAcA,UAAI,KAAK,YAAY;AACnB,cAAM,QAAQ,KAAK,qBAAA;AACnB,YAAI,OAAO;AACT,eAAK,aAAa;AAClB,eAAK,QAAQ,KAAK,mCAAmC;AACrD,eAAK,MAAM,KAAK,EAAE,MAAM,CAAC,QAAQ;AAC/B,iBAAK,QAAQ,MAAM,iBAAiB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AACpE,iBAAK,aAAa;AAClB,iBAAK,UAAU;AAAA,UACjB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,WAAW,CAAC,KAAK,YAAY;AAE3B,UAAI,CAAC,KAAK,cAAc;AACtB,aAAK,eAAe,WAAW,MAAM;AACnC,eAAK,eAAe;AACpB,cAAI,CAAC,KAAK,eAAe,CAAC,KAAK,cAAc,CAAC,KAAK,UAAU;AAC3D,iBAAK,QAAA;AAAA,UACP;AAAA,QACF,GAAG,gBAAgB;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,UAAgB;AACtB,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,UAAU;AAEf,SAAK,QAAQ,KAAK,+BAA+B;AAEjD,SAAK,oBAAA;AACL,SAAK,kBAAA;AAOL,SAAK,qBAAA;AAEL,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,kBAAkB;AACzB,oBAAc,KAAK,gBAAgB;AACnC,WAAK,mBAAmB;AAAA,IAC1B;AAEA,SAAK,UAAU,MAAA;AACf,SAAK,wBAAwB;AAC7B,SAAK,mBAAmB,CAAA;AACxB,SAAK,eAAe,iBAAA;AAGpB,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,kBAAkB;AACvB,SAAK,cAAc;AACnB,SAAK,cAAc;AACnB,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cAAc,MAA4D;AACxE,QAAI,KAAK,SAAU;AAEnB,QAAI,SAAS,MAAM;AACjB,UAAI,KAAK,mBAAmB;AAC1B,cAAM,UAAU,KAAK;AACrB,aAAK,oBAAoB;AACzB,aAAK,QAAQ,MAAA,EAAQ,MAAM,CAAC,QAAQ;AAClC,eAAK,QAAQ,KAAK,0CAA0C;AAAA,YAC1D,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CAC5B;AAAA,QACH,CAAC;AAAA,MACH;AACA,WAAK,iBAAiB;AACtB;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,MAAM,YAAA;AACzB,SAAK,aAAa,MAAM,YAAA;AACxB,SAAK,iBAAiB;AAAA,MACpB,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,UAAU,KAAK;AAAA,MACf,WAAW;AAAA,IAAA;AAKb,QAAI,UAAU,UAAU,UAAU,QAAQ;AACxC,WAAK,QAAQ,KAAK,sDAAsD;AAAA,QACtE,MAAM,EAAE,OAAO,YAAY,KAAK,YAAY,UAAU,KAAK,SAAA;AAAA,MAAS,CACrE;AACD;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK,QAAQ,KAAK,kEAAkE;AAAA,QAClF,MAAM,EAAE,OAAO,YAAY,KAAK,YAAY,UAAU,KAAK,SAAA;AAAA,MAAS,CACrE;AACD;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,qCAAqC,IAAI;AAC1D,QAAI,CAAC,KAAK;AACR,WAAK,QAAQ,KAAK,wDAAwD;AAAA,QACxE,MAAM,EAAE,OAAO,YAAY,KAAK,YAAY,UAAU,KAAK,SAAA;AAAA,MAAS,CACrE;AACD;AAAA,IACF;AAEA,QAAI,KAAK,mBAAmB;AAC1B,YAAM,QAAQ,KAAK;AACnB,WAAK,oBAAoB;AACzB,WAAK,MAAM,MAAA,EAAQ,MAAM,CAAC,QAAQ;AAChC,aAAK,QAAQ,MAAM,2CAA2C;AAAA,UAC5D,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5B;AAAA,MACH,CAAC;AAAA,IACH;AAEA,SAAK,oBAAoB,IAAI;AAAA,MAC3B,KAAK;AAAA,MACL;AAAA,MACA,KAAK,4BAAA;AAAA,MACL,KAAK,QAAQ,MAAM,aAAa;AAAA,IAAA;AAElC,SAAK,QAAQ,KAAK,oCAAoC;AAAA,MACpD,MAAM;AAAA,QACJ,OAAO,IAAI;AAAA,QACX,kBAAkB,IAAI;AAAA,QACtB,gBAAgB,IAAI;AAAA,MAAA;AAAA,IACtB,CACD;AAAA,EACH;AAAA,EAEA,kBAAkB,QAA6B;AAC7C,QAAI,KAAK,SAAU;AAGnB,QAAI,KAAK,QAAQ,SAAS,QAAQ;AAChC,WAAK,qBAAA;AACL,UAAI,KAAK,YAAY,QAAS,MAAK,UAAU;AAE7C,UAAI,KAAK,cAAc,KAAK,OAAO,YAAY;AAC7C,aAAK,aAAa;AAClB,aAAK,QAAQ,KAAK,yDAAyD;AAAA,MAC7E;AACA,WAAK,oBAAA;AAAA,IACP;AAQA,QAAI,OAAO,SAAS,WAAW,KAAK,QAAQ,SAAS,UAAU,KAAK,mBAAmB;AACrF,YAAM,QAAQ,OAAO,MAAM,YAAA;AAC3B,UAAI,UAAU,UAAU,UAAU,QAAQ;AACxC,aAAK,kBAAkB,aAAa,OAAO,IAAI;AAC/C;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,SAAS,SAAS;AAC3B,WAAK;AACL,WAAK,cAAc,OAAO,KAAK;AAC/B,WAAK,iBAAiB,OAAO,KAAK;AAClC,WAAK;AACL,WAAK,eAAe,KAAK,IAAA;AAEzB,YAAM,MAAM,KAAK,IAAA;AACjB,YAAM,WAAW,MAAM,KAAK;AAC5B,UAAI,YAAY,KAAM;AACpB,aAAK,cAAc,KAAK,MAAO,KAAK,gBAAgB,IAAK,QAAQ;AACjE,aAAK,kBAAmB,KAAK,kBAAkB,WAAY;AAC3D,aAAK,gBAAgB;AACrB,aAAK,kBAAkB;AACvB,aAAK,gBAAgB;AAAA,MACvB;AAEA,UAAI,OAAO,UAAU;AACnB,YAAI,KAAK,iBAAiB,GAAG;AAC3B,eAAK,gBAAgB,MAAM,KAAK;AAAA,QAClC;AACA,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF;AAGA,QAAI,OAAO,SAAS,SAAS;AAC3B,WAAK,UAAU,KAAK,MAAM;AAAA,IAC5B;AAEA,eAAW,MAAM,KAAK,kBAAkB;AACtC,SAAG,MAAM;AAAA,IACX;AAGA,QAAI,OAAO,SAAS,SAAS;AAC3B,WAAK,WAAW,UAAU,OAAO,IAAI;AAIrC,UAAI,CAAC,KAAK,eAAe,oBAAoB;AAC3C,aAAK,eAAe,WAAW,MAAM;AAAA,MACvC;AAIA,UAAI,CAAC,KAAK,yBAAyB,OAAO,UAAU;AAClD,aAAK,wBAAwB;AAC7B,aAAK,QAAQ,KAAK,6CAA6C;AAC/D,YAAI,KAAK,qBAAqB;AAC5B,eAAK,oBAAA;AACL,eAAK,sBAAsB;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAEA,eAAW,cAAc,KAAK,aAAa;AACzC,YAAM,WAAW,GAAG,KAAK,QAAQ;AACjC,iBAAW,WAAW,UAAU,MAAM;AAAA,IACxC;AAIA,QAAI,OAAO,SAAS,WAAW,KAAK,uBAAuB;AACzD,UAAI,KAAK,cAAc;AACrB,aAAK;AACL,YAAI,KAAK,qBAAqB,KAAK,KAAK,mBAAmB,QAAQ,GAAG;AACpE,eAAK,QAAQ,KAAK,gBAAgB;AAAA,YAChC,MAAM;AAAA,cACJ,WAAW,KAAK;AAAA,cAChB,MAAM,OAAO,KAAK;AAAA,cAClB,UAAU,OAAO;AAAA,cACjB,cAAc,KAAK;AAAA,YAAA;AAAA,UACrB,CACD;AAAA,QACH;AACA,aAAK,aAAa,WAAW,MAAM,EAAE,MAAM,CAAC,QAAiB;AAC3D,eAAK,QAAQ,KAAK,sBAAsB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,QAC1E,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc,UAAwD;AACpE,SAAK,iBAAiB,IAAI,QAAQ;AAClC,QAAI,KAAK,gBAAgB;AACvB,WAAK,QAAQ,KAAK,4BAA4B,EAAE,MAAM,EAAE,OAAO,KAAK,iBAAiB,KAAA,EAAK,CAAG;AAAA,IAC/F;AAKA,SAAK,mBAAmB,QAAQ;AAChC,SAAK,YAAA;AAEL,WAAO,MAAM;AACX,WAAK,iBAAiB,OAAO,QAAQ;AACrC,UAAI,KAAK,gBAAgB;AACvB,aAAK,QAAQ,KAAK,8BAA8B,EAAE,MAAM,EAAE,OAAO,KAAK,iBAAiB,KAAA,EAAK,CAAG;AAAA,MACjG;AACA,WAAK,YAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAAmB,UAAiD;AAC1E,QAAI,CAAC,KAAK,oBAAoB,KAAK,iBAAiB,WAAW,EAAG;AAClE,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AACZ,UAAM,YAAY,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AACtD,UAAM,SAAS,OAAO;AAAA,MACpB,KAAK,iBAAiB,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;AAAA,IAAA;AAEvD,QAAI,KAAK,gBAAgB;AACvB,WAAK,QAAQ,KAAK,iDAAiD;AAAA,QACjE,MAAM;AAAA,UACJ;AAAA,UACA,SAAS,KAAK,iBAAiB;AAAA,UAC/B,SAAS,KAAK,iBAAiB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG;AAAA,UAC5D,iBAAiB,KAAK,iBAAiB,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,CAAC,EAAE,SAAS,KAAK,CAAC,EAAE,KAAK,GAAG;AAAA,UAC5F,cAAc,OAAO;AAAA,QAAA;AAAA,MACvB,CACD;AAAA,IACH;AACA,aAAS;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,MACL,UAAU;AAAA,MACV;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA,EAGQ,yCAA+C;AACrD,QAAI,CAAC,KAAK,oBAAoB,KAAK,iBAAiB,SAAS,EAAG;AAChE,eAAW,MAAM,KAAK,iBAAkB,MAAK,mBAAmB,EAAE;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAciB,gDAAgC,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjD,mBAAmB,UAA4D;AAC7E,SAAK,0BAA0B,IAAI,QAAQ;AAC3C,QAAI,KAAK,oBAAoB,KAAK,iBAAiB,SAAS,GAAG;AAC7D,UAAI;AACF,iBAAS,KAAK,gBAAgB;AAAA,MAChC,SAAS,KAAK;AACZ,aAAK,QAAQ,KAAK,sDAAsD;AAAA,UACtE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5B;AAAA,MACH;AAAA,IACF;AACA,WAAO,MAAM;AACX,WAAK,0BAA0B,OAAO,QAAQ;AAAA,IAChD;AAAA,EACF;AAAA;AAAA,EAGQ,qCAA2C;AACjD,QAAI,CAAC,KAAK,oBAAoB,KAAK,0BAA0B,SAAS,EAAG;AACzE,UAAM,KAAK,KAAK;AAChB,eAAW,MAAM,KAAK,2BAA2B;AAC/C,UAAI;AACF,WAAG,EAAE;AAAA,MACP,SAAS,KAAK;AACZ,aAAK,QAAQ,KAAK,kCAAkC;AAAA,UAClD,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5B;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAAW,UAAkD;AAC3D,SAAK,kBAAkB,IAAI,QAAQ;AACnC,QAAI,KAAK,gBAAgB;AACvB,WAAK,QAAQ,KAAK,8BAA8B;AAAA,QAC9C,MAAM,EAAE,OAAO,KAAK,kBAAkB,KAAA;AAAA,MAAK,CAC5C;AAAA,IACH;AACA,SAAK,YAAA;AACL,WAAO,MAAM;AACX,WAAK,kBAAkB,OAAO,QAAQ;AACtC,UAAI,KAAK,gBAAgB;AACvB,aAAK,QAAQ,KAAK,gCAAgC;AAAA,UAChD,MAAM,EAAE,OAAO,KAAK,kBAAkB,KAAA;AAAA,QAAK,CAC5C;AAAA,MACH;AACA,WAAK,YAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEA,sBAAoD;AAClD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,gBAAyC;AACvC,WAAO,KAAK,QAAQ,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,cAAuB;AACrB,UAAM,IAAI,KAAK,QAAQ;AACvB,WAAO,MAAM,UAAU,MAAM,aAAa,MAAM;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,SAAuB;AAClC,QAAI,KAAK,SAAU;AACnB,QAAI,KAAK,kBAAkB,SAAS,EAAG;AACvC,eAAW,MAAM,KAAK,mBAAmB;AACvC,UAAI;AACF,WAAG,OAAO;AAAA,MACZ,SAAS,KAAK;AACZ,aAAK,QAAQ,KAAK,8BAA8B;AAAA,UAC9C,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5B;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,kBAAkB,SAAwB;AACxC,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,eAAe,UAAyC,SAAsC;AAC5F,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,eAAe,OAAO,oBAAoB;AAChD,UAAM,eAAe,IAAI,aAAa,MAAM;AAC5C,UAAM,MAAM,SAAS;AACrB,QAAI,CAAC,KAAK;AACR,WAAK,QAAQ,KAAK,oHAAoH;AAAA,IACxI;AAEA,UAAM,aAAgC;AAAA,MACpC;AAAA,MACA;AAAA,MACA,KAAK,OAAO;AAAA,MACZ;AAAA,MACA,QAAQ,SAAS,UAAU;AAAA,MAC3B,cAAc,KAAK,IAAA;AAAA,MACnB,iBAAiB;AAAA,MACjB,eAAe;AAAA,IAAA;AAEjB,SAAK,mBAAmB,IAAI,cAAc,UAAU;AACpD,SAAK,QAAQ,KAAK,4BAA4B;AAAA,MAC5C,MAAM,EAAE,KAAK,WAAW,KAAK,OAAO,KAAK,mBAAmB,MAAM,QAAQ,QAAQ,WAAW,OAAA;AAAA,IAAO,CACrG;AACD,SAAK,YAAA;AAGL,QAAI,KAAK,sBAAsB;AAC7B,mBAAa,KAAK,oBAAoB;AACtC,WAAK,uBAAuB;AAAA,IAC9B;AAIA,QAAI,CAAC,KAAK,gBAAgB,KAAK,QAAQ;AACrC,YAAM,QAAQ,KAAK,iBAAiB,KAAK,OAAO;AAChD,UAAI,OAAO;AACT,aAAK,2BAA2B,OAAO;AAAA,MACzC,OAAO;AACL,aAAK,uBAAuB;AAC5B,aAAK,QAAQ,KAAK,qDAAqD;AAAA,MACzE;AAAA,IACF,WAAW,KAAK,cAAc;AAE5B,WAAK,0BAAA;AAAA,IACP;AAEA,WAAO,MAAM;AACX,WAAK,mBAAmB,OAAO,YAAY;AAC3C,WAAK,QAAQ,KAAK,8BAA8B,EAAE,MAAM,EAAE,OAAO,KAAK,mBAAmB,KAAA,EAAK,CAAG;AACjG,WAAK,YAAA;AAEL,UAAI,KAAK,mBAAmB,SAAS,KAAK,KAAK,cAAc;AAC3D,aAAK,QAAQ,KAAK,8DAA8D;AAAA,UAC9E,MAAM,EAAE,SAAS,0BAAA;AAAA,QAA0B,CAC5C;AACD,aAAK,uBAAuB,WAAW,MAAM;AAC3C,eAAK,uBAAuB;AAC5B,cAAI,KAAK,mBAAmB,SAAS,KAAK,KAAK,cAAc;AAC3D,iBAAK,QAAQ,KAAK,+CAA+C;AACjE,iBAAK,eAAA;AAAA,UACP;AAAA,QACF,GAAG,yBAAyB;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,oBACE,UACA,SACa;AACb,UAAM,MAAM,SAAS;AACrB,QAAI,CAAC,KAAK;AACR,WAAK,QAAQ,KAAK,qFAAqF;AAAA,IACzG;AACA,UAAM,eAAe,OAAO,kBAAkB;AAC9C,UAAM,aAA8B;AAAA,MAClC;AAAA,MACA,KAAK,OAAO;AAAA,MACZ,cAAc,KAAK,IAAA;AAAA,MACnB,iBAAiB;AAAA,IAAA;AAEnB,SAAK,iBAAiB,IAAI,cAAc,UAAU;AAKlD,SAAK,YAAA;AAEL,WAAO,MAAM;AACX,WAAK,iBAAiB,OAAO,YAAY;AACzC,WAAK,YAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEA,WAAwB;AACtB,UAAM,eAAe,KAAK,aAAa,SAAY,KAAK;AAExD,WAAO;AAAA,MACL,QAAQ,KAAK,aAAa,SAAS,KAAK;AAAA,MACxC,UAAU,cAAc,YAAY,KAAK;AAAA,MACzC,WAAW,cAAc,aAAa;AAAA,MACtC,oBAAoB,KAAK,iBAAiB;AAAA,MAC1C,oBAAoB,KAAK,mBAAmB;AAAA,MAC5C,UAAU,KAAK,IAAA,IAAQ,KAAK;AAAA,MAC5B,aAAa,KAAK;AAAA,MAClB,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK,iBAAiB,KAAK,QAAQ;AAAA,MAC1C,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK,eAAe,gBAAA;AAAA,MACjC,aAAa,KAAK,WAAW,eAAA;AAAA,MAC7B,cAAc,KAAK,UAAU,YAAA;AAAA,MAC7B,aAAa,KAAK,UAAU,sBAAA;AAAA,MAC5B,kBAAkB,KAAK,UAAU,eAAA;AAAA,MACjC,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK,kBAAkB;AAAA,IAAA;AAAA,EAElC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAwBE;AACA,WAAO;AAAA,MACL,MAAM,KAAK,eAAe,iBAAA;AAAA,MAC1B,SAAS,CAAC,GAAG,KAAK,mBAAmB,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,QACzD,KAAK,EAAE;AAAA,QACP,cAAc,EAAE;AAAA,QAChB,QAAQ,EAAE;AAAA,QACV,iBAAiB,EAAE;AAAA,QACnB,eAAe,EAAE;AAAA,MAAA,EACjB;AAAA,MACF,OAAO,CAAC,GAAG,KAAK,iBAAiB,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,QACrD,KAAK,EAAE;AAAA,QACP,cAAc,EAAE;AAAA,QAChB,iBAAiB,EAAE;AAAA,MAAA,EACnB;AAAA,MACF,aAAa,KAAK,WAAW,eAAA;AAAA,MAC7B,oBAAoB,KAAK,iBAAiB;AAAA,IAAA;AAAA,EAE9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,WAAW,SAAuC,QAAyB;AACzE,QAAI,YAAY,QAAQ;AACtB,aAAO,KAAK,eAAe,YAAY,MAAM;AAAA,IAC/C;AACA,UAAM,cAAc,YAAY,YAAY,KAAK,qBAAqB,KAAK;AAC3E,UAAM,UAAoB,CAAA;AAC1B,eAAW,CAAC,KAAK,GAAG,KAAK,aAAa;AACpC,UAAI,IAAI,QAAQ,OAAQ,SAAQ,KAAK,GAAG;AAAA,IAC1C;AACA,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,eAAW,OAAO,QAAS,aAAY,OAAO,GAAG;AACjD,SAAK,QAAQ,KAAK,kCAAkC;AAAA,MAClD,MAAM,EAAE,SAAS,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY,KAAA;AAAA,IAAK,CAC9E;AACD,SAAK,YAAA;AACL,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAA4B;AAC1B,WAAO,KAAK,WAAW,OAAA;AAAA,EACzB;AAAA;AAAA,EAGA,oBAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,qBAA6B;AAC3B,WAAO,KAAK,WAAW,eAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAyC;AACvC,WAAO,KAAK,UAAU,WAAA;AAAA,EACxB;AAAA;AAAA,EAGA,qBAAqB,SAAuB;AAC1C,UAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,aAAa,kBAAkB,CAAC;AAC9E,SAAK,UAAU,YAAY,OAAO;AAClC,SAAK,QAAQ,KAAK,2BAA2B,EAAE,MAAM,EAAE,SAAS,QAAA,GAAW;AAAA,EAC7E;AAAA;AAAA,EAGA,uBAA+B;AAC7B,WAAO,KAAK,UAAU,YAAA;AAAA,EACxB;AAAA,EAEQ,gBAAgB,QAA4B;AAClD,SAAK,sBAAsB,MAAM;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,gBAAgB,QAA4B;AAClD,SAAK,QAAQ,KAAK,wBAAwB,EAAE,MAAM,EAAE,KAAKU,yBAAmB,OAAO,GAAG,EAAA,EAAE,CAAG;AAE3F,SAAK,kBAAA;AAEL,UAAM,SAAS,IAAI,WAAW,OAAO,KAAK,KAAK,QAAQ,MAAM,MAAM,GAAG;AAAA,MACpE,cAAc,CAAC,UAAU;AACvB,aAAK,gBAAgB;AACrB,aAAK,QAAQ,KAAK,8BAA8B,EAAE,MAAM,EAAE,MAAA,GAAS;AAAA,MACrE;AAAA,MACA,iBAAiB,CAAC,WAAW;AAC3B,YAAI,KAAK,SAAU;AACnB,aAAK,kBAAkB,MAAM;AAAA,MAC/B;AAAA,MACA,aAAa,CAAC,SAAS;AACrB,YAAI,KAAK,SAAU;AACnB,aAAK,cAAc,IAAI;AAAA,MACzB;AAAA,MACA,WAAW,MAAM;AACf,aAAK,QAAQ,KAAK,iBAAiB;AACnC,aAAK,UAAU;AACf,aAAK,mBAAmB;AACxB,aAAK,qBAAA;AAAA,MACP;AAAA,MACA,SAAS,CAAC,UAAU;AAClB,YAAI,KAAK,WAAY;AACrB,aAAK,QAAQ,KAAK,cAAc,EAAE,MAAM,EAAE,OAAO,MAAM,QAAA,GAAW;AAClE,aAAK,kBAAA;AACL,aAAK,UAAU;AACf,aAAK,kBAAA;AAAA,MACP;AAAA,MACA,YAAY,MAAM;AAChB,aAAK,aAAa;AAAA,MACpB;AAAA,IAAA,CACD;AAED,SAAK,aAAa;AAElB,WAAO,QAAA,EAAU,MAAM,CAAC,QAAQ;AAC9B,UAAI,KAAK,cAAc,KAAK,WAAY;AACxC,WAAK,QAAQ,KAAK,uBAAuB;AAAA,QACvC,MAAM,EAAE,OAAOV,MAAAA,OAAO,GAAG,GAAG,GAAG,KAAK,iBAAA,EAAiB;AAAA,MAAE,CACxD;AACD,WAAK,kBAAA;AACL,WAAK,UAAU;AACf,WAAK,kBAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEQ,oBAA0B;AAChC,QAAI,KAAK,YAAY;AACnB,WAAK,WAAW,QAAA;AAChB,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBQ,wBACN,OACA,aACsD;AACtD,WAAO;AAAA,MACH,cAAc,CAAC,OAAO,YAAY;AAChC,aAAK,gBAAgB,MAAM;AAC3B,aAAK,QAAQ,KAAK,gCAAgC;AAAA,UAChD,MAAM;AAAA,YACJ,OAAO,MAAM;AAAA,YACb,aAAa,MAAM;AAAA,YACnB,iBAAiB,QAAQ,MAAM,aAAa,eAAe,MAAM;AAAA,UAAA;AAAA,QACnE,CACD;AAMD,YAAI,MAAM,aAAa,eAAe,QAAQ;AAC5C,eAAK,mBAAmB,MAAM,YAAY,cAAc,IAAI,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AACjF,eAAK,uCAAA;AACL,eAAK,mCAAA;AAAA,QACP;AAGA,aAAK,kBAAkB,IAAI,gBAAgB,MAAM,OAAO,CAAC,iBAAiB;AACxE,eAAK,sBAAsB,YAAY;AAAA,QACzC,CAAC;AAID,aAAK,sBAAA;AAGL,aAAK,eAAe,aAAa,SAAS,MAAM,OAAO,MAAM,WAAW;AAGxE,YAAI,CAAC,KAAK,gBAAgB,KAAK,mBAAmB,OAAO,KAAK,KAAK,yBAAyB,QAAW;AACrG,eAAK,QAAQ,KAAK,sDAAsD;AAAA,YACtE,MAAM,EAAE,OAAO,MAAM,MAAA;AAAA,UAAM,CAC5B;AACD,eAAK,2BAA2B,KAAK,oBAAoB;AACzD,eAAK,uBAAuB;AAAA,QAC9B;AAAA,MACF;AAAA,MAEA,YAAY,CAAC,YAAY;AACvB,YAAI,KAAK,SAAU;AAEnB,aAAK,UAAU;AACf,aAAK,mBAAmB;AACxB,aAAK,qBAAA;AAGL,aAAK,yBAAA;AASL,aAAK;AACL,YAAI,KAAK,iBAAiB,KAAK,KAAK,iBAAiB,IAAI;AACvD,eAAK,QAAQ,KAAK,8BAA8B;AAAA,YAC9C,MAAM,EAAE,KAAK,KAAK,cAAc,OAAO,QAAQ,OAAA;AAAA,UAAO,CACvD;AAAA,QACH;AAMA,aAAK,mBAAmB;AACxB,aAAK,mBAAmB;AACxB,aAAK,iBAAiB,cAAc,OAAO;AAG3C,aAAK,eAAe;AAAA,UAClB;AAAA,UACA,KAAK;AAAA,UACL,KAAK;AAAA,QAAA;AAKP,YAAI,KAAK,kBAAkB,OAAO,GAAG;AACnC,qBAAW,MAAM,KAAK,mBAAmB;AACvC,gBAAI;AACF,iBAAG,OAAO;AAAA,YACZ,SAAS,KAAK;AACZ,mBAAK,QAAQ,KAAK,8BAA8B;AAAA,gBAC9C,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,cAAE,CAC5B;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MAEA,cAAc,CAAC,eAAe;AAC5B,cAAM,aAAa,WAAW,MAAM,YAAA;AACpC,aAAK,aAAa;AAClB,cAAM,kBAAkB,eAAe,UAAU,eAAe;AAKhE,aAAK,QAAQ,KAAK,yBAAyB;AAAA,UACzC,MAAM;AAAA,YACJ,OAAO,WAAW;AAAA,YAClB;AAAA,YACA,WAAW,WAAW;AAAA,YACtB,UAAU,WAAW;AAAA,YACrB,UAAU,OAAO,KAAK,WAAW,IAAI;AAAA,YACrC,cAAc,YAAY,WAAW,QAAQ,YAAY,WAAW;AAAA,YACpE,wBAAwB,KAAK,kBAAkB;AAAA,YAC/C;AAAA,UAAA;AAAA,QACF,CACD;AAED,YAAI,YAAY;AAChB,YAAI,sBAAsB,WAAW;AACrC,YAAI,oBAAoB,WAAW;AAEnC,YAAI,iBAAiB;AACnB,eAAK,kBAAkB,IAAI,gBAAgB,YAA0B,CAAC,UAAU;AAC9E,uBAAW,OAAO,KAAK,iBAAiB,OAAA,GAAU;AAChD,kBAAI,SAAS,KAAK;AAClB,kBAAI;AAAA,YACN;AAAA,UACF,CAAC;AACD,eAAK,QAAQ,KAAK,qCAAqC;AAAA,YACrD,MAAM,EAAE,OAAO,YAAY,YAAY,WAAW,WAAW,UAAU,WAAW,SAAA;AAAA,UAAS,CAC5F;AAAA,QACH,WAAW,KAAK,eAAe;AAC7B,gBAAM,SAAS,KAAK,6BAA6B,UAAU;AAC3D,cAAI,QAAQ;AACV,kCAAsB,OAAO;AAC7B,gCAAoB,OAAO;AAC3B,wBAAY;AAKZ,gBAAI,KAAK,mBAAmB;AAC1B,oBAAM,QAAQ,KAAK;AACnB,mBAAK,oBAAoB;AACzB,mBAAK,MAAM,MAAA,EAAQ,MAAM,CAAC,QAAQ;AAChC,qBAAK,QAAQ,MAAM,gDAAgD;AAAA,kBACjE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,gBAAE,CAC5B;AAAA,cACH,CAAC;AAAA,YACH;AACA,iBAAK,oBAAoB,IAAI;AAAA,cAC3B,KAAK;AAAA,cACL;AAAA,cACA,KAAK,4BAAA;AAAA,cACL,KAAK,QAAQ,MAAM,aAAa;AAAA,YAAA;AAElC,iBAAK,QAAQ,KAAK,qCAAqC;AAAA,cACrD,MAAM;AAAA,gBACJ,OAAO,OAAO;AAAA,gBACd,kBAAkB,OAAO;AAAA,gBACzB,gBAAgB,OAAO;AAAA,gBACvB,SAAS,OAAO,UAAU,YAAY;AAAA,cAAA;AAAA,YACxC,CACD;AAAA,UACH,OAAO;AACL,iBAAK,QAAQ,KAAK,yDAAyD;AAAA,cACzE,MAAM,EAAE,OAAO,WAAW,OAAO,YAAY,WAAW,WAAW,UAAU,WAAW,SAAA;AAAA,YAAS,CAClG;AAAA,UACH;AAAA,QACF,OAAO;AACL,eAAK,QAAQ,KAAK,8DAA8D;AAAA,YAC9E,MAAM,EAAE,OAAO,WAAW,OAAO,YAAY,WAAW,WAAW,UAAU,WAAW,SAAA;AAAA,UAAS,CAClG;AAAA,QACH;AAEA,aAAK,iBAAiB;AAAA,UACpB,OAAO;AAAA,UACP,YAAY;AAAA,UACZ,UAAU;AAAA,UACV;AAAA,QAAA;AAAA,MAEJ;AAAA,MAEA,YAAY,CAAC,YAAY;AACvB,YAAI,KAAK,SAAU;AACnB,aAAK;AACL,YAAI,KAAK,iBAAiB,KAAK,KAAK,iBAAiB,IAAI;AACvD,eAAK,QAAQ,KAAK,8BAA8B;AAAA,YAC9C,MAAM;AAAA,cACJ,KAAK,KAAK;AAAA,cACV,OAAO,QAAQ;AAAA,cACf,kBAAkB,KAAK,oBAAoB;AAAA,cAC3C,iBAAiB,KAAK,sBAAsB;AAAA,YAAA;AAAA,UAC9C,CACD;AAAA,QACH;AACA,aAAK,iBAAiB,cAAc,OAAO;AAC3C,aAAK,mBAAmB,cAAc,OAAO;AAM7C,cAAM,qBAAqB,KAAK,eAAe,UAAU,KAAK,eAAe;AAC7E,YAAI,sBAAsB,KAAK,iBAAiB,OAAO,KAAK,QAAQ,SAAS,IAAI;AAC/E,gBAAM,UAAU,QAAQ,SAAS,EAAE;AACnC,cAAI,QAAQ,SAAS,GAAG;AAEtB,kBAAM,eAAe,QAAQ,aAAa,CAAC;AAC3C,kBAAM,MAAM,KAAK,MAAM,eAAe,CAAC;AACvC,kBAAM,SAAwB;AAAA,cAC5B,MAAM;AAAA,cACN,MAAM;AAAA,cACN;AAAA,cACA,KAAK;AAAA,cACL,UAAU;AAAA,cACV,OAAO,KAAK,YAAY,iBAAiB;AAAA,YAAA;AAE3C,uBAAW,MAAM,KAAK,kBAAkB;AACtC,iBAAG,MAAM;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAGA,aAAK,eAAe,wBAAwB,OAAO;AAAA,MACrD;AAAA,MAEA,WAAW,MAAM;AACf,aAAK,QAAQ,KAAK,GAAG,KAAK,aAAa;AACvC,aAAK,UAAU;AAAA,MACjB;AAAA,MAEA,SAAS,CAAC,UAAU;AAClB,YAAI,KAAK,WAAY;AACrB,aAAK,QAAQ,KAAK,GAAG,KAAK,UAAU;AAAA,UAClC,MAAM;AAAA,YACJ,OAAO,MAAM;AAAA,YACb,GAAG,KAAK,iBAAA;AAAA,UAAiB;AAAA,QAC3B,CACD;AACD,oBAAA;AACA,aAAK,UAAU;AAKf,YAAI,KAAK,QAAQ,SAAS,WAAW;AACnC,eAAK,oBAAA;AAAA,QACP;AACA,aAAK,kBAAA;AAAA,MACP;AAAA,MAEA,YAAY,MAAM;AAChB,oBAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAEN;AAAA,EAEQ,sBAAsB,QAA4B;AACxD,SAAK,QAAQ,KAAK,6BAA6B;AAE/C,UAAM,SAAS,IAAI;AAAA,MACjB;AAAA,QACE,KAAK,OAAO;AAAA,QACZ,QAAQ,KAAK,QAAQ,MAAM,aAAa;AAAA,MAAA;AAAA,MAE1C,KAAK,wBAAwB,eAAe,MAAM,KAAK,qBAAqB;AAAA,IAAA;AAG9E,SAAK,eAAe;AAEpB,WAAO,QAAA,EAAU,MAAM,CAAC,QAAQ;AAC9B,UAAI,KAAK,cAAc,KAAK,WAAY;AACxC,WAAK,QAAQ,KAAK,8BAA8B;AAAA,QAC9C,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,GAAG,GAAG,KAAK,iBAAA,EAAiB;AAAA,MAAE,CACxD;AACD,WAAK,oBAAA;AACL,WAAK,UAAU;AACf,WAAK,kBAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBQ,mBAA6B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAe7B,sBAAsB,cAAqC;AACjE,UAAM,EAAE,KAAK,UAAU,cAAc,cAAc;AACnD,UAAM,QAAQ,KAAK,iBAAiB;AAGpC,QAAI,eAAe,mBAAmB;AACtC,QAAI,mBAAmB,mBAAmB;AAG1C,UAAM,SAAS,UAAU;AACzB,UAAM,UAAU,SAAU,IAAI,CAAC,IAAK,MAAU,IAAI,CAAC,IAAK,QAAS;AAIjE,UAAM,aAAa,SACd,YAAY,KAAK,YAAY,IAC7B,YAAY,MAAM,YAAY,MAAM,YAAY;AAErD,QAAI,YAAY;AAEd,YAAMY,aAAY,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AACtD,WAAK,iBAAiB,KAAK,OAAO,OAAO,CAACA,YAAW,GAAG,CAAC,CAAC;AAC1D;AAAA,IACF;AAGA,UAAM,YAAY,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AACtD,UAAM,eAAe,KAAK,iBAAiB,SAAS;AACpD,QAAI;AACJ,QAAI,cAAc;AAChB,WAAK,iBAAiB,KAAK,OAAO,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC;AAC1D,mBAAa,OAAO,OAAO,KAAK,gBAAgB;AAChD,WAAK,mBAAmB,CAAA;AAAA,IAC1B,OAAO;AACL,mBAAa,OAAO,OAAO,CAAC,WAAW,GAAG,CAAC;AAAA,IAC7C;AAEA,UAAM,MAAM,KAAK,MAAM,YAAY,EAAE;AAErC,UAAM,SAAwB;AAAA,MAC5B,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA,KAAK;AAAA,MACL,UAAU,YAAY;AAAA,MACtB;AAAA,IAAA;AAKF,SAAK,kBAAkB,MAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,uBAAuB,SAA6B;AAC1D,SAAK,QAAQ,MAAM,sBAAsB;AAEzC,QAAI,KAAK,sBAAsB,gBAAgB;AAI7C,WAAK,oBAAoB;AAAA,IAC3B;AACA,SAAK,UAAU;AAGf,SAAK,qBAAA;AAGL,SAAK,mBAAmB,YAAY,MAAM;AACxC,UAAI,KAAK,cAAc,KAAK,UAAU;AACpC,YAAI,KAAK,kBAAkB;AACzB,wBAAc,KAAK,gBAAgB;AACnC,eAAK,mBAAmB;AAAA,QAC1B;AACA;AAAA,MACF;AACA,WAAK,qBAAA;AAAA,IACP,GAAG,GAAI;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,mBAAqC;AAC3C,UAAM,SAAS,KAAK,iBAAiB,KAAK,QAAQ,cAAc,IAAI,YAAA;AACpE,QAAI,UAAU,UAAU,UAAU,OAAQ,QAAO;AACjD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,uBAA6B;AACnC,UAAM,QAA0B,KAAK,iBAAA;AACrC,UAAM,SAAwB;AAAA,MAC5B,MAAM;AAAA,MACN,MAAM,oBAAoB,KAAK,mBAAmB,KAAK;AAAA,MACvD,KAAK,KAAK,IAAA;AAAA,MACV,KAAK,KAAK,IAAA;AAAA,MACV,UAAU;AAAA;AAAA,MACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,eAAe;AAAA,IAAA;AAEjB,SAAK,kBAAkB,MAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,wBAA8B;AACpC,QAAI,KAAK,mBAAoB;AAC7B,QAAI,KAAK,cAAc,KAAK,SAAU;AACtC,SAAK,qBAAqB,WAAW,MAAM;AACzC,WAAK,qBAAqB;AAC1B,UAAI,KAAK,cAAc,KAAK,SAAU;AAGtC,UAAI,KAAK,eAAe,EAAG;AAC3B,WAAK,QAAQ;AAAA,QACX;AAAA,QACA,EAAE,MAAM,EAAE,WAAW,aAAa,yBAAuB;AAAA,MAAE;AAK7D,WAAK,eAAe;AAIpB,WAAK,qBAAA;AACL,WAAK,UAAU;AAKf,WAAK,mBAAmB;AACxB,WAAK,uBAAuB;AAG5B,WAAK,oBAAA;AACL,WAAK,kBAAA;AAAA,IACP,GAAG,aAAa,sBAAsB;AAAA,EACxC;AAAA;AAAA,EAGQ,2BAAiC;AACvC,QAAI,CAAC,KAAK,mBAAoB;AAC9B,iBAAa,KAAK,kBAAkB;AACpC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA,EAKQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,oBAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS7C,qBAAqB,QAA+B;AAClD,QAAI,KAAK,sBAAsB,OAAQ;AACvC,SAAK,oBAAoB;AAKzB,QAAI,KAAK,uBAAuB;AAC9B,WAAK,qBAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEQ,uBAA6B;AACnC,QAAI,KAAK,uBAAuB;AAC9B,oBAAc,KAAK,qBAAqB;AACxC,WAAK,wBAAwB;AAAA,IAC/B;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,KAAK,cAAc,CAAC,KAAK,QAAQ;AACnC;AAAA,IACF;AAKA,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,aAAa;AAClB,WAAK,UAAU;AACf,WAAK,qBAAA;AACL,UAAI,KAAK,gBAAgB;AACvB,qBAAa,KAAK,cAAc;AAChC,aAAK,iBAAiB;AAAA,MACxB;AACA,WAAK,QAAQ,KAAK,iDAAiD;AACnE;AAAA,IACF;AASA,QAAI,KAAK,gBAAgB;AACvB;AAAA,IACF;AAYA,SAAK,QAAQ;AAAA,MACX;AAAA,MACA,EAAE,MAAM,EAAE,SAAS,KAAK,mBAAiB;AAAA,IAAE;AAG7C,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AACtB,UAAI,KAAK,WAAY;AAIrB,YAAM,MAAM,KAAK,qBAAA;AACjB,UAAI,CAAC,IAAK;AACV,WAAK,UAAU;AACf,UAAI,IAAI,SAAS,QAAQ;AACvB,aAAK,gBAAgB,GAAG;AAAA,MAC1B,WAAW,IAAI,SAAS,WAAW;AACjC,aAAK,mBAAmB,GAAG;AAAA,MAC7B,OAAO;AACL,aAAK,gBAAgB,GAAG;AAAA,MAC1B;AAAA,IACF,GAAG,KAAK,gBAAgB;AAExB,SAAK,mBAAmB,KAAK;AAAA,MAC3B,KAAK,mBAAmB;AAAA,MACxB;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,sBAA4B;AAClC,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAAA,IAClC;AACA,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AACtB,UAAI,KAAK,YAAY,KAAK,WAAY;AACtC,UAAI,KAAK,QAAQ,SAAS,OAAQ;AAElC,UAAI,KAAK,QAAQ,YAAY;AAC3B,aAAK,QAAQ,MAAM,sEAAsE;AAAA,UACvF,MAAM,EAAE,WAAW,sBAAA;AAAA,QAAsB,CAC1C;AACD,aAAK,aAAa;AAClB;AAAA,MACF;AAEA,WAAK,QAAQ,KAAK,0CAA0C;AAAA,QAC1D,MAAM,EAAE,WAAW,sBAAA;AAAA,MAAsB,CAC1C;AACD,WAAK,UAAU;AAAA,IAKjB,GAAG,qBAAqB;AAAA,EAC1B;AAAA,EAEQ,iBAAgC;AACtC,QAAI,KAAK,0BAA0B;AACjC,mBAAa,KAAK,wBAAwB;AAC1C,WAAK,2BAA2B;AAAA,IAClC;AACA,SAAK,yBAAyB;AAC9B,UAAM,QAAQ,KAAK;AACnB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AACrB,SAAK,qBAAqB;AAC1B,WAAO,OAAO,aAAa,QAAQ,QAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAkC;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,qBAAqB,QAA+B;AACxD,QAAI,CAAC,KAAK,aAAc;AACxB,SAAK,QAAQ,KAAK,4BAA4B,EAAE,MAAM,EAAE,QAAQ,eAAe,KAAK,cAAA,EAAc,CAAG;AACrG,UAAM,KAAK,eAAA;AAGX,QAAI,KAAK,mBAAmB,OAAO,KAAK,KAAK,QAAQ;AACnD,YAAM,QAAQ,KAAK,iBAAiB,KAAK,OAAO;AAChD,UAAI,OAAO;AACT,aAAK,2BAA2B,KAAK,oBAAoB,EAAE,MAAM,CAAC,QAAiB;AACjF,eAAK,QAAQ,KAAK,iDAAiD,EAAE,MAAM,EAAE,OAAOZ,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,QACrG,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBQ,4BAA4B,OAAqB;AACvD,QAAI,KAAK,yBAA0B;AACnC,UAAM,UAAU,KAAK;AACrB,UAAM,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,OAAO,GAAG,GAAI;AAC3D,UAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,sBAAsB,IAAI,KAAK;AACtE,QAAI,eAAe,KAAO;AACxB,WAAK,QAAQ;AAAA,QACX;AAAA,QACA,EAAE,MAAM,EAAE,OAAO,UAAU,KAAK,yBAAuB;AAAA,MAAE;AAE3D,WAAK,yBAAyB;AAC9B;AAAA,IACF;AACA,QAAI,YAAY,GAAG;AACjB,WAAK,QAAQ;AAAA,QACX;AAAA,QACA,EAAE,MAAM,EAAE,MAAA,EAAM;AAAA,MAAE;AAAA,IAEtB;AACA,SAAK,2BAA2B,WAAW,MAAM;AAC/C,WAAK,2BAA2B;AAGhC,UAAI,KAAK,aAAc;AACvB,UAAI,KAAK,mBAAmB,SAAS,GAAG;AACtC,aAAK,yBAAyB;AAC9B,aAAK,uBAAuB;AAC5B;AAAA,MACF;AACA,WAAK,2BAA2B,KAAK,oBAAoB;AAAA,IAC3D,GAAG,SAAS;AAAA,EACd;AAAA,EAGQ,sBAA4B;AAClC,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,QAAA;AAClB,WAAK,eAAe;AAAA,IACtB;AACA,SAAK,iBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,mBAAmB,QAA4B;AACrD,SAAK,QAAQ,KAAK,0BAA0B;AAS5C,QAAI,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,WAAW,eAAe,GAAG;AAC5E,WAAK,QAAQ,KAAK,oEAAoE;AAAA,QACpF,MAAM,EAAE,KAAK,OAAO,IAAA;AAAA,MAAI,CACzB;AACD,WAAK,UAAU;AACf,WAAK,oBAAA;AACL,WAAK,kBAAA;AACL;AAAA,IACF;AAEA,UAAM,MAAM,OAAO,OAAO,WAAW,KAAK,MAAM,WAAW,OAAO,SAAS,KAAK,IAAc;AAC9F,QAAI,CAAC,KAAK;AAKR,WAAK,QAAQ,KAAK,qEAAqE;AACvF,WAAK,UAAU;AACf,WAAK,oBAAA;AACL,WAAK,kBAAA;AACL;AAAA,IACF;AAEA,UAAM,SAAS,IAAI;AAAA,MACjB;AAAA,QACE,KAAK,OAAO;AAAA,QACZ;AAAA,QACA,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,MAAM,SAAS,MAAM,CAAA;AAAA,MAAC;AAAA,MAEhE,KAAK,wBAAwB,WAAW,MAAM,KAAK,sBAAsB;AAAA,IAAA;AAG3E,SAAK,gBAAgB;AAErB,WAAO,QAAA,EAAU,MAAM,CAAC,QAAQ;AAC9B,UAAI,KAAK,cAAc,KAAK,WAAY;AACxC,WAAK,QAAQ,KAAK,0BAA0B;AAAA,QAC1C,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,GAAG,GAAG,KAAK,iBAAA,EAAiB;AAAA,MAAE,CACxD;AACD,WAAK,qBAAA;AACL,WAAK,UAAU;AASf,WAAK,oBAAA;AACL,WAAK,kBAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEQ,uBAA6B;AACnC,QAAI,KAAK,eAAe;AACtB,WAAK,cAAc,QAAA;AACnB,WAAK,gBAAgB;AAAA,IACvB;AACA,SAAK,iBAAA;AAKL,SAAK,yBAAA;AAAA,EACP;AAAA;AAAA,EAGQ,mBAAyB;AAC/B,QAAI,KAAK,iBAAiB;AACxB,WAAK,gBAAgB,MAAA;AACrB,WAAK,kBAAkB;AAAA,IACzB;AACA,QAAI,KAAK,iBAAiB;AACxB,WAAK,gBAAgB,MAAA;AACrB,WAAK,kBAAkB;AAAA,IACzB;AACA,QAAI,KAAK,mBAAmB;AAC1B,YAAM,UAAU,KAAK;AACrB,WAAK,oBAAoB;AACzB,WAAK,QAAQ,MAAA,EAAQ,MAAM,CAAC,QAAQ;AAClC,aAAK,QAAQ,KAAK,qCAAqC;AAAA,UACrD,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5B;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,2BAA2B,SAAwC;AAC/E,UAAM,QAAQ,KAAK,iBAAiB,KAAK,QAAQ,cAAc;AAE/D,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,QAAQ,KAAK,gEAAgE;AAClF;AAAA,IACF;AAGA,UAAM,YAAY,MAAM,KAAK,WAAW,cAAc,EAAE,OAAO;AAC/D,QAAI,CAAC,WAAW;AAEd,WAAK,uBAAuB;AAC5B,WAAK,4BAA4B,KAAK;AACtC;AAAA,IACF;AAIA,QAAI,KAAK,0BAA0B;AACjC,mBAAa,KAAK,wBAAwB;AAC1C,WAAK,2BAA2B;AAAA,IAClC;AACA,SAAK,yBAAyB;AAK9B,UAAM,iBAAiB,KAAK,qBAAA;AAI5B,UAAM,QAAQ,KAAK,SAAS,QAAQ,GAAG;AACvC,UAAM,kBAAkB,SAAS,IAC7B,OAAO,SAAS,KAAK,SAAS,MAAM,GAAG,KAAK,GAAG,EAAE,IACjD,OAAO,SAAS,KAAK,UAAU,EAAE;AACrC,UAAM,SAAS;AAAA,MACb;AAAA,MACA,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,OAAO,SAAS,SAAS;AAAA,MACzB,GAAI,OAAO,SAAS,eAAe,IAAI,EAAE,UAAU,gBAAA,IAAoB,CAAA;AAAA,MACvE,KAAK,UAAU,KAAK,QAAQ;AAAA,IAAA;AAE9B,SAAK,sBAAsB;AAE3B,UAAM,EAAE,WAAW,OAAA,IAAW,MAAM,KAAK,WAAW,cAAc,MAAM;AACxE,UAAM,QAAQ,IAAI,oBAAoB,KAAK,YAAY,SAAS;AAChE,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAUrB,UAAM,UAAU,CAAC,UAAwB;AACvC,WAAK;AACL,UAAI,KAAK,sBAAsB,GAAG;AAChC,aAAK,QAAQ,KAAK,uBAAuB;AAAA,UACvC,MAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,QAAQ,QAAQ,MAAM,OAAA;AAAA,QAAO,CACxE;AAAA,MACH;AAEA,UAAI,KAAK,oBAAoB,OAAO,GAAG;AACrC,cAAM,SAAA,EAAW,KAAK,CAAC,UAAU;AAC/B,eAAK,qBAAqB;AAAA,QAC5B,CAAC,EAAE,MAAM,MAAM;AAAA,QAA2C,CAAC;AAAA,MAC7D;AACA,WAAK,mBAAmB,KAAK;AAAA,IAC/B;AAGA,UAAM,aAAa,OAAO,EAAE,MAAM,CAAC,QAAiB;AAClD,WAAK,QAAQ,KAAK,yBAAyB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,IAC7E,CAAC;AAID,UAAM,OAAO,MAAM,KAAK,WAAW,QAAA;AACnC,QAAI,KAAK,YAAY;AACnB,YAAM,WAAW,KAAK,WAAW,OAAA;AACjC,YAAM,SAAS,UAAU,UAAU,UAAU;AAC7C,YAAM,WAAW,GAAG,QAAQ,WAAW,SAAS,SAAS,MAAM;AAE/D,YAAM,SAAS,MAAM;AACnB,aAAK,QAAQ,KAAK,yCAAyC;AAAA,UACzD,MAAM,EAAE,UAAU,MAAA;AAAA,QAAM,CACzB;AACD,cAAM,WAAW,QAAQ,EAAE,MAAM,CAAC,QAAiB;AACjD,eAAK,QAAQ,MAAM,4BAA4B,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,QACjF,CAAC;AAAA,MACH;AAEA,UAAI,KAAK,uBAAuB;AAC9B,eAAA;AAAA,MACF,OAAO;AACL,aAAK,QAAQ,KAAK,8DAA8D;AAChF,aAAK,sBAAsB;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,8BAA4F;AAClG,WAAO,CAAC,UAAU;AAIhB,iBAAW,OAAO,KAAK,iBAAiB,OAAA,GAAU;AAChD,YAAI,SAAS,KAAK;AAClB,YAAI;AAAA,MACN;AACA,UAAI,KAAK,iBAAiB,OAAO,GAAG;AAClC,cAAM,MAAM,IAAI,aAAa,MAAM,KAAK,QAAQ,MAAM,KAAK,YAAY,MAAM,KAAK,aAAa,CAAC;AAChG,cAAM,QAAQ,IAAI,WAAW,IAAI,MAAM;AACvC,iBAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,gBAAM,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC,CAAE,CAAC;AAC3C,gBAAM,CAAC,IAAI,IAAI,IAAI,IAAI,QAAS,IAAI;AAAA,QACtC;AACA,cAAM,OAAO,WAAW,KAAK;AAC7B,cAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,aAAa,GAAI,CAAC;AAC7D,cAAM,YAAY,QAAQ,IAAI,eAAe,MAAM,KAAK,IAAI;AAa5D,cAAM,wBAAwB;AAC9B,cAAM,SAAS,MAAM;AACrB,YAAI,cAAc;AAOlB,YAAI,gBAAgB;AACpB,eAAO,cAAc,UAAU,QAAQ;AACrC,gBAAM,MAAM,KAAK,IAAI,cAAc,uBAAuB,UAAU,MAAM;AAC1E,gBAAM,QAAQ,UAAU,SAAS,aAAa,GAAG;AACjD,gBAAM,SAAwB;AAAA,YAC5B,MAAM;AAAA,YACN,MAAM,OAAO,KAAK,KAAK;AAAA,YACvB,KAAK,SAAS;AAAA,YACd,KAAK,SAAS;AAAA,YACd,UAAU;AAAA,YACV,OAAO;AAAA,UAAA;AAET,qBAAW,MAAM,KAAK,kBAAkB;AACtC,eAAG,MAAM;AAAA,UACX;AACA,wBAAc;AACd,2BAAiB;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,qCACN,MACmE;AACnE,UAAM,QAAQ,KAAK,MAAM,YAAA;AACzB,UAAM,MAAM,UAAU,KAAK,QAAQ;AACnC,QAAI,UAAU,OAAO;AACnB,aAAO;AAAA,QACL,OAAO;AAAA,QACP,kBAAkB,KAAK;AAAA,QACvB,gBAAgB,KAAK;AAAA,QACrB,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAA,IAAc,CAAA;AAAA,QACrD,kBAAkB;AAAA,QAClB,gBAAgB;AAAA,QAChB;AAAA,MAAA;AAAA,IAEJ;AACA,QAAI,UAAU,QAAQ;AACpB,aAAO;AAAA,QACL,OAAO;AAAA,QACP,kBAAkB,KAAK;AAAA,QACvB,gBAAgB,KAAK;AAAA,QACrB,kBAAkB;AAAA,QAClB,gBAAgB;AAAA,QAChB;AAAA,MAAA;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,6BACN,YACmE;AACnE,UAAM,aAAa,WAAW,MAAM,YAAA;AACpC,UAAM,OAAO,WAAW;AACxB,UAAM,MAAM,UAAU,KAAK,QAAQ;AAEnC,QAAI,eAAe,mBAAmB,eAAe,OAAO;AAC1D,UAAI;AACJ,UAAI,mBAAmB,WAAW;AAClC,UAAI,iBAAiB,WAAW;AAChC,YAAM,YAAY,KAAK,QAAQ,KAAK,KAAK,QAAQ;AACjD,UAAI,WAAW;AACb,YAAI;AACF,gBAAM,MAAMF,2BAAyB,SAAS;AAC9C,6BAAmB,IAAI;AACvB,2BAAiB,IAAI,gBAAgB,IAAI,IAAI,gBAAgB;AAC7D,sBAAY,WAAW,SAAS;AAAA,QAClC,SAAS,KAAK;AACZ,eAAK,QAAQ,KAAK,iDAAiD;AAAA,YACjE,MAAM,EAAE,WAAW,OAAOE,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CACvC;AAAA,QACH;AAAA,MACF;AACA,YAAM,aAAa,aAAa,MAAM,cAAc,EAAE;AACtD,YAAM,cAAc,aAAa,MAAM,eAAe,CAAC;AACvD,YAAM,mBAAmB,aAAa,MAAM,oBAAoB,WAAW;AAC3E,aAAO;AAAA,QACL,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,GAAI,YAAY,EAAE,UAAA,IAAc,CAAA;AAAA,QAChC,kBAAkB;AAAA,QAClB,gBAAgB;AAAA,QAChB;AAAA,QACA,SAAS,EAAE,YAAY,aAAa,iBAAA;AAAA,MAAiB;AAAA,IAEzD;AAEA,QAAI,eAAe,QAAQ;AACzB,aAAO;AAAA,QACL,OAAO;AAAA,QACP,kBAAkB,WAAW;AAAA,QAC7B,gBAAgB,WAAW;AAAA,QAC3B,kBAAkB;AAAA,QAClB,gBAAgB;AAAA,QAChB;AAAA,MAAA;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aACP,MACA,KACA,UACQ;AACR,QAAM,MAAM,KAAK,GAAG,KAAK,KAAK,IAAI,YAAA,CAAa,KAAK,KAAK,IAAI,YAAA,CAAa;AAC1E,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,IAAI,OAAO,GAAG;AACpB,SAAO,OAAO,SAAS,CAAC,KAAK,IAAI,IAAI,IAAI;AAC3C;AAEA,SAAS,WAAW,KAAyB;AAC3C,QAAM,UAAU,IAAI,QAAQ,QAAQ,EAAE;AACtC,MAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B,UAAM,IAAI,MAAM,2CAA2C,QAAQ,MAAM,GAAG;AAAA,EAC9E;AACA,QAAM,MAAM,IAAI,WAAW,QAAQ,SAAS,CAAC;AAC7C,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,OAAO,SAAS,QAAQ,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AACzD,QAAI,OAAO,MAAM,IAAI,GAAG;AACtB,YAAM,IAAI,MAAM,sCAAsC,IAAI,CAAC,EAAE;AAAA,IAC/D;AACA,QAAI,CAAC,IAAI;AAAA,EACX;AACA,SAAO;AACT;ACxoFO,SAAS,sBAAsB,KAA8C;AAClF,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAU,IAAgC,YAAY;AAC5D,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO;AAAA,IACL,qBAAqB,MAAM,OAAO,oBAAoB,MAAA;AAAA,IACtD,WAAW,CAAC,UAAU,OAAO,UAAU,MAAM,KAAK;AAAA,IAClD,qBAAqB,CAAC,UAAU,OAAO,oBAAoB,OAAO,KAAK;AAAA,IACvE,qBAAqB,CAAC,UAAU,OAAO,oBAAoB,OAAO,KAAK;AAAA,IACvE,cAAc,CAAC,UAAU,OAAO,aAAa,OAAO,KAAK;AAAA,IACzD,kBAAkB,CAAC,UAAU,OAAO,iBAAiB,OAAO,KAAK;AAAA,IACjE,SAAS,CAAC,UAAU,OAAO,QAAQ,MAAM,KAAK;AAAA,IAC9C,SAAS,CAAC,UAAU,OAAO,QAAQ,OAAO,KAAK;AAAA,IAC/C,aAAa,CAAC,UAAU,OAAO,YAAY,MAAM,KAAK;AAAA,IACtD,aAAa,CAAC,UAAU,OAAO,YAAY,OAAO,KAAK;AAAA,EAAA;AAE3D;AC/DO,MAAM,YAAY;AAAA,EAkBvB,YACmB,QACjB,KACA,SACA,OACA;AAJiB,SAAA,SAAA;AAKjB,SAAK,MAAM;AACX,SAAK,UAAU;AACf,SAAK,QAAQ,SAAS;AACtB,SAAK,YAAYI,OAAAA,aAAa,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AAE3D,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,UAAI,KAAK,OAAQ;AACjB,WAAK,UAAU,MAAM,SAAS,OAAO;AACrC,WAAK,cAAA;AAAA,IACP,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AAAE,WAAK,SAAS;AAAM,WAAK,UAAA;AAAA,IAAY,CAAC;AACjE,WAAO,GAAG,SAAS,MAAM;AAAE,WAAK,SAAS;AAAM,WAAK,UAAA;AAAA,IAAY,CAAC;AAAA,EACnE;AAAA,EApCiB;AAAA,EACA,SAAuB,CAAA;AAAA,EAChC,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,cAAsB,KAAK,IAAA;AAAA;AAAA,EAEpC,YAAY;AAAA;AAAA,EAEZ,YAAY;AAAA;AAAA,EAGH;AAAA,EAuBjB,eAAuB;AAAE,WAAO,KAAK;AAAA,EAAU;AAAA,EAC/C,YAAqB;AAAE,WAAO,KAAK,WAAW,CAAC,KAAK;AAAA,EAAO;AAAA,EAC3D,UAAmB;AAAE,WAAO,KAAK;AAAA,EAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQvC,UAQE;AACA,UAAM,WAAW,KAAK,OAAO,iBAAiB;AAC9C,UAAM,aAAa,KAAK,OAAO,cAAc;AAC7C,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,YAAY,GAAG,QAAQ,IAAI,UAAU;AAAA,MACrC,SAAS,KAAK,UAAA;AAAA,MACd,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,IAAA;AAAA,EAEpB;AAAA;AAAA,EAGA,QAAQ,QAAyB;AAC/B,QAAI,CAAC,KAAK,WAAW,KAAK,OAAQ;AAClC,QAAI,KAAK,OAAO,WAAW,EAAG;AAG9B,QAAI,KAAK,OAAO,iBAAiB,kBAAkB;AACjD,WAAK,MAAA;AACL;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,OAAO,CAAC,EAAG;AAChC,UAAM,QAAQ,OAAO,YAAY,IAAI,OAAO,KAAK,MAAM;AACvD,UAAM,CAAC,IAAI;AACX,UAAM,CAAC,IAAI;AACX,UAAM,cAAc,OAAO,KAAK,QAAQ,CAAC;AACzC,WAAO,KAAK,KAAK,OAAO,CAAC;AAEzB,SAAK,OAAO,MAAM,KAAK;AACvB,SAAK,YAAY,KAAK,IAAA;AACtB,SAAK,aAAa,MAAM;AAAA,EAC1B;AAAA;AAAA,EAGA,aAAa,QAAyB;AACpC,QAAI,CAAC,KAAK,WAAW,KAAK,UAAU,KAAK,MAAO;AAChD,QAAI,KAAK,OAAO,SAAS,EAAG;AAE5B,QAAI,KAAK,OAAO,iBAAiB,kBAAkB;AACjD,WAAK,MAAA;AACL;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,OAAO,CAAC,EAAG;AAChC,UAAM,QAAQ,OAAO,YAAY,IAAI,OAAO,KAAK,MAAM;AACvD,UAAM,CAAC,IAAI;AACX,UAAM,CAAC,IAAI;AACX,UAAM,cAAc,OAAO,KAAK,QAAQ,CAAC;AACzC,WAAO,KAAK,KAAK,OAAO,CAAC;AAEzB,SAAK,OAAO,MAAM,KAAK;AACvB,SAAK,YAAY,KAAK,IAAA;AACtB,SAAK,aAAa,MAAM;AAAA,EAC1B;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,UAAU;AACf,QAAI;AAAE,WAAK,OAAO,QAAA;AAAA,IAAU,QAAQ;AAAA,IAAuB;AAC3D,SAAK,UAAA;AAAA,EACP;AAAA,EAEQ,gBAAsB;AAC5B,WAAO,MAAM;AAEX,UAAI,KAAK,OAAO,SAAS,KAAK,KAAK,OAAO,WAAW,CAAC,MAAM,mBAAmB;AAC7E,YAAI,KAAK,OAAO,SAAS,EAAG;AAC5B,cAAM,MAAO,KAAK,OAAO,WAAW,CAAC,KAAK,IAAK,KAAK,OAAO,WAAW,CAAC;AACvE,YAAI,KAAK,OAAO,SAAS,IAAI,IAAK;AAClC,aAAK,SAAS,KAAK,OAAO,MAAM,IAAI,GAAG;AACvC;AAAA,MACF;AAEA,YAAM,SAAS,KAAK,OAAO,QAAQ,UAAU;AAC7C,UAAI,SAAS,EAAG;AAEhB,YAAM,cAAc,KAAK,OAAO,MAAM,GAAG,MAAM;AAC/C,WAAK,SAAS,KAAK,OAAO,MAAM,SAAS,CAAC;AAE1C,YAAM,UAAU,KAAK,aAAa,WAAW;AAC7C,UAAI,QAAS,MAAK,cAAc,OAAO;AAAA,IACzC;AAAA,EACF;AAAA,EAEQ,aAAa,MAAkC;AACrD,UAAM,QAAQ,KAAK,MAAM,MAAM;AAC/B,UAAM,YAAY,MAAM,CAAC;AACzB,QAAI,CAAC,UAAW,QAAO;AAEvB,UAAM,QAAQ,UAAU,MAAM,GAAG;AACjC,QAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,UAAM,8BAAc,IAAA;AACpB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,WAAW,MAAM,CAAC,EAAG,QAAQ,GAAG;AACtC,UAAI,WAAW,EAAG;AAClB,YAAM,MAAM,MAAM,CAAC,EAAG,MAAM,GAAG,QAAQ,EAAE,KAAA,EAAO,YAAA;AAChD,YAAM,QAAQ,MAAM,CAAC,EAAG,MAAM,WAAW,CAAC,EAAE,KAAA;AAC5C,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AAEA,WAAO;AAAA,MACL,QAAQ,MAAM,CAAC;AAAA,MACf,KAAK,MAAM,CAAC;AAAA,MACZ,MAAM,SAAS,QAAQ,IAAI,MAAM,KAAK,KAAK,EAAE;AAAA,MAC7C;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEQ,cAAc,KAAwB;AAC5C,YAAQ,IAAI,QAAA;AAAA,MACV,KAAK;AAAW,eAAO,KAAK,cAAc,GAAG;AAAA,MAC7C,KAAK;AAAY,eAAO,KAAK,eAAe,GAAG;AAAA,MAC/C,KAAK;AAAS,eAAO,KAAK,YAAY,GAAG;AAAA,MACzC,KAAK;AAAQ,eAAO,KAAK,WAAW,GAAG;AAAA,MACvC,KAAK;AAAY,eAAO,KAAK,eAAe,GAAG;AAAA,MAC/C,KAAK;AAAiB,eAAO,KAAK,QAAQ,KAAK,KAAK,IAAI;AAAA,MACxD;AAAS,eAAO,KAAK,QAAQ,KAAK,KAAK,oBAAoB;AAAA,IAAA;AAAA,EAE/D;AAAA,EAEQ,cAAc,KAAwB;AAC5C,SAAK,QAAQ,KAAK,KAAK,MAAM;AAAA,MAC3B,UAAU,aAAa,KAAK,IAAI;AAAA,IAAA,CACjC;AAAA,EACH;AAAA,EAEQ,eAAe,KAAwB;AAC7C,SAAK,QAAQ,KAAK,KAAK,MAAM;AAAA,MAC3B,gBAAgB;AAAA,MAChB,kBAAkB,OAAO,OAAO,WAAW,KAAK,GAAG,CAAC;AAAA,IAAA,GACnD,KAAK,GAAG;AAAA,EACb;AAAA,EAEQ,YAAY,KAAwB;AAC1C,UAAM,YAAY,IAAI,QAAQ,IAAI,WAAW,KAAK;AAClD,UAAM,mBAAmB,UAAU,MAAM,yBAAyB;AAClE,UAAM,aAAa,mBAAmB,SAAS,iBAAiB,CAAC,GAAI,EAAE,IAAI;AAC3E,UAAM,cAAc,mBAAmB,SAAS,iBAAiB,CAAC,GAAI,EAAE,IAAI;AAE5E,SAAK,OAAO,KAAK,EAAE,SAAS,KAAK,OAAO,QAAQ,YAAY,aAAa;AAEzE,SAAK,QAAQ,KAAK,KAAK,MAAM;AAAA,MAC3B,aAAa,mCAAmC,UAAU,IAAI,WAAW;AAAA,MACzE,WAAW,KAAK;AAAA,IAAA,CACjB;AAAA,EACH;AAAA,EAEQ,WAAW,KAAwB;AACzC,SAAK,UAAU;AACf,SAAK,QAAQ,KAAK,KAAK,MAAM;AAAA,MAC3B,WAAW,KAAK;AAAA,MAChB,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA,EAEQ,eAAe,KAAwB;AAC7C,SAAK,QAAQ,KAAK,KAAK,IAAI;AAC3B,SAAK,MAAA;AAAA,EACP;AAAA,EAEQ,QAAQ,KAAkB,MAAc,QAAgB,SAAkC,MAAqB;AACrH,QAAI,KAAK,OAAQ;AAEjB,QAAI,WAAW,YAAY,IAAI,IAAI,MAAM;AAAA,QAAa,IAAI,IAAI;AAAA;AAC9D,QAAI,SAAS;AACX,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC5C,oBAAY,GAAG,CAAC,KAAK,CAAC;AAAA;AAAA,MACxB;AAAA,IACF;AACA,gBAAY;AACZ,QAAI,KAAM,aAAY;AAEtB,SAAK,OAAO,MAAM,QAAQ;AAAA,EAC5B;AACF;AC5OO,MAAM,iBAAiB;AAAA,EACpB,SAA4B;AAAA,EAC5B,OAAO;AAAA;AAAA,EAEE,kCAAkB,IAAA;AAAA;AAAA,EAElB,mCAAmB,IAAA;AAAA;AAAA,EAEnB,mCAAmB,IAAA;AAAA;AAAA,EAEnB,oCAAoB,IAAA;AAAA;AAAA,EAGrC,OAAO,gBAAwB;AAC7B,WAAOS,mBAAY,EAAE,EAAE,SAAS,KAAK;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmB,UAAkB,YAA4B,eAAgC;AAE/F,UAAM,WAAW,KAAK,aAAa,IAAI,QAAQ;AAC/C,QAAI,UAAU;AACZ,WAAK,YAAY,OAAO,QAAQ;AAChC,WAAK,aAAa,OAAO,QAAQ;AAAA,IACnC;AAEA,UAAM,QAAQ,iBAAiB,iBAAiB,cAAA;AAChD,SAAK,YAAY,IAAI,OAAO,UAAU;AACtC,SAAK,aAAa,IAAI,UAAU,KAAK;AACrC,SAAK,aAAa,IAAI,OAAO,QAAQ;AACrC,WAAO;AAAA,EACT;AAAA,EAEA,qBAAqB,UAAwB;AAC3C,UAAM,QAAQ,KAAK,aAAa,IAAI,QAAQ;AAC5C,QAAI,OAAO;AACT,WAAK,YAAY,OAAO,KAAK;AAC7B,WAAK,aAAa,OAAO,KAAK;AAAA,IAChC;AACA,SAAK,aAAa,OAAO,QAAQ;AAAA,EACnC;AAAA;AAAA,EAGA,gBAAgB,UAAiC;AAC/C,UAAM,WAAW,KAAK,aAAa,IAAI,QAAQ;AAC/C,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,aAAa,KAAK,YAAY,IAAI,QAAQ;AAChD,QAAI,CAAC,WAAY,QAAO;AAGxB,SAAK,YAAY,OAAO,QAAQ;AAChC,SAAK,aAAa,OAAO,QAAQ;AAGjC,UAAM,WAAW,iBAAiB,cAAA;AAClC,SAAK,YAAY,IAAI,UAAU,UAAU;AACzC,SAAK,aAAa,IAAI,UAAU,QAAQ;AACxC,SAAK,aAAa,IAAI,UAAU,QAAQ;AACxC,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,SAAS,UAAsC;AAC7C,WAAO,KAAK,aAAa,IAAI,QAAQ;AAAA,EACvC;AAAA;AAAA,EAGA,eAA4C;AAC1C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,WAAW,UAAkB,SAAwB;AACnD,SAAK,cAAc,IAAI,UAAU,OAAO;AAAA,EAC1C;AAAA;AAAA,EAGA,UAAU,UAA2B;AACnC,WAAO,KAAK,cAAc,IAAI,QAAQ,KAAK;AAAA,EAC7C;AAAA;AAAA,EAGA,gBAA8C;AAC5C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,YAAY,QAA4C;AACtD,eAAW,CAAC,GAAG,CAAC,KAAK,QAAQ;AAC3B,WAAK,cAAc,IAAI,GAAG,CAAC;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,UAAkB;AAAE,WAAO,KAAK;AAAA,EAAK;AAAA,EAErC,MAAM,MAAM,MAA6B;AACvC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,SAASV,eAAI,aAAa,CAAC,WAAW,KAAK,iBAAiB,MAAM,CAAC;AACxE,WAAK,OAAO,GAAG,SAAS,MAAM;AAC9B,WAAK,OAAO,OAAO,MAAM,MAAM;AAC7B,cAAM,OAAO,KAAK,OAAQ,QAAA;AAC1B,aAAK,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,OAAO;AAC3D,gBAAA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAsB;AAC1B,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,CAAC,KAAK,QAAQ;AAAE,gBAAA;AAAW;AAAA,MAAO;AACtC,WAAK,OAAO,MAAM,MAAM,QAAA,CAAS;AACjC,WAAK,SAAS;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,QAA0B;AACjD,QAAI,gBAAgB;AAEpB,UAAM,SAAS,CAAC,UAAkB;AAChC,uBAAiB,MAAM,SAAS,OAAO;AAGvC,YAAM,SAAS,cAAc,QAAQ,UAAU;AAC/C,UAAI,SAAS,EAAG;AAEhB,aAAO,eAAe,QAAQ,MAAM;AAGpC,YAAM,YAAY,cAAc,MAAM,MAAM,EAAE,CAAC,KAAK;AACpD,YAAM,WAAW,UAAU,MAAM,uBAAuB;AACxD,YAAM,MAAM,WAAW,SAAS,CAAC,IAAK;AAGtC,YAAM,YAAY,IAAI,MAAM,+BAA+B;AAC3D,YAAM,aAAa,YAAY,UAAU,CAAC,EAAG,QAAQ,OAAO,EAAE,EAAE,QAAQ,kBAAkB,EAAE,IAAI;AAGhG,YAAM,UAAU,WAAW,SAAS,QAAQ;AAC5C,YAAM,aAAa,UAAU,WAAW,MAAM,GAAG,CAAC,SAAS,MAAM,IAAI;AAErE,YAAM,aAAa,KAAK,YAAY,IAAI,UAAU;AAClD,UAAI,CAAC,YAAY;AACf,eAAO,MAAM;AAAA;AAAA;AAAA,CAA2C;AACxD,eAAO,QAAA;AACP;AAAA,MACF;AAGA,YAAM,WAAW,KAAK,aAAa,IAAI,UAAU;AACjD,UAAI,YAAY,CAAC,KAAK,UAAU,QAAQ,GAAG;AACzC,eAAO,MAAM;AAAA;AAAA;AAAA,CAA2C;AACxD,eAAO,QAAA;AACP;AAAA,MACF;AAEA,YAAM,MAAM,UACP,WAAW,YAAA,KAAiB,WAAW,OAAA,KAAY,KACnD,WAAW,OAAA,KAAY;AAC5B,YAAM,UAAU,IAAI,YAAY,QAAQ,KAAK,MAAM;AACjD,mBAAW,cAAc,QAAQ,cAAc;AAAA,MACjD,GAAG,OAAO;AACV,iBAAW,WAAW,OAAO;AAG7B,aAAO,QAAQ,OAAO,KAAK,eAAe,OAAO,CAAC;AAAA,IACpD;AAEA,WAAO,GAAG,QAAQ,MAAM;AACxB,WAAO,GAAG,SAAS,MAAM;AAAA,IAAC,CAAC;AAAA,EAC7B;AACF;ACjKO,MAAM,yBAAyB;AAAA,EAIpC,YACmB,QACA,QACjB;AAFiB,SAAA,SAAA;AACA,SAAA,SAAA;AAAA,EAChB;AAAA,EANK,iBAAwC;AAAA,EACxC,mBAA4C;AAAA;AAAA,EASpD,kBAAkB,WAAiC;AACjD,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,oBAAoB,WAAmC;AACrD,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,qBAAqB,QAA4C;AAC/D,SAAK,OAAO,YAAY,MAAM;AAAA,EAChC;AAAA;AAAA,EAIA,cAAsB;AACpB,WAAO,KAAK,OAAO,QAAA;AAAA,EACrB;AAAA,EAEA,cAAc,UAAiD;AAC7D,UAAM,OAAO,KAAK,OAAO,QAAA;AACzB,QAAI,SAAS,EAAG,QAAO,CAAA;AACvB,UAAM,OAAO,YAAY;AACzB,UAAM,SAA8B,CAAA;AACpC,eAAW,CAAC,UAAU,KAAK,KAAK,KAAK,OAAO,gBAAgB;AAC1D,aAAO,KAAK;AAAA,QACV;AAAA,QACA,KAAK,UAAU,IAAI,IAAI,IAAI,IAAI,KAAK;AAAA,QACpC,UAAU,UAAU,IAAI,IAAI,IAAI,IAAI,KAAK;AAAA,QACzC,SAAS,KAAK,OAAO,UAAU,QAAQ;AAAA,MAAA,CACxC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,oBAAoB,UAAkB,UAAiD;AACrF,UAAM,OAAO,KAAK,OAAO,QAAA;AACzB,QAAI,SAAS,EAAG,QAAO,CAAA;AACvB,UAAM,OAAO,YAAY;AACzB,UAAM,SAAS,GAAG,QAAQ;AAC1B,UAAM,SAA8B,CAAA;AACpC,eAAW,CAAC,UAAU,KAAK,KAAK,KAAK,OAAO,gBAAgB;AAC1D,UAAI,CAAC,SAAS,WAAW,MAAM,EAAG;AAClC,aAAO,KAAK;AAAA,QACV;AAAA,QACA,KAAK,UAAU,IAAI,IAAI,IAAI,IAAI,KAAK;AAAA,QACpC,UAAU,UAAU,IAAI,IAAI,IAAI,IAAI,KAAK;AAAA,QACzC,SAAS,KAAK,OAAO,UAAU,QAAQ;AAAA,MAAA,CACxC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,UAAkB,UAA6C;AACtE,UAAM,QAAQ,KAAK,OAAO,SAAS,QAAQ;AAC3C,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,OAAO,KAAK,OAAO,QAAA;AACzB,UAAM,OAAO,YAAY;AACzB,WAAO;AAAA,MACL;AAAA,MACA,KAAK,UAAU,IAAI,IAAI,IAAI,IAAI,KAAK;AAAA,MACpC,UAAU,UAAU,IAAI,IAAI,IAAI,IAAI,KAAK;AAAA,MACzC,SAAS,KAAK,OAAO,UAAU,QAAQ;AAAA,IAAA;AAAA,EAE3C;AAAA,EAEA,gBAAgB,UAAiC;AAC/C,UAAM,QAAQ,KAAK,OAAO,gBAAgB,QAAQ;AAClD,QAAI,OAAO;AACT,WAAK,cAAA;AACL,WAAK,OAAO,KAAK,0BAA0B;AAAA,QACzC,MAAM,EAAE,UAAU,aAAa,MAAM,MAAM,GAAG,CAAC,EAAA;AAAA,MAAE,CAClD;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,UAAkB,SAAwB;AACnD,SAAK,OAAO,WAAW,UAAU,OAAO;AACxC,SAAK,eAAA;AACL,SAAK,OAAO,KAAK,+BAA+B,EAAE,MAAM,EAAE,UAAU,QAAA,GAAW;AAAA,EACjF;AAAA,EAEA,UAAU,UAA2B;AACnC,WAAO,KAAK,OAAO,UAAU,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACpB,QAAI,KAAK,gBAAgB;AACvB,WAAK,eAAe,KAAK,OAAO,aAAA,CAAc;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,kBAAkB;AACzB,WAAK,iBAAiB,KAAK,OAAO,cAAA,CAAe;AAAA,IACnD;AAAA,EACF;AACF;ACzFA,SAAS,eAAe,OAAmD;AACzE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,MAAM,YAAA;AAChB,MAAI,MAAM,UAAU,MAAM,SAAS,MAAM,OAAQ,QAAO;AACxD,MAAI,MAAM,UAAU,MAAM,UAAU,MAAM,UAAU,MAAM,OAAQ,QAAO;AACzE,SAAO;AACT;AAEA,SAAS,eACP,UACA,YACA,YACA,OACA,QACQ;AACR,SAAO,GAAG,QAAQ,MAAM,UAAU,MAAM,UAAU,IAAI,KAAK,IAAI,MAAM;AACvE;AAEA,SAAS,iBACP,QACA,UACmB;AACnB,MAAI,CAAC,SAAU,QAAO,WAAW,SAAS,YAAY;AACtD,SAAO,WAAW,SAAS,SAAS,cAAc,SAAS;AAC7D;AAEA,SAAS,qBAAqB,OAA4C;AACxE,UAAQ,OAAA;AAAA,IACN,KAAK;AACH,aAAO,CAAC,QAAQ,OAAO,QAAQ,QAAQ,OAAO,SAAS,OAAO,GAAG;AAAA,IACnE,KAAK;AACH,aAAO,CAAC,QAAQ,WAAW,QAAQ,OAAO,OAAO,SAAS,OAAO,GAAG;AAAA,IACtE,KAAK;AACH,aAAO,CAAC,QAAQ,aAAa,OAAO,QAAQ,OAAO,GAAG;AAAA,IACxD,KAAK;AACH,aAAO,CAAC,KAAK;AAAA,IACf;AACE,aAAO,CAAC,QAAQ,OAAO,QAAQ,MAAM;AAAA,EAAA;AAE3C;AAEO,MAAM,yBAAyB;AAAA,EACnB,8BAAc,IAAA;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAuC;AACjD,SAAK,SAAS,KAAK;AACnB,SAAK,MAAM,KAAK;AAChB,SAAK,qBAAqB,KAAK;AAC/B,SAAK,kBAAkB,KAAK;AAC5B,SAAK,gBAAgB,KAAK;AAC1B,SAAK,iBAAiB,KAAK,kBAAkB;AAC7C,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA,EAEA,MAAM,QAAQ,OAAoD;AAChE,UAAM,cAAgC,MAAM;AAC5C,UAAM,cAAgC,MAAM,cAAc;AAE1D,UAAM,SAAS,KAAK,gBAAgB,MAAM,UAAU,aAAa,MAAM,aAAa;AACpF,QAAI,QAAQ;AACV,YAAMW,OAAM;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,QACP;AAAA,QACA,OAAO,WAAW;AAAA,QAClB,OAAO,WAAW;AAAA,MAAA;AAEpB,aAAO,KAAK,cAAcA,MAAK,OAAO;AAAA,QACpC,KAAK,OAAO;AAAA,QACZ,YAAY,OAAO;AAAA,QACnB,YAAY;AAAA,QACZ,YAAY,OAAO;AAAA,QACnB,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,aAAaA;AAAAA,MAAA,IACX,IAAI;AAAA,IACV;AAEA,UAAM,eAAe,KAAK,oBAAoB,MAAM,UAAU,MAAM,aAAa;AACjF,QAAI,CAAC,gBAAgB,CAAC,aAAa,KAAK;AACtC,YAAM,IAAI;AAAA,QACR,6DAA6D,MAAM,QAAQ;AAAA,MAAA;AAAA,IAE/E;AAEA,UAAM,cAA+B,gBAAgB,SAChD,eAAe,aAAa,KAAK,KAAK,SACvC;AACJ,UAAM,mBAAmB,MAAM,iBAC1B,aAAa,cACb,EAAE,OAAO,MAAM,QAAQ,IAAA;AAE5B,UAAM,MAAM;AAAA,MACV,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,IAAA;AAGnB,UAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,QAAI,UAAU;AACZ,aAAO,KAAK,WAAW,QAAQ,EAAE;AAAA,IACnC;AAEA,UAAM,WAAW,MAAM,KAAK,qBAAA;AAC5B,UAAM,UAAU,iBAAiB,aAAa,QAAQ;AACtD,UAAM,cAAc,KAAK,oBAAoB,MAAM,UAAU,GAAG;AAChE,UAAM,QAAQ,KAAK,YAAY;AAAA,MAC7B,WAAW,aAAa;AAAA,MACxB,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,OAAO,iBAAiB;AAAA,MACxB,QAAQ,iBAAiB;AAAA,MACzB,OAAO;AAAA,MACP,KAAK,MAAM,OAAO,qBAAqB,MAAM,QAAQ;AAAA,IAAA,CACtD;AAED,UAAM,SAAoB;AAAA,MACxB,KAAK;AAAA,MACL,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,MACA,aAAa;AAAA,IAAA;AAEf,UAAM,QAAuB,EAAE,KAAK,QAAQ,OAAO,UAAU,GAAG,cAAc,KAAA;AAC9E,SAAK,QAAQ,IAAI,KAAK,KAAK;AAC3B,SAAK,OAAO,KAAK,8BAA8B;AAAA,MAC7C,MAAM,EAAE,KAAK,SAAS,WAAW,aAAa,KAAK,WAAW,YAAA;AAAA,IAAY,CAC3E;AAED,QAAI,OAAO;AACT,YAAM,KAAK,QAAQ,CAAC,MAAM,WAAW;AACnC,aAAK,OAAO,KAAK,6BAA6B;AAAA,UAC5C,MAAM,EAAE,KAAK,MAAM,OAAA;AAAA,QAAO,CAC3B;AACD,cAAM,MAAM,KAAK,QAAQ,IAAI,GAAG;AAChC,YAAI,OAAO,IAAI,UAAU,OAAO;AAC9B,eAAK,QAAQ,OAAO,GAAG;AAAA,QACzB;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,aAA8D;AACpE,UAAM,QAAQ,KAAK,QAAQ,IAAI,WAAW;AAC1C,QAAI,CAAC,MAAO,QAAO,EAAE,UAAU,OAAO,UAAU,EAAA;AAChD,UAAM,WAAW,KAAK,IAAI,GAAG,MAAM,WAAW,CAAC;AAC/C,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,EAAE,UAAU,MAAM,UAAU,MAAM,SAAA;AAAA,IAC3C;AACA,QAAI,MAAM,cAAc;AACtB,mBAAa,MAAM,YAAY;AAAA,IACjC;AACA,UAAM,eAAe,WAAW,MAAM;AACpC,YAAM,MAAM,KAAK,QAAQ,IAAI,WAAW;AACxC,UAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,WAAK,cAAc,GAAG;AAAA,IACxB,GAAG,KAAK,cAAc;AACtB,WAAO,EAAE,UAAU,MAAM,UAAU,EAAA;AAAA,EACrC;AAAA,EAEA,cAAoB;AAClB,eAAW,SAAS,KAAK,QAAQ,OAAA,GAAU;AACzC,WAAK,cAAc,KAAK;AAAA,IAC1B;AACA,SAAK,QAAQ,MAAA;AAAA,EACf;AAAA,EAEQ,gBACN,UACA,QACA,QACoG;AACpG,QAAI,WAAW,OAAQ,QAAO;AAC9B,UAAM,UAAU,KAAK,mBAAmB,QAAQ;AAChD,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,EAAE,IAAK;AACZ,YAAM,QAAQ,eAAe,EAAE,KAAK;AACpC,UAAI,UAAU,OAAQ;AACtB,UAAI,UAAU,EAAE,YAAY;AAC1B,YAAI,EAAE,WAAW,QAAQ,OAAO,SAAS,EAAE,WAAW,SAAS,OAAO,OAAQ;AAAA,MAChF;AACA,YAAM,aAAa,EAAE,cAAc,EAAE,OAAO,MAAM,QAAQ,IAAA;AAC1D,aAAO,EAAE,KAAK,EAAE,KAAK,YAAY,OAAO,WAAA;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,oBACN,UACA,QACqB;AACrB,UAAM,UAAU,KAAK,mBAAmB,QAAQ;AAChD,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAI,CAAC,OAAQ,QAAO,QAAQ,CAAC,KAAK;AAClC,QAAI,OAA4B;AAChC,QAAI,WAAW;AACf,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,EAAE,IAAK;AACZ,YAAM,IAAI,EAAE,YAAY,SAAS;AACjC,YAAM,IAAI,EAAE,YAAY,UAAU;AAClC,UAAI,IAAI,OAAO,SAAS,IAAI,OAAO,OAAQ;AAC3C,YAAM,OAAO,IAAI;AACjB,UAAI,OAAO,UAAU;AACnB,mBAAW;AACX,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,KAAM,QAAO;AACjB,QAAI,WAAgC;AACpC,QAAI,eAAe,OAAO;AAC1B,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,EAAE,IAAK;AACZ,YAAM,IAAI,EAAE,YAAY,SAAS;AACjC,YAAM,IAAI,EAAE,YAAY,UAAU;AAClC,YAAM,OAAO,IAAI;AACjB,UAAI,OAAO,cAAc;AACvB,uBAAe;AACf,mBAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,oBAAoB,UAAkB,aAA6B;AACzE,UAAM,OAAO,KAAK,cAAA;AAClB,UAAM,QAAQ,KAAK,QAAQ,IAAIV,oBAAa,MAAM,GAAG,CAAC,CAAC;AAEvD,QAAI,CAAC,MAAM;AACT,aAAO,oBAAoB,KAAK;AAAA,IAClC;AACA,WAAO,oBAAoB,IAAI,IAAI,KAAK;AAAA,EAC1C;AAAA,EAEA,MAAc,uBAAyD;AACrE,QAAI,CAAC,KAAK,IAAK,QAAO;AACtB,QAAI;AACF,YAAM,MAAM,KAAK;AACjB,YAAM,QAAQ,IAAI,eAAe;AACjC,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,YAAM,KAAM,MAAkC,qBAAqB;AACnE,UAAI,CAAC,MAAM,OAAO,OAAO,SAAU,QAAO;AAC1C,YAAM,QAAS,GAA+B,OAAO;AACrD,UAAI,OAAO,UAAU,WAAY,QAAO;AACxC,YAAM,WAAW;AACjB,aAAO,MAAM,SAAA;AAAA,IACf,SAAS,KAAK;AACZ,WAAK,OAAO,KAAK,sEAAsE;AAAA,QACrF,MAAM,EAAE,OAAOJ,MAAAA,OAAO,GAAG,EAAA;AAAA,MAAE,CAC5B;AACD,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,YAAY,MASI;AACtB,UAAM,OAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAmB;AAAA,MACnB;AAAA,MAAM,KAAK;AAAA,MACX;AAAA,MAAO,SAAS,KAAK,KAAK,IAAI,KAAK,MAAM;AAAA,MACzC;AAAA,MAAQ,KAAK;AAAA,MACb,GAAG,qBAAqB,KAAK,KAAK;AAAA,MAClC;AAAA,MAAM;AAAA,MACN;AAAA,MAAmB;AAAA,MACnB,KAAK;AAAA,IAAA;AAGP,QAAI;AACF,YAAM,QAAQe,mBAAAA,MAAM,KAAK,YAAY,MAAM,EAAE,OAAO,CAAC,UAAU,UAAU,MAAM,EAAA,CAAG;AAClF,YAAM,QAAQ,YAAY,MAAM;AAChC,YAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC1C,aAAK,OAAO,MAAM,UAAU,EAAE,MAAM,EAAE,KAAK,KAAK,KAAK,MAAM,MAAM,KAAA,EAAK,GAAK;AAAA,MAC7E,CAAC;AACD,YAAM,KAAK,SAAS,CAAC,QAAQ;AAC3B,aAAK,OAAO,MAAM,sBAAsB;AAAA,UACtC,MAAM,EAAE,KAAK,KAAK,KAAK,OAAOf,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC3C;AAAA,MACH,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,OAAO,MAAM,0BAA0B;AAAA,QAC1C,MAAM,EAAE,KAAK,KAAK,KAAK,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,MAAE,CAC3C;AACD,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,WAAW,OAAqC;AACtD,UAAM,YAAY;AAClB,QAAI,MAAM,cAAc;AACtB,mBAAa,MAAM,YAAY;AAC/B,YAAM,eAAe;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cACN,KACA,SACA,OACW;AACX,UAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,QAAI,UAAU;AACZ,WAAK,WAAW,QAAQ;AACxB,aAAO,SAAS;AAAA,IAClB;AACA,UAAM,SAAS,QAAA;AACf,UAAM,QAAuB,EAAE,KAAK,QAAQ,OAAO,UAAU,GAAG,cAAc,KAAA;AAC9E,SAAK,QAAQ,IAAI,KAAK,KAAK;AAC3B,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,OAA4B;AAChD,QAAI,MAAM,cAAc;AACtB,mBAAa,MAAM,YAAY;AAC/B,YAAM,eAAe;AAAA,IACvB;AACA,QAAI,MAAM,SAAS,CAAC,MAAM,MAAM,QAAQ;AACtC,UAAI;AACF,cAAM,MAAM,KAAK,SAAS;AAAA,MAC5B,SAAS,KAAK;AACZ,aAAK,OAAO,KAAK,qBAAqB;AAAA,UACpC,MAAM,EAAE,KAAK,MAAM,KAAK,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5C;AAAA,MACH;AAAA,IACF;AACA,SAAK,QAAQ,OAAO,MAAM,GAAG;AAAA,EAC/B;AACF;AC/TA,MAAM,aAAyC,oBAAI,IAAI,CAAC,aAAa,CAAC;AAGtE,SAAS,qBACP,KACA,aACmB;AACnB,aAAW,WAAWgB,yBAAmB;AACvC,QAAI,IAAI,OAAO,MAAM,YAAa,QAAO;AAAA,EAC3C;AACA,SAAO;AACT;AAQA,MAAM,0BAA0B;AAChC,MAAM,wBAAwB;AAG9B,SAAS,YAAY,UAAkB,aAA6B;AAClE,SAAO,GAAG,QAAQ,IAAI,WAAW;AACnC;AAEO,MAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBd,8BAAc,IAAA;AAAA;AAAA,EAEd,oCAAoB,IAAA;AAAA;AAAA,EAEpB,qCAAqB,IAAA;AAAA;AAAA,EAErB,kCAAkB,IAAA;AAAA;AAAA,EAElB,sCAAsB,IAAA;AAAA,EAC/B,0BAA6F;AAAA,EAC7F,sBAAkD;AAAA,EAClD,WAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,2CAA2B,IAAA;AAAA,EACpC;AAAA;AAAA,EAEA,cAAsC,CAAA;AAAA,EAC7B;AAAA,EACA;AAAA,EACA,aAAa,IAAI,iBAAA;AAAA,EACjB;AAAA,EACT,sCAAsB,IAAA;AAAA,EACtB,eAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS1C,MAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,eAAsF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKtF,mBAAoD;AAAA,EAE5D,YACE,gBACA,QACA;AACA,SAAK,iBAAiB,kBAAkB,CAAA;AACxC,SAAK,SAAS;AACd,SAAK,eAAe,IAAI,yBAAyB,KAAK,YAAY,OAAO,MAAM,eAAe,CAAC;AAAA,EACjG;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAAgD;AACtD,QAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAK,mBAAmB,IAAI,yBAAyB;AAAA,QACnD,QAAQ,KAAK,OAAO,MAAM,WAAW;AAAA,QACrC,KAAK,KAAK;AAAA,QACV,oBAAoB,CAAC,aAAa,KAAK,0BAA0B,QAAQ;AAAA,QACzE,iBAAiB,CAAC,aAAa;AAC7B,gBAAM,QAAQ,KAAK,aAAa,SAAS,QAAQ;AACjD,iBAAO,QAAQ,EAAE,KAAK,MAAM,QAAQ;AAAA,QACtC;AAAA,QACA,eAAe,MAAM,KAAK,aAAa,YAAA;AAAA,MAAY,CACpD;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAIA,MAAM,mBAAmB,OAAoD;AAC3E,WAAO,KAAK,sBAAsB,QAAQ,KAAK;AAAA,EACjD;AAAA,EAEA,MAAM,uBAAuB,OAAkF;AAC7G,WAAO,KAAK,oBAAA,EAAsB,QAAQ,MAAM,WAAW;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,mBAAmB,WAAgD;AACzE,UAAM,WAAW,KAAK,wBAAA;AACtB,UAAM,aAAa,MAUP;AACV,UAAI,CAAC,KAAK,IAAK,QAAO;AACtB,YAAM,UAAW,KAAK,IAAgC,SAAS;AAC/D,aAAO,UAAW,UAAiG;AAAA,IACrH;AAEA,WAAO;AAAA,MACL,eAAe,OAAO,UAAU;AAC9B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,cAAc,MAAM,KAAK;AACjD,YAAI,CAAC,SAAU,QAAO;AACtB,eAAO,SAAS,cAAc,KAAK;AAAA,MACrC;AAAA,MACA,SAAS,YAAY;AACnB,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,QAAQ,MAAA;AAChC,YAAI,CAAC,SAAU,QAAO,EAAE,IAAI,QAAQ,MAAM,aAAA;AAC1C,eAAO,SAAS,QAAA;AAAA,MAClB;AAAA,MACA,eAAe,OAAO,WAAW;AAC/B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,cAAc,MAAM,MAAM;AAClD,YAAI,CAAC,SAAU,OAAM,IAAI,MAAM,+BAA+B;AAC9D,eAAO,SAAS,cAAc,MAAM;AAAA,MACtC;AAAA,MACA,YAAY,OAAO,UAAU;AAC3B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,WAAW,MAAM,KAAK;AAC9C,YAAI,CAAC,SAAU;AACf,eAAO,SAAS,WAAW,KAAK;AAAA,MAClC;AAAA,MACA,YAAY,OAAO,UAAU;AAC3B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,WAAW,MAAM,KAAK;AAC9C,YAAI,CAAC,SAAU,QAAO,CAAA;AACtB,eAAO,SAAS,WAAW,KAAK;AAAA,MAClC;AAAA,MACA,gBAAgB,OAAO,UAAU;AAC/B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,eAAe,MAAM,KAAK;AAClD,YAAI,CAAC,SAAU;AACf,eAAO,SAAS,eAAe,KAAK;AAAA,MACtC;AAAA,MACA,YAAY,OAAO,UAAU;AAC3B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,WAAW,MAAM,KAAK;AAC9C,YAAI,CAAC,SAAU;AACf,eAAO,SAAS,WAAW,KAAK;AAAA,MAClC;AAAA,MACA,cAAc,OAAO,UAAU;AAC7B,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,aAAa,MAAM,KAAK;AAChD,YAAI,CAAC,SAAU;AACf,eAAO,SAAS,aAAa,KAAK;AAAA,MACpC;AAAA,MACA,UAAU,OAAO,UAAU;AACzB,cAAM,QAAQ,WAAA;AACd,YAAI,MAAO,QAAO,MAAM,SAAS,MAAM,KAAK;AAC5C,YAAI,CAAC,SAAU,QAAO,EAAE,UAAU,GAAG,WAAW,GAAG,iBAAiB,GAAG,eAAe,EAAA;AACtF,eAAO,SAAS,SAAS,KAAK;AAAA,MAChC;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEQ,0BAAgD;AACtD,QAAI,KAAK,eAAe,WAAW,EAAG,QAAO;AAI7C,UAAM,wBAAwB;AAE9B,UAAM,mCAAmB,IAAA;AACzB,UAAM,+BAAe,IAAA;AACrB,UAAM,oCAAoB,IAAA;AAC1B,QAAI,SAAS;AAEb,UAAM,iBAAiB,OAAO,UAAoD;AAChF,iBAAW,KAAK,KAAK,gBAAgB;AACnC,YAAI,MAAM,EAAE,cAAc,EAAE,MAAA,CAAO,EAAG,QAAO;AAAA,MAC/C;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,eAAe,OAAO,UAAW,MAAM,eAAe,MAAM,KAAK,MAAO;AAAA,MACxE,SAAS,YAAY;AACnB,cAAM,WAAW,KAAK,eAAe,CAAC;AACtC,YAAI,CAAC,SAAU,QAAO,EAAE,IAAI,QAAQ,MAAM,aAAA;AAC1C,eAAO,EAAE,IAAI,SAAS,IAAI,MAAM,SAAS,MAAM,YAAY,SAAS,YAAY,UAAU,SAAS,SAAA;AAAA,MACrG;AAAA,MACA,eAAe,OAAO,WAAW;AAC/B,cAAM,WAAW,MAAM,eAAe,OAAO,KAAK;AAClD,YAAI,CAAC,SAAU,OAAM,IAAI,MAAM,kCAAkC,OAAO,KAAK,GAAG;AAChF,cAAM,UAAU,MAAM,SAAS,cAAc,MAAM;AACnD,cAAM,YAAY,OAAO,EAAE,MAAM;AACjC,iBAAS,IAAI,WAAW,OAAO;AAC/B,cAAM,SAAS,IAAIC,MAAAA,WAAyB,qBAAqB;AACjE,qBAAa,IAAI,WAAW,MAAM;AAClC,cAAM,QAAQ,QAAQ,QAAQ,CAAC,UAAU;AAAE,iBAAO,KAAK,KAAK;AAAA,QAAE,CAAC;AAC/D,sBAAc,IAAI,WAAW,KAAK;AAClC,eAAO,EAAE,WAAW,QAAQ,QAAA;AAAA,MAC9B;AAAA,MACA,YAAY,OAAO,UAAU;AAC3B,cAAM,UAAU,SAAS,IAAI,MAAM,SAAS;AAC5C,YAAI,QAAS,SAAQ,WAAW,MAAM,MAAM;AAAA,MAC9C;AAAA,MACA,YAAY,OAAO,UAAU;AAC3B,cAAM,SAAS,aAAa,IAAI,MAAM,SAAS;AAC/C,YAAI,CAAC,OAAQ,QAAO,CAAA;AACpB,eAAO,OAAO,MAAM,MAAM,QAAQ;AAAA,MACpC;AAAA,MACA,gBAAgB,OAAO,UAAU;AAC/B,cAAM,UAAU,SAAS,IAAI,MAAM,SAAS;AAC5C,YAAI,SAAS;AACX,gBAAM,QAAQ,cAAc,IAAI,MAAM,SAAS;AAC/C,cAAI,MAAO,OAAA;AACX,mBAAS,OAAO,MAAM,SAAS;AAC/B,uBAAa,OAAO,MAAM,SAAS;AACnC,wBAAc,OAAO,MAAM,SAAS;AACpC,gBAAM,QAAQ,QAAA;AAAA,QAChB;AAAA,MACF;AAAA,MACA,YAAY,OAAO,UAAU;AAC3B,cAAM,UAAU,SAAS,IAAI,MAAM,SAAS;AAC5C,YAAI,SAAS,WAAY,OAAM,QAAQ,WAAW,MAAM,GAAG;AAAA,MAC7D;AAAA,MACA,cAAc,OAAO,UAAU;AAC7B,cAAM,UAAU,SAAS,IAAI,MAAM,SAAS;AAC5C,YAAI,QAAS,SAAQ,aAAa,MAAM,MAAM;AAAA,MAChD;AAAA,MACA,UAAU,OAAO,UAAU;AACzB,cAAM,UAAU,SAAS,IAAI,MAAM,SAAS;AAC5C,YAAI,CAAC,QAAS,QAAO,EAAE,UAAU,GAAG,WAAW,GAAG,iBAAiB,GAAG,eAAe,EAAA;AACrF,eAAO,QAAQ,SAAA;AAAA,MACjB;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA,EAIA,sBAAsB,QAAkC;AACtD,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,KAAqB;AAChC,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,uBAAuB,KAAmB;AACxC,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,YAAY,KAAsB;AAChC,SAAK,WAAW;AAChB,SAAK,0BAAA;AACL,SAAK,iCAAiC,GAAG;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBQ,iCAAiC,KAAsB;AAC7D,QAAI,UAAU,EAAE,UAAUC,MAAAA,cAAc,uBAAA,GAA0B,CAAC,UAAuB;AACxF,YAAM,OAAO,MAAM;AAInB,YAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,UAAI,aAAa,KAAM;AAIvB,YAAM,WAAW,KAAK,QAAQ,aAAa;AAC3C,WAAK,+BAA+B,UAAU,WAAW,aAAa,cAAc;AAAA,IACtF,CAAC;AAED,UAAM,mBAAmB,CAAC,WACxB,CAAC,UAAuB;AACtB,YAAM,OAAO,MAAM;AACnB,YAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,UAAI,aAAa,KAAM;AACvB,WAAK,+BAA+B,UAAU,MAAM;AAAA,IACtD;AACF,QAAI,UAAU,EAAE,UAAUA,MAAAA,cAAc,iBAAiB,iBAAiB,SAAS,CAAC;AACpF,QAAI,UAAU,EAAE,UAAUA,MAAAA,cAAc,kBAAkB,iBAAiB,UAAU,CAAC;AAAA,EACxF;AAAA,EAEA,gBAAgB,QAA8E;AAC5F,SAAK,eAAe;AAMpB,WAAO,uBAAuB,CAAC,aAAa;AAC1C,YAAM,SAAS,KAAK,cAAc,QAAQ;AAC1C,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,qBAAqB,KAAK,YAAY,IAAI,OAAO,QAAQ,GAAG,OAAO,IAAI,OAAO,WAAW;AAAA,IAClG,CAAC;AAAA,EACH;AAAA,EAEA,uBAAuB,WAAsC;AAC3D,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,wBAAwB,SAAsD;AAC5E,SAAK,YAAY,MAAA;AACjB,eAAW,CAAC,GAAG,CAAC,KAAK,QAAS,MAAK,YAAY,IAAI,GAAG,CAAC;AAAA,EACzD;AAAA;AAAA,EAIQ,qBAA6C;AACnD,UAAM,OAAO,KAAK,cAAc,gBAA6B,YAAY;AACzE,QAAI,QAAQ,KAAK,SAAS,EAAG,QAAO;AACpC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,eAAe,aAA2C;AACxD,SAAK,cAAc;AACnB,eAAW,UAAU,KAAK,QAAQ,SAAU,QAAO,eAAe,WAAW;AAC7E,SAAK,OAAO,KAAK,uBAAuB,EAAE,MAAM,EAAE,OAAO,YAAY,OAAA,GAAU;AAAA,EACjF;AAAA,EAEA,iBAAyC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,0BAAoD;AAClD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,oBAAoB,QAA2C;AAC7D,SAAK,kBAAkB,IAAI,IAAI,MAAM;AAAA,EACvC;AAAA;AAAA,EAIA,MAAM,oBAAoB,OA+BK;AAC7B,UAAM,EAAE,UAAU,aAAa,MAAM,KAAK,OAAO,YAAY,KAAK,OAAO,SAAA,IAAa;AAEtF,QAAI,YAAY,KAAK,cAAc,IAAI,QAAQ;AAC/C,QAAI,CAAC,WAAW;AACd,sCAAgB,IAAA;AAChB,WAAK,cAAc,IAAI,UAAU,SAAS;AAAA,IAC5C;AACA,UAAM,WAAW,UAAU,IAAI,WAAW;AAC1C,UAAM,QAAQ,UAAU,SAAS,KAAK,UAAU,QAAQ;AASxD,UAAM,eAAe,aAAa,UAC7B,QAAQ,UACR,SAAS,QAAQ;AAItB,UAAM,iBAA2B,MAAM,iBACnC,CAAC,GAAG,MAAM,cAAc,IACvB,UAAU,iBAAiB,CAAC,GAAG,SAAS,cAAc,IAAI,CAAA;AAC/D,UAAM,eAAe,MAAM,gBAAgB,UAAU,gBAAgB;AACrE,UAAM,MAA4B;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,SAAY,EAAE,IAAA,IAAQ,CAAA;AAAA,MAClC,GAAI,UAAU,SAAY,EAAE,MAAA,IAAU,CAAA;AAAA,MACtC,GAAI,eAAe,SAAY,EAAE,WAAA,IAAe,CAAA;AAAA,MAChD,GAAI,QAAQ,SAAY,EAAE,IAAA,IAAQ,CAAA;AAAA,MAClC,GAAI,UAAU,SAAY,EAAE,MAAA,IAAU,CAAA;AAAA,MACtC,GAAI,aAAa,SAAY,EAAE,aAAc,UAAU,aAAa,SAAY,EAAE,UAAU,SAAS,SAAA,IAAa,CAAA;AAAA,MAClH;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,cAAU,IAAI,aAAa,GAAG;AAK9B,UAAM,UAAU,KAAK,YAAY,IAAI,QAAQ;AAC7C,UAAM,UAAU,CAAC,GAAG,UAAU,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxE,UAAM,SAAS,SAAS,QAAQ;AAChC,QAAI;AACJ,QAAI,CAAC,WAAY,UAAU,KAAK,mBAAmB,QAAQ,KAAK,OAAO,GAAI;AACzE,eAAS,KAAK,yBAAyB,OAAO;AAAA,IAChD,OAAO;AACL,eAAS,QAAQ;AAAA,IACnB;AAUA,UAAM,oBAAoB,WAAW,IAAI,IAAI,IACzC,qBAAqB,SAAS,OAAO,IAAI,WAAW,IACpD;AACJ,UAAM,qBAAqB,sBAAsB,QAAQ,aAAa;AAEtE,UAAM,KAAK,sBAAsB,UAAU,QAAQ,EAAE,MAAM,QAAQ,QAAQ,WAAW;AAEtF,QAAI,oBAAoB;AACtB,YAAM,eAAe,qBAAqB,KAAK,YAAY,IAAI,QAAQ,GAAG,OAAO,CAAA,GAAI,WAAW;AAChG,UAAI,iBAAiB,MAAM;AACzB,aAAK,oBAAoB,UAAU,aAAa,YAAY;AAAA,MAC9D;AAAA,IACF;AAOA,UAAM,KAAK,aAAa,UAAU,WAAW;AAM7C,QAAI,cAAc;AAChB,YAAM,SAAS,KAAK,QAAQ,IAAI,YAAY,UAAU,WAAW,CAAC;AAClE,cAAQ,cAAA;AAAA,IACV;AAEA,SAAK,sBAAsB,QAAQ;AACnC,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,MAAM,oBAAoB,OAGK;AAC7B,UAAM,EAAE,UAAU,YAAA,IAAgB;AAClC,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,QAAI,CAAC,WAAW,IAAI,WAAW,EAAG,QAAO,EAAE,SAAS,KAAA;AAEpD,UAAM,UAAU,KAAK,YAAY,IAAI,QAAQ,KAAK,EAAE,KAAK,CAAA,GAAI,MAAM,KAAA;AACnE,QAAI,SAAS,EAAE,GAAG,QAAQ,IAAA;AAC1B,eAAW,WAAWF,yBAAmB;AACvC,UAAI,OAAO,OAAO,MAAM,YAAa,QAAO,OAAO,OAAO;AAAA,IAC5D;AACA,QAAI,QAAQ,MAAM;AAChB,YAAM,YAAY,CAAC,GAAG,UAAU,QAAQ,EACrC,OAAO,CAAC,MAAM,EAAE,gBAAgB,WAAW,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACnC,eAAS,KAAK,yBAAyB,SAAS;AAAA,IAClD;AAKA,UAAM,KAAK,sBAAsB,UAAU,QAAQ,EAAE,MAAM,QAAQ,MAAM,QAAQ,WAAW;AAI5F,UAAM,KAAK,cAAc,UAAU,WAAW;AAE9C,cAAU,OAAO,WAAW;AAC5B,QAAI,UAAU,SAAS,EAAG,MAAK,cAAc,OAAO,QAAQ;AAE5D,SAAK,sBAAsB,QAAQ;AACnC,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA;AAAA,EAIA,MAAM,cAAc,OAIW;AAC7B,UAAM,EAAE,UAAU,SAAS,YAAA,IAAgB;AAC3C,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,QAAI,CAAC,aAAa,CAAC,UAAU,IAAI,WAAW,GAAG;AAC7C,YAAM,IAAI;AAAA,QACR,+BAA+B,WAAW,iCAAiC,QAAQ;AAAA,MAAA;AAAA,IAEvF;AACA,UAAM,UAAU,KAAK,YAAY,IAAI,QAAQ,KAAK,EAAE,KAAK,GAAe;AACxE,UAAM,SAAS,EAAE,GAAG,QAAQ,KAAK,CAAC,OAAO,GAAG,YAAA;AAC5C,UAAM,KAAK,sBAAsB,UAAU,QAAQ,EAAE,MAAM,OAAO,QAAQ,UAAU;AACpF,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,MAAM,gBAAgB,OAGS;AAC7B,UAAM,EAAE,UAAU,QAAA,IAAY;AAC9B,UAAM,UAAU,KAAK,YAAY,IAAI,QAAQ,KAAK,EAAE,KAAK,CAAA,GAAI,MAAM,KAAA;AACnE,QAAI,QAAQ,IAAI,OAAO,MAAM,QAAW;AAEtC,UAAI,QAAQ,MAAM;AAChB,cAAM,KAAK,sBAAsB,UAAU,QAAQ,KAAK,EAAE,MAAM,OAAO,QAAQ,YAAY;AAAA,MAC7F;AACA,aAAO,EAAE,SAAS,KAAA;AAAA,IACpB;AACA,UAAM,SAAS,EAAE,GAAG,QAAQ,IAAA;AAC5B,WAAO,OAAO,OAAO;AACrB,UAAM,KAAK,sBAAsB,UAAU,QAAQ,EAAE,MAAM,OAAO,QAAQ,YAAY;AACtF,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,MAAM,eAAe,OAGa;AAChC,UAAM,EAAE,UAAU,QAAA,IAAY;AAC9B,UAAM,cAAc,KAAK,YAAY,IAAI,QAAQ,GAAG,IAAI,OAAO;AAC/D,QAAI,CAAC,YAAa,QAAO,EAAE,SAAS,MAAA;AACpC,UAAM,KAAK,cAAc,UAAU,WAAW;AAC9C,SAAK,wBAAwB,QAAQ;AACrC,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,cAAc,UAAkB,aAAoC;AAChF,QAAI,CAAC,KAAK,cAAc,IAAI,QAAQ,GAAG,IAAI,WAAW,EAAG;AACzD,UAAM,KAAK,cAAc,UAAU,WAAW;AAC9C,UAAM,KAAK,aAAa,UAAU,WAAW;AAAA,EAC/C;AAAA;AAAA,EAIA,MAAM,uBAAyD;AAC7D,UAAM,MAAsB,CAAA;AAC5B,eAAW,aAAa,KAAK,cAAc,OAAA,GAAU;AACnD,YAAM,SAAS,CAAC,GAAG,UAAU,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvE,iBAAW,KAAK,OAAQ,KAAI,KAAK,KAAK,eAAe,CAAC,CAAC;AAAA,IACzD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,sBAAuD;AAC3D,UAAM,MAAqB,CAAA;AAC3B,eAAW,YAAY,KAAK,YAAY,KAAA,GAAQ;AAC9C,iBAAW,QAAQ,KAAK,qBAAqB,QAAQ,EAAG,KAAI,KAAK,IAAI;AAAA,IACvE;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,0BAA0B,UAA2C;AACnE,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,QAAI,CAAC,UAAW,QAAO,CAAA;AACvB,WAAO,CAAC,GAAG,UAAU,OAAA,CAAQ,EAC1B,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC;AAAA,EACtC;AAAA,EAEA,yBAAyB,UAA0C;AAGjE,QAAI,CAAC,KAAK,cAAc,IAAI,QAAQ,KAAK,CAAC,KAAK,YAAY,IAAI,QAAQ,UAAU,CAAA;AACjF,WAAO,KAAK,qBAAqB,QAAQ;AAAA,EAC3C;AAAA;AAAA,EAIA,MAAM,UAAU,OAA4D;AAC1E,WAAO,KAAK,QAAQ,IAAI,MAAM,QAAQ,KAAK;AAAA,EAC7C;AAAA,EAEA,MAAM,eAAe,OAAmD;AACtE,UAAM,SAAS,KAAK,QAAQ,IAAI,MAAM,QAAQ;AAC9C,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,WAAW;AAAA,QACX,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,QACpB,UAAU;AAAA,QACV,aAAa;AAAA,QACb,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,aAAa;AAAA,QACb,aAAa;AAAA,QACb,cAAc;AAAA,QACd,aAAa;AAAA,QACb,kBAAkB;AAAA,QAClB,eAAe;AAAA,QACf,OAAO;AAAA,MAAA;AAAA,IAEX;AACA,WAAO,OAAO,SAAA;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,OAA+E;AAC/F,UAAM,SAAS,KAAK,QAAQ,IAAI,MAAM,QAAQ;AAC9C,QAAI,CAAC,QAAQ;AACX,aAAO,EAAE,MAAM,IAAI,SAAS,CAAA,GAAI,OAAO,CAAA,GAAI,aAAa,GAAG,oBAAoB,EAAA;AAAA,IACjF;AACA,WAAO,OAAO,YAAA;AAAA,EAChB;AAAA,EAEA,MAAM,WAAW,OAIgB;AAC/B,UAAM,SAAS,KAAK,QAAQ,IAAI,MAAM,QAAQ;AAC9C,QAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,MAAA;AAC9B,WAAO,EAAE,QAAQ,OAAO,WAAW,MAAM,SAAS,MAAM,MAAM,EAAA;AAAA,EAChE;AAAA,EAEA,MAAM,qBAAqB,aAAqB,QAAiC;AAC/E,QAAI,UAAU;AACd,eAAW,UAAU,KAAK,QAAQ,OAAA,GAAU;AAC1C,YAAM,SAAS,OAAO,iBAAA;AACtB,UAAI,CAAC,OAAQ;AACb,YAAM,cAAc,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,CAAC,IAAK;AACnE,UAAI,gBAAgB,YAAa;AACjC,UAAI;AACF,cAAM,OAAO,qBAAqB,MAAM;AACxC;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,OAAO,KAAK,2BAA2B;AAAA,UAC1C,MAAM,EAAE,UAAU,OAAO,UAAU,aAAa,QAAQ,OAAOhB,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CAC5E;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,qBAA8C;AAC5C,WAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ;AAAA,EAClC;AAAA,EAEA,MAAM,aAA4B;AAChC,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AACA,QAAI,KAAK,kBAAkB;AACzB,WAAK,iBAAiB,YAAA;AACtB,WAAK,mBAAmB;AAAA,IAC1B;AACA,UAAM,eAAe,CAAC,GAAG,KAAK,QAAQ,OAAA,CAAQ,EAAE,IAAI,CAAC,WAAW,OAAO,MAAM;AAC7E,UAAM,QAAQ,IAAI,YAAY;AAC9B,SAAK,QAAQ,MAAA;AACb,SAAK,qBAAqB,MAAA;AAC1B,UAAM,KAAK,WAAW,KAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,4BAAkC;AACxC,QAAI,KAAK,kBAAmB;AAC5B,SAAK,oBAAoB,YAAY,MAAM,KAAK,qBAAA,GAAwB,qBAAqB;AAAA,EAC/F;AAAA,EAEQ,uBAA6B;AACnC,QAAI,CAAC,KAAK,SAAU;AACpB,UAAM,MAAM,KAAK,IAAA;AACjB,eAAW,CAAC,UAAU,MAAM,KAAK,KAAK,SAAS;AAC7C,YAAM,SAAS,KAAK,cAAc,QAAQ;AAC1C,UAAI,CAAC,OAAQ;AACb,YAAM,EAAE,UAAU,YAAA,IAAgB;AAClC,YAAM,eAAe,OAAO,gBAAA;AAC5B,YAAM,aAAa,KAAK,qBAAqB,IAAI,QAAQ;AACzD,YAAM,YAAY,eAAe,KAAK,MAAM,gBAAgB;AAC5D,UAAI,eAAe,UAAW;AAC9B,WAAK,qBAAqB,IAAI,UAAU,SAAS;AACjD,WAAK,iBAAiB,WAAW;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,SAAS,qBAAqB,KAAK,YAAY,IAAI,QAAQ,GAAG,OAAO,CAAA,GAAI,WAAW;AAAA,QACpF;AAAA,QACA,YAAY,OAAO,oBAAA;AAAA,QACnB;AAAA,QACA,QAAQ,YAAa,eAAe,SAAY,iBAAiB,cAAe;AAAA,MAAA,CACjF;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,iBACN,QACA,SASM;AACN,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,UAAM,WAAW,SAAS,kBAAkB;AAC5C,QAAI,KAAK;AAAA,MACP,IAAI,GAAG,QAAQ,IAAI,QAAQ,QAAQ,IAAI,KAAK,IAAA,CAAK;AAAA,MACjD,+BAAe,KAAA;AAAA,MACf;AAAA,MACA,QAAQ,EAAE,MAAM,iBAAiB,IAAI,QAAQ,SAAA;AAAA,MAC7C,MAAM;AAAA,IAAA,CACP;AAaD,SAAK,KAAK,wBAAwB,QAAQ,UAAU,KAAK,qBAAqB,QAAQ,QAAQ,CAAC;AAAA,EACjG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,cAAc,UAAoE;AACxF,UAAM,MAAM,SAAS,QAAQ,GAAG;AAChC,QAAI,OAAO,EAAG,QAAO;AACrB,UAAM,WAAW,OAAO,SAAS,MAAM,GAAG,GAAG,CAAC;AAC9C,UAAM,cAAc,SAAS,MAAM,MAAM,CAAC;AAC1C,QAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,WAAW,EAAG,QAAO;AACnE,WAAO,EAAE,UAAU,YAAA;AAAA,EACrB;AAAA,EAEA,MAAM,gBAAgB,OAAe,MAAqB;AACxD,UAAM,KAAK,WAAW,MAAM,IAAI;AAChC,SAAK,OAAO,KAAK,kCAAkC,EAAE,MAAM,EAAE,MAAM,KAAK,WAAW,QAAA,EAAQ,EAAE,CAAG;AAAA,EAClG;AAAA;AAAA,EAIA,MAAM,qBAAqB,OAA6D;AACtF,UAAM,SAAS,KAAK,QAAQ,IAAI,MAAM,QAAQ;AAC9C,QAAI,CAAC,OAAQ;AACb,WAAO,qBAAqB,MAAM,OAAO;AAAA,EAC3C;AAAA,EAEA,MAAM,iBAAiB,OAEyD;AAC9E,UAAM,SAAS,KAAK,QAAQ,IAAI,MAAM,QAAQ;AAC9C,QAAI,CAAC,OAAQ,QAAO,EAAE,eAAe,GAAG,YAAY,GAAG,aAAa,EAAA;AACpE,UAAM,QAAQ,OAAO,SAAA;AACrB,WAAO;AAAA,MACL,eAAe,MAAM;AAAA,MACrB,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,IAAA;AAAA,EAEvB;AAAA;AAAA,EAIA,MAAM,aAAa,OAA6E;AAC9F,UAAM,SAAS,MAAM,SAAS,MAAM,GAAG,EAAE,CAAC;AAC1C,QAAI,CAAC,OAAQ,QAAO,EAAE,KAAK,GAAA;AAC3B,UAAM,WAAW,OAAO,MAAM;AAC9B,QAAI,CAAC,OAAO,SAAS,QAAQ,EAAG,QAAO,EAAE,KAAK,GAAA;AAC9C,eAAW,cAAc,KAAK,sBAAsB;AAClD,YAAM,YAAY,WAAW,oBAAoB,QAAQ;AACzD,YAAM,QAAQ,UAAU,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM,MAAM;AAC7D,UAAI,MAAO,QAAO,EAAE,KAAK,MAAM,MAAA;AAAA,IACjC;AACA,WAAO,EAAE,KAAK,GAAA;AAAA,EAChB;AAAA;AAAA,EAIA,MAAM,cAA+B;AACnC,WAAO,KAAK,aAAa,YAAA;AAAA,EAC3B;AAAA,EAEA,MAAM,kBAAkB,OAAsE;AAC5F,WAAO,KAAK,aAAa,cAAc,OAAO,QAAQ;AAAA,EACxD;AAAA;AAAA,EAGA,wBAAwB,UAAkB,UAAiD;AACzF,WAAO,KAAK,aAAa,oBAAoB,UAAU,QAAQ;AAAA,EACjE;AAAA,EAEA,MAAM,aAAa,OAAmF;AACpG,WAAO,KAAK,aAAa,SAAS,MAAM,UAAU,MAAM,QAAQ;AAAA,EAClE;AAAA,EAEA,MAAM,oBAAoB,OAAqD;AAC7E,SAAK,OAAO,KAAK,8BAA8B,EAAE,MAAM,EAAE,UAAU,MAAM,SAAA,GAAY;AACrF,UAAM,SAAS,KAAK,aAAa,gBAAgB,MAAM,QAAQ;AAC/D,QAAI,QAAQ;AACV,WAAK,OAAO,KAAK,iCAAiC;AAAA,QAChD,MAAM,EAAE,UAAU,MAAM,UAAU,gBAAgB,OAAO,MAAM,GAAG,CAAC,EAAA;AAAA,MAAE,CACtE;AAAA,IACH,OAAO;AACL,WAAK,OAAO,KAAK,iDAAiD;AAAA,QAChE,MAAM,EAAE,UAAU,MAAM,SAAA;AAAA,MAAS,CAClC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAe,OAA8D;AACjF,SAAK,aAAa,WAAW,MAAM,UAAU,MAAM,OAAO;AAAA,EAC5D;AAAA,EAEA,MAAM,cAAc,OAA+C;AACjE,WAAO,KAAK,aAAa,UAAU,MAAM,QAAQ;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,+BAA+B,UAAkB,QAAiC;AACxF,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,QAAI,CAAC,UAAW;AAChB,eAAW,eAAe,UAAU,QAAQ;AAC1C,YAAM,SAAS,KAAK,QAAQ,IAAI,YAAY,UAAU,WAAW,CAAC;AAClE,cAAQ,qBAAqB,MAAM;AAAA,IACrC;AAAA,EACF;AAAA;AAAA,EAIA,2BAA2B,IAAoE;AAC7F,SAAK,0BAA0B;AAAA,EACjC;AAAA,EAEA,6BAA6B,WAAsD;AACjF,SAAK,gBAAgB,MAAA;AACrB,eAAW,CAAC,GAAG,CAAC,KAAK,UAAW,MAAK,gBAAgB,IAAI,GAAG,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,yBACE,UACA,aACA,cACA,oBAAoB,OACZ;AACR,UAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ;AAClD,UAAM,YAAY,UAAU,YAAY,WAAW;AACnD,QAAI,WAAW;AACb,YAAM,UAAU,UAAU,WAAW;AACrC,aAAO,UAAU,UAAU,UAAU;AAAA,IACvC;AACA,QAAI,UAAU,yBAAyB,OAAW,QAAO,SAAS;AAClE,QAAI,KAAK,iBAAiB,UAAUmB,MAAAA,cAAc,eAAe,EAAG,QAAO;AAC3E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,UAA2B;AAC1C,WAAO,KAAK,gBAAgB,IAAI,QAAQ,GAAG,mBAAmB;AAAA,EAChE;AAAA;AAAA,EAIA,MAAM,8BAA8B,OAES;AAC3C,UAAM,WAAW,MAAM;AACvB,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,QAAI,CAAC,aAAa,UAAU,SAAS,EAAG,QAAO;AAC/C,UAAM,aAAa,KAAK,YAAY,IAAI,QAAQ,KAAK,EAAE,KAAK,GAAe;AAC3E,UAAM,UAAU,CAAC,GAAG,UAAU,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxE,UAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ;AAElD,UAAM,gBAAgB;AAAA,MACpB,EAAE,OAAO,IAAI,OAAO,eAAA;AAAA,MACpB,GAAG,QAAQ,IAAI,CAAC,OAAO;AAAA,QACrB,OAAO,EAAE;AAAA,QACT,OAAO,KAAK,iBAAiB,CAAC;AAAA,MAAA,EAC9B;AAAA,IAAA;AAGJ,UAAM,WAA8D;AAAA,MAClE,EAAE,KAAK,QAAQ,OAAO,OAAA;AAAA,MACtB,EAAE,KAAK,OAAO,OAAO,MAAA;AAAA,MACrB,EAAE,KAAK,OAAO,OAAO,MAAA;AAAA,IAAM;AAI7B,UAAM,gBAAiD;AAAA,MACrD,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,SAAS,IAAI,CAAC,EAAE,KAAK,SAAS,aAAa;AAAA,QACjD,MAAM;AAAA,QACN,KAAK,iBAAiB,OAAO;AAAA,QAC7B,OAAO,GAAG,KAAK;AAAA,QACf,aAAa,mCAAmC,KAAK;AAAA,QACrD,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO,WAAW,IAAI,OAAO,KAAK;AAAA,MAAA,EAClC;AAAA,IAAA;AAOJ,UAAM,aAAgD,QAAQ,IAAI,CAAC,WAAW;AAC5E,YAAM,cAAc,OAAO;AAC3B,YAAM,WAAW,YAAY,UAAU,WAAW;AAClD,YAAM,UAAU,qBAAqB,WAAW,KAAK,WAAW;AAChE,YAAM,cAAc,KAAK,aAAa,UAAU,QAAQ;AACxD,YAAM,YAAY,KAAK,aAAa,SAAS,QAAQ;AACrD,YAAM,YAAY,UAAU,YAAY,WAAW;AACnD,YAAM,mBAAmB,WAAW,WAAW;AAC/C,YAAM,eAAe,WAAW,WAAW,KAAK;AAEhD,YAAM,SAAiC;AAAA,QACrC;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK,eAAe,WAAW;AAAA,UAC/B,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,OAAO;AAAA,QAAA;AAAA,QAET;AAAA,UACE,MAAM;AAAA,UACN,KAAK,WAAW,WAAW;AAAA,UAC3B,OAAO;AAAA,UACP,eAAe;AAAA,UACf,MAAM;AAAA,UACN,OAAO,WAAW,OAAO;AAAA,UACzB,SAAS;AAAA,YACP,EAAE,QAAQ,cAAc,MAAM,QAAQ,SAAS,wBAAA;AAAA,YAC/C;AAAA,cACE,QAAQ;AAAA,cACR,MAAM;AAAA,cACN,SAAS;AAAA,cACT,SAAS;AAAA,cACT,gBAAgB,6BAA6B,OAAO,SAAS,WAAW;AAAA,YAAA;AAAA,UAC1E;AAAA,QACF;AAAA,QAEF;AAAA,UACE,MAAM;AAAA,UACN,KAAK,gBAAgB,WAAW;AAAA,UAChC,OAAO;AAAA,UACP,aAAa;AAAA,UACb,eAAe;AAAA,UACf,MAAM;AAAA,UACN,OAAO,WAAW,MAAM,GAAG,UAAU,GAAG,WAAW;AAAA,UACnD,SAAS;AAAA,YACP,EAAE,QAAQ,cAAc,MAAM,QAAQ,SAAS,8BAAA;AAAA,UAA8B;AAAA,QAC/E;AAAA,QAEF;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK,oBAAoB,WAAW;AAAA,UACpC,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,OAAO;AAAA,QAAA;AAAA,QAET;AAAA,UACE,MAAM;AAAA,UACN,KAAK,gBAAgB,WAAW;AAAA,UAChC,OAAO;AAAA,UACP,KAAK;AAAA,UACL,KAAK;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS,KAAK;AAAA,UACd,WAAW;AAAA,UACX,OAAO;AAAA,QAAA;AAAA,MACT;AAGF,YAAM,WAAW,UACb,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,OAAO,GAAG,QACzC;AACJ,YAAM,WAAW,OAAO,SAAS;AACjC,YAAM,MAAuC;AAAA,QAC3C,IAAI,UAAU,WAAW;AAAA,QACzB,OAAO;AAAA,QACP;AAAA,QACA,GAAI,WAAW,EAAE,OAAO,aAAa,CAAA;AAAA,MAAC;AAExC,aAAO;AAAA,IACT,CAAC;AAED,UAAM,sBAA+C;AAAA,MACnD,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,KAAK;AAAA,MACL,OAAO;AAAA,MACP,QAAQ;AAAA,QACN;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,OAAO;AAAA,UACP,MAAM;AAAA,UACN,MAAM,CAAC,eAAe,GAAG,UAAU;AAAA,QAAA;AAAA,MACrC;AAAA,IACF;AAGF,UAAM,qBAA8C;AAAA,MAClD,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,KAAK;AAAA,MACL,OAAO;AAAA,MACP,QAAQ;AAAA,QACN;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,OAAO,UAAU,mBAAmB;AAAA,QAAA;AAAA,MACtC;AAAA,IACF;AAGF,WAAO,EAAE,UAAU,CAAC,qBAAqB,kBAAkB,EAAA;AAAA,EAC7D;AAAA,EAEA,MAAM,0BAA0B,QAEa;AAC3C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,yBAAyB,OAGA;AAC7B,UAAM,WAAW,MAAM;AACvB,QAAI,CAAC,KAAK,cAAc,IAAI,QAAQ,GAAG;AACrC,WAAK,OAAO,MAAM,sDAAsD;AAAA,QACtE,MAAM,EAAE,SAAA;AAAA,MAAS,CAClB;AACD,aAAO,EAAE,SAAS,KAAA;AAAA,IACpB;AAEA,QAAI,gBAAgB;AACpB,UAAM,eAA+B,EAAE,GAAI,KAAK,gBAAgB,IAAI,QAAQ,KAAK,GAAC;AAElF,eAAW,CAAC,UAAU,KAAK,KAAK,OAAO,QAAQ,MAAM,KAAK,GAAG;AAG3D,UAAI,SAAS,WAAW,gBAAgB,GAAG;AACzC,cAAM,UAAU,SAAS,MAAM,iBAAiB,MAAM;AACtD,YAAI,CAACH,MAAAA,kBAAkB,SAAS,OAAO,EAAG;AAC1C,cAAM,WAAW,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ;AACrE,YAAI,aAAa,MAAM;AACrB,gBAAM,KAAK,gBAAgB,EAAE,UAAU,SAAS;AAAA,QAClD,OAAO;AACL,gBAAM,KAAK,cAAc,EAAE,UAAU,SAAS,aAAa,UAAU;AAAA,QACvE;AACA;AAAA,MACF;AACA,UAAI,SAAS,WAAW,cAAc,GAAG;AACvC,cAAM,cAAc,SAAS,MAAM,eAAe,MAAM;AACxD,cAAM,WAAW,YAAY,UAAU,WAAW;AAClD,aAAK,aAAa,WAAW,UAAU,QAAQ,KAAK,CAAC;AACrD;AAAA,MACF;AACA,UAAI,aAAa,wBAAwB;AACvC,wBAAgB;AAChB,YAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,iBAAO,aAAa;AAAA,QACtB,WAAW,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AAC9D,uBAAa,uBAAuB;AAAA,QACtC;AACA;AAAA,MACF;AACA,UAAI,SAAS,WAAW,mBAAmB,GAAG;AAC5C,cAAM,cAAc,SAAS,MAAM,oBAAoB,MAAM;AAC7D,wBAAgB;AAChB,cAAM,KAAK,EAAE,GAAI,aAAa,aAAa,CAAA,EAAC;AAC5C,cAAM,UAAU,GAAG,WAAW,KAAK,KAAK,uBAAuB,UAAU,WAAW;AACpF,cAAM,UAAU,QAAQ,KAAK;AAC7B,WAAG,WAAW,IAAI,EAAE,GAAG,SAAS,QAAA;AAChC,qBAAa,YAAY;AACzB,cAAM,SAAS,KAAK,QAAQ,IAAI,YAAY,UAAU,WAAW,CAAC;AAClE,YAAI,QAAQ;AACV,iBAAO,qBAAqB,UAAU,GAAG,WAAW,GAAG,WAAW,KAAK,sBAAsB,CAAC;AAC9F,iBAAO,oBAAoB,OAAO;AAAA,QACpC;AACA;AAAA,MACF;AACA,UAAI,SAAS,WAAW,eAAe,GAAG;AACxC,cAAM,cAAc,SAAS,MAAM,gBAAgB,MAAM;AACzD,cAAM,UACJ,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAK;AACrE,wBAAgB;AAChB,cAAM,KAAK,EAAE,GAAI,aAAa,aAAa,CAAA,EAAC;AAC5C,cAAM,UAAU,GAAG,WAAW,KAAK,KAAK,uBAAuB,UAAU,WAAW;AACpF,WAAG,WAAW,IAAI,EAAE,GAAG,SAAS,QAAA;AAChC,qBAAa,YAAY;AACzB,cAAM,SAAS,KAAK,QAAQ,IAAI,YAAY,UAAU,WAAW,CAAC;AAClE,YAAI,UAAU,GAAG,WAAW,GAAG,YAAY,OAAO;AAChD,iBAAO,qBAAqB,OAAO;AAAA,QACrC;AACA;AAAA,MACF;AACA,UAAI,aAAa,kBAAkB;AACjC,wBAAgB;AAChB,cAAM,OAAO,UAAU,QAAQ,UAAU,SAAY,SAAY,QAAQ,KAAK;AAC9E,YAAI,SAAS,QAAW;AACtB,iBAAO,aAAa;AAAA,QACtB,OAAO;AACL,uBAAa,iBAAiB;AAAA,QAChC;AAGA,mBAAW,CAAC,KAAK,MAAM,KAAK,KAAK,SAAS;AACxC,cAAI,IAAI,WAAW,GAAG,QAAQ,GAAG,GAAG;AAClC,mBAAO,kBAAkB,SAAS,IAAI;AAAA,UACxC;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,eAAe;AACjB,UACE,aAAa,yBAAyB,WACrC,CAAC,aAAa,aAAa,OAAO,KAAK,aAAa,SAAS,EAAE,WAAW,MAC3E,aAAa,mBAAmB,QAChC;AACA,aAAK,gBAAgB,OAAO,QAAQ;AAAA,MACtC,OAAO;AACL,aAAK,gBAAgB,IAAI,UAAU,YAAY;AAAA,MACjD;AACA,WAAK,0BAA0B,KAAK,eAAe;AAAA,IACrD;AACA,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA;AAAA,EAIQ,UAAU,UAA0B;AAC1C,UAAM,QAAQ,KAAK,eAAe,IAAI,QAAQ,KAAK,KAAK;AACxD,SAAK,eAAe,IAAI,UAAU,IAAI;AACtC,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,GAAuC;AAC5D,UAAM,EAAE,OAAO,QAAQ,gBAAgB,KAAK,GAAG,SAAS;AACxD,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,UAAkB,SAA0B;AACnE,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,QAAI,CAAC,UAAW,QAAO;AACvB,eAAW,KAAK,UAAU,UAAU;AAClC,UAAI,EAAE,eAAe,SAAS,OAAO,EAAG,QAAO;AAAA,IACjD;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,GAAiC;AACxD,UAAM,OAAO,EAAE,SAAS,EAAE;AAC1B,QAAI,EAAE,WAAY,QAAO,GAAG,IAAI,KAAK,EAAE,WAAW,KAAK,IAAI,EAAE,WAAW,MAAM;AAC9E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBQ,yBACN,SACqC;AACrC,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAA;AACjC,UAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY;AACrD,UAAM,OAAO,SAAS,SAAS,IAAI,WAAW;AAC9C,UAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM;AACtC,YAAM,MAAM,EAAE,aAAa,EAAE,WAAW,QAAQ,EAAE,WAAW,SAAS;AACtE,YAAM,MAAM,EAAE,aAAa,EAAE,WAAW,QAAQ,EAAE,WAAW,SAAS;AACtE,UAAI,QAAQ,IAAK,QAAO,MAAM;AAC9B,aAAO,EAAE,QAAQ,EAAE;AAAA,IACrB,CAAC;AACD,QAAI,OAAO,WAAW,EAAG,QAAO,EAAE,KAAK,OAAO,CAAC,EAAG,YAAA;AAClD,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,EAAE,MAAM,OAAO,CAAC,EAAG,aAAa,KAAK,OAAO,CAAC,EAAG,YAAA;AAAA,IACzD;AACA,WAAO;AAAA,MACL,MAAM,OAAO,CAAC,EAAG;AAAA,MACjB,KAAK,OAAO,CAAC,EAAG;AAAA,MAChB,KAAK,OAAO,CAAC,EAAG;AAAA,IAAA;AAAA,EAEpB;AAAA,EAEQ,mBACN,SACA,SACS;AACT,UAAM,WAAW,KAAK,yBAAyB,OAAO;AACtD,eAAW,KAAKA,yBAAmB;AACjC,UAAI,QAAQ,CAAC,MAAM,SAAS,CAAC,EAAG,QAAO;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,sBACZ,UACA,QACA,MACe;AACf,UAAM,UAAU,KAAK,YAAY,IAAI,QAAQ,KAAK,EAAE,KAAK,CAAA,GAAI,MAAM,KAAA;AACnE,UAAM,iBAAmC,EAAE,KAAK,QAAQ,MAAM,KAAK,KAAA;AAGnE,UAAM,mBAAmB,KAAK,mBAAmB,QAAQ,KAAK,QAAQ;AACtE,UAAM,mBAAmB,KAAK,mBAAmB,QAAQ,QAAQ;AAEjE,QAAI,YAAY;AAChB,eAAW,WAAWA,yBAAmB;AACvC,UAAI,QAAQ,IAAI,OAAO,MAAM,OAAO,OAAO,GAAG;AAC5C,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AAEA,SAAK,YAAY,IAAI,UAAU,cAAc;AAG7C,eAAW,CAAC,OAAO,SAAS,KAAK,kBAAkB;AACjD,WAAK,iBAAiB,IAAI,KAAK,KAAK,OAAO,KAAK,YAAY,GAAG;AAC7D,aAAK,kBAAkB,UAAU,KAAK;AAAA,MACxC;AAAA,IACF;AACA,eAAW,CAAC,OAAO,SAAS,KAAK,kBAAkB;AACjD,WAAK,iBAAiB,IAAI,KAAK,KAAK,OAAO,KAAK,YAAY,GAAG;AAE7D,YAAI,iBAA6B;AACjC,mBAAW,KAAKA,yBAAmB;AACjC,cAAI,OAAO,CAAC,MAAM,OAAO;AAAE,6BAAiB;AAAG;AAAA,UAAM;AAAA,QACvD;AACA,aAAK,oBAAoB,UAAU,OAAO,cAAc;AAAA,MAC1D;AAAA,IACF;AAEA,QAAI,aAAa,QAAQ,SAAS,eAAe,MAAM;AACrD,WAAK,kBAAA;AACL,WAAK,wBAAwB,QAAQ;AAAA,IACvC,WAAW,KAAK,WAAW,WAAW;AAGpC,WAAK,wBAAwB,QAAQ;AAAA,IACvC;AAAA,EACF;AAAA,EAEQ,mBACN,KACA,UACqB;AACrB,UAAM,0BAAU,IAAA;AAChB,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,eAAW,WAAWA,yBAAmB;AACvC,YAAM,QAAQ,IAAI,OAAO;AACzB,UAAI,CAAC,MAAO;AACZ,YAAM,MAAM,WAAW,IAAI,KAAK;AAChC,UAAI,CAAC,OAAO,CAAC,WAAW,IAAI,IAAI,IAAI,EAAG;AACvC,UAAI,IAAI,QAAQ,IAAI,IAAI,KAAK,KAAK,KAAK,CAAC;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,oBAA0B;AAChC,SAAK,sBAAsB,KAAK,WAAW;AAAA,EAC7C;AAAA,EAEQ,yBAAyB,KAAyC;AACxE,UAAM,QAAQ,IAAI,SAAS,IAAI;AAC/B,UAAM,QAAQ,IAAI,UAAU,SAAY,EAAE,YAAY,IAAI,MAAA,IAAU,CAAA;AAKpE,UAAM,QAAQ,IAAI,eAAe,SAASG,MAAAA,cAAc,eAAe,IACnE,EAAE,YAAY,KAAA,IACd,CAAA;AAGJ,UAAM,kBAAkB,IAAI,YAAY,CAAA;AACxC,YAAQ,IAAI,MAAA;AAAA,MACV,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,GAAG,OAAO,UAAU,EAAE,MAAM,eAAe,OAAO,GAAG,kBAAgB;AAAA,MACjH,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,OAAO,IAAI,GAAG,OAAO,UAAU,EAAE,MAAM,aAAa,OAAO,GAAG,kBAAgB;AAAA,MAChH,KAAK;AACH,eAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,OAAO,IAAI,GAAG,OAAO,UAAU,EAAE,MAAM,aAAa,OAAO,GAAG,kBAAgB;AAAA,MAChH,KAAK;AACH,eAAO,EAAE,MAAM,cAAc,KAAK,IAAI,OAAO,IAAI,GAAG,OAAO,UAAU,EAAE,MAAM,aAAa,OAAO,GAAG,kBAAgB;AAAA,MACtH,KAAK;AACH,eAAO,EAAE,MAAM,WAAW,KAAK,IAAI,OAAO,IAAI,GAAG,OAAO,GAAG,OAAO,UAAU,EAAE,MAAM,gBAAgB,OAAO,GAAG,kBAAgB;AAAA,MAChI,SAAS;AACP,cAAM,cAAqB,IAAI;AAC/B,cAAM,IAAI,MAAM,4BAA4B,OAAO,WAAW,CAAC,EAAE;AAAA,MACnE;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,aACZ,UACA,aACe;AACf,UAAM,MAAM,KAAK,cAAc,IAAI,QAAQ,GAAG,IAAI,WAAW;AAC7D,QAAI,CAAC,IAAK;AACV,UAAM,WAAW,YAAY,UAAU,WAAW;AAClD,QAAI,KAAK,QAAQ,IAAI,QAAQ,EAAG;AAEhC,UAAM,eAAe,KAAK,OACvB,MAAM,WAAW,EACjB,SAAS,EAAE,UAAU,aAAa;AACrC,UAAM,SAAS,KAAK,yBAAyB,GAAG;AAChD,UAAM,SAAS,IAAI;AAAA,MACjB;AAAA,MACA,KAAK,mBAAmB,QAAQ;AAAA,MAChC;AAAA,MACA,sBAAsB,KAAK,GAAG;AAAA,IAAA;AAEhC,WAAO,eAAe,KAAK,WAAW;AACtC,SAAK,qBAAqB,QAAQ,UAAU,aAAa,GAAG;AAC5D,UAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ;AAClD,QAAI,UAAU,eAAgB,QAAO,kBAAkB,IAAI;AAM3D,WAAO,kBAAkB,MAAM;AAC7B,YAAM,SAAS,KAAK,cAAc,IAAI,QAAQ,GAAG,IAAI,WAAW;AAChE,aAAO,SAAS,KAAK,yBAAyB,MAAM,IAAI;AAAA,IAC1D,CAAC;AACD,WAAO,wBAAwB,MAAM;AACnC,WAAK,+BAA+B,UAAU,aAAa,QAAQ;AAAA,IACrE,CAAC;AAED,UAAM,OAAO,MAAM,MAAM;AACzB,SAAK,QAAQ,IAAI,UAAU,MAAM;AAEjC,UAAM,gBAAgB,KAAK,gBAAgB,IAAI,QAAQ;AACvD,SAAK,WAAW,mBAAmB,UAAU,OAAO,kBAAA,GAAqB,aAAa;AACtF,QAAI,CAAC,cAAe,MAAK,aAAa,cAAA;AAEtC,UAAM,KAAK,mBAAmB,UAAU,UAAU,KAAK,MAAM;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,cACZ,UACA,aACe;AACf,UAAM,WAAW,YAAY,UAAU,WAAW;AAClD,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,OAAQ;AAEb,UAAM,KAAK,qBAAqB,UAAU,QAAQ;AAClD,SAAK,WAAW,qBAAqB,QAAQ;AAI7C,UAAM,aAAa,KAAK,qBAAqB,IAAI,QAAQ;AACzD,QAAI,cAAc,KAAK,UAAU;AAC/B,WAAK,iBAAiB,OAAO;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,SAAS,qBAAqB,KAAK,YAAY,IAAI,QAAQ,GAAG,OAAO,CAAA,GAAI,WAAW;AAAA,QACpF;AAAA,QACA,YAAY,OAAO,oBAAA;AAAA,QACnB,cAAc,OAAO,gBAAA;AAAA,QACrB,QAAQ;AAAA,MAAA,CACT;AAAA,IACH;AACA,SAAK,qBAAqB,OAAO,QAAQ;AACzC,UAAM,OAAO,KAAA;AACb,SAAK,QAAQ,OAAO,QAAQ;AAC5B,SAAK,aAAa,cAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,uBAAuB,WAAmB,cAAuC;AACvF,WAAO,EAAE,SAAS,OAAO,SAAS,KAAK,oBAAA;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,qBACN,QACA,UACA,aACA,MACM;AACN,UAAM,WAAW,KAAK,gBAAgB,IAAI,QAAQ;AAClD,UAAM,YAAY,UAAU,YAAY,WAAW;AACnD,QAAI,WAAW;AACb,aAAO,oBAAoB,UAAU,OAAO;AAC5C,aAAO,qBAAqB,UAAU,UAAU,UAAU,UAAU,CAAC;AACrE;AAAA,IACF;AACA,WAAO,oBAAoB,KAAK;AAChC,WAAO,qBAAqB,CAAC;AAAA,EAC/B;AAAA,EAEA,MAAc,mBACZ,UACA,UACA,KACA,QACe;AACf,UAAM,MAAM,KAAK,OAAO,SAAS,EAAE,UAAU,UAAU,aAAa,IAAI,aAAa;AACrF,UAAM,iBAAiB,OAAO,kBAAA;AAC9B,UAAM,QAAQ;AACd,UAAM,QAAQ,IAAI,SAAS;AAE3B,eAAW,cAAc,KAAK,sBAAsB;AAClD,UAAI;AACF,cAAM,WAAW,eAAe,UAAU;AAAA,UACxC,EAAE,UAAU,UAAU,OAAO,OAAO,MAAM,SAAkB,WAAW,eAAA;AAAA,QAAe,CACvF;AAAA,MACH,SAAS,KAAK;AACZ,YAAI,MAAM,sCAAsC;AAAA,UAC9C,MAAM,EAAE,cAAc,WAAW,IAAI,OAAOnB,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CACzD;AAAA,MACH;AAAA,IACF;AAIA,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,eAAe,UAAU,MAAM;AAAA,IACnD;AAAA,EACF;AAAA,EAEA,MAAc,qBAAqB,UAAkB,UAAiC;AACpF,UAAM,MAAM,KAAK,OAAO,SAAS,EAAE,UAAU,UAAU;AACvD,eAAW,cAAc,KAAK,sBAAsB;AAClD,UAAI;AACF,cAAM,WAAW,iBAAiB,QAAQ;AAAA,MAC5C,SAAS,KAAK;AACZ,YAAI,MAAM,wCAAwC;AAAA,UAChD,MAAM,EAAE,cAAc,WAAW,IAAI,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CACzD;AAAA,MACH;AAAA,IACF;AAEA,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,iBAAiB,QAAQ;AAAA,IAC7C;AAAA,EACF;AAAA,EAEQ,yBAAyB,QAAkD;AAKjF,QAAI,WAAW,UAAW,QAAO;AACjC,WAAO;AAAA,EACT;AAAA,EAEQ,qBAAqB,UAAiC;AAC5D,UAAM,aAAa,KAAK,YAAY,IAAI,QAAQ,KAAK,EAAE,KAAK,GAAe;AAC3E,UAAM,YAAY,KAAK,cAAc,IAAI,QAAQ;AACjD,UAAM,QAAuB,CAAA;AAC7B,eAAW,WAAWgB,yBAAmB;AACvC,YAAM,QAAQ,WAAW,IAAI,OAAO,KAAK;AAKzC,YAAM,WAAW,QAAQ,YAAY,UAAU,KAAK,IAAI,GAAG,QAAQ,IAAI,OAAO;AAC9E,YAAM,SAAS,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI;AACpD,YAAM,QAAQ,QAAQ,SAAA;AACtB,YAAM,MAAM,QAAQ,WAAW,IAAI,KAAK,IAAI;AAC5C,YAAM,SAA4B,QAC9B,QAAQ,KAAK,yBAAyB,MAAM,MAAM,IAAI,SACtD;AAMJ,YAAM,gBAAgB,OAAO,SAAS,KAAK;AAC3C,YAAM,OAAoB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA,mBAAmB;AAAA,QACnB;AAAA,QACA,GAAI,KAAK,eAAe,SAAY,EAAE,YAAY,IAAI,WAAA,IAAe,CAAA;AAAA,QACrE,GAAI,kBAAkB,SAAY,EAAE,OAAO,cAAA,IAAkB,CAAA;AAAA,QAC7D,GAAI,UAAU,SAAY,EAAE,cAAc,MAAM,aAAA,IAAiB,CAAA;AAAA,MAAC;AAEpE,YAAM,KAAK,IAAI;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIQ,aAAa,UAAkB,UAAkB,MAAqC;AAC5F,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,KAAK;AAAA,MACP,IAAI,OAAO,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,MAC/D,+BAAe,KAAA;AAAA,MACf,QAAQ,EAAE,MAAM,UAAU,IAAI,UAAU,SAAS,iBAAiB,SAAA;AAAA,MAClE;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,oBAAoB,UAAkB,aAAqB,SAA2B;AAC5F,SAAK,aAAaE,MAAAA,cAAc,+BAA+B,UAAU,EAAE,UAAU,aAAa,SAAS;AAAA,EAC7G;AAAA,EAEQ,kBAAkB,UAAkB,aAA2B;AACrE,SAAK,aAAaA,oBAAc,6BAA6B,UAAU,EAAE,UAAU,aAAa;AAAA,EAClG;AAAA,EAEQ,+BAA+B,UAAkB,aAAqB,UAAwB;AACpG,SAAK,aAAaA,oBAAc,0CAA0C,UAAU;AAAA,MAClF;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,sBAAsB,UAAwB;AACpD,UAAM,aAAa,KAAK,0BAA0B,QAAQ;AAC1D,SAAK,aAAa,sCAAsC,UAAU,EAAE,UAAU,YAAY;AAAA,EAC5F;AAAA,EAEQ,wBAAwB,UAAwB;AACtD,UAAM,eAAe,KAAK,qBAAqB,QAAQ;AACvD,SAAK,aAAa,wCAAwC,UAAU,EAAE,UAAU,cAAc;AAC9F,SAAK,KAAK,wBAAwB,UAAU,YAAY;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,wBAAwB,UAAkB,OAA8C;AACpG,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,UAAM,eAA+F,CAAA;AACrG,UAAM,aAA4D,CAAA;AAClE,QAAI,SAAS;AACb,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,WAAW,cAAc;AAChC,qBAAa,KAAK,OAAO,IAAI,KAAK;AAClC,YAAI,KAAK,aAAc,YAAW,KAAK,OAAO,IAAI,KAAK;AACvD,YAAI,KAAK,WAAW,YAAa,UAAS;AAAA,MAC5C;AAAA,IACF;AACA,QAAI;AACF,YAAM,IAAI,YAAY,YAAY,OAAO;AAAA,QACvC;AAAA,QACA,SAAS;AAAA,QACT,OAAO,EAAE,QAAQ,cAAc,YAAY,eAAe,KAAK,MAAI;AAAA,MAAE,CACtE;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,OAAO,MAAM,kCAAkC;AAAA,QAClD,MAAM,EAAE,SAAA;AAAA,QACR,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAA;AAAA,MAAE,CACjE;AAAA,IACH;AAAA,EACF;AACF;AChzDA,MAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AAC9D,MAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,CAAI,CAAC;AAKjD,SAAS,cAAc,MAAuB;AACnD,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,MAAI,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,iBAAiB,EAAG,QAAO;AAC1D,MAAI,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,iBAAiB,EAAG,QAAO;AAC1D,SAAO;AACT;AAMO,SAAS,kBAAkB,QAA0B;AAC1D,QAAM,OAAiB,CAAA;AACvB,QAAM,MAAM,OAAO;AAEnB,QAAM,gBAAgB,CAACE,OAAsB;AAC3C,QAAIA,KAAI,KAAK,OAAO,OAAOA,EAAC,MAAM,KAAQ,OAAOA,KAAI,CAAC,MAAM,GAAM;AAChE,UAAI,OAAOA,KAAI,CAAC,MAAM,EAAM,QAAO;AACnC,UAAIA,KAAI,KAAK,OAAO,OAAOA,KAAI,CAAC,MAAM,KAAQ,OAAOA,KAAI,CAAC,MAAM;AAC9D,eAAO;AAAA,IACX;AACA,WAAO;AAAA,EACT;AAEA,MAAI,IAAI;AAER,SAAO,IAAI,KAAK;AACd,UAAM,KAAK,cAAc,CAAC;AAC1B,QAAI,GAAI;AACR;AAAA,EACF;AAEA,SAAO,IAAI,KAAK;AACd,UAAM,KAAK,cAAc,CAAC;AAC1B,QAAI,CAAC,IAAI;AACP;AACA;AAAA,IACF;AACA,UAAM,WAAW,IAAI;AACrB,QAAI,IAAI;AACR,WAAO,IAAI,KAAK;AACd,YAAM,MAAM,cAAc,CAAC;AAC3B,UAAI,IAAK;AACT;AAAA,IACF;AACA,QAAI,WAAW,GAAG;AAChB,YAAM,MAAM,OAAO,SAAS,UAAU,CAAC;AACvC,UAAI,IAAI,SAAS,EAAG,MAAK,KAAK,GAAG;AAAA,IACnC;AACA,QAAI;AAAA,EACN;AAEA,SAAO;AACT;ACzDA,SAASC,6BACP,MACA,SACe;AACf,QAAM,SAAmB,CAAA;AACzB,MAAI,SAAS;AACb,MAAI,WAAW;AACf,SAAO,SAAS,KAAK,QAAQ;AAC3B,QAAI,SAAS,IAAI,KAAK,OAAQ,QAAO;AACrC,UAAM,YAAY,QAAQ,MAAM,MAAM;AACtC,cAAU;AACV,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,YAAY,KAAK,SAAS,OAAQ,QAAO;AAC7C,WAAO,KAAK,iBAAiB;AAC7B,WAAO,KAAK,KAAK,SAAS,QAAQ,SAAS,SAAS,CAAC;AACrD,cAAU;AACV;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAASC,+BACP,MACA,SACe;AACf,QAAM,SAAmB,CAAA;AACzB,MAAI,SAAS;AACb,MAAI,WAAW;AACf,SAAO,SAAS,KAAK,QAAQ;AAC3B,QAAI,SAAS,IAAI,KAAK,OAAQ,QAAO;AACrC,UAAM,YAAY,QAAQ,MAAM,MAAM;AACtC,cAAU;AACV,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,YAAY,KAAK,SAAS,OAAQ,QAAO;AAC7C,WAAO,KAAK,iBAAiB;AAC7B,WAAO,KAAK,KAAK,SAAS,QAAQ,SAAS,SAAS,CAAC;AACrD,cAAU;AACV;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAASC,+BACP,MACA,QACe;AACf,QAAM,SAAmB,CAAA;AACzB,MAAI,SAAS;AACb,MAAI,WAAW;AACf,QAAM,YAAY,CAAC,KAAa,OAAuB;AACrD,QAAI,KAAK,IAAI,IAAI,OAAQ,QAAO;AAChC,UAAM,KAAK,IAAI,EAAE;AACjB,UAAM,KAAK,IAAI,KAAK,CAAC;AACrB,UAAM,KAAK,IAAI,KAAK,CAAC;AACrB,WAAO,WAAW,QACZ,MAAM,KAAO,MAAM,IAAK,QAAQ,KAChC,MAAM,KAAO,MAAM,IAAK,QAAQ;AAAA,EACxC;AACA,SAAO,SAAS,KAAK,QAAQ;AAC3B,QAAI,SAAS,IAAI,KAAK,OAAQ,QAAO;AACrC,UAAM,YAAY,UAAU,MAAM,MAAM;AACxC,cAAU;AACV,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,YAAY,KAAK,SAAS,OAAQ,QAAO;AAC7C,WAAO,KAAK,iBAAiB;AAC7B,WAAO,KAAK,KAAK,SAAS,QAAQ,SAAS,SAAS,CAAC;AACrD,cAAU;AACV;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAAS,uBAAuB,YAA6B;AAC3D,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,QAAM,KAAK,WAAW,CAAC;AACvB,MAAI,OAAO,OAAW,QAAO;AAC7B,OAAK,KAAK,SAAU,EAAG,QAAO;AAC9B,QAAM,UAAU,KAAK;AACrB,SAAO,WAAW,KAAK,WAAW;AACpC;AAEA,SAAS,kCAAkC,SAAgC;AACzE,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,QAAM,YAAY,QAAQ,CAAC;AAC3B,QAAM,UAAU,YAAY;AAC5B,QAAM,MAAgB,CAAA;AACtB,QAAM,UAAU,CAAC,QAAgB;AAC/B,QAAI,IAAI,WAAW,EAAG;AACtB,QAAI,KAAK,mBAAmB,GAAG;AAAA,EACjC;AAGA,MAAI,YAAY,IAAI;AAClB,QAAI,MAAM;AACV,WAAO,MAAM,KAAK,QAAQ,QAAQ;AAChC,YAAM,OAAO,QAAQ,aAAa,GAAG;AACrC,aAAO;AACP,UAAI,QAAQ,KAAK,MAAM,OAAO,QAAQ,OAAQ,QAAO;AACrD,cAAQ,QAAQ,SAAS,KAAK,MAAM,IAAI,CAAC;AACzC,aAAO;AAAA,IACT;AACA,WAAO,IAAI,SAAS,OAAO,OAAO,GAAG,IAAI;AAAA,EAC3C;AAGA,MAAI,YAAY,IAAI;AAClB,QAAI,MAAM,IAAI;AACd,QAAI,MAAM,QAAQ,OAAQ,QAAO;AACjC,WAAO,MAAM,KAAK,QAAQ,QAAQ;AAChC,YAAM,OAAO,QAAQ,aAAa,GAAG;AACrC,aAAO;AACP,UAAI,QAAQ,KAAK,MAAM,OAAO,QAAQ,OAAQ,QAAO;AACrD,cAAQ,QAAQ,SAAS,KAAK,MAAM,IAAI,CAAC;AACzC,aAAO;AAAA,IACT;AACA,WAAO,IAAI,SAAS,OAAO,OAAO,GAAG,IAAI;AAAA,EAC3C;AAGA,MAAI,YAAY,IAAI;AAClB,QAAI,MAAM,IAAI;AACd,QAAI,MAAM,QAAQ,OAAQ,QAAO;AACjC,WAAO,MAAM,KAAK,QAAQ,QAAQ;AAChC,YAAM,OAAO,QAAQ,aAAa,GAAG;AACrC,aAAO;AACP,UAAI,MAAM,IAAI,IAAI,QAAQ,OAAQ,QAAO;AACzC,aAAO,IAAI;AACX,UAAI,QAAQ,KAAK,MAAM,OAAO,QAAQ,OAAQ,QAAO;AACrD,cAAQ,QAAQ,SAAS,KAAK,MAAM,IAAI,CAAC;AACzC,aAAO;AAAA,IACT;AACA,WAAO,IAAI,SAAS,OAAO,OAAO,GAAG,IAAI;AAAA,EAC3C;AAGA,MAAI,YAAY,IAAI;AAClB,QAAI,MAAM,IAAI;AACd,QAAI,MAAM,QAAQ,OAAQ,QAAO;AACjC,WAAO,MAAM,KAAK,QAAQ,QAAQ;AAChC,YAAM,OAAO,QAAQ,aAAa,GAAG;AACrC,aAAO;AACP,UAAI,MAAM,IAAI,IAAI,QAAQ,OAAQ,QAAO;AACzC,aAAO,IAAI;AACX,UAAI,QAAQ,KAAK,MAAM,OAAO,QAAQ,OAAQ,QAAO;AACrD,cAAQ,QAAQ,SAAS,KAAK,MAAM,IAAI,CAAC;AACzC,aAAO;AAAA,IACT;AACA,WAAO,IAAI,SAAS,OAAO,OAAO,GAAG,IAAI;AAAA,EAC3C;AAEA,SAAO;AACT;AAQO,SAAS,oBAAoB,MAAsB;AACxD,MAAI,cAAc,IAAI,EAAG,QAAO;AAGhC,QAAM,MAAM,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AAChD,QAAM,MAAM,OAAO,KAAK,CAAC,GAAM,GAAM,CAAI,CAAC;AAC1C,QAAM,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM;AACxC,QAAM,OAAO,KAAK,SAAS,GAAG,OAAO,EAAE,QAAQ,GAAG;AAClD,MAAI,OAAO,EAAG,QAAO,KAAK,SAAS,IAAI;AACvC,QAAM,OAAO,KAAK,SAAS,GAAG,OAAO,EAAE,QAAQ,GAAG;AAClD,MAAI,OAAO,EAAG,QAAO,KAAK,SAAS,IAAI;AAGvC,QAAM,KAAKF,6BAA2B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AACvE,MAAI,GAAI,QAAO;AACf,QAAM,KAAKA,6BAA2B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AACvE,MAAI,GAAI,QAAO;AAGf,QAAM,OAAOE,+BAA6B,MAAM,IAAI;AACpD,MAAI,KAAM,QAAO;AACjB,QAAM,OAAOA,+BAA6B,MAAM,IAAI;AACpD,MAAI,KAAM,QAAO;AAGjB,QAAM,OAAOD,+BAA6B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3E,MAAI,KAAM,QAAO;AACjB,QAAM,OAAOA,+BAA6B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3E,MAAI,KAAM,QAAO;AAGjB,QAAM,MAAM,kCAAkC,IAAI;AAClD,MAAI,IAAK,QAAO;AAGhB,MAAI,uBAAuB,IAAI,GAAG;AAChC,WAAO,OAAO,OAAO,CAAC,mBAAmB,IAAI,CAAC;AAAA,EAChD;AAEA,SAAO;AACT;AAiCO,SAAS,oBAAoB,QAAyB;AAC3D,QAAM,OAAO,kBAAkB,MAAM;AACrC,MAAI,SAAS;AACb,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,EAAG;AACpB,UAAM,KAAK,IAAI,CAAC,KAAK,KAAK;AAC1B,QAAI,MAAM,EAAG,QAAO;AACpB,QAAI,MAAM,EAAG,UAAS;AAAA,EACxB;AACA,SAAO;AACT;ACrPA,SAAS,2BACP,MACA,SACe;AACf,QAAM,SAAmB,CAAA;AACzB,MAAI,SAAS;AACb,MAAI,WAAW;AACf,SAAO,SAAS,KAAK,QAAQ;AAC3B,QAAI,SAAS,IAAI,KAAK,OAAQ,QAAO;AACrC,UAAM,YAAY,QAAQ,MAAM,MAAM;AACtC,cAAU;AACV,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,YAAY,KAAK,SAAS,OAAQ,QAAO;AAC7C,WAAO,KAAK,iBAAiB;AAC7B,WAAO,KAAK,KAAK,SAAS,QAAQ,SAAS,SAAS,CAAC;AACrD,cAAU;AACV;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAAS,6BACP,MACA,SACe;AACf,QAAM,SAAmB,CAAA;AACzB,MAAI,SAAS;AACb,MAAI,WAAW;AACf,SAAO,SAAS,KAAK,QAAQ;AAC3B,QAAI,SAAS,IAAI,KAAK,OAAQ,QAAO;AACrC,UAAM,YAAY,QAAQ,MAAM,MAAM;AACtC,cAAU;AACV,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,YAAY,KAAK,SAAS,OAAQ,QAAO;AAC7C,WAAO,KAAK,iBAAiB;AAC7B,WAAO,KAAK,KAAK,SAAS,QAAQ,SAAS,SAAS,CAAC;AACrD,cAAU;AACV;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAAS,6BACP,MACA,QACe;AACf,QAAM,SAAmB,CAAA;AACzB,MAAI,SAAS;AACb,MAAI,WAAW;AACf,QAAM,YAAY,CAAC,KAAa,OAAuB;AACrD,QAAI,KAAK,IAAI,IAAI,OAAQ,QAAO;AAChC,UAAM,KAAK,IAAI,EAAE;AACjB,UAAM,KAAK,IAAI,KAAK,CAAC;AACrB,UAAM,KAAK,IAAI,KAAK,CAAC;AACrB,WAAO,WAAW,QACZ,MAAM,KAAO,MAAM,IAAK,QAAQ,KAChC,MAAM,KAAO,MAAM,IAAK,QAAQ;AAAA,EACxC;AACA,SAAO,SAAS,KAAK,QAAQ;AAC3B,QAAI,SAAS,IAAI,KAAK,OAAQ,QAAO;AACrC,UAAM,YAAY,UAAU,MAAM,MAAM;AACxC,cAAU;AACV,QAAI,aAAa,EAAG,QAAO;AAC3B,QAAI,YAAY,KAAK,SAAS,OAAQ,QAAO;AAC7C,WAAO,KAAK,iBAAiB;AAC7B,WAAO,KAAK,KAAK,SAAS,QAAQ,SAAS,SAAS,CAAC;AACrD,cAAU;AACV;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAAS,uBAAuB,YAA6B;AAC3D,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,QAAM,KAAK,WAAW,CAAC;AACvB,MAAI,OAAO,OAAW,QAAO;AAC7B,OAAK,KAAK,SAAU,EAAG,QAAO;AAC9B,QAAM,UAAW,MAAM,IAAK;AAC5B,SAAO,WAAW;AACpB;AAQO,SAAS,oBAAoB,MAAsB;AACxD,MAAI,cAAc,IAAI,EAAG,QAAO;AAEhC,QAAM,MAAM,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AAChD,QAAM,MAAM,OAAO,KAAK,CAAC,GAAM,GAAM,CAAI,CAAC;AAC1C,QAAM,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM;AACxC,QAAM,OAAO,KAAK,SAAS,GAAG,OAAO,EAAE,QAAQ,GAAG;AAClD,MAAI,OAAO,EAAG,QAAO,KAAK,SAAS,IAAI;AACvC,QAAM,OAAO,KAAK,SAAS,GAAG,OAAO,EAAE,QAAQ,GAAG;AAClD,MAAI,OAAO,EAAG,QAAO,KAAK,SAAS,IAAI;AAGvC,QAAM,KAAK,2BAA2B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AACvE,MAAI,GAAI,QAAO;AACf,QAAM,KAAK,2BAA2B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AACvE,MAAI,GAAI,QAAO;AAEf,QAAM,OAAO,6BAA6B,MAAM,IAAI;AACpD,MAAI,KAAM,QAAO;AACjB,QAAM,OAAO,6BAA6B,MAAM,IAAI;AACpD,MAAI,KAAM,QAAO;AAEjB,QAAM,OAAO,6BAA6B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3E,MAAI,KAAM,QAAO;AACjB,QAAM,OAAO,6BAA6B,MAAM,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3E,MAAI,KAAM,QAAO;AAEjB,MAAI,uBAAuB,IAAI,GAAG;AAChC,WAAO,OAAO,OAAO,CAAC,mBAAmB,IAAI,CAAC;AAAA,EAChD;AACA,SAAO;AACT;AAiBO,SAAS,WAAW,SAA0B;AACnD,SAAO,WAAW,MAAM,WAAW;AACrC;AA2BO,SAAS,qBAAqB,QAAyB;AAC5D,QAAM,OAAO,kBAAkB,MAAM;AACrC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,EAAG;AACpB,UAAM,KAAK,IAAI,CAAC;AAChB,QAAI,OAAO,OAAW;AACtB,SAAK,KAAK,SAAU,EAAG;AACvB,UAAM,UAAW,MAAM,IAAK;AAC5B,QAAI,WAAW,OAAO,EAAG,QAAO;AAAA,EAClC;AACA,SAAO;AACT;ACrKA,eAAsB,2BACpB,KACA,QACA,YACiB;AACjB,QAAM,aAAa;AACnB,QAAM,4BAAY,IAAA;AAClB,aAAW,SAAS,IAAI,SAAS,UAAU,EAAG,OAAM,IAAI,MAAM,CAAC,CAAE;AACjE,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,QAAM,eAAe,MAAM,QAAQ;AAAA,IACjC,CAAC,GAAG,KAAK,EAAE,IAAI,OAAO,SAAS;AAC7B,UAAI;AACF,cAAM,EAAE,YAAY,MAAME,SAAAA,OAAU,MAAM,EAAE,QAAQ,GAAG;AACvD,eAAO,KAAK,0BAA0B,EAAE,MAAM,EAAE,YAAY,MAAM,QAAA,GAAW;AAC7E,eAAO,CAAC,MAAM,OAAO;AAAA,MACvB,SAAS,KAAK;AACZ,eAAO,KAAK,uBAAuB,EAAE,MAAM,EAAE,YAAY,MAAM,OAAOxB,MAAAA,OAAO,GAAG,EAAA,EAAE,CAAG;AACrF,eAAO,CAAC,MAAM,IAAI;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EAAA;AAGH,MAAI,YAAY;AAChB,aAAW,CAAC,MAAM,OAAO,KAAK,cAAc;AAC1C,QAAI,CAAC,QAAS;AACd,UAAM,UAAU,KAAK,QAAQ,OAAO,KAAK;AACzC,gBAAY,UAAU,QAAQ,IAAI,OAAO,SAAS,GAAG,GAAG,OAAO;AAAA,EACjE;AACA,SAAO;AACT;ACzBO,SAAS,uBAAuB,IAAY,IAAoB;AACnE,MAAI,OAAO;AACP,WAAO;AACX,QAAM,WAAW,KAAK;AACtB,MAAI;AACJ,MAAI,KAAK;AACL,uBAAmB,KAAK,QAAU;AAAA;AAElC,uBAAmB,KAAK,QAAU;AAEtC,MAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,IAAI,gBAAgB;AAC9C,WAAO;AACX,SAAO;AACX;AAEO,SAAS,mBAAmB,SAAiB,YAAY,GAAG;AAC/D,UAAQ,UAAU,YAAY,SAAW;AAC7C;AAEwB,OAAO,UAAU;AAKlC,SAAS,qBAAqB,SAAiB,MAAc;AAChE,SAAO,mBAAmB,OAAO,MAAM;AAC3C;AAEO,MAAM,aAAa;AAAA,EAItB,YAAmByB,UAAyB,YAAqB;AAA9C,SAAA,UAAAA;AAAyB,SAAA,aAAA;AAAA,EAC5C;AAAA,EAJA;AAAA,EACA,UAAqC,CAAA;AAAA,EAKrC,aAAa,qBAA6B,KAA+B;AACrE,QAAI,CAAC,KAAK;AACN,aAAO;AAEX,UAAM,QAAQ,mBAAmB,mBAAmB;AAEpD,aAAS,IAAI,GAAG,IAAI,KAAK,YAAY,KAAK;AACtC,YAAM,SAAS,QAAQ,KAAK,KAAK;AACjC,YAAM,SAAS,KAAK,QAAQ,KAAK;AACjC,UAAI,CAAC;AACD;AACJ,YAAM,EAAE,mBAAmB,OAAO;AAClC,YAAM,KAAK,uBAAuB,KAAK,sBAAsB,gBAAgB,cAAc;AAE3F,UAAI,MAAM,GAAG;AACT,aAAK,QAAQ,IAAI,gCAAgC,cAAc;AAC/D,aAAK,QAAQ,KAAK,IAAI;AACtB,YAAI,KAAK,MAAM;AAAA,MACnB,WACS,OAAO,GAAG;AACf,aAAK,QAAQ,KAAK,IAAI;AACtB,aAAK,qBAAqB;AAC1B,YAAI,KAAK,MAAM;AAAA,MACnB,MACK;AAAA,IAGT;AACA,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,QAAgC;AAClC,QAAI,KAAK,uBAAuB,UAAa,qBAAqB,KAAK,oBAAoB,OAAO,OAAO,cAAc,GAAG;AACtH,WAAK,qBAAqB,OAAO,OAAO;AACxC,aAAO,KAAK,aAAa,KAAK,oBAAoB,CAAC,MAAM,CAAC;AAAA,IAC9D;AAEA,UAAM,EAAE,mBAAmB,OAAO;AAClC,UAAM,iBAAiB,uBAAuB,KAAK,oBAAoB,cAAc;AAErF,QAAI,kBAAkB;AAClB,aAAO,CAAA;AAEX,UAAM,MAAmB,CAAA;AAGzB,QAAI,iBAAiB,KAAK,YAAY;AAElC,YAAM,EAAE,uBAAuB;AAC/B,WAAK,qBAAqB,iBAAiB,KAAK;AAGhD,WAAK,aAAa,oBAAoB,GAAG;AAAA,IAC7C;AAEA,SAAK,QAAQ,OAAO,OAAO,iBAAiB,KAAK,UAAU,IAAI;AAC/D,WAAO,KAAK,aAAa,KAAK,oBAAoB,GAAG;AAAA,EACzD;AACJ;ACrGA,MAAM,mBAAmB;AAgBzB,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;AAEzB,MAAM,0BAA0B;AAShC,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AAIrB,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAS5B,MAAM,cAAc;AACpB,MAAM,cAAc;AAiBpB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB,kBAAkB;AAGzC,SAAS,WAAW,MAAsB;AACtC,UAAQ,KAAK,CAAC,IAAK,QAAS;AAChC;AAEA,SAAS,WAAW,SAA0B;AAE1C,MAAI,YAAY,uBAAuB,YAAY,qBAC/C,YAAY,qBAAqB,YAAY,uBAC7C,YAAY,qBAAqB,YAAY,kBAAkB;AAClE,WAAO;AAAA,EACR;AACA,SAAO;AACX;AAGO,SAAS,cAAc,MAAwB;AAClD,QAAM,MAAgB,CAAA;AACtB,MAAI;AACJ,MAAI,MAAM;AACV,SAAO,MAAM,KAAK,QAAQ;AACtB,QAAI,YAAY;AACZ,UAAI,KAAK,KAAK,SAAS,SAAS,GAAG,CAAC;AACxC,UAAM,WAAW,KAAK,aAAa,GAAG;AACtC,WAAO;AACP,cAAU;AACV,WAAO;AAAA,EACX;AACA,MAAI,YAAY,OAAW,KAAI,KAAK,KAAK,SAAS,OAAO,CAAC;AAC1D,SAAO;AACX;AAEO,SAAS,uBAAuB,MAAc;AACjD,QAAM,MAAgB,CAAA;AACtB,MAAI,WAAW;AACf,MAAI,SAAS;AACb,QAAM,gBAAgB,MAAM;AACxB,UAAM,QAAQ,KAAK,SAAS,UAAU,MAAM;AAC5C,QAAI,MAAM;AACN,UAAI,KAAK,KAAK;AAClB,cAAU;AACV,eAAW;AAAA,EACf;AAEA,SAAO,SAAS,KAAK,SAAS,GAAG;AAC7B,UAAM,YAAY,KAAK,aAAa,MAAM;AAC1C,QAAI,cAAc,GAAG;AACjB,oBAAA;AAAA,IACJ,OACK;AACD;AAAA,IACJ;AAAA,EACJ;AACA,WAAS,KAAK;AACd,gBAAA;AAEA,SAAO;AACX;AAUO,MAAM,iBAAiB;AAAA,EAoB1B,YAAmBA,UAA0B,eAA8B,WAAkC,eAAe,IAAI,aAAaA,UAAS,CAAC,GAAG;AAAvI,SAAA,UAAAA;AAA0B,SAAA,gBAAA;AAA8B,SAAA,YAAA;AAAkC,SAAA,eAAA;AACzG,SAAK,iBAAiB,aAAa;AAAA,EACvC;AAAA,EArBA,eAAe;AAAA,EACf;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,aAAa;AAAA,EAMb,iBAAiB,eAAuB;AACpC,SAAK,gBAAgB;AAErB,SAAK,QAAQ,gBAAgB;AAC7B,SAAK,QAAQ,KAAK,MAAM,gBAAgB,GAAE;AAAA,EAC9C;AAAA,EAEA,kBAAiC;AAC7B,QAAI,CAAC,KAAK,WAAW;AACjB,WAAK,YAAY,CAAA;AAAA,IACrB;AACA,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,UAAU,KAAmB;AACzB,SAAK,kBAAkB,MAAM;AAAA,EACjC;AAAA,EAEA,UAAU,KAAmB;AACzB,SAAK,kBAAkB,MAAM;AAAA,EACjC;AAAA,EAEA,UAAU,KAAmB;AACzB,SAAK,kBAAkB,MAAM;AAAA,EACjC;AAAA,EAEA,gBAAgB,KAAmB;AAC/B,SAAK,kBAAkB,YAAY;AAAA,EACvC;AAAA,EAEA,gBAAgB,KAAmB;AAC/B,SAAK,kBAAkB,YAAY;AAAA,EACvC;AAAA,EAEA,aAAa,UAA2B;AAEpC,WAAO;AAAA,EACX;AAAA;AAAA;AAAA,EAIA,YAAY,MAAc,SAAmB,OAA2B;AAEpE,UAAM,iBAAiB,WAAW,IAAI;AAGtC,QAAI,mBAAmB,aAAa;AAEhC,YAAM,kBAAkB,KAAK,CAAC,IAAK;AACnC,YAAM,YAAY,CAAC,EAAE,KAAK,CAAC,IAAK;AAChC,YAAM,UAAU,CAAC,EAAE,KAAK,CAAC,IAAK;AAC9B,YAAM,aAAa,CAAC,aAAa,CAAC;AAGlC,YAAMC,YAAY,KAAK,CAAC,IAAK,MAAS,KAAO,KAAK,CAAC,IAAK,QAAS;AACjE,YAAMC,OAAM,KAAK,CAAC,IAAK;AAEvB,YAAM,oBAAoB,OAAO,MAAM,CAAC;AACxC,wBAAkB,CAAC,IAAK,mBAAmB,IAAMD,YAAW;AAC5D,wBAAkB,CAAC,KAAMA,WAAU,OAAS,IAAKC;AAEjD,aAAO,OAAO,OAAO,CAAC,mBAAmB,KAAK,SAAS,cAAc,CAAC,CAAC;AAEvE,UAAI,WAAW;AACX,gBAAQ;AAAA,MACZ,WACS,SAAS;AACd,kBAAU;AAAA,MACd,WACS,YAAY;AACjB,kBAAU;AACV,gBAAQ;AAAA,MACZ;AAAA,IACJ;AAGA,UAAM,UAAU,WAAW,IAAI;AAC/B,UAAM,WAAY,KAAK,CAAC,IAAK,MAAS,KAAO,KAAK,CAAC,IAAK,QAAS;AACjE,UAAM,MAAM,KAAK,CAAC,IAAK;AAGvB,UAAM,cAAc,OAAO,MAAM,CAAC;AAClC,gBAAY,CAAC,IAAK,eAAe,IAAM,WAAW;AAClD,gBAAY,CAAC,KAAM,UAAU,OAAS,IAAK;AAG3C,UAAM,iBAAiB,OAAO,KAAK,CAAC,GAAG,aAAa,OAAO,CAAC;AAC5D,UAAM,gBAAgB,UAAU,iBAAiB,OAAO,KAAK,CAAC,GAAG,aAAa,UAAU,GAAI,CAAC;AAC7F,UAAM,cAAc,QAAQ,iBAAiB,OAAO,KAAK,CAAC,GAAG,aAAa,UAAU,EAAI,CAAC;AACzF,QAAI,WAAW;AAEf,UAAM,WAAqB,CAAA;AAC3B,QAAI,SAAS;AAEb,WAAO,SAAS,KAAK,QAAQ;AACzB,UAAI;AACJ,YAAM,cAAc,KAAK,IAAI,KAAK,OAAO,KAAK,SAAS,MAAM;AAC7D,gBAAU,KAAK,SAAS,QAAQ,SAAS,WAAW;AACpD,gBAAU;AAEV,UAAI,WAAW,KAAK,QAAQ;AACxB,mBAAW;AAAA,MACf;AAEA,eAAS,KAAK,OAAO,OAAO,CAAC,UAAU,OAAO,CAAC,CAAC;AAEhD,iBAAW;AAAA,IACf;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA,EAIA,eAAe,OAAyB;AACpC,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,0CAA0C;AAE9D,QAAI,UAAU;AACd,QAAI,gBAAgB,KAAK,gBAAgB;AAGzC,UAAM,WAAW,OAAO,MAAM,CAAC;AAC/B,aAAS,CAAC,IAAI,eAAe;AAC7B,aAAS,CAAC,IAAI;AAEd,UAAM,UAAoB,CAAC,QAAQ;AAEnC,WAAO,MAAM,UAAU,MAAM,CAAC,EAAG,SAAS,qBAAqB,iBAAiB,UAAU,GAAG;AACzF,YAAM,OAAO,MAAM,MAAA;AACnB,uBAAiB,oBAAoB,KAAK;AAC1C,iBAAW;AACX,YAAM,cAAc,OAAO,MAAM,CAAC;AAClC,kBAAY,cAAc,KAAK,QAAQ,CAAC;AACxC,cAAQ,KAAK,aAAa,IAAI;AAAA,IAClC;AAGA,QAAI,YAAY;AACZ,aAAO,MAAM,MAAA;AAGjB,QAAI,YAAY,GAAG;AACf,aAAO,QAAQ,CAAC;AAAA,IACpB;AAEA,WAAO,OAAO,OAAO,OAAO;AAAA,EAChC;AAAA,EAEA,YAAY,OAAiB;AACzB,UAAM,MAAgB,CAAA;AACtB,WAAO,MAAM,QAAQ;AACjB,YAAM,OAAO,KAAK,eAAe,KAAK;AACtC,UAAI,KAAK,SAAS,KAAK,eAAe;AAClC,YAAI,KAAK,IAAI;AACb;AAAA,MACJ;AACA,YAAM,MAAM,KAAK,YAAY,IAAI;AACjC,UAAI,KAAK,GAAG,GAAG;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AAAA,EAEA,aAAa,KAAgB,MAAc,QAAiB;AACxD,UAAM,MAAM,IAAI,MAAA;AAChB,QAAI,OAAO,kBAAkB,IAAI,OAAO,iBAAiB,KAAK,eAAe,SAAW;AACxF,QAAI,OAAO,SAAS;AACpB,QAAI,OAAO,UAAU;AACrB,QAAI,UAAU;AACd,QAAI,KAAK,SAAS,KAAK;AACnB,WAAK,QAAQ,KAAK,qDAAqD;AAC3E,SAAK,aAAa,IAAI,OAAO;AAC7B,WAAO;AAAA,EACX;AAAA,EAEA,eAAe,KAAkB;AAC7B,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,WAAW,QAAQ,WAAW;AAC/B;AAIJ,UAAM,QAAQ,QAAQ,CAAC;AACvB,UAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC;AACvC,UAAM,kBAAkB,MAAM,QAAQ,CAAC,IAAK;AAC5C,UAAM,aAAa,CAAC,EAAE,MAAM,QAAQ,CAAC,IAAK;AAC1C,UAAM,WAAW,CAAC,EAAE,KAAK,QAAQ,CAAC,IAAK;AAGvC,UAAM,WAAY,MAAM,QAAQ,CAAC,IAAK,MAAS,KAAO,MAAM,QAAQ,CAAC,IAAK,QAAS;AACnF,UAAM,MAAM,MAAM,QAAQ,CAAC,IAAK;AAGhC,UAAM,oBAAoB,OAAO,MAAM,CAAC;AACxC,sBAAkB,CAAC,IAAK,mBAAmB,IAAM,WAAW;AAC5D,sBAAkB,CAAC,KAAM,UAAU,OAAS,IAAK;AAEjD,UAAM,2BAA2B,MAAc;AAC3C,YAAM,oBAAoB,QAAQ,IAAI,CAAA,WAAU,OAAO,QAAQ,SAAS,cAAc,CAAC;AACvF,wBAAkB,QAAQ,iBAAiB;AAC3C,aAAO,OAAO,OAAO,iBAAiB;AAAA,IAC1C;AAGA,QAAI,oBAAoB,gBAAgB,oBAAoB,cAAc;AACtE,YAAM,eAAe,yBAAA;AACrB,YAAM,SAAS,uBAAuB,YAAY;AAElD,aAAO,OAAO,QAAQ;AAClB,cAAM,QAAQ,OAAO,MAAA;AACrB,cAAM,gBAAgB,WAAW,KAAK;AAEtC,YAAI,kBAAkB,cAAc;AAChC,eAAK,UAAU,KAAK;AAAA,QACxB,WACS,kBAAkB,cAAc;AACrC,eAAK,UAAU,KAAK;AAAA,QACxB,WACS,kBAAkB,cAAc;AACrC,eAAK,UAAU,KAAK;AAAA,QACxB,OACK;AAED,cAAI,WAAW,aAAa,GAAG;AAC3B,iBAAK,qBAAqB,OAAO,GAAG;AAAA,UACxC;AAEA,eAAK,SAAS,OAAO,KAAK;AAAA,YACtB,SAAS;AAAA,YACT,SAAS,CAAC;AAAA,YACV,OAAO,CAAC;AAAA,YACR,QAAQ,KAAK,OAAO;AAAA,UAAA,CACvB;AAAA,QACL;AAAA,MACJ;AAAA,IACJ,OACK;AAED,aAAO,QAAQ,QAAQ;AACnB,cAAM,KAAK,QAAQ,CAAC;AACpB,YAAI,GAAG,QAAQ,SAAS,KAAK,iBAAiB,GAAG,QAAQ,SAAS,KAAK;AACnE;AACJ,gBAAQ,MAAA;AACR,YAAI,KAAK,KAAK,aAAa,IAAI,GAAG,SAAS,GAAG,OAAO,MAAM,CAAC;AAAA,MAChE;AAEA,UAAI,CAAC,QAAQ,QAAQ;AACjB,aAAK,YAAY;AACjB;AAAA,MACJ;AAGA,YAAM,cAAc,QAAQ,CAAC;AAC7B,YAAM,aAAa,QAAQ,QAAQ,SAAS,CAAC;AAC7C,YAAM,mBAAmB,CAAC,EAAE,YAAY,QAAQ,CAAC,IAAK;AACtD,YAAM,iBAAiB,CAAC,EAAE,WAAW,QAAQ,CAAC,IAAK;AAEnD,YAAM,eAAe,yBAAA;AAErB,WAAK,SAAS,aAAa,KAAK;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS,CAAC;AAAA,QACV,OAAO,CAAC;AAAA,QACR,QAAQ,WAAW,OAAO;AAAA,MAAA,CAC7B;AAAA,IACL;AAEA,SAAK,gBAAgB,QAAQ,SAAS;AACtC,SAAK,YAAY;AAAA,EACrB;AAAA,EAEA,iBAAiB,QAAmB,OAAiB,KAAkB,YAAY,OAAO,OAAO,QAAQ;AACrG,UAAM,QAAQ,CAAC,YAAY,UAAU;AACjC,UAAI,UAAU;AACV,aAAK;AACT,YAAM,SAAS,aAAa,UAAU,MAAM,SAAS;AACrD,UAAI,KAAK,KAAK,aAAa,QAAQ,YAAY,MAAM,CAAC;AAAA,IAC1D,CAAC;AAAA,EACL;AAAA,EAEA,qBAAqB,QAAmB,KAAkB;AACtD,QAAI,KAAK,IAAI;AAET,WAAK,KAAK;AACV;AAAA,IACJ;AAGA,QAAI,CAAC,KAAK;AACN;AAGJ,QAAI,CAAC,KAAK,WAAW,OAAO,CAAC,KAAK,WAAW;AACzC;AAEJ,UAAM,MAAM,CAAC,KAAK,UAAU,KAAK,KAAK,UAAU,GAAG;AACnD,QAAI,KAAK,UAAU;AACf,UAAI,QAAQ,KAAK,UAAU,GAAG;AAClC,QAAI,KAAK,WAAW;AAChB,UAAI,KAAK,KAAK,UAAU,SAAS;AACrC,QAAI,KAAK,WAAW;AAChB,UAAI,KAAK,KAAK,UAAU,SAAS;AAErC,UAAM,aAAa,KAAK,YAAY,GAAG;AACvC,QAAI,WAAW,WAAW,GAAG;AACzB,WAAK,QAAQ,MAAM,2CAA2C;AAC9D;AAAA,IACJ;AAEA,SAAK,iBAAiB,QAAQ,YAAY,KAAK,KAAK;AACpD,SAAK;AAAA,EACT;AAAA;AAAA,EAGA,SAAS,QAAmB,KAAkB,YAK1C;AAAA,IACI,SAAS,OAAO;AAAA,IAChB,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ,OAAO,OAAO;AAAA,EAAA,GACvB;AACH,UAAM,EAAE,SAAS,SAAS,OAAO,WAAW;AAC5C,QAAI,QAAQ,SAAS,KAAK,iBAAiB,WAAW,OAAO;AACzD,YAAM,YAAY,KAAK,YAAY,SAAS,SAAS,KAAK;AAC1D,WAAK,iBAAiB,QAAQ,WAAW,KAAK,MAAM;AAAA,IACxD,OACK;AAED,UAAI,KAAK,KAAK,aAAa,QAAQ,SAAS,MAAM,CAAC;AAAA,IACvD;AAAA,EACJ;AAAA,EAEA,YAAiC,QAAgB;AAC7C,UAAM,MAAW,CAAA;AACjB,eAAW,cAAc,KAAK,aAAa,MAAM,MAAM,GAAG;AACtD,WAAK,eAAe,YAAY,GAAG;AAAA,IACvC;AACA,WAAO;AAAA,EACX;AAAA,EAEA,eAAe,QAAmB,KAAkB;AAEhD,QAAI,CAAC,OAAO,QAAQ,QAAQ;AACxB,WAAK,eAAe,GAAG;AACvB,WAAK;AACL;AAAA,IACJ;AAEA,UAAM,UAAU,WAAW,OAAO,OAAO;AAGzC,QAAI,KAAK,aAAa,KAAK,UAAU,CAAC,EAAG,OAAO,cAAc,OAAO,OAAO,WAAW;AACnF,WAAK,eAAe,GAAG;AAAA,IAC3B;AAEA,QAAI,YAAY,aAAa;AAEzB,YAAM,OAAO,OAAO;AACpB,YAAM,kBAAkB,KAAK,CAAC,IAAK;AAEnC,UAAI,KAAK,aAAa,eAAe,GAAG;AACpC,aAAK;AACL;AAAA,MACJ;AAEA,YAAM,YAAY,CAAC,EAAE,KAAK,CAAC,IAAK;AAChC,YAAM,UAAU,CAAC,EAAE,KAAK,CAAC,IAAK;AAE9B,UAAI,WAAW;AACX,YAAI,KAAK;AACL,eAAK,QAAQ,MAAM,0DAA0D,eAAe;AAEhG,aAAK,YAAY;AAGjB,YAAI,oBAAoB,uBAAuB,oBAAoB,mBAAmB;AAClF,eAAK,qBAAqB,QAAQ,GAAG;AAAA,QACzC;AAAA,MACJ,OACK;AACD,YAAI,KAAK,WAAW;AAEhB,gBAAM,OAAO,KAAK,UAAU,KAAK,UAAU,SAAS,CAAC;AACrD,cAAI,CAAC,qBAAqB,KAAK,OAAO,gBAAgB,OAAO,OAAO,cAAc,GAAG;AACjF,iBAAK,QAAQ,MAAM,gDAAgD,eAAe;AAClF;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,CAAC,KAAK;AACN,aAAK,YAAY,CAAA;AAErB,WAAK,UAAU,KAAK,MAAM;AAE1B,UAAI,SAAS;AACT,aAAK,eAAe,GAAG;AAAA,MAC3B,WACS,KAAK,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,SAAS,gBAAgB,eAAe,IAAI,KAAK,eAAe;AAEnH,cAAM,OAAO,KAAK,UAAU,KAAK,UAAU,SAAS,CAAC,EAAG,MAAA;AACxD,cAAM,UAAuB,CAAA;AAC7B,aAAK,eAAe,OAAO;AAE3B,cAAM,SAAS,QAAQ,IAAA;AACvB,aAAK,UAAU,OAAO;AACtB,aAAK,YAAY,CAAC,IAAI;AACtB,YAAI,KAAK,GAAG,OAAO;AACnB,aAAK,aAAa;AAAA,MACtB;AAAA,IACJ,WACS,YAAY,aAAa;AAC9B,WAAK,eAAe,GAAG;AAEvB,UAAI,SAAS;AACb,UAAI,SAAS;AACb,UAAI,SAAS;AAGb,YAAM,eAAe,cAAc,OAAO,OAAO;AACjD,mBAAa,QAAQ,CAAA,YAAW;AAC5B,cAAMC,WAAU,WAAW,OAAO;AAClC,YAAIA,aAAY,cAAc;AAC1B,mBAAS;AACT,eAAK,UAAU,OAAO;AAAA,QAC1B,WACSA,aAAY,cAAc;AAC/B,mBAAS;AACT,eAAK,UAAU,OAAO;AAAA,QAC1B,WACSA,aAAY,cAAc;AAC/B,mBAAS;AACT,eAAK,UAAU,OAAO;AAAA,QAC1B,WACSA,aAAY,qBAAqB;AACtC,eAAK,gBAAgB,OAAO;AAAA,QAChC,WACSA,aAAY,qBAAqB;AACtC,eAAK,gBAAgB,OAAO;AAAA,QAChC,WACSA,aAAY,aAAc;AAAA,iBAG1BA,YAAW,oBAAoBA,YAAW,EAAyB;AAAA,iBAGnEA,aAAY,oBAAqB;AAAA,iBAGjCA,aAAY,EAAG;AAAA,aAGnB;AACD,eAAK,QAAQ,KAAK,uBAAuBA,QAAO;AAAA,QACpD;AAAA,MACJ,CAAC;AAGD,UAAI,UAAU,UAAU,UAAU,OAAO,QAAQ,UAAU,KAAK,eAAe;AAC3E,aAAK,KAAK;AAEV,cAAM,KAAK,KAAK,YAAY,YAAY;AACxC,aAAK,iBAAiB,QAAQ,IAAI,GAAG;AAAA,MACzC,OACK;AAMD,cAAM,MAAgB,CAAA;AACtB,eAAO,aAAa,QAAQ;AACxB,gBAAM,OAAO,aAAa,MAAA;AAC1B,cAAI,KAAK,UAAU,KAAK,eAAe;AACnC,gBAAI,KAAK,IAAI;AACb;AAAA,UACJ;AACA,cAAI,KAAK,GAAG,KAAK,YAAY,IAAI,CAAC;AAAA,QACtC;AACA,aAAK,iBAAiB,QAAQ,KAAK,GAAG;AAAA,MAC1C;AAAA,IACJ,WACS,WAAW,2BAA4B,WAAW,gBAAgB,WAAW,qBAAsB;AACxG,WAAK,eAAe,GAAG;AAEvB,UAAI,KAAK,aAAa,OAAO,GAAG;AAC5B,aAAK;AACL;AAAA,MACJ;AAGA,UAAI,YAAY,cAAc;AAC1B,aAAK;AACL,aAAK,UAAU,OAAO,OAAO;AAC7B;AAAA,MACJ,WACS,YAAY,cAAc;AAC/B,aAAK;AACL,aAAK,UAAU,OAAO,OAAO;AAC7B;AAAA,MACJ,WACS,YAAY,cAAc;AAC/B,aAAK;AACL,aAAK,UAAU,OAAO,OAAO;AAC7B;AAAA,MACJ,WACS,YAAY,qBAAqB;AACtC,aAAK;AACL,aAAK,gBAAgB,OAAO,OAAO;AACnC;AAAA,MACJ,WACS,YAAY,qBAAqB;AACtC,aAAK;AACL,aAAK,gBAAgB,OAAO,OAAO;AACnC;AAAA,MACJ;AAEA,UAAI,KAAK,aAAa,OAAO,GAAG;AAC5B,aAAK;AACL;AAAA,MACJ;AAGA,UAAI,WAAW,OAAO,GAAG;AACrB,aAAK,qBAAqB,QAAQ,GAAG;AAAA,MACzC;AAEA,WAAK,SAAS,QAAQ,GAAG;AAAA,IAC7B,OACK;AACD,WAAK,QAAQ,MAAM,2BAA2B,OAAO;AACrD,WAAK;AAAA,IACT;AAEA;AAAA,EACJ;AACJ;AC/qBA,IAAI;AAEJ,eAAe,aAAoC;AACjD,MAAI,QAAS,QAAO;AACpB,MAAI;AACF,UAAM,aAAa;AAGnB,cAAU,MAAO,SAAS,KAAK,kBAAkB,EAAE,UAAU;AAC7D,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAGJ;AACF;AAiEO,MAAM,gBAAgB;AAAA,EACV;AAAA,EACT;AAAA,EACS;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACjB;AAAA,EACiB;AAAA;AAAA,EAET,kBAAmC;AAAA;AAAA,EAE3C,IAAI,iBAA0B;AAAE,WAAO,KAAK,gBAAgB,UAAU,KAAK,oBAAoB;AAAA,EAAO;AAAA,EAC9F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAyB;AAAA,EACzB,UAAyB;AAAA;AAAA,EAEzB,UAAyB;AAAA;AAAA,EAEzB,iBAAiB;AAAA,EACR;AAAA,EAET,QAA8B;AAAA,EAC9B,KAAkC;AAAA,EAClC,aAA4C;AAAA,EAC5C,aAA4C;AAAA;AAAA,EAE5C,cAAsC;AAAA,EACtC,cAAsC;AAAA,EACtC,YAAoC;AAAA,EACpC,SAAS;AAAA,EACT,aAAoD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY5D,WAAgC;AAAA;AAAA,EAGxB,cAAc;AAAA,EACd,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQd,mBAAoC;AAAA,EACpC,oBAAoB;AAAA;AAAA,EAE5B,OAAwB,yBAAyB;AAAA,EACzC,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUlB,mBAA4C;AAAA;AAAA,EAEpD,OAAwB,wBAAwB;AAAA;AAAA;AAAA,EAGxC,kBAAiC;AAAA,EAEzC,YAAY,SAAiC;AAC3C,SAAK,YAAY,QAAQ;AACzB,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ;AACtB,SAAK,WAAW,QAAQ;AACxB,SAAK,YAAY,QAAQ;AACzB,SAAK,UAAU,QAAQ;AACvB,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,YAAY,KAAK,IAAA;AAAA,EACxB;AAAA;AAAA,EAGA,MAAc,iBAAgF;AAC5F,UAAM,SAAS,MAAM,WAAA;AAKrB,UAAM,aAAgC,CAAA;AACtC,eAAW,SAAS,KAAK,WAAW,cAAc,CAAA,GAAI;AACpD,YAAM,UAAU,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,OAAO,CAAC,MAAM,IAAI;AACpE,iBAAW,OAAO,SAAS;AACzB,mBAAW,KAAK;AAAA,UACd,MAAM;AAAA,UACN,GAAI,MAAM,aAAa,SAAY,EAAE,UAAU,MAAM,SAAA,IAAa,CAAA;AAAA,UAClE,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,eAAe,CAAA;AAAA,QAAC,CAC1E;AAAA,MACH;AAAA,IACF;AAEA,UAAM,eAAe;AAAA,MACnB,EAAE,MAAM,eAAA;AAAA,MACR,EAAE,MAAM,OAAO,WAAW,MAAA;AAAA,MAC1B,EAAE,MAAM,OAAA;AAAA,MACR,EAAE,MAAM,QAAQ,WAAW,MAAA;AAAA,MAC3B,EAAE,MAAM,YAAA;AAAA,IAAY;AAMtB,UAAM,YAAY,IAAI,OAAO,sBAAsB;AAAA,MACjD,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,MACb,YAAY;AAAA,MACZ;AAAA,IAAA,CACD;AAOD,UAAM,YAAY,IAAI,OAAO,sBAAsB;AAAA,MACjD,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,MACb,YAAY;AAAA,MACZ;AAAA,IAAA,CACD;AAED,UAAM,cAAc,KAAK,gBAAgB,SACrC,CAAC,WAAW,SAAS,IACrB,CAAC,SAAS;AAEd,UAAM,YAA6B;AAAA,MACjC,QAAQ;AAAA,QACN,OAAO;AAAA,QACP,OAAO;AAAA,UACL,IAAI,OAAO,sBAAsB;AAAA,YAC/B,UAAU;AAAA,YACV,WAAW;AAAA,YACX,aAAa;AAAA,YACb,UAAU;AAAA,YACV,YAAY;AAAA,UAAA,CACb;AAAA,QAAA;AAAA,MACH;AAAA;AAAA;AAAA,MAIF,kBAAkB;AAAA,QAChB,OAAO;AAAA,UACL,EAAE,KAAK,sCAAA;AAAA,UACP,EAAE,KAAK,4EAAA;AAAA,UACP,EAAE,KAAK,6DAAA;AAAA,QAA6D;AAAA,QAEtE,OAAO;AAAA,UACL,EAAE,KAAK,sCAAA;AAAA,QAAsC;AAAA,MAC/C;AAAA,IACF;AAEF,QAAI,WAAW,SAAS,EAAG,WAAU,aAAa;AAClD,QAAI,KAAK,WAAW,UAAW,WAAU,eAAe,KAAK,UAAU;AACvE,QAAI,KAAK,WAAW,yBAAyB,QAAQ;AACnD,gBAAU,6BAA6B,CAAC,GAAG,KAAK,UAAU,uBAAuB;AAAA,IACnF;AAEA,WAAO,EAAE,QAAQ,UAAA;AAAA,EACnB;AAAA;AAAA,EAGA,MAAM,cAAuD;AAC3D,UAAM,EAAE,QAAQ,UAAA,IAAc,MAAM,KAAK,eAAA;AAEzC,SAAK,KAAK,IAAI,OAAO,kBAAkB,SAAS;AAOhD,SAAK,GAAG,yBAAyB,UAAU,CAAC,UAAkB;AAC5D,WAAK,OAAO,KAAK,qBAAqB,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,MAAA,GAAS;AACtG,UAAI,UAAU,aAAa;AACzB,aAAK,QAAQ;AACb,aAAK,qBAAA;AAAA,MACP,WAAW,UAAU,kBAAkB,UAAU,YAAY,UAAU,UAAU;AAC/E,aAAK,QAAQ,UAAU,iBAAiB,iBAAiB;AACzD,aAAK,KAAK,MAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAKD,SAAK,GAAG,wBAAwB,UAAU,CAAC,UAAkB;AAC3D,WAAK,OAAO,KAAK,+BAA+B,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,MAAA,GAAS;AAAA,IAClH,CAAC;AAGD,SAAK,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,SAAS;AAC/D,UAAM,mBAAmB,KAAK,GAAG,eAAe,KAAK,YAAY,EAAE,WAAW,YAAY;AAC1F,SAAK,cAAc,iBAAiB;AAOpC,SAAK,iBAAA;AAGL,SAAK,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,SAAS;AAC/D,UAAM,WAAW,KAAK,WAAW,aAAa;AAC9C,UAAM,mBAAmB,KAAK,GAAG,eAAe,KAAK,YAAY,EAAE,WAAW,UAAU;AACxF,SAAK,cAAc,iBAAiB;AAGpC,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK,KAAK,SAAS;AACzB,uBAAiB,QAAQ,UAAU,CAAC,UAAU;AAC5C,cAAM,aAAa,UAAU,CAAC,QAAQ;AACpC,cAAI;AACF,kBAAM,UAAU,IAAI;AACpB,gBAAI,SAAS,SAAS,EAAG,MAAK,GAAG,SAAS,MAAM;AAAA,UAClD,SAAS,KAAK;AACZ,iBAAK,OAAO,MAAM,kBAAkB,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAO5B,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,UACnH;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,MAAM,KAAK,GAAG,YAAA;AAC5B,UAAM,KAAK,GAAG,oBAAoB,KAAK;AAIvC,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,UAAI,KAAK,IAAI,sBAAsB,YAAY;AAAE,gBAAA;AAAW;AAAA,MAAQ;AACpE,WAAK,IAAI,wBAAwB,UAAU,CAAC,UAAkB;AAC5D,YAAI,UAAU,WAAY,SAAA;AAAA,MAC5B,CAAC;AAED,iBAAW,SAAS,GAAI;AAAA,IAC1B,CAAC;AAID,QAAI,WAAW,KAAK,GAAG,kBAAkB,OAAO,MAAM;AAEtD,eAAW,SAAS,QAAQ,wBAAwB,qBAAqB;AACzE,SAAK,QAAQ;AAQb,UAAM,qBAAqB,SACxB,MAAM,IAAI,EACV,IAAI,CAAA,MAAK,EAAE,KAAA,CAAM,EACjB,OAAO,CAAA,MAAK,EAAE,WAAW,cAAc,CAAC;AAC3C,SAAK,OAAO,KAAK,iCAAiC;AAAA,MAChD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAO,mBAAmB,QAAQ,YAAY,mBAAA;AAAA,IAAmB,CACvH;AAGD,UAAM,kBAAkB,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAc,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,WAAW,KAAK,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,kBAAkB,CAAC,EAAE,IAAI,CAAC,MAAc,EAAE,MAAM;AACxM,QAAI,KAAK,MAAO,MAAK,OAAO,KAAK,6BAA6B;AAAA,MAC5D,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,gBAAA;AAAA,IAAgB,CACtE;AACD,WAAO,EAAE,KAAK,UAAU,MAAM,QAAA;AAAA,EAChC;AAAA;AAAA,EAGA,MAAM,aAAa,QAAwD;AACzE,QAAI,CAAC,KAAK,GAAI,OAAM,IAAI,MAAM,0BAA0B;AACxD,UAAM,SAAS,MAAM,WAAA;AAQrB,UAAM,mBAAmB,OAAO,IAC7B,MAAM,IAAI,EACV,IAAI,CAAA,MAAK,EAAE,KAAA,CAAM,EACjB,OAAO,OAAK,EAAE,WAAW,cAAc,CAAC;AAC3C,SAAK,OAAO,KAAK,kCAAkC;AAAA,MACjD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAO,iBAAiB,QAAQ,YAAY,iBAAA;AAAA,IAAiB,CACnH;AAKD,UAAM,cAAc,MAAM,2BAA2B,OAAO,KAAK,KAAK,QAAQ,WAAW,KAAK,SAAS,EAAE;AACzG,UAAM,OAAO,IAAI,OAAO,sBAAsB,aAAa,OAAO,IAAI;AACtE,UAAM,KAAK,GAAG,qBAAqB,IAAI;AAKvC,UAAM,mBAAmB,YAAY,MAAM,kCAAkC,IAAI,CAAC,GAAG,YAAA;AACrF,QAAI,qBAAqB,UAAU,qBAAqB,QAAQ;AAC9D,WAAK,kBAAkB;AAAA,IACzB;AACA,SAAK,OAAO,KAAK,oBAAoB;AAAA,MACnC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,QAAQ,KAAK,aAAa,YAAY,KAAK,iBAAiB,gBAAgB,KAAK,eAAA;AAAA,IAAe,CACtJ;AAGD,UAAM,gBAAgB,KAAK,aAAa;AACxC,QAAI,iBAAiB,cAAc,UAAU,aAAa;AACxD,WAAK,OAAO,MAAM,uBAAuB;AAAA,QACvC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,SAAS,cAAc,MAAA;AAAA,MAAM,CACnF;AACD,YAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,aAAO,cAAc,UAAU,eAAe,KAAK,IAAA,IAAQ,UAAU;AACnE,cAAM,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,MACnD;AACA,WAAK,OAAO,MAAM,sBAAsB;AAAA,QACtC,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,WAAW,KAAK;AAAA,UAChB,OAAO,cAAc;AAAA,UACrB,YAAY,OAAS,WAAW,KAAK,UAAU;AAAA,QAAA;AAAA,MACjD,CACD;AAAA,IACH;AAEA,SAAK,OAAO,MAAM,+BAA+B;AAAA,MAC/C,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,MAAM,eAAe,SAAS,UAAA;AAAA,IAAU,CAC9F;AACD,SAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAAY,aAAuF;AACvG,UAAM,EAAE,QAAQ,UAAA,IAAc,MAAM,KAAK,eAAA;AAEzC,SAAK,KAAK,IAAI,OAAO,kBAAkB,SAAS;AAEhD,SAAK,GAAG,yBAAyB,UAAU,CAAC,UAAkB;AAC5D,WAAK,OAAO,MAAM,aAAa,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,MAAA,GAAS;AAC/F,UAAI,UAAU,aAAa;AACzB,aAAK,QAAQ;AACb,aAAK,qBAAA;AAAA,MACP,WAAW,UAAU,kBAAkB,UAAU,YAAY,UAAU,UAAU;AAC/E,aAAK,QAAQ,UAAU,iBAAiB,iBAAiB;AACzD,aAAK,KAAK,MAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAMD,UAAM,mBAAmB,MAAM,2BAA2B,YAAY,KAAK,KAAK,QAAQ,WAAW,KAAK,SAAS,EAAE;AACnH,UAAM,aAAa,IAAI,OAAO,sBAAsB,kBAAkB,YAAY,IAAI;AACtF,UAAM,KAAK,GAAG,qBAAqB,UAAU;AAG7C,UAAM,eAAoC,KAAK,GAAG,gBAAA;AAClD,eAAW,KAAK,cAAc;AAC5B,YAAM,OAAO,EAAE,UAAU,OAAO,QAAQ,EAAE;AAC1C,UAAI,SAAS,WAAW,CAAC,KAAK,YAAY;AACxC,aAAK,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,SAAS;AAC/D,cAAM,EAAE,OAAO,aAAa,KAAK,UAAU;AAAA,MAC7C,WAAW,SAAS,WAAW,CAAC,KAAK,YAAY;AAC/C,aAAK,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,SAAS;AAC/D,cAAM,EAAE,OAAO,aAAa,KAAK,UAAU;AAAA,MAC7C;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,OAAO,KAAK,mDAAmD;AAAA,QAClE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,MAAU,CACrD;AACD,WAAK,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,SAAS;AAC/D,WAAK,GAAG,eAAe,KAAK,YAAY,EAAE,WAAW,YAAY;AAAA,IACnE;AACA,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,OAAO,KAAK,mDAAmD;AAAA,QAClE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,MAAU,CACrD;AACD,WAAK,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,SAAS;AAC/D,WAAK,GAAG,eAAe,KAAK,YAAY,EAAE,WAAW,YAAY;AAAA,IACnE;AAEA,UAAM,aAAa,MAAM,KAAK,GAAG,aAAA;AACjC,UAAM,KAAK,GAAG,oBAAoB,UAAU;AAC5C,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,uBAAuB,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA,EAAU,CAAG;AAEjG,SAAK,mBAAA;AAEL,WAAO,EAAE,KAAK,WAAW,KAAK,MAAM,SAAA;AAAA,EACtC;AAAA;AAAA,EAGA,MAAM,gBAAgB,WAAmC;AACvD,QAAI,CAAC,KAAK,GAAI,OAAM,IAAI,MAAM,0BAA0B;AACxD,UAAM,SAAS,MAAM,WAAA;AACrB,UAAM,KAAK,GAAG,gBAAgB,IAAI,OAAO,gBAAgB,SAAS,CAAC;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAqB;AACnB,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,MAAA;AACf,WAAK,YAAY;AAAA,IACnB;AACA,SAAK,OAAO,MAAM,0BAA0B,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA,EAAU,CAAG;AAAA,EACvG;AAAA;AAAA,EAGA,IAAI,YAAqB;AACvB,WAAO,KAAK,cAAc,QAAQ,CAAC,KAAK,UAAU,OAAO;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,WAA8B;AAC1C,SAAK,SAAS;AAEd,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,MAAA;AACf,WAAK,YAAY;AAAA,IACnB;AACA,SAAK,mBAAA;AAAA,EACP;AAAA,EAEA,UAAuB;AACrB,WAAO,EAAE,WAAW,KAAK,WAAW,OAAO,KAAK,OAAO,WAAW,KAAK,UAAA;AAAA,EACzE;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,WAAW,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA,EAAU,CAAG;AAErF,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAC7B,WAAK,aAAa;AAAA,IACpB;AACA,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,MAAA;AACf,WAAK,YAAY;AAAA,IACnB;AACA,QAAI;AAAE,YAAM,KAAK,OAAO,OAAO,MAAS;AAAA,IAAG,QAAQ;AAAA,IAAQ;AAC3D,QAAI,KAAK,IAAI;AACX,UAAI;AAAE,cAAM,KAAK,GAAG,MAAA;AAAA,MAAS,QAAQ;AAAA,MAAQ;AAC7C,WAAK,KAAK;AAAA,IACZ;AACA,SAAK,aAAa;AAClB,SAAK,aAAa;AAMlB,UAAM,OAAO,KAAK;AAClB,SAAK,WAAW;AAChB,QAAI;AAAE,aAAA;AAAA,IAAU,QAAQ;AAAA,IAAsD;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,yBAAyB,eAA4C;AACnE,QAAI,KAAK,oBAAoB,OAAQ;AACrC,QAAI,CAAC,cAAc,OAAQ;AAC3B,SAAK,uBAAA;AACL,UAAM,MAAM,KAAK;AACjB,eAAW,MAAM,eAAe;AAC9B,YAAM,WAAW,GAAG,CAAC,IAAK,QAAS;AACnC,UAAI,YAAY,GAAI,KAAI,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,eACxC,YAAY,GAAI,KAAI,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,eAC7C,YAAY,GAAI,KAAI,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IACxD;AACA,QAAI,KAAK,OAAO;AACd,WAAK,OAAO,KAAK,gDAAgD;AAAA,QAC/D,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,WAAW,KAAK;AAAA,UAChB,SAAS,cAAc;AAAA,UACvB,QAAQ,CAAC,CAAC,IAAI,WAAW;AAAA,UACzB,QAAQ,CAAC,CAAC,IAAI,WAAW;AAAA,UACzB,QAAQ,CAAC,CAAC,IAAI,WAAW;AAAA,QAAA;AAAA,MAC3B,CACD;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,yBAA+B;AACrC,QAAI,KAAK,iBAAkB;AAC3B,UAAM,YAAuC;AAC7C,SAAK,mBAAmB,IAAI;AAAA,MAC1B;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,sBAAsB,SAAuB;AAC3C,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,oBAAoB,OAAQ;AACrC,QAAI,CAAC,KAAK,eAAe,CAAC,QAAS;AACnC,UAAM,SAAS;AACf,SAAK,uBAAA;AACL,UAAM,MAAM,KAAK;AAEjB,QAAI;AACJ,QAAI;AACF,eAAS,OAAO,UAAU,YAAY,OAAO;AAAA,IAC/C,SAAS,KAAK;AACZ,WAAK,OAAO,KAAK,+BAA+B;AAAA,QAC9C,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,GAAG,GAAG,KAAK,QAAQ,OAAA;AAAA,MAAO,CAC9F;AACD;AAAA,IACF;AAEA,QAAI,KAAK,oBAAoB,MAAM;AACjC,WAAK,kBAAkB,OAAO,OAAO;AAAA,IACvC;AAEA,UAAM,cAAc,KAAK,YAAY;AACrC,UAAM,KAAK,aAAa,eAAe;AAEvC,QAAI;AACJ,QAAI;AACF,gBAAU,IAAI,YAAY,MAAM;AAAA,IAClC,SAAS,KAAK;AACZ,WAAK,OAAO,KAAK,2BAA2B;AAAA,QAC1C,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,MAAE,CACzE;AACD;AAAA,IACF;AAEA,eAAW,OAAO,SAAS;AACzB,UAAI,OAAO,cAAc;AAGzB,UAAI,CAAC,KAAK,gBAAgB;AACxB,aAAK,iBAAiB;AACtB,YAAI;AACF,eAAK,YAAY;AAAA,YACf,EAAE,gBAAgB,IAAI,OAAO,gBAAgB,WAAW,IAAI,OAAO,UAAA;AAAA,YACnE;AAAA,UAAA;AAAA,QAEJ,SAAS,GAAG;AACV,eAAK,OAAO,KAAK,iCAAiC;AAAA,YAChD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,CAAC,EAAA;AAAA,UAAE,CACvE;AAAA,QACH;AAAA,MACF;AACA,UAAI;AACF,aAAK,YAAY,QAAQ,GAAG;AAC5B,aAAK;AACL,YAAI,KAAK,UAAU,KAAK,mBAAmB,KAAK,KAAK,iBAAiB,QAAQ,IAAI;AAChF,eAAK,OAAO,KAAK,6BAA6B;AAAA,YAC5C,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAO,KAAK,eAAA;AAAA,UAAe,CACjF;AAAA,QACH;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,KAAK,kBAAkB,IAAI;AAC7B,eAAK,OAAO,MAAM,gCAAgC;AAAA,YAChD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CACzE;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAyB;AAC/B,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAQ;AAKb,UAAM,QAAQ,MAAM,KAAK,UAAA;AAEzB,QAAI,OAAO,yBAAyB;AAClC,aAAO,wBAAwB,UAAU,KAAK;AAC9C,WAAK,OAAO,MAAM,mDAAmD;AAAA,QACnE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,MAAU,CACrD;AAAA,IACH,WAAW,OAAO,gBAAgB;AAChC,aAAO,eAAe,UAAU,CAAC,OAAO;AACtC,YAAI,GAAG,SAAS,UAAU,GAAG,cAAc,MAAO,OAAA;AAClD,YAAI,GAAG,SAAS,SAAS,GAAG,cAAc,MAAO,OAAA;AAAA,MACnD,CAAC;AACD,WAAK,OAAO,MAAM,0CAA0C;AAAA,QAC1D,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,MAAU,CACrD;AAAA,IACH,OAAO;AACL,WAAK,OAAO,MAAM,4EAA4E;AAAA,QAC5F,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,MAAU,CACrD;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,YAAkB;AAOxB,QAAI,KAAK,iBAAkB;AAE3B,UAAM,MAAM,KAAK,IAAA;AACjB,QAAI,MAAM,KAAK,kBAAkB,gBAAgB,uBAAwB;AACzE,SAAK,kBAAkB;AAEvB,QAAI,CAAC,KAAK,oBAAoB,KAAK,iBAAiB,WAAW,GAAG;AAChE,WAAK,OAAO,MAAM,2CAA2C;AAAA,QAC3D,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,MAAU,CACrD;AACD;AAAA,IACF;AAEA,SAAK,OAAO,KAAK,6CAA6C;AAAA,MAC5D,MAAM;AAAA,QACJ,OAAO;AAAA,QACP,WAAW,KAAK;AAAA,QAChB,MAAM,KAAK,iBAAiB;AAAA,QAC5B,YAAY,KAAK,iBAAiB,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AAAA,MAAA;AAAA,IACpE,CACD;AAKD,SAAK,eAAe,KAAK,kBAAkB,KAAK,mBAAmB,KAAK,eAAe;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAA2B;AACjC,SAAK,YAAY,IAAI,gBAAA;AACrB,UAAM,EAAE,WAAW,KAAK;AAExB,SAAK,OAAO,KAAK,sBAAsB;AAAA,MACrC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,MAAM,KAAK,aAAa,eAAe,SAAS,WAAW,gBAAgB,KAAK,eAAA;AAAA,IAAe,CACrJ;AAED,QAAI,KAAK,gBAAgB;AACvB,WAAK,mBAAmB,MAAM;AAAA,IAChC,OAAO;AACL,WAAK,gBAAgB,MAAM;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAgB,QAA2B;AAEjD,UAAM,YAAY;AAChB,UAAI,cAAc;AAClB,UAAI,qBAAoC;AACxC,UAAI,qBAAoC;AACxC,UAAI,aAAa;AAEjB,UAAI;AACF,yBAAiB,cAAc,KAAK,QAAQ;AAC1C,cAAI,OAAO,WAAW,KAAK,OAAQ;AACnC;AACA,cAAI,cAAc,KAAK,aAAa,QAAQ,GAAG;AAC7C,iBAAK,OAAO,MAAM,kBAAkB;AAAA,cAClC,MAAM;AAAA,gBACJ,OAAO;AAAA,gBACP,WAAW,KAAK;AAAA,gBAChB;AAAA,gBACA,MAAM,WAAW;AAAA,gBACjB,MAAM,WAAW,MAAM,KAAK;AAAA,gBAC5B,YAAY,WAAW,SAAS,WAAW,gBAAgB,WAAW,QAClE,WAAW,MAAM,aACjB;AAAA,cAAA;AAAA,YACN,CACD;AAAA,UACH;AAEA,cAAI,WAAW,SAAS,SAAS;AAC/B,kBAAM,QAAQ,WAAW;AACzB,kBAAM,SAAS,MAAM,UAAU,SAC3B,oBAAoB,MAAM,IAAI,IAC9B,oBAAoB,MAAM,IAAI;AAUlC,kBAAM,mBAAmB,kBAAkB,MAAM;AACjD,gBAAI,MAAM,UAAU,QAAQ;AAC1B,yBAAW,KAAK,kBAAkB;AAChC,sBAAM,WAAW,EAAE,CAAC,IAAK,QAAS;AAClC,oBAAI,YAAY,GAAI,MAAK,UAAU,OAAO,KAAK,CAAC;AAChD,oBAAI,YAAY,GAAI,MAAK,UAAU,OAAO,KAAK,CAAC;AAChD,oBAAI,YAAY,GAAI,MAAK,UAAU,OAAO,KAAK,CAAC;AAAA,cAClD;AAAA,YACF,OAAO;AACL,yBAAW,KAAK,kBAAkB;AAChC,sBAAM,UAAU,EAAE,CAAC,IAAK;AACxB,oBAAI,YAAY,EAAG,MAAK,UAAU,OAAO,KAAK,CAAC;AAC/C,oBAAI,YAAY,EAAG,MAAK,UAAU,OAAO,KAAK,CAAC;AAAA,cACjD;AAAA,YACF;AAEA,gBAAI,CAAC,aAAa;AAChB,oBAAM,QAAQ,MAAM,UAAU,SAC1B,oBAAoB,MAAM,IAC1B,qBAAqB,MAAM;AAC/B,kBAAI,CAAC,OAAO;AACV,oBAAI,cAAc,GAAG;AACnB,uBAAK,OAAO,MAAM,yCAAyC;AAAA,oBACzD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,YAAY,MAAM,OAAO,OAAA;AAAA,kBAAO,CACtF;AAAA,gBACH;AACA;AAAA,cACF;AACA,4BAAc;AACd,oBAAM,WAAW,KAAK,IAAI,sBAAsB;AAChD,mBAAK,OAAO,KAAK,kBAAkB;AAAA,gBACjC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,YAAY,MAAM,OAAO,QAAQ,KAAK,SAAA;AAAA,cAAS,CACrG;AAAA,YACH;AAEA,gBAAI,uBAAuB,KAAM,sBAAqB,MAAM;AAC5D,kBAAM,QAAQ,KAAK;AAAA,eACf,MAAM,kBAAkB,sBAAsB,MAAS;AAAA,YAAA,MACrD;AAIN,kBAAM,SAAS,MAAM,UAAU;AAC/B,kBAAM,UAAU,kBAAkB,MAAM;AACxC,kBAAM,OAAO,QAAQ,OAAO,CAAC,MAAc;AACzC,kBAAI,QAAQ;AACV,sBAAM6B,MAAK,EAAE,CAAC,IAAK,QAAS;AAC5B,uBAAOA,OAAM,MAAMA,OAAM,MAAMA,OAAM;AAAA,cACvC;AACA,oBAAM,IAAI,EAAE,CAAC,IAAK;AAClB,qBAAO,MAAM,KAAK,MAAM;AAAA,YAC1B,CAAC;AAGD,gBAAI,KAAK,UAAU,cAAc,KAAM,eAAe,eAAe,KAAK,kBAAkB,KAAK,IAAK;AACpG,oBAAM,UAAU,QAAQ,IAAI,CAAC,MAAc;AACzC,oBAAI,QAAQ;AACV,wBAAMA,MAAK,EAAE,CAAC,IAAK,QAAS;AAC5B,wBAAMC,SAAgC,EAAC,GAAE,WAAU,GAAE,WAAU,IAAG,cAAa,IAAG,YAAW,IAAG,OAAM,IAAG,OAAM,IAAG,OAAM,IAAG,OAAM,IAAG,OAAM,IAAG,SAAQ,IAAG,QAAA;AACxJ,yBAAO,GAAGA,OAAMD,EAAC,KAAK,IAAIA,EAAC,EAAE,IAAI,EAAE,MAAM;AAAA,gBAC3C;AACA,sBAAM,IAAI,EAAE,CAAC,IAAK;AAClB,sBAAM,QAAgC,EAAC,GAAE,SAAQ,GAAE,OAAM,GAAE,OAAM,GAAE,OAAM,GAAE,OAAM,GAAE,MAAA;AACnF,uBAAO,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM;AAAA,cAC3C,CAAC,EAAE,KAAK,KAAK;AACb,mBAAK,OAAO,KAAK,uBAAuB;AAAA,gBACtC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,YAAY,OAAO,MAAM,OAAO,MAAM,SAAS,SAAS,KAAK,OAAA;AAAA,cAAO,CAC1H;AACD,kBAAI,eAAe,CAAC,KAAK,qBAAqB,iBAAiB;AAAA,YACjE;AAEA,gBAAI,KAAK,SAAS,KAAK,KAAK,YAAY;AAQtC,kBAAI,QAAQ;AAEV,2BAAW,KAAK,MAAM;AACpB,wBAAM,WAAW,EAAE,CAAC,IAAK,QAAS;AAClC,sBAAI,YAAY,GAAI,MAAK,UAAU,OAAO,KAAK,CAAC;AAChD,sBAAI,YAAY,GAAI,MAAK,UAAU,OAAO,KAAK,CAAC;AAChD,sBAAI,YAAY,GAAI,MAAK,UAAU,OAAO,KAAK,CAAC;AAAA,gBAClD;AAEA,oBAAI,qBAAqB,MAAM,GAAG;AAEhC,wBAAM,YAAY,KAAK,KAAK,CAAC,OAAQ,EAAE,CAAC,IAAK,QAAS,MAAO,EAAE,KAAK,KAAK;AACzE,wBAAM,YAAY,KAAK,KAAK,CAAC,OAAQ,EAAE,CAAC,IAAK,QAAS,MAAO,EAAE,KAAK,KAAK;AACzE,wBAAM,YAAY,KAAK,KAAK,CAAC,OAAQ,EAAE,CAAC,IAAK,QAAS,MAAO,EAAE,KAAK,KAAK;AAEzE,wBAAM,UAAU,KAAK,OAAO,CAAC,MAAM;AACjC,0BAAM,KAAK,EAAE,CAAC,IAAK,QAAS;AAC5B,2BAAO,MAAM,MAAM,MAAM,MAAM,MAAM;AAAA,kBACvC,CAAC;AACD,sBAAI,aAAa,aAAa,aAAa,QAAQ,SAAS,GAAG;AAW7D,0BAAM,SAAS,CAAC,WAAW,WAAW,WAAW,GAAG,OAAO;AAC3D,yBAAK,eAAe,QAAQ,OAAO,MAAM,KAAK;AAC9C,yBAAK,mBAAmB,OAAO,IAAI,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AACxD,yBAAK,oBAAoB;AAAA,kBAC3B,OAAO;AAEL,yBAAK,eAAe,MAAM,OAAO,MAAM,KAAK;AAAA,kBAC9C;AAAA,gBACF,OAAO;AAEL,uBAAK,eAAe,MAAM,OAAO,MAAM,KAAK;AAAA,gBAC9C;AAAA,cACF,OAAO;AAEL,oBAAI,SAAmB;AACvB,2BAAW,KAAK,MAAM;AACpB,wBAAM,UAAU,EAAE,CAAC,IAAK;AACxB,sBAAI,YAAY,EAAG,MAAK,UAAU,OAAO,KAAK,CAAC;AAC/C,sBAAI,YAAY,EAAG,MAAK,UAAU,OAAO,KAAK,CAAC;AAAA,gBACjD;AACA,oBAAI,oBAAoB,MAAM,GAAG;AAC/B,wBAAM,eAAe,KAAK,KAAK,CAAC,OAAe,EAAE,CAAC,IAAK,QAAU,CAAC;AAClE,sBAAI,CAAC,gBAAgB,KAAK,WAAW,KAAK,SAAS;AACjD,6BAAS,CAAC,KAAK,SAAS,KAAK,SAAS,GAAG,IAAI;AAAA,kBAC/C;AACA,uBAAK,mBAAmB,OAAO,IAAI,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AACxD,uBAAK,oBAAoB;AAAA,gBAC3B;AACA,qBAAK,eAAe,QAAQ,OAAO,MAAM,KAAK;AAAA,cAChD;AACA,kBAAI,KAAK,SAAS,aAAa,QAAQ,GAAG;AACxC,qBAAK,OAAO,KAAK,iBAAiB;AAAA,kBAChC,MAAM;AAAA,oBACJ,OAAO;AAAA,oBACP,WAAW,KAAK;AAAA,oBAChB,QAAQ;AAAA,oBACR,YAAY,KAAK;AAAA,oBACjB,KAAK,KAAK,IAAI,sBAAsB;AAAA,oBACpC,MAAM,KAAK,IAAI,mBAAmB;AAAA,kBAAA;AAAA,gBACpC,CACD;AAAA,cACH;AAAA,YACF;AAAA,UACF,WAAW,WAAW,SAAS,SAAS;AACtC,kBAAM,QAAQ,WAAW;AACzB,gBAAI,CAAC,KAAK,YAAa;AAIvB,gBAAI,uBAAuB,KAAM,sBAAqB;AACtD,iCAAuB,qBAAgC,MAAM,KAAK,WAAY;AAC9E,kBAAM,QAAQ;AAEd,iBAAK,WAAW,MAAM,MAAM,OAAO,MAAM,KAAK;AAAA,UAChD;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,OAAO,WAAW,CAAC,KAAK,QAAQ;AACnC,eAAK,OAAO,MAAM,cAAc,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAO7B,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,QAC/G;AAAA,MACF,UAAA;AACE,YAAI,CAAC,KAAK,QAAQ;AAChB,cAAI,KAAK,MAAO,MAAK,OAAO,KAAK,cAAc,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA,GAAa;AACxG,eAAK,KAAK,MAAA;AAAA,QACZ;AAAA,MACF;AAAA,IACF,GAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAAmB,QAA2B;AACpD,UAAM,EAAE,MAAA,IAAU,QAAQ,oBAAoB;AAE9C,SAAK,OAAO,KAAK,uCAAuC;AAAA,MACtD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,UAAA;AAAA,IAAU,CACrD;AAED,UAAM,KAAK,MAAM,UAAU;AAAA,MACzB;AAAA,MAAgB;AAAA,MAAa;AAAA,MAC7B;AAAA,MAAM;AAAA,MAAQ;AAAA,MAAM;AAAA,MACpB;AAAA,MAAQ;AAAA,MAAW;AAAA,MAAW;AAAA,MAAa;AAAA,MAAS;AAAA,MACpD;AAAA,MAAc;AAAA,MAAY;AAAA,MAAM;AAAA,MAAM;AAAA,MAAO;AAAA,MAC7C;AAAA,MAAO;AAAA,MAAM;AAAA,MAAQ;AAAA,IAAA,GACpB,EAAE,OAAO,CAAC,QAAQ,QAAQ,MAAM,GAAG;AAEtC,WAAO,iBAAiB,SAAS,MAAM;AACrC,UAAI,CAAC,GAAG,OAAQ,IAAG,KAAK,SAAS;AAAA,IACnC,CAAC;AAED,OAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,WAAK,OAAO,KAAK,2BAA2B,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,KAAA,GAAQ;AAAA,IAC7G,CAAC;AACD,OAAG,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACtC,YAAM,MAAM,MAAM,SAAA,EAAW,KAAA;AAC7B,UAAI,IAAI,SAAS,EAAG,MAAK,OAAO,KAAK,2BAA2B,EAAE,MAAM,EAAE,WAAW,KAAK,WAAW,IAAA,GAAO;AAAA,IAC9G,CAAC;AAGD,QAAI,aAAa,OAAO,MAAM,CAAC;AAC/B,QAAI,QAAQ;AACZ,UAAM,qBAAqB;AAE3B,OAAG,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACtC,mBAAa,OAAO,OAAO,CAAC,YAAY,KAAK,CAAC;AAE9C,YAAM,SAAS,kBAAkB,UAAU;AAC3C,UAAI,UAAU,EAAG;AACjB,YAAM,WAAW,WAAW,SAAS,GAAG,MAAM;AAC9C,mBAAa,OAAO,KAAK,WAAW,SAAS,MAAM,CAAC;AAEpD,YAAM,gBAAgB,kBAAkB,QAAQ;AAChD,UAAI,cAAc,WAAW,EAAG;AAEhC,cAAS,QAAQ,uBAAwB;AACzC,WAAK,eAAe,eAAe,OAAO,MAAM;AAAA,IAClD,CAAC;AAGD,UAAM,YAAY;AAChB,UAAI;AACF,yBAAiB,cAAc,KAAK,QAAQ;AAC1C,cAAI,OAAO,WAAW,KAAK,OAAQ;AACnC,cAAI,WAAW,SAAS,SAAS;AAC/B,gBAAI,CAAC,GAAG,MAAM,WAAW;AACvB,iBAAG,MAAM,MAAM,WAAW,MAAM,IAAI;AAAA,YACtC;AAAA,UACF,WAAW,WAAW,SAAS,SAAS;AAEtC,kBAAM,QAAQ,WAAW;AACzB,iBAAK,WAAW,MAAM,MAAM,MAAM,iBAAiB,MAAM,KAAK;AAAA,UAChE;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,OAAO,WAAW,CAAC,KAAK,QAAQ;AACnC,eAAK,OAAO,MAAM,wBAAwB,EAAE,MAAM,EAAE,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,QACvG;AAAA,MACF,UAAA;AACE,YAAI,CAAC,GAAG,MAAM,UAAW,IAAG,MAAM,IAAA;AAClC,YAAI,CAAC,KAAK,OAAQ,MAAK,KAAK,MAAA;AAAA,MAC9B;AAAA,IACF,GAAA;AAAA,EACF;AAAA,EAEA,OAAwB,kBAAkB;AAAA,EAElC,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAEhB,eAAe,MAAgB,OAAe,OAAyB;AAC7E,QAAI,CAAC,KAAK,eAAe,CAAC,SAAS;AACjC,UAAI,KAAK,kBAAkB,GAAG;AAC5B,aAAK,OAAO,KAAK,6BAA6B;AAAA,UAC5C,MAAM;AAAA,YACJ,OAAO;AAAA,YACP,WAAW,KAAK;AAAA,YAChB,gBAAgB,CAAC,CAAC,KAAK;AAAA,YACvB,WAAW,CAAC,CAAC;AAAA,UAAA;AAAA,QACf,CACD;AACD,aAAK;AAAA,MACP;AACA;AAAA,IACF;AACA,UAAM,SAAS;AAKf,UAAM,cAAc,KAAK,YAAY;AACrC,UAAM,KAAK,aAAa,gBAAgB,UAAU,SAAS,KAAK;AAEhE,UAAM,UAAU,CAAC,SAAiB,WAAoB;AACpD,UAAI;AACF,cAAM,SAAS,IAAI,OAAO,UAAA;AAC1B,eAAO,cAAc;AACrB,eAAO,YAAY;AACnB,eAAO,SAAS;AAChB,eAAO,iBAAkB,KAAK,cAAe,KAAK,cAAc,IAAK;AACrE,cAAM,MAAM,IAAI,OAAO,UAAU,QAAQ,OAAO;AAKhD,YAAI,CAAC,KAAK,gBAAgB;AACxB,eAAK,iBAAiB;AACtB,cAAI;AACF,iBAAK,YAAa,WAAW,QAAQ,IAAI;AAAA,UAC3C,SAAS,GAAG;AACV,iBAAK,OAAO,KAAK,iCAAiC;AAAA,cAChD,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,CAAC,EAAA;AAAA,YAAE,CACvE;AAAA,UACH;AAAA,QACF;AAGA,YAAI,KAAK,SAAS,KAAK,iBAAiB,GAAG;AACzC,gBAAM,OAAO,KAAK,aAAa;AAC/B,gBAAM+B,eAAc,KAAK,aAAa;AACtC,gBAAM,OAAO,KAAK,aAAa;AAC/B,eAAK,OAAO,KAAK,iBAAiB;AAAA,YAChC,MAAM;AAAA,cACJ,OAAO;AAAA,cACP,WAAW,KAAK;AAAA,cAChB,aAAa,KAAK;AAAA,cAClB,MAAM,MAAM,SAAS;AAAA,cACrB,OAAOA,cAAa,YAAY;AAAA,cAChC,aAAaA,cAAa,eAAe;AAAA,cACzC,MAAM,OAAO,IAAI;AAAA,cACjB,aAAa,QAAQ;AAAA,YAAA;AAAA,UACvB,CACD;AAAA,QACH;AACA,aAAK,YAAa,QAAQ,GAAG;AAE7B,aAAK;AACL,YAAI,KAAK,UAAU,KAAK,mBAAmB,KAAK,KAAK,iBAAiB,QAAQ,IAAI;AAChF,eAAK,OAAO,KAAK,oBAAoB;AAAA,YACnC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAO,KAAK,gBAAgB,SAAS,QAAQ,OAAA;AAAA,UAAO,CAC1G;AAAA,QACH;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,KAAK,kBAAkB,IAAI;AAC7B,eAAK,OAAO,MAAM,iBAAiB;AAAA,YACjC,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,aAAa,KAAK,gBAAgB,OAAO/B,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CAC3G;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,YAAY,MAAM,KAAK,SAAS;AAEtC,UAAI,IAAI,UAAU,gBAAgB,iBAAiB;AACjD,gBAAQ,KAAK,SAAS;AAAA,MACxB,WAAW,UAAU,QAAQ;AAG3B,cAAM,WAAW,IAAI,CAAC,IAAK,QAAS;AACpC,cAAM,WAAY,IAAI,CAAC,IAAK,MAAS,KAAO,IAAI,CAAC,IAAK,QAAS;AAC/D,cAAM,MAAM,IAAI,CAAC,IAAK;AAEtB,cAAM,eAAgB,MAAM,IAAM,WAAW;AAC7C,cAAM,gBAAiB,UAAU,OAAS,IAAK;AAC/C,cAAM,UAAU,IAAI,SAAS,CAAC;AAE9B,YAAI,SAAS;AACb,YAAI,UAAU;AACd,eAAO,SAAS,QAAQ,QAAQ;AAC9B,gBAAM,MAAM,KAAK,IAAI,SAAS,gBAAgB,kBAAkB,GAAG,QAAQ,MAAM;AACjF,gBAAM,SAAS,OAAO,QAAQ;AAE9B,cAAI,WAAW,UAAU;AACzB,cAAI,QAAS,aAAY;AACzB,cAAI,OAAQ,aAAY;AAExB,gBAAM,WAAW,OAAO,MAAM,KAAK,MAAM,OAAO;AAChD,mBAAS,CAAC,IAAI;AACd,mBAAS,CAAC,IAAI;AACd,mBAAS,CAAC,IAAI;AACd,kBAAQ,KAAK,UAAU,GAAG,QAAQ,GAAG;AAErC,kBAAQ,UAAU,aAAa,MAAM;AACrC,mBAAS;AACT,oBAAU;AAAA,QACZ;AAAA,MACF,OAAO;AAEL,cAAM,YAAY,IAAI,CAAC;AACvB,cAAM,OAAO,YAAY;AACzB,cAAM,UAAU,YAAY;AAC5B,cAAM,cAAc,OAAO;AAC3B,cAAM,UAAU,IAAI,SAAS,CAAC;AAE9B,YAAI,SAAS;AACb,YAAI,UAAU;AACd,eAAO,SAAS,QAAQ,QAAQ;AAC9B,gBAAM,MAAM,KAAK,IAAI,SAAS,gBAAgB,kBAAkB,GAAG,QAAQ,MAAM;AACjF,gBAAM,SAAS,OAAO,QAAQ;AAE9B,cAAI,WAAW;AACf,cAAI,QAAS,aAAY;AACzB,cAAI,OAAQ,aAAY;AAExB,gBAAM,WAAW,OAAO,MAAM,KAAK,MAAM,OAAO;AAChD,mBAAS,CAAC,IAAI;AACd,mBAAS,CAAC,IAAI;AACd,kBAAQ,KAAK,UAAU,GAAG,QAAQ,GAAG;AAErC,kBAAQ,UAAU,aAAa,MAAM;AACrC,mBAAS;AACT,oBAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,WAAW,MAAc,OAAe,OAAsB;AACpE,QAAI,CAAC,KAAK,eAAe,CAAC,QAAS;AACnC,UAAM,SAAS;AAgBf,UAAM,cAAc,KAAK,YAAY;AACrC,UAAM,aAAa,UAAU,UAAU,UAAU,SAAS,IAAI;AAC9D,UAAM,KAAK,aAAa,eAAe;AACvC,QAAI;AACF,YAAM,SAAS,IAAI,OAAO,UAAA;AAC1B,aAAO,cAAc;AACrB,aAAO,YAAY;AAMnB,aAAO,SAAS;AAChB,aAAO,iBAAkB,KAAK,cAAe,KAAK,cAAc,IAAK;AACrE,YAAM,MAAM,IAAI,OAAO,UAAU,QAAQ,IAAI;AAC7C,WAAK,YAAY,QAAQ,GAAG;AAAA,IAC9B,SAAS,KAAK;AACZ,WAAK,OAAO,MAAM,qBAAqB,EAAE,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK,WAAW,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,IACtH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,uBAA6B;AACnC,QAAI,KAAK,cAAc,CAAC,KAAK,QAAS;AAEtC,SAAK,aAAa,YAAY,MAAM;AAClC,UAAI,CAAC,KAAK,MAAM,KAAK,OAAQ;AAC7B,WAAK,aAAA;AAAA,IACP,GAAG,GAAK;AAAA,EACV;AAAA,EAEQ,eAAqB;AAC3B,QAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAS;AAE/B,QAAI;AAGF,YAAM,UAAU,KAAK,GAAG,aAAA,KAAkB,CAAA;AAE1C,iBAAW,UAAU,SAAS;AAC5B,cAAM,QAAQ,OAAO;AACrB,YAAI,CAAC,SAAS,MAAM,SAAS,QAAS;AAKtC,cAAM,SAAS,OAAO,sBAAsB,OAAO;AACnD,YAAI,CAAC,OAAQ;AAEb,cAAM,eAAe,OAAO,gBAAgB;AAC5C,cAAM,cAAc,OAAO,eAAe,OAAO,kBAAkB;AACnE,cAAM,SAAS,OAAO,UAAU;AAChC,cAAM,MAAM,OAAO,iBAAiB,OAAO,OAAO;AAElD,cAAM,aAAa,eAAe;AAElC,aAAK,QAAQ;AAAA,UACX,WAAW,KAAK;AAAA,UAChB;AAAA,UACA,UAAU;AAAA,UACV,OAAO,MAAM;AAAA;AAAA,UACb,iBAAiB;AAAA;AAAA,UACjB;AAAA,UACA,WAAW,KAAK,IAAA;AAAA,QAAI,CACrB;AACD;AAAA,MACF;AAAA,IAIF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAIA,SAAS,kBAAkB,KAAqB;AAC9C,WAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,QAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAG,QAAO;AAAA,EACvF;AACA,SAAO;AACT;AC3yCA,MAAM,iBAAoF;AAAA,EACxF,MAAM,EAAE,QAAQ,OAAO,MAAM,aAAa,IAAA;AAAA,EAC1C,KAAM,EAAE,QAAQ,OAAO,KAAM,aAAa,KAAA;AAAA,EAC1C,KAAM,EAAE,QAAQ,MAAM,KAAO,aAAa,IAAA;AAC5C;AACA,MAAM,kBAAqC,CAAC,QAAQ,OAAO,KAAK;AAUhE,SAAS,YACP,MACA,QACA,OACQ;AACR,QAAM,QAAQ,OAAO,SAAA;AACrB,QAAM,WAAW,OAAO,eAAe,IAAI,IAAI;AAC/C,QAAM,cAAc,MAAM,cAAc,IAAI,MAAM,cAAe,UAAU,eAAe;AAE1F,QAAM,YAAY,UAAU,UAAU,OAAO;AAE7C,QAAM,MAAM,MAAM,oBAAoB;AACtC,QAAM,WAAW,MAAM,iBAAiB,QAAQ;AAChD,QAAM,WAAW,MAAM,kBAAkB,QAAQ;AACjD,QAAM,eAAe,UAAU;AAE/B,QAAM,gBAAgB,KAAK,IAAI,YAAY,YAAY,IAAI;AAE3D,MAAI,mBAAmB;AACvB,MAAI,MAAM,gBAAgB,MAAM,eAAe,KAAK,cAAc,GAAG;AACnE,UAAM,gBAAgB,MAAM,eAAe,MAAO;AAClD,QAAI,cAAc,eAAe;AAC/B,0BAAqB,cAAc,iBAAiB,gBAAiB;AAAA,IACvE;AAAA,EACF;AACA,SAAO,gBAAgB;AACzB;AAMO,MAAM,mBAAmB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,8BAAc,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUvB,sBAAoF;AAAA;AAAA,EAE3E,+BAAe,IAAA;AAAA,EACxB,UAAU;AAAA,EAElB,YAAY,SAAoC;AAC9C,SAAK,SAAS,QAAQ;AACtB,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,mBAAmB,QAAQ;AAChC,SAAK,eAAe,QAAQ;AAC5B,SAAK,6BAA6B,QAAQ;AAAA,EAC5C;AAAA;AAAA,EAIA,eAAe,UAAkB,QAA6B;AAC5D,SAAK,QAAQ,IAAI,UAAU,MAAM;AACjC,SAAK,OAAO,KAAK,4BAA4B,EAAE,MAAM,EAAE,SAAA,GAAY;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,uBACE,UACM;AACN,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,iBAAiB,UAAwB;AACvC,SAAK,QAAQ,OAAO,QAAQ;AAE5B,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,UAAU;AACxC,UAAI,MAAM,aAAa,UAAU;AAC/B,aAAK,eAAe,GAAG;AACvB,aAAK,MAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,SAAK,OAAO,KAAK,8BAA8B,EAAE,MAAM,EAAE,SAAA,GAAY;AAAA,EACvE;AAAA,EAEA,eAAe,UAA2B;AAGxC,QAAI,KAAK,QAAQ,IAAI,QAAQ,EAAG,QAAO;AAEvC,UAAM,QAAQ,SAAS,YAAY,GAAG;AACtC,QAAI,QAAQ,GAAG;AACb,YAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AACrC,UAAI,SAAS,YAAY;AACvB,cAAM,YAAY,SAAS,MAAM,GAAG,KAAK;AACzC,mBAAW,OAAO,KAAK,QAAQ,KAAA,GAAQ;AACrC,cAAI,IAAI,WAAW,GAAG,SAAS,GAAG,EAAG,QAAO;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,MAAM,cACJ,UACA,QAAqB,CAAA,GACrB,OAAqC,CAAA,GACa;AAClD,UAAM,QAAQ,MAAM,KAAK,sBAAsB,UAAU,OAAO,IAAI;AACpE,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM,QAAQ,YAAA;AAClC,YAAM,cAAc,KAAK,0BAA0B;AAAA,QACjD,MAAM;AAAA,UACJ,WAAW,MAAM;AAAA,UACjB,UAAU,MAAM;AAAA,UAChB,YAAY,MAAM;AAAA,UAClB,OAAO,MAAM;AAAA,UACb,qBAAqB,MAAM;AAAA,QAAA;AAAA,MAC7B,CACD;AACD,aAAO,EAAE,WAAW,MAAM,WAAW,UAAU,MAAM,IAAA;AAAA,IACvD,SAAS,KAAK;AACZ,WAAK,eAAe,MAAM,SAAS;AACnC,YAAM,MAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM;AAAA,MAAC,CAAC;AAC1C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,YACJ,UACA,gBACA,QAAqB,CAAA,GACrB,OAAyD,IACN;AACnD,UAAM,QAAQ,MAAM,KAAK,sBAAsB,UAAU,OAAO,IAAI;AACpE,QAAI;AACF,YAAM,SAAS,MAAM,MAAM,QAAQ,YAAY,EAAE,KAAK,gBAAgB,MAAM,SAAS;AACrF,YAAM,cAAc,KAAK,yCAAyC;AAAA,QAChE,MAAM;AAAA,UACJ,WAAW,MAAM;AAAA,UACjB,UAAU,MAAM;AAAA,UAChB,YAAY,MAAM;AAAA,UAClB,OAAO,MAAM;AAAA,UACb,qBAAqB,MAAM;AAAA,QAAA;AAAA,MAC7B,CACD;AACD,aAAO,EAAE,WAAW,MAAM,WAAW,WAAW,OAAO,IAAA;AAAA,IACzD,SAAS,KAAK;AACZ,WAAK,eAAe,MAAM,SAAS;AACnC,YAAM,MAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM;AAAA,MAAC,CAAC;AAC1C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAc,sBACZ,UACA,OACA,MASC;AACD,QAAI,KAAK,QAAS,OAAM,IAAI,MAAM,gBAAgB;AAElD,UAAM,WAAW,KAAK,gBAAgB,UAAU,KAAK;AACrD,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,yBAAyB,QAAQ,iBAAiB,QAAQ,IAAI;AAQ3F,UAAM,WAAW,SAAS,QAAQ,GAAG;AACrC,UAAM,aAAa,WAAW,IAC1B;AAAA,MACE,UAAU,OAAO,SAAS,SAAS,MAAM,GAAG,QAAQ,GAAG,EAAE;AAAA,MACzD,aAAa,SAAS,MAAM,WAAW,CAAC;AAAA,IAAA,IAE1C,EAAE,UAAU,IAAI,aAAa,SAAA;AACjC,UAAM,gBAAgB,KAAK,OAAO,SAAS,UAAU;AAErD,UAAM,cAAc,OAAO,SAAA,EAAW,SAAS;AAC/C,UAAM,SAAS,gBAAgB,UAAU,gBAAgB;AACzD,UAAM,eAAe,SAAS,SAAS;AAMvC,UAAM,aAAa,OAAO,cAAA;AAC1B,UAAM,QAAQ,OAAO,YAAA;AACrB,UAAM,sBAAsB,iBAAiB,UAAU;AAQvD,kBAAc;AAAA,MACZ,yBAAyB,YAAY,gBAAgB,WAAW,eAAe,cAAc,MAAM,UAAU,KAAK,iBAAiB,mBAAmB;AAAA,MACtJ,EAAE,MAAM,EAAE,UAAU,cAAc,aAAa,YAAY,OAAO,oBAAA,EAAoB;AAAA,IAAE;AAI1F,UAAM,EAAE,QAAQ,WAAW,OAAO,YAAA,IAAgB,sBAAA;AAUlD,UAAM,mBAA6B,CAAA;AAenC,QAAI,kBAAiC;AAYrC,QAAI,iBAAiB;AACrB,UAAM,cAAc,OAAO,cAAc,CAAC,WAA0B;AAClE,UAAI,OAAO,SAAS,SAAS;AAa3B,YAAI,oBAAqB;AAMzB,YAAI,CAAC,OAAO,iBAAiB,CAAC,gBAAgB;AAC5C,2BAAiB;AACjB,2BAAiB,SAAS;AAC1B,4BAAkB;AAAA,QACpB;AAEA,cAAM,SAAS,oBAAoB,OAAO,IAAI;AAG9C,cAAM,cAAc,mBAAmB,QAAQ,MAAM;AACrD,YAAI,YAAY,YAAY;AAY1B,cAAI,OAAO,eAAe;AACxB,sBAAU;AAAA,cACR,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,YAAY;AAAA,gBACZ,iBAAiB,OAAO;AAAA,cAAA;AAAA,YAC1B,CACD;AACD;AAAA,UACF;AACA,2BAAiB,KAAK,OAAO,KAAK,MAAM,CAAC;AAGzC,4BAAkB,OAAO,OAAO,gBAAgB;AAChD;AAAA,QACF;AAIA,YAAI,cAAc;AAClB,YAAI,iBAAiB,SAAS,GAAG;AAC/B,2BAAiB,KAAK,MAAM;AAC5B,wBAAc,OAAO,OAAO,gBAAgB;AAC5C,2BAAiB,SAAS;AAAA,QAC5B,WAAW,YAAY,cAAc,iBAAiB;AACpD,wBAAc,OAAO,OAAO,CAAC,iBAAiB,MAAM,CAAC;AAAA,QACvD;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,YACL,MAAM;AAAA,YACN,OAAO;AAAA,YACP,YAAY,OAAO,YAAY,YAAY;AAAA,YAC3C,iBAAiB,OAAO;AAAA,UAAA;AAAA,QAC1B,CACD;AAAA,MACH,WAAW,OAAO,SAAS,SAAS;AAClC,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,YACL,MAAM,OAAO;AAAA,YACb,OAAO,OAAO,OAAO,YAAA,MAAkB,SAAS,SAAS;AAAA,YACzD,YAAY;AAAA,YACZ,UAAU;AAAA,YACV,iBAAiB,OAAO;AAAA,UAAA;AAAA,QAC1B,CACD;AAAA,MACH;AAAA,IACF,CAAC;AAQD,UAAM,YAAY,OAAO,aAAA;AACzB,UAAM,eAAyB,kBAC3B,CAAC,OAAO,KAAK,eAAe,CAAC,IAC7B,iBAAiB,SAAS,IACxB,iBAAiB,IAAI,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC,IAC1C,CAAA;AACN,eAAW,OAAO,WAAW;AAC3B,UAAI,IAAI,SAAS,SAAS;AAOxB,YAAI,oBAAqB;AAEzB,cAAM,SAAS,oBAAoB,IAAI,IAAI;AAC3C,cAAM,cAAc,mBAAmB,QAAQ,MAAM;AACrD,YAAI,YAAY,YAAY;AAAE,uBAAa,KAAK,OAAO,KAAK,MAAM,CAAC;AAAG;AAAA,QAAS;AAE/E,YAAI,cAAc;AAClB,YAAI,aAAa,SAAS,GAAG;AAC3B,uBAAa,KAAK,MAAM;AACxB,wBAAc,OAAO,OAAO,YAAY;AACxC,uBAAa,SAAS;AAAA,QACxB,WAAW,YAAY,cAAc,iBAAiB;AACpD,wBAAc,OAAO,OAAO,CAAC,iBAAiB,MAAM,CAAC;AAAA,QACvD;AACA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,YACL,MAAM;AAAA,YACN,OAAO;AAAA,YACP,YAAY,IAAI,YAAY,YAAY;AAAA,YACxC,iBAAiB,IAAI;AAAA,UAAA;AAAA,QACvB,CACD;AAAA,MACH;AAAA,IACF;AAOA,UAAM,qBAAqB,KAAK;AAChC,QAAI,uBAAuB,UAAa,KAAK,SAAS,IAAI,kBAAkB,GAAG;AAC7E,YAAM,IAAI,MAAM,8BAA8B,kBAAkB,EAAE;AAAA,IACpE;AACA,UAAM,YAAY,sBAAsB,OAAO,WAAA;AAC/C,UAAM,aAAa,MAAM,KAAK,kBAAA;AAE9B,UAAM,UAAU,IAAI,gBAAgB;AAAA,MAClC;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,QACA,WAAW,KAAK;AAAA,QAChB,yBAAyB,KAAK;AAAA,MAAA;AAAA,MAEhC,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK,mBAAmB;AAAA,IAAA,CAChC;AASD,QAAI,WAA+B;AACnC,QAAI,iBAAqC;AACzC,QAAI,qBAAqB;AACvB,YAAM,QAAQ,OAAO,oBAAA;AACrB,UAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,gBAAQ,yBAAyB,KAAK;AACtC,sBAAc,KAAK,gDAAgD;AAAA,UACjE,MAAM,EAAE,WAAW,UAAU,SAAS,MAAM,OAAA;AAAA,QAAO,CACpD;AAAA,MACH,OAAO;AASL,sBAAc,KAAK,+EAA+E;AAAA,UAChG,MAAM,EAAE,WAAW,SAAA;AAAA,QAAS,CAC7B;AACD,yBAAiB,OAAO,mBAAmB,CAAC,OAAO;AACjD,cAAI;AACF,oBAAQ,yBAAyB,EAAE;AACnC,0BAAc,KAAK,+CAA+C;AAAA,cAChE,MAAM,EAAE,WAAW,UAAU,SAAS,GAAG,OAAA;AAAA,YAAO,CACjD;AAAA,UACH,SAAS,KAAK;AACZ,0BAAc,KAAK,kCAAkC;AAAA,cACnD,MAAM,EAAE,WAAW,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,YAAE,CACvC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AACA,UAAI,oBAAoB;AACxB,iBAAW,OAAO,WAAW,CAAC,YAAY;AACxC,YAAI,CAAC,mBAAmB;AACtB,8BAAoB;AACpB,wBAAc,KAAK,6DAA6D;AAAA,YAC9E,MAAM,EAAE,WAAW,UAAU,OAAO,QAAQ,OAAA;AAAA,UAAO,CACpD;AAAA,QACH;AACA,YAAI;AACF,kBAAQ,sBAAsB,OAAO;AAAA,QACvC,SAAS,KAAK;AACZ,wBAAc,KAAK,+BAA+B;AAAA,YAChD,MAAM,EAAE,WAAW,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CACvC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,QAAsB,EAAE,SAAS,UAAU,aAAa,UAAU,gBAAgB,YAAA;AACxF,SAAK,SAAS,IAAI,WAAW,KAAK;AAElC,YAAQ,WAAW,MAAM,KAAK,eAAe,SAAS;AAEtD,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB,WAAW;AAAA,IAAA;AAAA,EAE/B;AAAA,EAEA,MAAM,aAAa,WAAmB,WAAkC;AACtE,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sBAAsB,SAAS,EAAE;AAC7D,UAAM,MAAM,QAAQ,aAAa,EAAE,KAAK,WAAW,MAAM,UAAU;AAAA,EACrE;AAAA,EAEA,MAAM,aAAa,WAAkC;AACnD,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO;AACZ,SAAK,eAAe,SAAS;AAC7B,UAAM,MAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM;AAAA,IAAC,CAAC;AAC1C,SAAK,OAAO,KAAK,yBAAyB,EAAE,MAAM,EAAE,UAAA,GAAa;AAAA,EACnE;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,UAAM,SAA0B,CAAA;AAChC,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,UAAU;AACxC,WAAK,eAAe,GAAG;AACvB,aAAO,KAAK,MAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM;AAAA,MAAC,CAAC,CAAC;AAAA,IACnD;AACA,UAAM,QAAQ,IAAI,MAAM;AACxB,SAAK,QAAQ,MAAA;AACb,SAAK,OAAO,KAAK,uBAAuB;AAAA,EAC1C;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA,EAIQ,eAAe,WAAyB;AAC9C,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO;AACZ,SAAK,SAAS,OAAO,SAAS;AAC9B,UAAM,YAAA;AACN,UAAM,WAAA;AACN,UAAM,iBAAA;AACN,UAAM,YAAA;AAAA,EACR;AAAA,EAEQ,gBAAgB,UAAkB,QAAqB,IAAY;AAKzE,QAAI,KAAK,QAAQ,IAAI,QAAQ,EAAG,QAAO;AAMvC,UAAM,QAAQ,SAAS,QAAQ,GAAG;AAClC,QAAI,QAAQ,GAAG;AACb,YAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AACrC,UAAI,SAAS,YAAY;AACvB,eAAO,KAAK,iBAAiB,SAAS,MAAM,GAAG,KAAK,GAAG,KAAK;AAAA,MAC9D;AAAA,IACF,OAAO;AACL,aAAO,KAAK,iBAAiB,UAAU,KAAK;AAAA,IAC9C;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,iBAAiB,WAAmB,OAA4B;AAWtE,UAAM,aAA0B,CAAA;AAChC,eAAW,CAAC,KAAK,MAAM,KAAK,KAAK,SAAS;AACxC,UAAI,CAAC,IAAI,WAAW,GAAG,SAAS,GAAG,EAAG;AACtC,YAAM,OAAO,KAAK,sBAAsB,GAAG,KAAK;AAChD,iBAAW,KAAK,EAAE,UAAU,KAAK,MAAM,QAAQ;AAAA,IACjD;AACA,QAAI,WAAW,WAAW,EAAG,QAAO,GAAG,SAAS;AAChD,QAAI,WAAW,WAAW,EAAG,QAAO,WAAW,CAAC,EAAG;AAGnD,QAAI,MAAM,gBAAgB,UAAU,MAAM,gBAAgB,SAAS,MAAM,gBAAgB,OAAO;AAC9F,YAAM,WAAW,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,WAAW;AACpE,UAAI,iBAAiB,SAAS;AAAA,IAChC;AAGA,UAAM,WAAW,MAAM,kBAAkB,UACpC,MAAM,mBAAmB,UACzB,MAAM,iBAAiB;AAC5B,QAAI,CAAC,UAAU;AACb,iBAAW,QAAQ,iBAAiB;AAClC,cAAM,IAAI,WAAW,KAAK,CAACgC,OAAMA,GAAE,SAAS,IAAI;AAChD,YAAI,UAAU,EAAE;AAAA,MAClB;AACA,aAAO,WAAW,CAAC,EAAG;AAAA,IACxB;AAGA,QAAI,SAAS,WAAW,CAAC,EAAG;AAC5B,QAAI,WAA0C,WAAW,CAAC,EAAG;AAC7D,QAAI,YAAY;AAChB,eAAW,KAAK,YAAY;AAC1B,YAAM,QAAQ,YAAY,EAAE,MAAM,EAAE,QAAQ,KAAK;AACjD,YAAM,QAAQ,EAAE,OAAO,gBAAgB,QAAQ,EAAE,IAAI,IAAI,gBAAgB;AACzE,YAAM,WAAW,WAAW,gBAAgB,QAAQ,QAAQ,IAAI,gBAAgB;AAChF,UAAI,QAAQ,aAAc,UAAU,aAAa,QAAQ,UAAW;AAClE,iBAAS,EAAE;AACX,mBAAW,EAAE;AACb,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,SAAK,OAAO,MAAM,4BAA4B;AAAA,MAC5C,MAAM,EAAE,WAAW,UAAU,QAAQ,MAAM,UAAU,OAAO,KAAK,MAAM,YAAY,GAAG,IAAI,KAAK,MAAA;AAAA,IAAM,CACtG;AACD,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,oBAAwD;AACpE,QAAI,KAAK,iBAAiB;AACxB,UAAI;AAAE,eAAO,MAAM,KAAK,gBAAA;AAAA,MAAkB,SAAS,KAAK;AACtD,aAAK,OAAO,KAAK,wBAAwB,EAAE,MAAM,EAAE,OAAOhC,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,MAC3E;AAAA,IACF;AACA,WAAO,KAAK,oBAAoB,CAAA;AAAA,EAClC;AACF;AAiBA,SAAS,mBAAmB,QAAgB,QAA8B;AACxE,WAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,QAAI,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,QAAQ;AACjH,UAAI,QAAQ;AACV,cAAM,WAAW,OAAO,IAAI,CAAC,IAAK,QAAS;AAC3C,cAAM,aAAa,YAAY,MAAM,YAAY,MAAM,YAAY;AACnE,cAAM,aAAa,cAAe,WAAW,MAAM,WAAW;AAC9D,eAAO,EAAE,YAAY,WAAA;AAAA,MACvB,OAAO;AACL,cAAM,UAAU,OAAO,IAAI,CAAC,IAAK;AACjC,cAAM,aAAa,YAAY,KAAK,YAAY;AAChD,cAAM,aAAa,YAAY,KAAK;AACpC,eAAO,EAAE,YAAY,WAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,YAAY,OAAO,YAAY,MAAA;AAC1C;AAMA,SAAS,wBAIP;AACA,QAAM,QAAsB,CAAA;AAC5B,MAAI,UAAgE;AACpE,MAAI,OAAO;AAEX,QAAM,YAAY,CAAC,OAAyB;AAC1C,QAAI,KAAM;AACV,QAAI,SAAS;AACX,YAAM,IAAI;AACV,gBAAU;AACV,QAAE,EAAE,OAAO,IAAI,MAAM,OAAO;AAAA,IAC9B,OAAO;AACL,YAAM,KAAK,EAAE;AAEb,UAAI,MAAM,SAAS,IAAK,OAAM,OAAO,GAAG,MAAM,SAAS,EAAE;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,QAAQ,MAAY;AACxB,WAAO;AACP,QAAI,SAAS;AACX,YAAM,IAAI;AACV,gBAAU;AACV,QAAE,EAAE,OAAO,QAAoB,MAAM,MAAM;AAAA,IAC7C;AAAA,EACF;AAEA,QAAM,UAAuB,mBAAmB;AAC9C,QAAI;AACF,aAAO,MAAM;AACX,cAAM,OAAO,MAAM,MAAA;AACnB,YAAI,MAAM;AAAE,gBAAM;AAAM;AAAA,QAAS;AACjC,YAAI,KAAM;AACV,cAAM,SAAS,MAAM,IAAI,QAAoC,CAAC,MAAM;AAClE,oBAAU;AAAA,QACZ,CAAC;AACD,YAAI,OAAO,KAAM;AACjB,cAAM,OAAO;AAAA,MACf;AAAA,IACF,UAAA;AACE,aAAO;AAAA,IACT;AAAA,EACF,GAAA;AAEA,SAAO,EAAE,QAAQ,WAAW,MAAA;AAC9B;ACrzBO,MAAM,sBAAsB;AAAA,EACjC,YAA6B,SAA8B;AAA9B,SAAA,UAAA;AAAA,EAA+B;AAAA,EAE5D,MAAM,iBAAiB,OAA+D;AACpF,WAAO,KAAK,QAAQ,0BAA0B,MAAM,QAAQ;AAAA,EAC9D;AAAA,EAEA,MAAM,iBAAiB,OAA8D;AACnF,WAAO,KAAK,QAAQ,yBAAyB,MAAM,QAAQ;AAAA,EAC7D;AAAA,EAEA,MAAM,eACJ,OACuC;AACvC,WAAO,KAAK,QAAQ,wBAAwB,MAAM,UAAU,MAAM,QAAQ;AAAA,EAC5E;AACF;AAaO,MAAM,sBAAsB;AAAA,EACjC,YACmB,cACA,SACjB;AAFiB,SAAA,eAAA;AACA,SAAA,UAAA;AAAA,EAChB;AAAA,EAEH,OAAwB,iBAA6C;AAAA,IACnE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUC,wBAAwB,UAAkB,QAAoC;AACpF,YAAQ,OAAO,MAAA;AAAA,MACb,KAAK;AACH,eAAO,GAAG,QAAQ;AAAA,MACpB,KAAK,WAAW;AACd,cAAM,QAAQ,KAAK,QAAQ,yBAAyB,QAAQ;AAC5D,cAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,OAAO;AAC3D,YAAI,CAAC,MAAM,mBAAmB;AAC5B,gBAAM,IAAI,MAAM,4BAA4B,OAAO,OAAO,uCAAuC,QAAQ,EAAE;AAAA,QAC7G;AACA,eAAO,GAAG,QAAQ,IAAI,KAAK,iBAAiB;AAAA,MAC9C;AAAA,MACA,KAAK;AACH,eAAO,GAAG,QAAQ,IAAI,OAAO,WAAW;AAAA,IAAA;AAAA,EAE9C;AAAA,EAEA,MAAM,YAAY,OAAqE;AACrF,UAAM,EAAE,aAAa;AACrB,UAAM,QAAQ,KAAK,QAAQ,yBAAyB,QAAQ;AAC5D,UAAM,gBAAgB,MAAM,OAAO,CAAC,MAAM,EAAE,sBAAsB,IAAI;AACtE,UAAM,aAAa,KAAK,QAAQ,0BAA0B,QAAQ;AAClE,UAAM,oCAAoB,IAAA;AAC1B,eAAW,KAAK,WAAY,eAAc,IAAI,EAAE,aAAa,CAAC;AAE9D,UAAM,UAAgC,CAAA;AAEtC,QAAI,cAAc,SAAS,GAAG;AAC5B,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,QAAQ,EAAE,MAAM,WAAA;AAAA,QAChB,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,WAAW;AAAA,QACX,aAAa;AAAA,MAAA,CACd;AAAA,IACH;AAKA,eAAW,QAAQ,eAAe;AAChC,YAAM,UAAU,KAAK;AACrB,YAAM,cAAc,KAAK;AACzB,UAAI,CAAC,YAAa;AAClB,YAAM,SAAS,MAAM,KAAK,QAAQ,UAAU,EAAE,UAAU,GAAG,QAAQ,IAAI,WAAW,GAAA,CAAI;AACtF,YAAM,QAAQ,QAAQ,SAAA;AACtB,YAAM,MAAM,cAAc,IAAI,WAAW;AACzC,cAAQ,KAAK;AAAA,QACX,IAAI,WAAW,OAAO;AAAA,QACtB,OAAO,sBAAsB,eAAe,OAAO,KAAK;AAAA,QACxD,QAAQ,EAAE,MAAM,WAAW,QAAA;AAAA,QAC3B,OAAO,OAAO,SAAS,KAAK,SAAS,KAAK,SAAS;AAAA,QACnD,YAAY,KAAK,cAAc,KAAK,cAAc;AAAA,QAClD,QAAQ,OAAO,UAAU;AAAA,QACzB,UAAU,OAAO,YAAY;AAAA,QAC7B,WAAW,OAAO,aAAa;AAAA,QAC/B,aAAa,OAAO,eAAe;AAAA,MAAA,CACpC;AAAA,IACH;AAKA,eAAW,OAAO,YAAY;AAC5B,YAAM,cAAc,IAAI;AACxB,YAAM,SAAS,MAAM,KAAK,QAAQ,UAAU,EAAE,UAAU,GAAG,QAAQ,IAAI,WAAW,GAAA,CAAI;AACtF,YAAM,QAAQ,QAAQ,SAAA;AACtB,cAAQ,KAAK;AAAA,QACX,IAAI,UAAU,WAAW;AAAA,QACzB,OAAO,IAAI,SAAS;AAAA,QACpB,QAAQ,EAAE,MAAM,cAAc,YAAA;AAAA,QAC9B,OAAO,OAAO,SAAS,IAAI,SAAS;AAAA,QACpC,YAAY,IAAI,cAAc;AAAA,QAC9B,QAAQ,OAAO,UAAU;AAAA,QACzB,UAAU,OAAO,YAAY;AAAA,QAC7B,WAAW,OAAO,aAAa;AAAA,QAC/B,aAAa,OAAO,eAAe;AAAA,MAAA,CACpC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cAAc,OAIiC;AACnD,UAAM,WAAW,KAAK,wBAAwB,MAAM,UAAU,MAAM,MAAM;AAC1E,UAAM,iBAAiB,OAAO,KAAK,QAAQ,qBAAqB,aAC5D,KAAK,QAAQ,iBAAiB,MAAM,QAAQ,IAC5C;AACJ,WAAO,KAAK,aAAa,cAAc,UAAU,MAAM,OAAO,EAAE,gBAAgB;AAAA,EAClF;AAAA,EAEA,MAAM,YAAY,OAKoC;AAKpD,UAAM,SAA6B,MAAM,UAAU,EAAE,MAAM,WAAA;AAC3D,UAAM,WAAW,KAAK,wBAAwB,MAAM,UAAU,MAAM;AACpE,UAAM,iBAAiB,OAAO,KAAK,QAAQ,qBAAqB,aAC5D,KAAK,QAAQ,iBAAiB,MAAM,QAAQ,IAC5C;AACJ,WAAO,KAAK,aAAa,YAAY,UAAU,MAAM,UAAU,QAAW;AAAA,MACxE;AAAA,MACA,WAAW,MAAM;AAAA,IAAA,CAClB;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,OAID;AAChB,UAAM,KAAK,aAAa,aAAa,MAAM,WAAW,MAAM,SAAS;AAAA,EACvE;AAAA,EAEA,MAAM,aAAa,OAA+D;AAChF,UAAM,KAAK,aAAa,aAAa,MAAM,SAAS;AAAA,EACtD;AAAA,EAEA,MAAM,mBAAmB,QAGJ;AAGnB,WAAO;AAAA,EACT;AACF;AC5KA,MAAM,eAAsC,CAAC,QAAQ,OAAO,KAAK;AAQjE,MAAM,sCAAsC;AAW5C,SAAS,kBAAqB,SAAY,UAAqC;AAC7E,QAAM,UAAU,IAAI,IAAI,QAAQ;AAChC,QAAM,QAAQ,CAAC,MAAwB;AACrC,QAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,IAAI,KAAK;AACxC,QAAI,KAAK,OAAO,MAAM,UAAU;AAC9B,YAAM,MAA+B,CAAA;AACrC,iBAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,CAA4B,GAAG;AACnE,YAAI,QAAQ,IAAI,CAAC,EAAG;AACpB,YAAI,CAAC,IAAI,MAAM,GAAG;AAAA,MACpB;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO,KAAK,UAAU,MAAM,OAAO,CAAC;AACtC;AAQA,MAAM,8BAA8B;AAE7B,MAAM,0BAA0BiC,MAAAA,UAA8B;AAAA,EAC3D,gBAA4C;AAAA,EAC5C,uBAA8D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcrD,gDAAgC,IAAA;AAAA,EAEjD,cAAc;AACZ,UAAM;AAAA,MACJ,qBAAqB;AAAA,MACrB,UAAU;AAAA,MACV,cAAc;AAAA,MACd,yBAAyB;AAAA,MACzB,qBAAqB;AAAA,IAAA,CACtB;AAAA,EACH;AAAA,EAEA,MAAgB,eAAgD;AAC9D,SAAK,gBAAgB,IAAI,oBAAoB,QAAW,KAAK,IAAI,MAAM;AACvE,SAAK,cAAc,uBAAuB,KAAK,OAAO,mBAAmB;AACzE,SAAK,cAAc,YAAY,KAAK,IAAI,QAAQ;AAMhD,SAAK,cAAc,aAAa,KAAK,IAAI,GAAG;AAC5C,QAAI,KAAK,cAAc;AACrB,WAAK,cAAc,sBAAsB,KAAK,YAAY;AAAA,IAC5D;AAKA,UAAM,eAAe,KAAK,cAAc,wBAAA;AAGxC,QAAI;AACF,YAAM,aAAc,MAAM,KAAK,IAAI,UAAU,eAAA,KAAqB,CAAA;AAClE,YAAM,YAAYC,MAAAA,aAAa,WAAW,YAAY,CAAC;AACvD,UAAI,WAAW;AACb,cAAM,6BAAa,IAAA;AACnB,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC9C,cAAI,OAAO,MAAM,SAAU,QAAO,IAAI,GAAG,CAAC;AAAA,QAC5C;AACA,aAAK,cAAc,oBAAoB,MAAM;AAC7C,aAAK,IAAI,OAAO,KAAK,gCAAgC,EAAE,MAAM,EAAE,YAAY,OAAO,KAAA,EAAK,CAAG;AAAA,MAC5F;AACA,YAAM,cAAcA,MAAAA,aAAa,WAAW,aAAa,CAAC;AAC1D,UAAI,aAAa;AACf,cAAM,6BAAa,IAAA;AACnB,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,WAAW,GAAG;AAChD,iBAAO,IAAI,GAAG,QAAQ,CAAC,CAAC;AAAA,QAC1B;AACA,qBAAa,qBAAqB,MAAM;AACxC,aAAK,IAAI,OAAO,KAAK,wCAAwC,EAAE,MAAM,EAAE,YAAY,OAAO,KAAA,EAAK,CAAG;AAAA,MACpG;AAAA,IACF,QAAQ;AAAA,IAA4C;AAEpD,iBAAa,kBAAkB,CAAC,WAAW;AACzC,YAAM,MAA8B,CAAA;AACpC,iBAAW,CAAC,GAAG,CAAC,KAAK,QAAQ;AAAE,YAAI,CAAC,IAAI;AAAA,MAAE;AAC1C,WAAK,IAAI,UAAU,gBAAgB,EAAE,YAAY,KAAK,EAAE,MAAM,CAAC,QAAiB;AAC9E,aAAK,IAAI,OAAO,KAAK,iCAAiC,EAAE,MAAM,EAAE,OAAOlC,MAAAA,OAAO,GAAG,EAAA,EAAE,CAAG;AAAA,MACxF,CAAC;AAAA,IACH,CAAC;AAED,iBAAa,oBAAoB,CAAC,WAAW;AAC3C,YAAM,MAA+B,CAAA;AACrC,iBAAW,CAAC,GAAG,CAAC,KAAK,QAAQ;AAAE,YAAI,CAAC,IAAI;AAAA,MAAE;AAC1C,WAAK,IAAI,UAAU,gBAAgB,EAAE,aAAa,KAAK,EAAE,MAAM,CAAC,QAAiB;AAC/E,aAAK,IAAI,OAAO,KAAK,yCAAyC,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,EAAE,CAAG;AAAA,MAChG,CAAC;AAAA,IACH,CAAC;AAKD,QAAI;AACF,YAAM,aAAc,MAAM,KAAK,IAAI,UAAU,eAAA,KAAqB,CAAA;AAClE,YAAM,eAAekC,MAAAA,aAAa,WAAW,iBAAiB,CAAC;AAC/D,UAAI,cAAc;AAChB,cAAM,gCAAgB,IAAA;AAKtB,mBAAW,CAAC,QAAQ,GAAG,KAAK,OAAO,QAAQ,YAAY,GAAG;AACxD,gBAAM,aAAa,OAAO,MAAM;AAChC,cAAI,CAAC,OAAO,SAAS,UAAU,KAAK,CAAC,OAAO,UAAU,UAAU,EAAG;AACnE,gBAAM,IAAIA,MAAAA,aAAa,GAAG;AAC1B,cAAI,CAAC,EAAG;AACR,gBAAM,QAAiC,CAAA;AACvC,cAAI,OAAO,EAAE,sBAAsB,MAAM,UAAU;AACjD,kBAAM,sBAAsB,IAAI,EAAE,sBAAsB;AAAA,UAC1D;AACA,cAAI,OAAO,EAAE,gBAAgB,MAAM,WAAW;AAC5C,kBAAM,gBAAgB,IAAI,EAAE,gBAAgB;AAAA,UAC9C;AACA,gBAAM,QAAQA,MAAAA,aAAa,EAAE,WAAW,CAAC;AACzC,cAAI,OAAO;AACT,kBAAM,YAAmE,CAAA;AACzE,uBAAW,CAAC,SAAS,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,oBAAM,KAAKA,MAAAA,aAAa,CAAC;AACzB,kBAAI,MAAM,OAAO,GAAG,SAAS,MAAM,UAAU;AAC3C,0BAAU,OAAO,IAAI,EAAE,SAAS,GAAG,SAAS,MAAM,OAAO,SAAS,GAAG,SAAS,EAAA;AAAA,cAChF;AAAA,YACF;AACA,kBAAM,WAAW,IAAI;AAAA,UACvB;AACA,oBAAU,IAAI,YAAY,KAAK;AAAA,QACjC;AACA,aAAK,cAAc,6BAA6B,SAAS;AACzD,aAAK,IAAI,OAAO,KAAK,qCAAqC,EAAE,MAAM,EAAE,eAAe,UAAU,KAAA,EAAK,CAAG;AAAA,MACvG;AAAA,IACF,QAAQ;AAAA,IAA4C;AAEpD,SAAK,cAAc,2BAA2B,CAAC,cAAc;AAC3D,YAAM,MAA+C,CAAA;AACrD,iBAAW,CAAC,UAAU,EAAE,KAAK,WAAW;AACtC,cAAM,QAAiC,CAAA;AACvC,YAAI,GAAG,yBAAyB,OAAW,OAAM,sBAAsB,IAAI,GAAG;AAC9E,YAAI,GAAG,mBAAmB,OAAW,OAAM,gBAAgB,IAAI,GAAG;AAClE,YAAI,GAAG,UAAW,OAAM,WAAW,IAAI,GAAG;AAC1C,YAAI,GAAG,QAAQ,EAAE,IAAI;AAAA,MACvB;AACA,WAAK,IAAI,UAAU,gBAAgB,EAAE,iBAAiB,KAAK,EAAE,MAAM,CAAC,QAAiB;AACnF,aAAK,IAAI,OAAO,KAAK,sCAAsC,EAAE,MAAM,EAAE,OAAOlC,MAAAA,OAAO,GAAG,EAAA,EAAE,CAAG;AAAA,MAC7F,CAAC;AAAA,IACH,CAAC;AAGD,QAAI;AACF,YAAM,aAAc,MAAM,KAAK,IAAI,UAAU,eAAA,KAAqB,CAAA;AAClE,YAAM,MAAMkC,MAAAA,aAAa,WAAW,YAAY,CAAC;AACjD,UAAI,KAAK;AACP,cAAM,6BAAa,IAAA;AACnB,mBAAW,CAAC,QAAQ,MAAM,KAAK,OAAO,QAAQ,GAAG,GAAG;AAClD,gBAAM,WAAW,OAAO,MAAM;AAC9B,cAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,CAAC,OAAO,UAAU,QAAQ,EAAG;AAC/D,gBAAM,MAAMA,MAAAA,aAAa,MAAM;AAC/B,cAAI,CAAC,IAAK;AACV,gBAAM,SAASA,MAAAA,aAAa,IAAI,KAAK,CAAC,KAAK,CAAA;AAC3C,gBAAM,MAA2C,CAAA;AACjD,qBAAW,WAAW,cAAc;AAClC,kBAAM,IAAI,OAAO,OAAO;AACxB,gBAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,KAAI,OAAO,IAAI;AAAA,UAC5D;AACA,gBAAM,OAAO,IAAI,MAAM,MAAM;AAC7B,iBAAO,IAAI,UAAU,EAAE,KAAK,MAAM;AAAA,QACpC;AACA,aAAK,cAAc,wBAAwB,MAAM;AACjD,aAAK,IAAI,OAAO,KAAK,gCAAgC,EAAE,MAAM,EAAE,aAAa,OAAO,KAAA,EAAK,CAAG;AAAA,MAC7F;AAAA,IACF,QAAQ;AAAA,IAA4C;AAEpD,UAAM,sBAA2C,CAAC,gBAAgB;AAChE,YAAM,MAA2C,CAAA;AACjD,iBAAW,CAAC,UAAU,UAAU,KAAK,aAAa;AAChD,YAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,WAAW,IAAA,GAAO,MAAM,WAAW,KAAA;AAAA,MACtE;AACA,WAAK,IAAI,UAAU,gBAAgB,EAAE,YAAY,KAAK,EAAE,MAAM,CAAC,QAAiB;AAC9E,aAAK,IAAI,OAAO,KAAK,iCAAiC,EAAE,MAAM,EAAE,OAAOlC,MAAAA,OAAO,GAAG,EAAA,EAAE,CAAG;AAAA,MACxF,CAAC;AAAA,IACH;AACA,SAAK,cAAc,uBAAuB,mBAAmB;AAG7D,UAAM,KAAK,cAAc,gBAAgB,KAAK,OAAO,QAAQ;AAI7D,SAAK;AAAA,MACH,EAAE,UAAUkB,MAAAA,cAAc,4BAAA;AAAA,MAC1B,CAAC,UAAU;AACT,cAAM,EAAE,aAAa,OAAA,IAAW,MAAM;AACtC,YAAI,OAAO,gBAAgB,YAAY,YAAY,WAAW,EAAG;AACjE,cAAM,UAAU,KAAK;AACrB,YAAI,CAAC,QAAS;AACd,cAAM,cAAc,OAAO,WAAW,WAAW,SAAS;AAC1D,aAAK,QAAQ,qBAAqB,aAAa,WAAW,EAAE,KAAK,CAAC,YAAY;AAC5E,cAAI,UAAU,GAAG;AACf,iBAAK,IAAI,OAAO,KAAK,uCAAuC;AAAA,cAC1D,MAAM,EAAE,QAAQ,YAAA;AAAA,cAChB,MAAM,EAAE,SAAS,QAAQ,YAAA;AAAA,YAAY,CACtC;AAAA,UACH;AAAA,QACF,CAAC,EAAE,MAAM,CAAC,QAAiB;AACzB,eAAK,IAAI,OAAO,KAAK,0CAA0C;AAAA,YAC7D,MAAM,EAAE,QAAQ,YAAA;AAAA,YAChB,MAAM,EAAE,OAAOlB,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CAC5B;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IAAA;AAIF,SAAK,gBAAgB,CAAC,SAAS,GAAG;AAAA,MAChC,QAAQ,CAAC,QAAQ,YAAY;AAC3B,YAAI,YAAY,UAAW;AAC3B,cAAM,UAAU,KAAK;AACrB,YAAI,CAAC,QAAS;AACd,cAAM,cAAc,gBAAgB,MAAM;AAC1C,aAAK,QAAQ,qBAAqB,QAAQ,WAAW,EAAE,KAAK,CAAC,YAAY;AACvE,cAAI,UAAU,GAAG;AACf,iBAAK,IAAI,OAAO,KAAK,6CAA6C;AAAA,cAChE,MAAM,EAAE,OAAA;AAAA,cACR,MAAM,EAAE,SAAS,QAAQ,YAAA;AAAA,YAAY,CACtC;AAAA,UACH;AAAA,QACF,CAAC,EAAE,MAAM,CAAC,QAAiB;AACzB,eAAK,IAAI,OAAO,KAAK,gCAAgC;AAAA,YACnD,MAAM,EAAE,OAAA;AAAA,YACR,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,UAAE,CAC5B;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IAAA,CACD;AAED,UAAM,wBAAwB,IAAI,sBAAsB,KAAK,aAAa;AAc1E,UAAM,UAAU,KAAK,IAAI;AAKzB,UAAM,eAAe,IAAI,mBAAmB;AAAA,MAC1C,QAAQ,KAAK,IAAI,OAAO,MAAM,QAAQ;AAAA,MACtC,eAAe,YAAY;AACzB,YAAI,CAAC,QAAQ,iBAAkB,QAAO,CAAA;AACtC,YAAI;AACF,gBAAM,UAAU,MAAM,QAAQ,iBAAiB,cAAc,MAAM,EAAE;AAGrE,gBAAM,MAA6E,CAAA;AACnF,qBAAW,KAAK,SAAS;AACvB,gBAAI,CAAC,EAAE,QAAS,MAAM,QAAQ,EAAE,IAAI,KAAK,EAAE,KAAK,WAAW,EAAI;AAC/D,gBAAI,KAAK;AAAA,cACP,MAAM,MAAM,QAAQ,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE;AAAA,cAC9C,GAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAA,IAAa,CAAA;AAAA,cAC5C,GAAI,EAAE,aAAa,EAAE,YAAY,EAAE,WAAA,IAAe,CAAA;AAAA,YAAC,CACpD;AAAA,UACH;AACA,iBAAO;AAAA,QACT,SAAS,KAAK;AAGZ,eAAK,IAAI,OAAO,KAAK,2EAA2E;AAAA,YAC9F,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAA;AAAA,UAAE,CACjE;AACD,iBAAO,CAAA;AAAA,QACT;AAAA,MACF;AAAA,IAAA,CACD;AAGD,SAAK,cAAc,gBAAgB,YAAY;AAE/C,UAAM,wBAAwB,IAAI,sBAAsB,cAAc,KAAK,aAAa;AAKxF,SAAK,uBAAuB;AAAA,MAC1B,MAAM,KAAK,0BAAA;AAAA,MACX;AAAA,IAAA;AAUF,UAAM,kBAA+C;AAAA,MACnD,aAAa,YAAY;AAAA,QACvB;AAAA,UACE,UAAU;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,OAAO,CAAC,cAAc,WAAW;AAAA,UACjC,UAAU,EAAE,eAAe,MAAM,oBAAoB,MAAA;AAAA,UACrD,aAAa;AAAA,UACb,cAAc,CAAC,MAAM,MAAM,IAAI;AAAA,UAC/B,gBAAgB;AAAA,UAChB,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,IACF;AAGF,SAAK,IAAI,OAAO,KAAK,mCAAmC;AACxD,UAAM,gBAAwC;AAAA,MAC5C,EAAE,YAAYmC,MAAAA,wBAAwB,UAAU,KAAK,cAAA;AAAA,MACrD;AAAA,QACE,YAAYC,MAAAA;AAAAA,QACZ,UAAU;AAAA,QACV,MAAM;AAAA,QACN,eAAe;AAAA,MAAA;AAAA,MAEjB;AAAA,QACE,YAAYC,MAAAA;AAAAA,QACZ,UAAU;AAAA,QACV,MAAM;AAAA,QACN,eAAe;AAAA,MAAA;AAAA,MAEjB,EAAE,YAAYC,oCAA8B,UAAU,gBAAA;AAAA,IAAgB;AAExE,WAAO;AAAA,EACT;AAAA,EAEA,MAAgB,aAA4B;AAC1C,QAAI,KAAK,sBAAsB;AAC7B,oBAAc,KAAK,oBAAoB;AACvC,WAAK,uBAAuB;AAAA,IAC9B;AACA,UAAM,KAAK,eAAe,WAAA;AAC1B,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,4BAAkC;AACxC,UAAM,UAAU,KAAK;AACrB,UAAM,WAAW,KAAK,IAAI;AAC1B,QAAI,CAAC,WAAW,CAAC,SAAU;AAC3B,UAAM,UAAU,QAAQ,mBAAA;AACxB,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,YAAY,KAAK,IAAI,OAAO,eAAe,KAAK,IAAI;AAC1D,UAAM,SAAS,UAAU,SAAS,GAAG,IAAI,UAAU,MAAM,GAAG,EAAE,CAAC,IAAK;AACpE,UAAM,YAAY,KAAK,IAAA;AACvB,UAAM,oCAAoB,IAAA;AAC1B,eAAW,UAAU,SAAS;AAC5B,YAAM,QAAQ,OAAO,SAAA;AAGrB,YAAM,WAAW,OAAO;AACxB,oBAAc,IAAI,QAAQ;AAC1B,YAAM,QAAQ,SAAS,QAAQ,GAAG;AAClC,YAAM,WAAW,QAAQ,IAAI,OAAO,SAAS,MAAM,GAAG,KAAK,CAAC,IAAI,OAAO,QAAQ;AAC/E,YAAM,UAAU,QAAQ,IAAI,SAAS,MAAM,QAAQ,CAAC,IAAI;AACxD,UAAI,CAAC,OAAO,SAAS,QAAQ,EAAG;AAchC,YAAM,OAAO,kBAAkB,OAAO,CAAC,YAAY,cAAc,aAAa,CAAC;AAC/E,YAAM,OAAO,KAAK,0BAA0B,IAAI,QAAQ;AACxD,YAAM,eAAe,CAAC,QAAQ,YAAY,KAAK,aAAa;AAC5D,UAAI,QAAQ,KAAK,SAAS,QAAQ,CAAC,aAAc;AACjD,WAAK,0BAA0B,IAAI,UAAU,EAAE,MAAM,WAAW,WAAW;AAC3E,eAAS,KAAKC,MAAAA;AAAAA,QACZrB,MAAAA,cAAc;AAAA,QACd,EAAE,MAAM,UAAU,IAAI,UAAU,OAAA;AAAA,QAChC,EAAE,UAAU,UAAU,SAAS,QAAQ,OAAO,UAAA;AAAA,MAAU,CACzD;AAAA,IACH;AAIA,QAAI,KAAK,0BAA0B,OAAO,cAAc,MAAM;AAC5D,iBAAW,YAAY,KAAK,0BAA0B,KAAA,GAAQ;AAC5D,YAAI,CAAC,cAAc,IAAI,QAAQ,EAAG,MAAK,0BAA0B,OAAO,QAAQ;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAAA,EAEU,uBAAuB;AAC/B,WAAO,KAAK,OAAO;AAAA,MACjB,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,MAAM;AAAA,YAAA;AAAA,YAER;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,YAAA;AAAA,YAEX;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,YAAA;AAAA,UACX;AAAA,QACF;AAAA,QAEF;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,MAAM;AAAA,YAAA;AAAA,YAER;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,MAAM;AAAA,YAAA;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF,CACD;AAAA,EACH;AACF;ACliBA,MAAM,aAA4B;AAAA,EAChC,QAAQ;AAAA,EAAC;AAAA,EACT,OAAO;AAAA,EAAC;AAAA,EACR,OAAO;AAAA,EAAC;AAAA,EACR,QAAQ;AAAA,EAAC;AAAA,EACT,QAAQ;AAAE,WAAO;AAAA,EAAW;AAAA,EAC5B,WAAW;AAAE,WAAO;AAAA,EAAW;AACjC;AAEA,MAAM,MAAM,OAAO,KAAK,CAAC,KAAM,GAAI,CAAC;AACpC,MAAM,MAAM,OAAO,KAAK,CAAC,KAAM,GAAI,CAAC;AAEpC,SAAS,mBAAmB,OAAuB;AACjD,UAAQ,OAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EAAA;AAEb;AAEA,SAAS,gBAAgB,QAAwC;AAC/D,QAAM,cAAc,mBAAmB,OAAO,KAAK;AACnD,QAAM,OAAO;AAAA,IACX;AAAA,IAAgB;AAAA,IAAa;AAAA;AAAA,IAE7B;AAAA,IAAW;AAAA,IACX;AAAA,IAAU;AAAA,IACV;AAAA,IAAc;AAAA,IACd;AAAA,IAAoB;AAAA,IACpB;AAAA,IAAM;AAAA,IAAa;AAAA,IAAM;AAAA,EAAA;AAG3B,MAAI,OAAO,QAAQ,GAAG;AACpB,SAAK,KAAK,OAAO,YAAY,OAAO,KAAK,OAAO,OAAO,KAAK,EAAE;AAAA,EAChE;AAEA,OAAK,KAAK,MAAM,cAAc,WAAW,SAAS,QAAQ,KAAK,YAAY,KAAK,QAAQ;AACxF,SAAO;AACT;AAEO,MAAM,qBAAgD;AAAA,EACnD;AAAA,EACA;AAAA,EACA,UAA+B;AAAA,EAC/B,qCAAqB,IAAA;AAAA,EACrB,eAAe,OAAO,MAAM,CAAC;AAAA,EAC7B,YAAY;AAAA,EACH;AAAA;AAAA,EAGT,cAAc;AAAA,EACd,eAAe;AAAA;AAAA,EAGf,eAAe;AAAA,EACf,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,YAAY,KAAK,IAAA;AAAA,EAEzB,YAAY,QAA8B,SAAwB,YAAY;AAC5E,SAAK,SAAS,EAAE,GAAG,OAAA;AACnB,SAAK,SAAS;AACd,SAAK,eAAe,IAAI,aAAa,OAAO,MAAM;AAClD,SAAK,YAAA;AAAA,EACP;AAAA,EAEQ,cAAoB;AAC1B,QAAI,KAAK,UAAW;AAEpB,SAAK,WAAA;AACL,SAAK,eAAe,OAAO,MAAM,CAAC;AAClC,SAAK,cAAc;AACnB,SAAK,eAAe;AAEpB,UAAM,OAAO,gBAAgB,KAAK,MAAM;AACxC,SAAK,UAAUH,yBAAM,UAAU,IAAI;AAGnC,SAAK,QAAQ,OAAO,GAAG,SAAS,MAAM;AAAA,IAAC,CAAC;AAExC,SAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AACjD,WAAK,iBAAiB,KAAK;AAAA,IAC7B,CAAC;AAED,SAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,YAAM,OAAO,KAAK,SAAA,EAAW,KAAA;AAC7B,UAAI,KAAM,MAAK,OAAO,MAAM,iBAAiB,EAAE,MAAM,EAAE,KAAA,GAAQ;AAAA,IACjE,CAAC;AAED,SAAK,QAAQ,GAAG,SAAS,CAAC,QAAe;AACvC,WAAK,OAAO,MAAM,8BAA8B,EAAE,MAAM,EAAE,OAAO,IAAI,QAAA,GAAW;AAAA,IAClF,CAAC;AAED,SAAK,QAAQ,GAAG,SAAS,CAAC,OAAO,YAAY;AAC3C,UAAI,CAAC,KAAK,WAAW;AACnB,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,aAAmB;AACzB,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,aAAK,QAAQ,KAAK,SAAS;AAAA,MAC7B,QAAQ;AAAA,MAER;AACA,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAqB;AAC5C,SAAK,eAAe,OAAO,OAAO,CAAC,KAAK,cAAc,KAAK,CAAC;AAG5D,QAAI,aAAa;AACjB,WAAO,MAAM;AACX,YAAM,WAAW,KAAK,aAAa,QAAQ,KAAK,UAAU;AAC1D,UAAI,aAAa,GAAI;AAErB,YAAM,WAAW,KAAK,aAAa,QAAQ,KAAK,WAAW,CAAC;AAC5D,UAAI,aAAa,GAAI;AAErB,YAAM,WAAW,WAAW;AAC5B,YAAM,WAAW,KAAK,aAAa,SAAS,UAAU,QAAQ;AAG9D,mBAAa;AAEb,WAAK,UAAU,OAAO,KAAK,QAAQ,CAAC;AAAA,IACtC;AAGA,QAAI,aAAa,GAAG;AAClB,WAAK,eAAe,OAAO,KAAK,KAAK,aAAa,SAAS,UAAU,CAAC;AAAA,IACxE;AAAA,EACF;AAAA,EAEQ,UAAU,MAAoB;AACpC,UAAM,cAAc,KAAK,IAAA;AAEzB,QAAI,CAAC,KAAK,aAAa,cAAc;AACnC,WAAK;AACL;AAAA,IACF;AAEA,UAAM,aAAa,KAAK,IAAA,IAAQ;AAChC,SAAK,qBAAqB;AAC1B,SAAK;AACL,SAAK;AAGL,QAAI,KAAK,gBAAgB,GAAG;AAC1B,YAAM,OAAO,oBAAoB,IAAI;AACrC,WAAK,cAAc,KAAK;AACxB,WAAK,eAAe,KAAK;AAAA,IAC3B;AAEA,UAAM,QAAsB;AAAA,MAC1B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,QAAQ;AAAA,MACR,WAAW,KAAK,IAAA;AAAA,IAAI;AAGtB,eAAW,MAAM,KAAK,gBAAgB;AACpC,SAAG,KAAK;AAAA,IACV;AAAA,EACF;AAAA,EAEA,WAAW,QAA6B;AACtC,QAAI,KAAK,aAAa,CAAC,KAAK,SAAS,MAAO;AAC5C,SAAK;AACL,QAAI;AACF,WAAK,QAAQ,MAAM,MAAM,OAAO,IAAI;AAAA,IACtC,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,QAAQ,UAAsD;AAC5D,SAAK,eAAe,IAAI,QAAQ;AAChC,WAAO,MAAM;AACX,WAAK,eAAe,OAAO,QAAQ;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,aAAa,QAA6C;AACxD,UAAM,eACH,OAAO,UAAU,UAAa,OAAO,UAAU,KAAK,OAAO,SAC3D,OAAO,iBAAiB,UAAa,OAAO,iBAAiB,KAAK,OAAO,gBACzE,OAAO,UAAU,UAAa,OAAO,UAAU,KAAK,OAAO;AAE9D,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAA;AAEnC,QAAI,OAAO,WAAW,QAAW;AAC/B,WAAK,aAAa,UAAU,OAAO,MAAM;AAAA,IAC3C;AAEA,QAAI,cAAc;AAChB,WAAK,YAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,SAAK,WAAA;AACL,SAAK,eAAe,MAAA;AAAA,EACtB;AAAA,EAEA,WAAyB;AACvB,UAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,KAAK,aAAa,KAAM,CAAC;AAClE,WAAO;AAAA,MACL,UAAU,KAAK,eAAe;AAAA,MAC9B,WAAW,KAAK,eAAe;AAAA,MAC/B,iBAAiB,KAAK,cAAc,IAAI,KAAK,oBAAoB,KAAK,cAAc;AAAA,MACpF,eAAe,KAAK;AAAA,IAAA;AAAA,EAExB;AACF;AAOA,SAAS,oBAAoB,MAAiD;AAC5E,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACxC,QAAI,KAAK,CAAC,MAAM,QAAS,KAAK,IAAI,CAAC,MAAM,OAAQ,KAAK,IAAI,CAAC,MAAM,MAAO;AACtE,YAAM,SAAU,KAAK,IAAI,CAAC,KAAM,IAAK,KAAK,IAAI,CAAC;AAC/C,YAAM,QAAS,KAAK,IAAI,CAAC,KAAM,IAAK,KAAK,IAAI,CAAC;AAC9C,aAAO,EAAE,OAAO,OAAA;AAAA,IAClB;AAAA,EACF;AACA,SAAO,EAAE,OAAO,GAAG,QAAQ,EAAA;AAC7B;ACtPA,MAAM,uCAAuB,IAAI,CAAC,QAAQ,QAAQ,QAAQ,OAAO,CAAC;AAE3D,MAAM,sBAAkD;AAAA,EACpD,KAAK;AAAA,EACL,OAAO;AAAA;AAAA,EAEP,WAAW;AAAA,EAEpB,MAAM,cAAc,OAA4C;AAC9D,WAAO,iBAAiB,IAAI,MAAM,KAAK;AAAA,EACzC;AAAA,EAEA,MAAM,cAAc,QAAwD;AAC1E,WAAO,IAAI,qBAAqB,MAAM;AAAA,EACxC;AACF;ACEA,MAAM,sBAAsB;AAE5B,MAAM,oBAAoB;AAWnB,MAAM,kBAA4D;AAAA,EAC9D;AAAA,EACQ;AAAA,EACA;AAAA,EAEjB,YAAY,SAA6C;AACvD,SAAK,YAAY,QAAQ;AACzB,SAAK,cAAc,QAAQ;AAC3B,SAAK,OAAO,QAAQ,QAAQ;AAAA,EAC9B;AAAA,EAEA,OAAO,KAA2B;AAChC,UAAM,UAAU,KAAK,UAAU,GAAG;AAClC,QAAI,QAAQ,SAAS,mBAAmB;AACtC,YAAM,IAAI,MAAM,gDAAgD,QAAQ,MAAM,eAAe,iBAAiB,GAAG;AAAA,IACnH;AACA,UAAM,MAAM,OAAO,YAAY,sBAAsB,QAAQ,MAAM;AACnE,QAAI,cAAc,QAAQ,QAAQ,CAAC;AACnC,QAAI,IAAI,SAAS,mBAAmB;AACpC,WAAO;AAAA,EACT;AAAA,EAEA,gBAAwC;AACtC,WAAO,IAAI,oBAA8B,KAAK,WAAW;AAAA,EAC3D;AACF;AAEA,MAAM,oBAAgE;AAAA;AAAA,EAE5D,SAAS,OAAO,MAAM,CAAC;AAAA,EACd;AAAA,EAEjB,YAAY,aAA8C;AACxD,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,KAAK,OAAwC;AAG3C,SAAK,SAAS,KAAK,OAAO,WAAW,IACjC,OAAO,KAAK,KAAK,IACjB,OAAO,OAAO,CAAC,KAAK,QAAQ,OAAO,KAAK,KAAK,CAAC,CAAC;AAEnD,UAAM,MAAkB,CAAA;AACxB,QAAI,SAAS;AAEb,WAAO,MAAM;AAEX,UAAI,KAAK,OAAO,SAAS,SAAS,oBAAqB;AAEvD,YAAM,aAAa,KAAK,OAAO,aAAa,MAAM;AAClD,UAAI,aAAa,mBAAmB;AAElC,aAAK,SAAS,OAAO,MAAM,CAAC;AAC5B,cAAM,IAAI,MAAM,gDAAgD,UAAU,gBAAgB,iBAAiB,EAAE;AAAA,MAC/G;AAEA,YAAM,kBAAkB,sBAAsB;AAC9C,UAAI,KAAK,OAAO,SAAS,SAAS,iBAAiB;AAEjD;AAAA,MACF;AAEA,YAAM,eAAe,SAAS;AAC9B,YAAM,aAAa,eAAe;AAClC,YAAM,UAAU,KAAK,OAAO,SAAS,cAAc,UAAU;AAC7D,UAAI,KAAK,KAAK,YAAY,OAAO,CAAC;AAClC,eAAS;AAAA,IACX;AAGA,QAAI,SAAS,GAAG;AACd,WAAK,SAAS,KAAK,OAAO,SAAS,MAAM;AAAA,IAC3C;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS,OAAO,MAAM,CAAC;AAAA,EAC9B;AACF;ACnFA,MAAM,eAAe;AACrB,MAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,KAAK;AACP;AAIA,MAAM,iBAAyE;AAAA,EAC7E,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,SAAS,oBAAoB,GAA8B;AACzD,SAAO,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM;AAC3D;AAEO,SAAS,mBAAmB,OAAiC;AAClE,QAAM,UAAU,OAAO,YAAY,eAAe,MAAM,KAAK,MAAM;AACnE,UAAQ,gBAAgB,OAAO,MAAM,SAAS,GAAG,CAAC;AAClD,UAAQ,cAAc,MAAM,OAAO,CAAC;AACpC,UAAQ,cAAc,MAAM,QAAQ,EAAE;AACtC,UAAQ,WAAW,WAAW,MAAM,MAAM,GAAG,EAAE;AAE/C,UAAQ,WAAW,GAAG,EAAE;AACxB,UAAQ,WAAW,GAAG,EAAE;AACxB,UAAQ,WAAW,GAAG,EAAE;AACxB,UAAQ,IAAI,MAAM,MAAM,YAAY;AACpC,SAAO;AACT;AAEO,SAAS,mBAAmB,OAAiC;AAClE,MAAI,MAAM,SAAS,cAAc;AAC/B,UAAM,IAAI,MAAM,0CAA0C,MAAM,MAAM,MAAM,YAAY,GAAG;AAAA,EAC7F;AACA,QAAM,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;AACxE,QAAM,eAAe,IAAI,eAAe,CAAC;AAEzC,QAAM,YAAY,OAAO,YAAY;AACrC,QAAM,QAAQ,IAAI,aAAa,CAAC;AAChC,QAAM,SAAS,IAAI,aAAa,EAAE;AAClC,QAAM,YAAY,IAAI,UAAU,EAAE;AAClC,MAAI,CAAC,oBAAoB,SAAS,GAAG;AACnC,UAAM,IAAI,MAAM,2CAA2C,SAAS,EAAE;AAAA,EACxE;AACA,QAAM,SAAS,eAAe,SAAS;AACvC,QAAM,OAAO,OAAO,KAAK,IAAI,SAAS,YAAY,CAAC;AACnD,SAAO,EAAE,MAAM,OAAO,QAAQ,QAAQ,UAAA;AACxC;AAMO,MAAM,oBAAoB,IAAI,kBAAgC;AAAA,EACnE,MAAM;AAAA,EACN,WAAW;AAAA,EACX,aAAa;AACf,CAAC;AC/DD,MAAMyB,eAAa;AAEZ,MAAM,aAAuC;AAAA,EACzC,OAAO;AAAA,EAEhB,MAAM,OAAO,SAA8D;AACzE,UAAM,OAAO,SAAS,QAAQ;AAC9B,UAAM,OAAO,SAAS,QAAQ;AAC9B,WAAO,IAAI,QAA2B,CAAC,SAAS,WAAW;AACzD,YAAM,yCAAyB,IAAA;AAC/B,YAAM,kCAAkB,IAAA;AAExB,YAAM,SAAS,IAAI,aAAa,CAAC,WAAW;AAC1C,eAAO,WAAW,IAAI;AACtB,cAAM,OAAO,IAAI,cAAc,QAAQ,MAAM;AAC3C,sBAAY,OAAO,IAAI;AAAA,QACzB,CAAC;AACD,oBAAY,IAAI,IAAI;AACpB,mBAAW,WAAW,mBAAoB,SAAQ,IAAI;AAAA,MACxD,CAAC;AAED,aAAO,KAAK,SAAS,CAAC,QAAQ,OAAO,GAAG,CAAC;AAEzC,aAAO,OAAO,MAAM,MAAM,MAAM;AAC9B,eAAO,mBAAmB,OAAO;AACjC,cAAM,UAAU,OAAO,QAAA;AACvB,YAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;AACnD,iBAAO,MAAA;AACP,iBAAO,IAAI,MAAM,0DAA0D,CAAC;AAC5E;AAAA,QACF;AAEA,cAAM,YAAY,QAAQ,YAAY,OAAO,YAAY,QAAQ;AACjE,cAAM,MAAM,GAAGA,YAAU,GAAG,SAAS,IAAI,QAAQ,IAAI;AACrD,cAAM,YAAY,SAAS;AAC3B,cAAM,WAAW,cAAc,SAAY,GAAG,GAAG,GAAG,SAAS,KAAK;AAElE,cAAM,WAA8B;AAAA,UAClC,MAAM;AAAA,UACN,KAAK;AAAA,UACL,IAAI,oBAAoB;AAAE,mBAAO,YAAY;AAAA,UAAK;AAAA,UAClD,aAAa,SAAS;AACpB,+BAAmB,IAAI,OAAO;AAC9B,mBAAO,MAAM,mBAAmB,OAAO,OAAO;AAAA,UAChD;AAAA,UACA,MAAM,QAAQ;AACZ,uBAAW,QAAQ,CAAC,GAAG,WAAW,GAAG;AACnC,mBAAK,MAAM,iBAAiB;AAAA,YAC9B;AACA,kBAAM,IAAI,QAAc,CAAC,QAAQ;AAC/B,qBAAO,MAAM,MAAM,KAAK;AAAA,YAC1B,CAAC;AAAA,UACH;AAAA,QAAA;AAEF,gBAAQ,QAAQ;AAAA,MAClB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,KAAa,SAAiE;AAC1F,UAAM,SAAS,YAAY,GAAG;AAC9B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,8BAA8B,GAAG,GAAG;AAAA,IACtD;AAEA,UAAM,YAAY,SAAS,aAAa;AAExC,WAAO,IAAI,QAA6B,CAAC,SAAS,WAAW;AAC3D,YAAM,SAAS,IAAI,IAAI,OAAA;AACvB,aAAO,WAAW,IAAI;AAEtB,UAAI,UAAU;AACd,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,QAAS;AACb,kBAAU;AACV,eAAO,QAAA;AACP,eAAO,IAAI,MAAM,uCAAuC,SAAS,OAAO,GAAG,GAAG,CAAC;AAAA,MACjF,GAAG,SAAS;AAEZ,aAAO,KAAK,SAAS,CAAC,QAAQ;AAC5B,YAAI,QAAS;AACb,kBAAU;AACV,qBAAa,KAAK;AAClB,eAAO,GAAG;AAAA,MACZ,CAAC;AAED,aAAO,KAAK,WAAW,MAAM;AAC3B,YAAI,QAAS;AACb,kBAAU;AACV,qBAAa,KAAK;AAClB,eAAO,mBAAmB,OAAO;AACjC,gBAAQ,IAAI,cAAc,QAAQ,MAAM;AAAA,QAAiC,CAAC,CAAC;AAAA,MAC7E,CAAC;AAED,aAAO,QAAQ,OAAO,MAAM,OAAO,IAAI;AAAA,IACzC,CAAC;AAAA,EACH;AACF;AAEA,SAAS,YAAY,KAAoD;AACvE,MAAI,CAAC,IAAI,WAAWA,YAAU,EAAG,QAAO;AACxC,QAAM,OAAO,IAAI,MAAMA,aAAW,MAAM;AAExC,QAAM,YAAY,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AACxC,QAAM,YAAY,UAAU,YAAY,GAAG;AAC3C,MAAI,cAAc,GAAI,QAAO;AAC7B,QAAM,OAAO,UAAU,MAAM,GAAG,SAAS;AACzC,QAAM,OAAO,OAAO,UAAU,MAAM,YAAY,CAAC,CAAC;AAClD,MAAI,CAAC,QAAQ,CAAC,OAAO,UAAU,IAAI,KAAK,QAAQ,KAAK,OAAO,MAAO,QAAO;AAC1E,SAAO,EAAE,MAAM,KAAA;AACjB;AAEA,MAAM,cAA6C;AAAA,EASjD,YACmB,QACA,WACjB;AAFiB,SAAA,SAAA;AACA,SAAA,YAAA;AAEjB,SAAK,gBAAgB,OAAO,kBAAkB,UAAa,OAAO,eAAe,SAC7E,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU,KAC5C;AAEJ,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,UAAI,KAAK,OAAQ;AACjB,iBAAW,WAAW,KAAK,aAAc,SAAQ,KAAK;AAAA,IACxD,CAAC;AAED,UAAM,YAAY,CAAC,WAAoB;AACrC,UAAI,KAAK,OAAQ;AACjB,WAAK,SAAS;AACd,iBAAW,WAAW,KAAK,cAAe,SAAQ,MAAM;AACxD,WAAK,UAAA;AAAA,IACP;AACA,WAAO,GAAG,SAAS,MAAM,UAAU,eAAe,CAAC;AACnD,WAAO,GAAG,SAAS,CAAC,QAAQ,UAAU,IAAI,OAAO,CAAC;AAClD,WAAO,GAAG,OAAO,MAAM,UAAU,YAAY,CAAC;AAAA,EAChD;AAAA,EA9BS,KAAKpC,OAAAA,WAAA;AAAA,EACL,OAAO;AAAA,EACP;AAAA,EAEQ,mCAAmB,IAAA;AAAA,EACnB,oCAAoB,IAAA;AAAA,EAC7B,SAAS;AAAA,EA0BjB,KAAK,OAAyB;AAC5B,QAAI,KAAK,OAAQ;AAGjB,SAAK,OAAO,MAAM,KAAK;AAAA,EACzB;AAAA,EAEA,OAAO,SAAmD;AACxD,SAAK,aAAa,IAAI,OAAO;AAC7B,WAAO,MAAM,KAAK,aAAa,OAAO,OAAO;AAAA,EAC/C;AAAA,EAEA,QAAQ,SAAiD;AACvD,SAAK,cAAc,IAAI,OAAO;AAC9B,WAAO,MAAM,KAAK,cAAc,OAAO,OAAO;AAAA,EAChD;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,WAAW,KAAK,cAAe,SAAQ,MAAM;AACxD,SAAK,OAAO,QAAA;AACZ,SAAK,UAAA;AAAA,EACP;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;AC3KA,MAAMoC,eAAa;AAEZ,MAAM,aAAuC;AAAA,EACzC,OAAO;AAAA,EAEhB,MAAM,OAAO,SAA8D;AACzE,UAAM,aAAa,SAAS,cAAcC,gBAAK,KAAKC,cAAG,OAAA,GAAU,kBAAkBtC,kBAAA,CAAY,OAAO;AAGtG,QAAI;AACFuC,oBAAG,WAAW,UAAU;AAAA,IAC1B,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,SAAU,OAAM;AAAA,IAC/B;AAEA,WAAO,IAAI,QAA2B,CAAC,SAAS,WAAW;AACzD,YAAM,yCAAyB,IAAA;AAC/B,YAAM,kCAAkB,IAAA;AAExB,YAAM,SAAS,IAAI,aAAa,CAAC,WAAW;AAC1C,cAAM,OAAO,IAAI,cAAc,QAAQ,MAAM;AAC3C,sBAAY,OAAO,IAAI;AAAA,QACzB,CAAC;AACD,oBAAY,IAAI,IAAI;AACpB,mBAAW,WAAW,mBAAoB,SAAQ,IAAI;AAAA,MACxD,CAAC;AAED,aAAO,KAAK,SAAS,CAAC,QAAQ,OAAO,GAAG,CAAC;AAEzC,aAAO,OAAO,YAAY,MAAM;AAC9B,eAAO,mBAAmB,OAAO;AACjC,cAAM,MAAM,GAAGH,YAAU,GAAG,UAAU;AAEtC,cAAM,WAA8B;AAAA,UAClC,MAAM;AAAA,UACN;AAAA,UACA,IAAI,oBAAoB;AAAE,mBAAO,YAAY;AAAA,UAAK;AAAA,UAClD,aAAa,SAAS;AACpB,+BAAmB,IAAI,OAAO;AAC9B,mBAAO,MAAM,mBAAmB,OAAO,OAAO;AAAA,UAChD;AAAA,UACA,MAAM,QAAQ;AACZ,uBAAW,QAAQ,CAAC,GAAG,WAAW,GAAG;AACnC,mBAAK,MAAM,iBAAiB;AAAA,YAC9B;AACA,kBAAM,IAAI,QAAc,CAAC,QAAQ;AAC/B,qBAAO,MAAM,MAAM,KAAK;AAAA,YAC1B,CAAC;AACD,gBAAI;AACFG,4BAAG,WAAW,UAAU;AAAA,YAC1B,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QAAA;AAEF,gBAAQ,QAAQ;AAAA,MAClB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,KAAa,SAAiE;AAC1F,UAAM,aAAa,YAAY,GAAG;AAClC,QAAI,eAAe,MAAM;AACvB,YAAM,IAAI,MAAM,8BAA8B,GAAG,GAAG;AAAA,IACtD;AAEA,UAAM,YAAY,SAAS,aAAa;AAExC,WAAO,IAAI,QAA6B,CAAC,SAAS,WAAW;AAC3D,YAAM,SAAS,IAAI,IAAI,OAAA;AAEvB,UAAI,UAAU;AACd,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,QAAS;AACb,kBAAU;AACV,eAAO,QAAA;AACP,eAAO,IAAI,MAAM,uCAAuC,SAAS,OAAO,GAAG,GAAG,CAAC;AAAA,MACjF,GAAG,SAAS;AAEZ,aAAO,KAAK,SAAS,CAAC,QAAQ;AAC5B,YAAI,QAAS;AACb,kBAAU;AACV,qBAAa,KAAK;AAClB,eAAO,GAAG;AAAA,MACZ,CAAC;AAED,aAAO,KAAK,WAAW,MAAM;AAC3B,YAAI,QAAS;AACb,kBAAU;AACV,qBAAa,KAAK;AAClB,eAAO,mBAAmB,OAAO;AACjC,gBAAQ,IAAI,cAAc,QAAQ,MAAM;AAAA,QAAiC,CAAC,CAAC;AAAA,MAC7E,CAAC;AAED,aAAO,QAAQ,UAAU;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,YAAY,KAA4B;AAC/C,MAAI,CAAC,IAAI,WAAWH,YAAU,EAAG,QAAO;AACxC,QAAM,OAAO,IAAI,MAAMA,aAAW,MAAM;AAExC,MAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO;AAClC,SAAO;AACT;AAEA,MAAM,cAA6C;AAAA,EASjD,YACmB,QACA,WACjB;AAFiB,SAAA,SAAA;AACA,SAAA,YAAA;AAEjB,SAAK,gBAAgB;AAErB,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,UAAI,KAAK,OAAQ;AACjB,iBAAW,WAAW,KAAK,aAAc,SAAQ,KAAK;AAAA,IACxD,CAAC;AAED,UAAM,YAAY,CAAC,WAAoB;AACrC,UAAI,KAAK,OAAQ;AACjB,WAAK,SAAS;AACd,iBAAW,WAAW,KAAK,cAAe,SAAQ,MAAM;AACxD,WAAK,UAAA;AAAA,IACP;AACA,WAAO,GAAG,SAAS,MAAM,UAAU,eAAe,CAAC;AACnD,WAAO,GAAG,SAAS,CAAC,QAAQ,UAAU,IAAI,OAAO,CAAC;AAClD,WAAO,GAAG,OAAO,MAAM,UAAU,YAAY,CAAC;AAAA,EAChD;AAAA,EA5BS,KAAKpC,OAAAA,WAAA;AAAA,EACL,OAAO;AAAA,EACP;AAAA,EAEQ,mCAAmB,IAAA;AAAA,EACnB,oCAAoB,IAAA;AAAA,EAC7B,SAAS;AAAA,EAwBjB,KAAK,OAAyB;AAC5B,QAAI,KAAK,OAAQ;AACjB,SAAK,OAAO,MAAM,KAAK;AAAA,EACzB;AAAA,EAEA,OAAO,SAAmD;AACxD,SAAK,aAAa,IAAI,OAAO;AAC7B,WAAO,MAAM,KAAK,aAAa,OAAO,OAAO;AAAA,EAC/C;AAAA,EAEA,QAAQ,SAAiD;AACvD,SAAK,cAAc,IAAI,OAAO;AAC9B,WAAO,MAAM,KAAK,cAAc,OAAO,OAAO;AAAA,EAChD;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,WAAW,KAAK,cAAe,SAAQ,MAAM;AACxD,SAAK,OAAO,QAAA;AACZ,SAAK,UAAA;AAAA,EACP;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;AC7KA,MAAM,aAAa;AAGnB,MAAM,+BAAe,IAAA;AAEd,MAAM,mBAA6C;AAAA,EAC/C,OAAO;AAAA,EAEhB,MAAM,OAAO,SAA8D;AACzE,UAAM,YAAY,SAAS,aAAa,MAAMA,OAAAA,YAAY;AAC1D,QAAI,SAAS,IAAI,SAAS,GAAG;AAC3B,YAAM,IAAI,MAAM,kCAAkC,SAAS,kBAAkB;AAAA,IAC/E;AACA,UAAM,WAAW,IAAI,kBAAkB,SAAS;AAChD,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,KAAa,UAAkE;AAC3F,QAAI,CAAC,IAAI,WAAW,UAAU,GAAG;AAC/B,YAAM,IAAI,MAAM,wCAAwC,GAAG,GAAG;AAAA,IAChE;AACA,UAAM,YAAY,IAAI,MAAM,WAAW,MAAM;AAC7C,UAAM,WAAW,SAAS,IAAI,SAAS;AACvC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,kDAAkD,SAAS,GAAG;AAAA,IAChF;AACA,WAAO,SAAS,kBAAA;AAAA,EAClB;AACF;AAEA,MAAM,kBAA+C;AAAA,EAOnD,YAA6B,WAAmB;AAAnB,SAAA,YAAA;AAC3B,SAAK,MAAM,GAAG,UAAU,GAAG,SAAS;AAAA,EACtC;AAAA,EARS,OAAO;AAAA,EACP;AAAA,EACQ,yCAAyB,IAAA;AAAA,EACzB,kCAAkB,IAAA;AAAA,EAC3B,SAAS;AAAA,EAMjB,IAAI,oBAA4B;AAC9B,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,aAAa,SAA2D;AACtE,SAAK,mBAAmB,IAAI,OAAO;AACnC,WAAO,MAAM,KAAK,mBAAmB,OAAO,OAAO;AAAA,EACrD;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,QAAQ,CAAC,GAAG,KAAK,WAAW,GAAG;AACxC,WAAK,MAAM,iBAAiB;AAAA,IAC9B;AACA,aAAS,OAAO,KAAK,SAAS;AAAA,EAChC;AAAA;AAAA,EAGA,oBAAyC;AACvC,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,uBAAuB,KAAK,SAAS,aAAa;AAAA,IACpE;AAIA,UAAM,gBAAgB,IAAIwC,yBAAA;AAC1B,UAAM,gBAAgB,IAAIA,yBAAA;AAC1B,UAAM,aAAa,IAAI,oBAAoB,eAAe,eAAe,MAAM;AAC7E,WAAK,YAAY,OAAO,UAAU;AAAA,IACpC,CAAC;AACD,UAAM,aAAa,IAAI,oBAAoB,eAAe,eAAe,MAAM;AAE7E,iBAAW,MAAM,aAAa;AAAA,IAChC,CAAC;AACD,SAAK,YAAY,IAAI,UAAU;AAC/B,eAAW,WAAW,KAAK,oBAAoB;AAI7C,cAAQ,UAAU;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACF;AAEA,MAAM,oBAAmD;AAAA,EASvD,YAEmB,UAEA,SACA,WACjB;AAJiB,SAAA,WAAA;AAEA,SAAA,UAAA;AACA,SAAA,YAAA;AAEjB,SAAK,QAAQ,GAAG,QAAQ,CAAC,UAAsB;AAC7C,UAAI,KAAK,OAAQ;AACjB,iBAAW,WAAW,KAAK,aAAc,SAAQ,KAAK;AAAA,IACxD,CAAC;AACD,SAAK,QAAQ,GAAG,SAAS,CAAC,WAAoB;AAC5C,UAAI,KAAK,OAAQ;AACjB,WAAK,SAAS;AACd,iBAAW,WAAW,KAAK,cAAe,SAAQ,MAAM;AACxD,WAAK,UAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAzBS,KAAKxC,OAAAA,WAAA;AAAA,EACL,OAAO;AAAA,EACP,gBAAgB;AAAA,EAER,mCAAmB,IAAA;AAAA,EACnB,oCAAoB,IAAA;AAAA,EAC7B,SAAS;AAAA,EAqBjB,KAAK,OAAyB;AAC5B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS,KAAK,QAAQ,KAAK;AAAA,EAClC;AAAA,EAEA,OAAO,SAAmD;AACxD,SAAK,aAAa,IAAI,OAAO;AAC7B,WAAO,MAAM,KAAK,aAAa,OAAO,OAAO;AAAA,EAC/C;AAAA,EAEA,QAAQ,SAAiD;AACvD,SAAK,cAAc,IAAI,OAAO;AAC9B,WAAO,MAAM,KAAK,cAAc,OAAO,OAAO;AAAA,EAChD;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AAGjB,SAAK,SAAS,KAAK,SAAS,MAAM;AAClC,SAAK,QAAQ,KAAK,SAAS,MAAM;AAAA,EACnC;AAAA,EAEA,IAAI,gBAAwB;AAE1B,WAAO;AAAA,EACT;AACF;ACpHA,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,iCAAiC,IAAI,OAAO;AAElD,eAAsB,kBACpB,SACgC;AAChC,QAAM,WAAW,MAAM,QAAQ,UAAU,OAAO,QAAQ,MAAM;AAC9D,QAAM,SAAS,IAAI,gBAA0B,UAAU,OAAO;AAC9D,SAAO;AACT;AAEA,MAAM,gBAA2D;AAAA,EAa/D,YACmB,UACA,SACjB;AAFiB,SAAA,WAAA;AACA,SAAA,UAAA;AAEjB,SAAK,wBAAwB,QAAQ,8BAA8B;AACnE,aAAS,aAAa,CAAC,SAAS,KAAK,yBAAyB,IAAI,CAAC;AAAA,EACrE;AAAA,EAlBiB,8BAAc,IAAA;AAAA,EACd,6BAAa,IAAA;AAAA,EACb,sCAAsB,IAAA;AAAA,EACtB,yCAAyB,IAAA;AAAA,EACzB,YAAY,KAAK,IAAA;AAAA,EAC1B,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,UAAU;AAAA,EAED;AAAA,EAUjB,IAAI,MAAc;AAChB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,kBAA0B;AAC5B,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,UAAU,KAAqB;AAC7B,QAAI,KAAK,WAAW,KAAK,OAAO,SAAS,EAAG;AAC5C,UAAM,QAAQ,KAAK,QAAQ,MAAM,OAAO,GAAG;AAC3C,eAAW,YAAY,KAAK,OAAO,OAAA,GAAU;AAC3C,WAAK,cAAc,UAAU,KAAK;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,OAAO,cAAsB,KAAqB;AAChD,QAAI,KAAK,QAAS;AAClB,UAAM,WAAW,KAAK,OAAO,IAAI,YAAY;AAC7C,QAAI,CAAC,SAAU;AACf,UAAM,QAAQ,KAAK,QAAQ,MAAM,OAAO,GAAG;AAC3C,SAAK,cAAc,UAAU,KAAK;AAAA,EACpC;AAAA,EAEA,kBAAkB,SAA4C;AAC5D,SAAK,gBAAgB,IAAI,OAAO;AAChC,WAAO,MAAM,KAAK,gBAAgB,OAAO,OAAO;AAAA,EAClD;AAAA,EAEA,qBAAqB,SAA6D;AAChF,SAAK,mBAAmB,IAAI,OAAO;AACnC,WAAO,MAAM,KAAK,mBAAmB,OAAO,OAAO;AAAA,EACrD;AAAA,EAEA,WAA6B;AAC3B,WAAO;AAAA,MACL,iBAAiB,KAAK,OAAO;AAAA,MAC7B,YAAY,KAAK;AAAA,MACjB,eAAe,KAAK;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,UAAU,KAAK,IAAA,IAAQ,KAAK;AAAA,IAAA;AAAA,EAEhC;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,eAAW,WAAW,KAAK,QAAQ,OAAA,GAAU;AAC3C,mBAAa,QAAQ,KAAK;AAC1B,cAAQ,gBAAA;AACR,cAAQ,iBAAA;AACR,cAAQ,KAAK,MAAM,gBAAgB;AAAA,IACrC;AACA,SAAK,QAAQ,MAAA;AACb,eAAW,YAAY,KAAK,OAAO,OAAA,GAAU;AAC3C,eAAS,iBAAA;AACT,eAAS,KAAK,MAAM,gBAAgB;AAAA,IACtC;AACA,SAAK,OAAO,MAAA;AACZ,UAAM,KAAK,SAAS,MAAA;AAAA,EACtB;AAAA;AAAA,EAIQ,yBAAyB,MAAiC;AAChE,UAAM,QAAQ,WAAW,MAAM;AAC7B,YAAM,IAAI,KAAK,QAAQ,IAAI,KAAK,EAAE;AAClC,UAAI,CAAC,EAAG;AACR,WAAK,QAAQ,OAAO,KAAK,EAAE;AAC3B,QAAE,gBAAA;AACF,QAAE,iBAAA;AACF,WAAK,MAAM,mBAAmB;AAAA,IAChC,GAAG,oBAAoB;AAEvB,UAAM,kBAAkB,KAAK,OAAO,CAAC,UAAU;AAC7C,YAAM,UAAU,KAAK,QAAQ,IAAI,KAAK,EAAE;AACxC,UAAI,CAAC,QAAS;AACd,cAAQ,SAAS,QAAQ,OAAO,WAAW,IACvC,OAAO,KAAK,KAAK,IACjB,OAAO,OAAO,CAAC,QAAQ,QAAQ,OAAO,KAAK,KAAK,CAAC,CAAC;AACtD,UAAI,QAAQ,OAAO,SAAS,qBAAqB;AAC/C,aAAK,cAAc,KAAK,IAAI,qBAAqB;AACjD;AAAA,MACF;AACA,WAAK,qBAAqB,KAAK,EAAE;AAAA,IACnC,CAAC;AAED,UAAM,mBAAmB,KAAK,QAAQ,CAAC,WAAW;AAChD,YAAM,IAAI,KAAK,QAAQ,IAAI,KAAK,EAAE;AAClC,UAAI,GAAG;AACL,qBAAa,EAAE,KAAK;AACpB,UAAE,gBAAA;AACF,aAAK,QAAQ,OAAO,KAAK,EAAE;AAAA,MAC7B;AACA,YAAM,IAAI,KAAK,OAAO,IAAI,KAAK,EAAE;AACjC,UAAI,GAAG;AACL,aAAK,OAAO,OAAO,KAAK,EAAE;AAC1B,mBAAW,KAAK,KAAK,mBAAoB,GAAE,KAAK,IAAI,MAAM;AAAA,MAC5D;AAAA,IACF,CAAC;AAED,SAAK,QAAQ,IAAI,KAAK,IAAI;AAAA,MACxB;AAAA,MACA,QAAQ,OAAO,MAAM,CAAC;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,qBAAqB,cAA4B;AACvD,UAAM,UAAU,KAAK,QAAQ,IAAI,YAAY;AAC7C,QAAI,CAAC,QAAS;AACd,QAAI,QAAQ,OAAO,SAAS,EAAG;AAC/B,UAAM,MAAM,QAAQ,OAAO,aAAa,CAAC;AACzC,QAAI,QAAQ,OAAO,SAAS,IAAI,IAAK;AAErC,UAAM,iBAAiB,QAAQ,OAAO,SAAS,GAAG,IAAI,GAAG;AACzD,QAAI,QAAuB;AAC3B,QAAI;AACF,YAAM,OAAgB,KAAK,MAAM,eAAe,SAAS,OAAO,CAAC;AACjE,UAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,eAAe,MAAM;AACpE,cAAM,IAAK,KAAgC;AAC3C,YAAI,OAAO,MAAM,SAAU,SAAQ;AAAA,MACrC;AAAA,IACF,QAAQ;AACN,WAAK,cAAc,cAAc,wBAAwB;AACzD;AAAA,IACF;AAGA,UAAM,UAAU,YAAY;AAC1B,UAAI,KAAK,QAAQ,cAAc;AAC7B,cAAM,KAAK,QAAQ,aAAa,OAAO,QAAQ,IAAI;AAAA,MACrD;AAAA,IACF;AAEA,YAAA,EAAU;AAAA,MACR,MAAM;AAEJ,cAAM,IAAI,KAAK,QAAQ,IAAI,YAAY;AACvC,YAAI,CAAC,EAAG;AACR,qBAAa,EAAE,KAAK;AACpB,UAAE,gBAAA;AACF,aAAK,QAAQ,OAAO,YAAY;AAEhC,aAAK,OAAO,IAAI,cAAc;AAAA,UAC5B,MAAM,EAAE;AAAA,UACR,kBAAkB,EAAE;AAAA,QAAA,CACrB;AAED,mBAAW,KAAK,KAAK,gBAAiB,GAAE,YAAY;AAAA,MACtD;AAAA,MACA,CAAC,QAAiB;AAChB,cAAM,MAAMJ,MAAAA,OAAO,GAAG;AACtB,aAAK,cAAc,cAAc,eAAe,GAAG,EAAE;AAAA,MACvD;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEQ,cAAc,cAAsB,QAAsB;AAChE,UAAM,IAAI,KAAK,QAAQ,IAAI,YAAY;AACvC,QAAI,CAAC,EAAG;AACR,iBAAa,EAAE,KAAK;AACpB,MAAE,gBAAA;AACF,MAAE,iBAAA;AACF,SAAK,QAAQ,OAAO,YAAY;AAChC,MAAE,KAAK,MAAM,MAAM;AAAA,EACrB;AAAA,EAEQ,cAAc,UAA0B,OAAyB;AACvE,UAAM,SAAS,KAAK,QAAQ,cAAc;AAE1C,QAAI,SAAS,KAAK,iBAAiB,KAAK,uBAAuB;AAC7D,UAAI,WAAW,eAAe;AAC5B,aAAK;AACL;AAAA,MACF;AACA,UAAI,WAAW,eAAe;AAG5B,aAAK;AACL;AAAA,MACF;AAAA,IAEF;AAEA,aAAS,KAAK,KAAK,KAAK;AACxB,SAAK;AACL,SAAK,aAAa,MAAM;AAAA,EAC1B;AACF;AC1PO,SAAS,kBACd,SACuB;AACvB,SAAO,IAAI,gBAA0B,OAAO;AAC9C;AAUA,MAAM,gBAA2D;AAAA,EAgB/D,YAA6B,SAAuC;AAAvC,SAAA,UAAA;AAAA,EAAwC;AAAA,EAf7D,aAAyC;AAAA,EACzC,UAAyC;AAAA,EAChC,sCAAsB,IAAA;AAAA,EACtB,yCAAyB,IAAA;AAAA,EACzB,YAAqC;AAAA,IACpD,QAAQ,CAAA;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA;AAAA,EAIA,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,gBAA+B;AAAA,EAIvC,MAAM,QAAQ,KAA4B;AACxC,QAAI,KAAK,eAAe,MAAM;AAC5B,YAAM,IAAI,MAAM,gCAAgC;AAAA,IAClD;AAEA,UAAM,OAAO,MAAM,KAAK,QAAQ,UAAU,QAAQ,KAAK,KAAK,QAAQ,OAAO;AAC3E,SAAK,aAAa;AAClB,SAAK,UAAU,KAAK,QAAQ,MAAM,cAAA;AAClC,SAAK,gBAAgB,KAAK,IAAA;AAE1B,SAAK,OAAO,CAAC,UAAU,KAAK,oBAAoB,KAAK,CAAC;AACtD,SAAK,QAAQ,CAAC,WAAW,KAAK,iBAAiB,MAAM,CAAC;AAItD,UAAM,UAAU,OAAO,KAAK,KAAK,UAAU;AAAA,MACzC,WAAW,KAAK,QAAQ,SAAS,aAAa;AAAA,IAAA,CAC/C,GAAG,OAAO;AACX,UAAM,SAAS,OAAO,YAAY,CAAC;AACnC,WAAO,cAAc,QAAQ,QAAQ,CAAC;AACtC,SAAK,KAAK,OAAO,OAAO,CAAC,QAAQ,OAAO,CAAC,CAAC;AAAA,EAC5C;AAAA,EAEA,IAAI,WAAoC;AACtC,UAAM,QAAQ,KAAK;AACnB,WAAO;AAAA,MACL,CAAC,OAAO,aAAa,IAA6B;AAChD,eAAO;AAAA,UACL,OAA0C;AACxC,gBAAI,MAAM,OAAO,SAAS,GAAG;AAC3B,oBAAM,QAAQ,MAAM,OAAO,MAAA;AAC3B,qBAAO,QAAQ,QAAQ,EAAE,OAAO,MAAM,OAAO;AAAA,YAC/C;AACA,gBAAI,MAAM,MAAM;AACd,qBAAO,QAAQ,QAAQ,EAAE,OAAO,QAAW,MAAM,MAAM;AAAA,YACzD;AACA,mBAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,oBAAM,SAAS;AAAA,YACjB,CAAC;AAAA,UACH;AAAA,UACA,SAA4C;AAC1C,kBAAM,OAAO;AACb,kBAAM,SAAS,CAAA;AACf,gBAAI,MAAM,QAAQ;AAChB,oBAAM,OAAO,EAAE,OAAO,QAAW,MAAM,MAAM;AAC7C,oBAAM,SAAS;AAAA,YACjB;AACA,mBAAO,QAAQ,QAAQ,EAAE,OAAO,QAAW,MAAM,MAAM;AAAA,UACzD;AAAA,QAAA;AAAA,MAEJ;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,UAAU,SAA+C;AACvD,SAAK,gBAAgB,IAAI,OAAO;AAChC,WAAO,MAAM,KAAK,gBAAgB,OAAO,OAAO;AAAA,EAClD;AAAA,EAEA,aAAa,SAAiD;AAC5D,SAAK,mBAAmB,IAAI,OAAO;AACnC,WAAO,MAAM,KAAK,mBAAmB,OAAO,OAAO;AAAA,EACrD;AAAA,EAEA,WAA6B;AAC3B,WAAO;AAAA,MACL,gBAAgB,KAAK;AAAA,MACrB,eAAe,KAAK;AAAA,MACpB,WAAW,KAAK,eAAe;AAAA,MAC/B,eAAe,KAAK;AAAA,IAAA;AAAA,EAExB;AAAA,EAEA,MAAM,aAA4B;AAChC,UAAM,OAAO,KAAK;AAClB,QAAI,SAAS,KAAM;AACnB,SAAK,MAAM,mBAAmB;AAAA,EAEhC;AAAA;AAAA,EAIQ,oBAAoB,OAAyB;AACnD,QAAI,KAAK,YAAY,KAAM;AAC3B,SAAK,iBAAiB,MAAM;AAC5B,QAAI;AACJ,QAAI;AACF,iBAAW,KAAK,QAAQ,KAAK,KAAK;AAAA,IACpC,SAAS,KAAK;AAGZ,YAAM,SAAS,eAAe,QAAQ,iBAAiB,IAAI,OAAO,KAAK;AACvE,WAAK,iBAAiB,MAAM;AAC5B,UAAI,KAAK,WAAY,MAAK,WAAW,MAAM,MAAM;AACjD;AAAA,IACF;AAEA,eAAW,OAAO,UAAU;AAC1B,WAAK;AAGL,iBAAW,WAAW,KAAK,gBAAiB,SAAQ,GAAG;AAGvD,UAAI,KAAK,UAAU,KAAM;AACzB,UAAI,KAAK,UAAU,QAAQ;AACzB,cAAM,IAAI,KAAK,UAAU;AACzB,aAAK,UAAU,SAAS;AACxB,UAAE,EAAE,OAAO,KAAK,MAAM,OAAO;AAAA,MAC/B,OAAO;AACL,aAAK,UAAU,OAAO,KAAK,GAAG;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAiB,QAAuB;AAC9C,QAAI,KAAK,eAAe,KAAM;AAC9B,SAAK,aAAa;AAClB,SAAK,SAAS,MAAA;AACd,SAAK,UAAU;AAEf,eAAW,WAAW,KAAK,mBAAoB,SAAQ,MAAM;AAG7D,SAAK,UAAU,OAAO;AACtB,QAAI,KAAK,UAAU,QAAQ;AACzB,YAAM,IAAI,KAAK,UAAU;AACzB,WAAK,UAAU,SAAS;AACxB,QAAE,EAAE,OAAO,QAAW,MAAM,MAAM;AAAA,IACpC;AAAA,EACF;AACF;AC1JO,SAAS,iBAAiE;AAC/E,QAAM,SAAS6C,kBAAO,YAAY,EAAE;AACpC,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,MAAM,CAAC,WAAW,kBAAkB,UAAU,QAAQ,WAAW,aAAa;AAAA,IAAA;AAAA,IAEhF,UAAU;AAAA,MACR,QAAQ,CAAC,OAAO,sBAAsB,YAAY,QAAQ,OAAO,iBAAiB;AAAA,IAAA;AAAA,EACpF;AAEJ;AAIA,MAAM,YAAY;AAElB,SAAS,UAAU,QAAgB,WAAmB,eAA+B;AACnF,MAAI,UAAU,SAAS,SAAS,GAAG;AACjC,UAAM,IAAI,MAAM,+BAA+B,SAAS,GAAG;AAAA,EAC7D;AACA,QAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa;AACxD,QAAM,MAAMA,kBAAO,WAAW,UAAU,MAAM,EAAE,OAAO,OAAO,EAAE,OAAA;AAChE,SAAO,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,CAAC;AAClD;AAEA,SAAS,YAAY,QAAgB,OAAe,mBAA6C;AAC/F,QAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,EAAE,IAAI,OAAO,QAAQ,YAAA;AAAA,EAC9B;AACA,QAAM,CAAC,WAAW,WAAW,YAAY,IAAI;AAE7C,QAAM,gBAAgB,OAAO,SAAS;AACtC,MAAI,CAAC,OAAO,SAAS,aAAa,KAAK,iBAAiB,GAAG;AACzD,WAAO,EAAE,IAAI,OAAO,QAAQ,YAAA;AAAA,EAC9B;AAEA,MAAI,cAAc,mBAAmB;AACnC,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAA;AAAA,EAC9B;AAEA,QAAM,cAAcA,kBAAO,WAAW,UAAU,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,EAAE,EAAE,OAAA;AACvG,QAAM,cAAc,cAAc,YAAY;AAE9C,MAAI,gBAAgB,QAAQ,YAAY,WAAW,YAAY,QAAQ;AACrE,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAA;AAAA,EAC9B;AACA,MAAI,CAACA,kBAAO,gBAAgB,aAAa,WAAW,GAAG;AACrD,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAA;AAAA,EAC9B;AAEA,MAAI,KAAK,IAAA,IAAQ,eAAe;AAC9B,WAAO,EAAE,IAAI,OAAO,QAAQ,UAAA;AAAA,EAC9B;AAEA,SAAO,EAAE,IAAI,MAAM,WAAW,WAAW,cAAA;AAC3C;AAEA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,SAAS,QAAQ,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACzF;AAEA,SAAS,cAAc,GAA0B;AAC/C,QAAM,aAAa,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACzD,QAAM,SAAS,aAAa,IAAI,QAAQ,IAAK,WAAW,SAAS,KAAM,CAAC;AACxE,MAAI;AACF,WAAO,OAAO,KAAK,QAAQ,QAAQ;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;;;;;;;;;;;;;;;;"}
|