@camstack/addon-pipeline 0.1.20 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/audio-analyzer/index.js +736 -719
  2. package/dist/audio-analyzer/index.mjs +726 -679
  3. package/dist/audio-codec-nodeav/index.js +304 -461
  4. package/dist/audio-codec-nodeav/index.mjs +300 -462
  5. package/dist/chunk-BdkLduGY.mjs +5 -0
  6. package/dist/chunk-D6vf50IK.js +28 -0
  7. package/dist/codec-runtime-BOk-13PN.js +202 -0
  8. package/dist/codec-runtime-BsqlEjPi.mjs +197 -0
  9. package/dist/constants-B_b0a-6h.mjs +3119 -0
  10. package/dist/{index-CMcx_k6Y.js → constants-D65v6yp6.js} +3107 -2935
  11. package/dist/decoder-nodeav/index.js +1374 -1444
  12. package/dist/decoder-nodeav/index.mjs +1369 -1425
  13. package/dist/detection-pipeline/index.js +6462 -5613
  14. package/dist/detection-pipeline/index.mjs +6451 -5574
  15. package/dist/dist-7ewQjTle.js +22454 -0
  16. package/dist/dist-C5jnNl0n.mjs +22089 -0
  17. package/dist/motion-wasm/index.js +469 -467
  18. package/dist/motion-wasm/index.mjs +464 -446
  19. package/dist/pipeline-runner/index.js +2029 -1827
  20. package/dist/pipeline-runner/index.mjs +2025 -1811
  21. package/dist/recorder/index.js +2045 -2157
  22. package/dist/recorder/index.mjs +2042 -2156
  23. package/dist/stream-broker/_stub.js +1806 -1352
  24. package/dist/stream-broker/_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D4-DHanK.mjs +156 -0
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Tf-HACFd.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C9WX5HNw.mjs +26 -0
  27. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs +26 -0
  28. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs +26 -0
  29. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs +26 -0
  30. package/dist/stream-broker/dist-CYZr2fwk.mjs +2726 -0
  31. package/dist/stream-broker/hostInit-Di6vceAU.mjs +129 -0
  32. package/dist/stream-broker/index.js +17778 -15470
  33. package/dist/stream-broker/index.mjs +17769 -15465
  34. package/dist/stream-broker/remoteEntry.js +134 -2973
  35. package/dist/stream-broker/remoteEntry.ssr.js +33 -0
  36. package/dist/stream-broker/virtualExposes-dYNvIwoR.mjs +27 -0
  37. package/dist/stream-broker/virtual_mf-exposes-ssr___mfe_internal__addon_stream_broker_widgets__remoteEntry_js-Cmqfp4i_.mjs +10 -0
  38. package/embed-dist/assets/index-B8VlSD0-.js +150 -0
  39. package/embed-dist/assets/index-ZhDdp1Nd.css +2 -0
  40. package/embed-dist/index.html +13 -0
  41. package/package.json +25 -7
  42. package/wasm/assembly/index.ts +41 -16
  43. package/dist/audio-analyzer/index.js.map +0 -1
  44. package/dist/audio-analyzer/index.mjs.map +0 -1
  45. package/dist/audio-codec-nodeav/index.js.map +0 -1
  46. package/dist/audio-codec-nodeav/index.mjs.map +0 -1
  47. package/dist/decoder-nodeav/index.js.map +0 -1
  48. package/dist/decoder-nodeav/index.mjs.map +0 -1
  49. package/dist/detection-pipeline/index.js.map +0 -1
  50. package/dist/detection-pipeline/index.mjs.map +0 -1
  51. package/dist/index-5aYef068.mjs +0 -17514
  52. package/dist/index-5aYef068.mjs.map +0 -1
  53. package/dist/index-B36NMAdu.js +0 -17513
  54. package/dist/index-B36NMAdu.js.map +0 -1
  55. package/dist/index-CMcx_k6Y.js.map +0 -1
  56. package/dist/index-CYb7cFrv.mjs +0 -5790
  57. package/dist/index-CYb7cFrv.mjs.map +0 -1
  58. package/dist/motion-wasm/index.js.map +0 -1
  59. package/dist/motion-wasm/index.mjs.map +0 -1
  60. package/dist/pipeline-runner/index.js.map +0 -1
  61. package/dist/pipeline-runner/index.mjs.map +0 -1
  62. package/dist/recorder/index.js.map +0 -1
  63. package/dist/recorder/index.mjs.map +0 -1
  64. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/FfmpegParamsField.d.ts +0 -41
  65. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/GeometryBuilder.d.ts +0 -54
  66. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/StreamBrokerPanel.d.ts +0 -21
  67. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/format-ua.d.ts +0 -13
  68. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/index.d.ts +0 -15
  69. package/dist/stream-broker/@mf-types/widgets.d.ts +0 -2
  70. package/dist/stream-broker/@mf-types.d.ts +0 -3
  71. package/dist/stream-broker/@mf-types.zip +0 -0
  72. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs +0 -12
  73. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-DJ3UNg7O.mjs +0 -30
  74. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-CYXy_bhS.mjs +0 -21
  75. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.mjs-U1EUeEPs.mjs +0 -104
  76. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_trpc_mf_1_client__loadShare__.mjs-DeouEaSs.mjs +0 -85
  77. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.mjs-DHUwjbb9.mjs +0 -62
  78. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-CaDEYBIU.mjs +0 -89
  79. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-D6EROtlA.mjs +0 -29
  80. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-x6pP3Ghk.mjs +0 -36
  81. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs-DYEKzzY-.mjs +0 -45
  82. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CcnN6sbA.mjs +0 -6
  83. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom_mf_1_client__loadShare__.mjs-DICOtMTl.mjs +0 -34
  84. package/dist/stream-broker/_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CL9DR49k.mjs +0 -156
  85. package/dist/stream-broker/client-BvTmMOQu.mjs +0 -9836
  86. package/dist/stream-broker/getErrorShape-BPSzUA7W-TlK8ipWe.mjs +0 -211
  87. package/dist/stream-broker/hostInit-ChmiMPS0.mjs +0 -168
  88. package/dist/stream-broker/index-BxsFuFmE.mjs +0 -2603
  89. package/dist/stream-broker/index-C-248uOU.mjs +0 -725
  90. package/dist/stream-broker/index-C05B6jqp.mjs +0 -185
  91. package/dist/stream-broker/index-CWkKuNLr.mjs +0 -232
  92. package/dist/stream-broker/index-DOJoSShD.mjs +0 -67784
  93. package/dist/stream-broker/index-DtOI1aTU.mjs +0 -18504
  94. package/dist/stream-broker/index-oMq6ilgR.mjs +0 -1641
  95. package/dist/stream-broker/index-vIWZQBIL.mjs +0 -435
  96. package/dist/stream-broker/index-xncRG7-x.mjs +0 -2713
  97. package/dist/stream-broker/index.js.map +0 -1
  98. package/dist/stream-broker/index.mjs.map +0 -1
  99. package/dist/stream-broker/jsx-runtime-BRT_HL0A.mjs +0 -55
  100. package/dist/stream-broker/schemas-B7L0qZtq.mjs +0 -3599
  101. package/dist/stream-broker/virtualExposes-pCd777Rp.mjs +0 -42
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/stream-broker/stream-broker/decoder-session-proxy.ts","../../src/stream-broker/stream-broker/frame-handle-plane.ts","../../src/stream-broker/stream-broker/audio-chunk-plane.ts","../../src/stream-broker/rtsp/audio/aac-config.ts","../../src/stream-broker/stream-broker/audio-codec-session.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/egress-audio-sdp.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/ffmpeg-args-common.ts","../../src/stream-broker/stream-broker/ffmpeg-invocation.ts","../../src/stream-broker/rtsp/udp-audio-rtp-receiver.ts","../../src/stream-broker/stream-broker/transcode-egress.ts","../../src/stream-broker/stream-broker/transcode-pipeline.ts","../../src/stream-broker/stream-broker/profile-restreamers.ts","../../src/stream-broker/stream-broker/stream-broker-manager.ts","../../src/stream-broker/encode-profile-resolver.ts","../../src/stream-broker/webrtc/webrtc-fallback-profile.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/h264-repacketizer.ts","../../src/stream-broker/webrtc/session.ts","../../src/stream-broker/webrtc/loss-tracker.ts","../../src/stream-broker/webrtc/adaptive-controller.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/frame-dropper.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, FrameHandle } 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 /**\n * Drain the zero-pixel `FrameHandle[]` of a `frameSink: 'shm'` session\n * (Phase 5 / D9). Returns `[]` for a `'callback'` session — a session is\n * one mode or the other.\n */\n pullHandles(input: { readonly sessionId: string; readonly maxCount: number }): Promise<readonly FrameHandle[]>\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 /**\n * Poll a `frameSink: 'shm'` session for `FrameHandle`s (Phase 5 / D9).\n * Mirrors `startPolling` but drains `pullHandles` — the decoder has\n * already written the pixels into a shared-memory ring, so what crosses\n * the cap boundary is the tiny serialisable handle. Runs until\n * `stopPolling`, `destroy`, or the session is destroyed externally.\n *\n * When the decoder reports the session is gone (`unknown sessionId` /\n * `Service … not found`) the loop exits gracefully — those errors are\n * expected during decoder restart/shutdown and must not be re-thrown to\n * the caller's `.catch()` handler, which would otherwise spin forever.\n */\n async startHandlePolling(onHandle: (handle: FrameHandle) => void): Promise<void> {\n this.polling = true\n while (this.polling) {\n try {\n const handles = await this.api.pullHandles({ sessionId: this.sessionId, maxCount: 4 })\n for (const handle of handles) onHandle(handle)\n if (handles.length === 0) await new Promise((r) => setTimeout(r, 1))\n } catch (err) {\n // Session destroyed externally — stop polling gracefully so the\n // caller's .catch() doesn't spin forever.\n this.polling = false\n const msg = err instanceof Error ? err.message : String(err)\n // Re-throw only for unexpected errors; session-gone errors are\n // normal during decoder shutdown or restart.\n if (!msg.includes('unknown sessionId') && !msg.includes('Service') && !msg.includes('not found')) {\n throw err\n }\n return\n }\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 * `FrameHandlePlane` — the broker's shared-memory frame-handle surface\n * (Phase 5 / D9).\n *\n * This is the handle-based replacement for `StreamBroker.onDecodedFrame`'s\n * live-object callback path. Instead of fanning pixel `DecodedFrame`s out to\n * in-process callbacks (which cannot cross a process boundary), the broker\n * publishes zero-pixel, serialisable `FrameHandle`s a consumer drains over\n * tRPC and reads back from a shared-memory ring with a `FrameRingReader`.\n *\n * ## Ring-per-format model\n *\n * A `frameSink: 'shm'` decoder session produces exactly ONE pixel format\n * (the decoder's scaler is configured for it). The broker therefore keeps\n * one decoder session — hence one shm ring — per `(brokerId, format)` that a\n * consumer actually subscribes to:\n *\n * - motion subscribes `gray` → one `gray` decoder session + ring\n * - detection subscribes `rgb` → one `rgb` decoder session + ring\n *\n * Only the formats consumers ask for are materialised. There is NO\n * broker-side `sharp` conversion any more — each consumer reads the exact\n * format it requested. A session is reference-counted by its subscriber set\n * and torn down when the last subscriber of that format leaves.\n *\n * ## fps throttling\n *\n * Throttling is implicit: the ring is latest-wins, so a slow consumer that\n * polls `pullFrameHandles` infrequently simply receives the newest handles\n * and the intervening frames are dropped at the ring (a recycled slot the\n * consumer never read). The optional `maxFps` from `subscribeFrames` is NOT\n * enforced here — it is echoed back to the consumer as a reader-side polling\n * cadence hint.\n *\n * ## Packet feed\n *\n * The plane's decoder sessions are push-mode by default: the broker forwards\n * every video `EncodedPacket` to `pushPacket`. For a pull-mode decoder the\n * broker supplies a `pullModeUrl` and the plane calls `openStream` once the\n * session exists.\n */\nimport { randomUUID } from 'node:crypto'\n\nimport { RingBuffer, errMsg } from '@camstack/types'\nimport type {\n EncodedPacket,\n FrameHandle,\n FrameHandleFormat,\n IScopedLogger,\n} from '@camstack/types'\n\nimport type { DecoderCapApi } from './decoder-session-proxy.js'\nimport { DecoderSessionProxy } from './decoder-session-proxy.js'\n\n/** Per-subscription handle-queue capacity. A handle is tiny; 64 absorbs a\n * burst between two `pullFrameHandles` polls without unbounded growth. */\nconst HANDLE_QUEUE_CAPACITY = 64\n\n/** Default reader-side cadence hint when a subscriber omits `maxFps`. */\nconst DEFAULT_HINT_FPS = 5\n\n/** Rolling window for the aggregate `decodeFps` rate, ms. Mirrors the\n * broker's 2s `inputFps` window so the two stats are comparable. */\nconst DECODE_FPS_WINDOW_MS = 2000\n\n/** A consumer's frame-handle subscription. */\ninterface HandleSubscription {\n readonly id: string\n readonly format: FrameHandleFormat\n /** Reader-side cadence hint, frames/s — echoed to the consumer, not enforced. */\n readonly maxFps: number\n /** Short caller-identity tag for diagnostics. */\n readonly tag: string\n /** Ms epoch the subscription was opened. */\n readonly subscribedAt: number\n /** Latest-wins handle queue drained by `pullFrameHandles`. */\n readonly queue: RingBuffer<FrameHandle>\n framesDelivered: number\n}\n\n/** A diagnostic row for one frame-handle subscription (mirrors `BrokerDecodedClient`). */\nexport interface FrameHandleSubscriberInfo {\n readonly tag: string\n readonly subscribedAt: number\n readonly maxFps: number\n readonly framesDelivered: number\n}\n\n/**\n * One decoder session producing a single pixel format, shared by every\n * subscription of that format. Reference-counted by `subscriberIds`.\n */\ninterface FormatSession {\n readonly format: FrameHandleFormat\n /** The decoder session, or `null` before it has been created. */\n proxy: DecoderSessionProxy | null\n /** Moleculer nodeID of the decoder provider hosting `proxy`. */\n nodeId: string | null\n /** Subscription ids currently reading this format. */\n readonly subscriberIds: Set<string>\n}\n\n/** What the broker tells the plane about the stream it is decoding. */\nexport interface FrameHandlePlaneStreamInfo {\n /** Video codec — `h264` / `h265` / `hevc`. */\n readonly codec: string\n /** Numeric device id for decoder log scoping, when known. */\n readonly numericDeviceId?: number\n /** Decoder session log tag (`broker:<deviceId>/<profile>`). */\n readonly tag: string\n /**\n * Local pipe URL for a pull-mode decoder. `null` for push-mode decoders\n * (the broker feeds packets through `pushPacket`).\n */\n readonly pullModeUrl: string | null\n}\n\n/** Dependencies a `FrameHandlePlane` needs from its owning broker. */\nexport interface FrameHandlePlaneOptions {\n readonly decoderApi: DecoderCapApi\n readonly logger: IScopedLogger | undefined\n /**\n * Resolve the current stream descriptor at session-creation time. Called\n * lazily so the plane always decodes with the freshest detected codec.\n * Returns `null` when the broker has no stream to decode yet.\n */\n readonly resolveStreamInfo: () => FrameHandlePlaneStreamInfo | null\n}\n\n/**\n * Owns the broker's shared-memory frame-handle subscriptions and the\n * per-format `frameSink: 'shm'` decoder sessions that feed them.\n */\nexport class FrameHandlePlane {\n private readonly decoderApi: DecoderCapApi\n private readonly logger: IScopedLogger | undefined\n private readonly resolveStreamInfo: () => FrameHandlePlaneStreamInfo | null\n\n private readonly subscriptions = new Map<string, HandleSubscription>()\n private readonly sessions = new Map<FrameHandleFormat, FormatSession>()\n private disposed = false\n\n /** Re-entrancy guard for `armPendingSessions` — collapses the per-packet\n * `pushPacket` calls into a single in-flight deferred-arm pass. */\n private armInFlight = false\n\n /** Aggregate decoded-frame rate state — frames fanned out in the current\n * rolling window, the window's start epoch, and the last computed fps. */\n private framesInWindow = 0\n private windowStartMs = Date.now()\n private decodedFps = 0\n\n constructor(options: FrameHandlePlaneOptions) {\n this.decoderApi = options.decoderApi\n this.logger = options.logger\n this.resolveStreamInfo = options.resolveStreamInfo\n }\n\n /** Number of active handle subscriptions — surfaced for diagnostics. */\n get subscriberCount(): number {\n return this.subscriptions.size\n }\n\n /**\n * Moleculer nodeIDs of the decoder providers currently hosting this plane's\n * `frameSink: 'shm'` sessions. Used by the broker-manager's per-agent\n * hwaccel-change handler to decide which brokers need a decoder rotation.\n */\n decoderNodeIds(): readonly string[] {\n const ids: string[] = []\n for (const session of this.sessions.values()) {\n if (session.nodeId) ids.push(session.nodeId)\n }\n return ids\n }\n\n /**\n * Rotate every shm decoder session hosted on `agentNodeId` — destroy and\n * rebuild it so the new session re-pulls the latest per-agent decoder\n * preferences (hwaccel backend, …). Subscriptions are preserved: the same\n * `subscriptionId`s read from the rebuilt session's fresh ring. A no-op for\n * sessions on other nodes. Returns the number of sessions rotated.\n */\n async rotate(agentNodeId: string, reason: string): Promise<number> {\n if (this.disposed) return 0\n let rotated = 0\n for (const session of this.sessions.values()) {\n const sessionAgent = session.nodeId && session.nodeId.includes('/')\n ? session.nodeId.split('/')[0]\n : session.nodeId\n if (sessionAgent !== agentNodeId) continue\n this.logger?.info('frame-handle plane: rotating shm decoder session', {\n meta: { format: session.format, reason, nodeId: session.nodeId },\n })\n await this.destroySession(session)\n await this.startSessionDecoder(session)\n rotated += 1\n }\n return rotated\n }\n\n /**\n * Register a frame-handle subscription. Spins up (or reuses) a\n * `frameSink: 'shm'` decoder session producing `format`. The returned\n * `subscriptionId` is the handle the consumer passes to\n * `pullFrameHandles` / `unsubscribe`.\n */\n async subscribe(input: {\n readonly format: FrameHandleFormat\n readonly maxFps?: number\n readonly tag?: string\n }): Promise<{ subscriptionId: string; maxFps: number }> {\n if (this.disposed) {\n throw new Error('FrameHandlePlane: subscribe after dispose')\n }\n const subscriptionId = `fh-${randomUUID()}`\n const maxFps = input.maxFps !== undefined && input.maxFps > 0\n ? input.maxFps\n : DEFAULT_HINT_FPS\n const subscription: HandleSubscription = {\n id: subscriptionId,\n format: input.format,\n maxFps,\n tag: input.tag ?? 'unknown',\n subscribedAt: Date.now(),\n queue: new RingBuffer<FrameHandle>(HANDLE_QUEUE_CAPACITY),\n framesDelivered: 0,\n }\n this.subscriptions.set(subscriptionId, subscription)\n\n await this.ensureSession(input.format, subscriptionId)\n\n this.logger?.info('frame-handle subscription added', {\n meta: { subscriptionId, format: input.format, tag: subscription.tag, maxFps },\n })\n return { subscriptionId, maxFps }\n }\n\n /**\n * Drain up to `maxCount` `FrameHandle`s for a subscription, latest-wins.\n * An unknown subscription id returns `[]` (the consumer may have been\n * torn down concurrently).\n */\n pullHandles(subscriptionId: string, maxCount: number): readonly FrameHandle[] {\n const subscription = this.subscriptions.get(subscriptionId)\n if (!subscription) return []\n // Latest-wins (for real): frames are fanned in at the FULL decode rate but\n // a consumer only pulls `maxCount` per poll. The ring drops oldest on\n // overflow, but `drain` reads oldest-first — so a consumer slower than the\n // decode rate would perpetually read the BACK of a full 64-deep ring, i.e.\n // frames ~ringDepth/fps old (~2.4s at 25fps), even though downstream queues\n // are latest-only. Discard the stale backlog beyond `maxCount` so the\n // consumer always receives the FRESHEST handles; the skipped slots are\n // simply not read (the decoder recycles them).\n const stale = subscription.queue.size - maxCount\n if (stale > 0) subscription.queue.drain(stale)\n const handles = subscription.queue.drain(maxCount)\n subscription.framesDelivered += handles.length\n return handles\n }\n\n /**\n * Release a subscription. When it was the last reader of its format the\n * underlying decoder session is destroyed (and its shm segment unlinked\n * by the decoder). Returns `true` when a known subscription was released.\n */\n async unsubscribe(subscriptionId: string): Promise<boolean> {\n const subscription = this.subscriptions.get(subscriptionId)\n if (!subscription) return false\n this.subscriptions.delete(subscriptionId)\n\n const session = this.sessions.get(subscription.format)\n if (session) {\n session.subscriberIds.delete(subscriptionId)\n if (session.subscriberIds.size === 0) {\n this.sessions.delete(subscription.format)\n await this.destroySession(session)\n }\n }\n this.logger?.info('frame-handle subscription removed', {\n meta: { subscriptionId, format: subscription.format },\n })\n return true\n }\n\n /**\n * Forward a video `EncodedPacket` to every active shm decoder session.\n * Push-mode decoders consume this; a pull-mode decoder ignores it (it\n * reads its own pipe via `openStream`). Audio packets are not forwarded.\n *\n * The packet feed doubles as the plane's stream-ready signal: the broker\n * only calls `pushPacket` once it has a source and the first keyframe has\n * landed — exactly the point `resolveStreamInfo` starts returning a\n * descriptor. Each packet therefore triggers `armPendingSessions`, which\n * re-attempts session creation for any deferred subscription so a lone\n * first subscriber on a not-yet-started broker self-arms without a\n * redundant re-`subscribe`.\n */\n pushPacket(packet: EncodedPacket): void {\n if (this.disposed || packet.type !== 'video') return\n void this.armPendingSessions(packet).catch((err: unknown) => {\n this.logger?.warn('frame-handle plane: arm pending sessions error', {\n meta: { error: errMsg(err) },\n })\n })\n this.feedSessions(packet)\n }\n\n /** Forward a video `EncodedPacket` to every live shm decoder session. */\n private feedSessions(packet: EncodedPacket): void {\n for (const session of this.sessions.values()) {\n if (!session.proxy) continue\n session.proxy.pushPacket(packet).catch((err: unknown) => {\n const msg = errMsg(err)\n this.logger?.warn('frame-handle plane: decoder push error', {\n meta: { format: session.format, error: msg },\n })\n // Self-heal: when the decoder reports the session is gone (crash,\n // restart, or shutdown), invalidate the stale proxy so we don't\n // flood with thousands of warnings per second. `armPendingSessions`\n // on the next packet will recreate a fresh session.\n if (msg.includes('unknown sessionId') || msg.includes('Service') || msg.includes('not found')) {\n this.logger?.info('frame-handle plane: invalidating stale proxy', {\n meta: { format: session.format },\n })\n // Best-effort cleanup: the decoder is already gone, so\n // destroySession may also fail — that's fine.\n void this.destroySession(session).catch(() => {})\n this.sessions.delete(session.format)\n }\n })\n }\n }\n\n /**\n * Re-attempt session creation for every subscription whose format has no\n * live `FormatSession` yet — the subscriptions whose `startSessionDecoder`\n * was deferred because the broker had no stream at `subscribe` time. Driven\n * off the `pushPacket` feed (event-driven, no polling timer); a no-op once\n * every subscribed format already owns a session.\n *\n * `armInFlight` collapses re-entrant calls: `pushPacket` fires per packet,\n * but `ensureSession` is async — without the guard a burst of packets would\n * launch overlapping decoder-creation round-trips for the same format.\n *\n * `triggerPacket` is the packet whose arrival armed the pass. Because\n * `ensureSession` is async, the synchronous `feedSessions` in `pushPacket`\n * runs before the session lands in `this.sessions` — so this method feeds\n * the triggering packet to each session it just created, otherwise that\n * first (keyframe-carrying) packet would be lost and a push-mode H264/H265\n * decoder would stall waiting for SPS/PPS.\n */\n private async armPendingSessions(triggerPacket: EncodedPacket): Promise<void> {\n if (this.disposed || this.armInFlight) return\n // Collect the formats that still lack a session — skip the formats that\n // already have a live one so we never re-run `ensureSession` for them.\n const pending = new Map<FrameHandleFormat, string>()\n for (const subscription of this.subscriptions.values()) {\n if (this.sessions.has(subscription.format)) continue\n if (!pending.has(subscription.format)) {\n pending.set(subscription.format, subscription.id)\n }\n }\n if (pending.size === 0) return\n\n this.armInFlight = true\n try {\n for (const [format, subscriptionId] of pending) {\n if (this.disposed) return\n await this.ensureSession(format, subscriptionId)\n const armed = this.sessions.get(format)\n // Seed the freshly-armed session with the triggering packet so its\n // decoder receives the keyframe that drove the arm.\n if (armed?.proxy) {\n armed.proxy.pushPacket(triggerPacket).catch((err: unknown) => {\n this.logger?.warn('frame-handle plane: decoder push error', {\n meta: { format, error: errMsg(err) },\n })\n })\n }\n }\n } finally {\n this.armInFlight = false\n }\n }\n\n /**\n * Aggregate decoded-frame rate across every `frameSink: 'shm'` session in\n * this plane, frames/s. Each `fanoutHandle` call is one decoded frame\n * delivered into the plane; the rate is recomputed over a rolling\n * `DECODE_FPS_WINDOW_MS` window. Returns the last computed value between\n * window rolls, and `0` while no session is producing.\n */\n decodeFps(): number {\n this.rollDecodeFpsWindow()\n return this.decodedFps\n }\n\n /**\n * Roll the decoded-fps window when it has elapsed: convert frames seen in\n * the window into a per-second rate, then reset the counter. Called both\n * on every fanout and on every `decodeFps()` read so a stalled session\n * (no fanout) decays its rate to `0` rather than reporting a stale value.\n */\n private rollDecodeFpsWindow(): void {\n const now = Date.now()\n const windowMs = now - this.windowStartMs\n if (windowMs < DECODE_FPS_WINDOW_MS) return\n this.decodedFps = (this.framesInWindow / windowMs) * 1000\n this.framesInWindow = 0\n this.windowStartMs = now\n }\n\n /** Diagnostic snapshot of every active frame-handle subscription. */\n listSubscribers(): readonly FrameHandleSubscriberInfo[] {\n return [...this.subscriptions.values()].map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n maxFps: s.maxFps,\n framesDelivered: s.framesDelivered,\n }))\n }\n\n /**\n * Force-release every subscription whose tag matches `tag`. Returns the\n * number released. Decoder sessions wind down when their last subscriber\n * leaves, same as a normal `unsubscribe`.\n */\n async killByTag(tag: string): Promise<number> {\n const victims = [...this.subscriptions.values()]\n .filter((s) => s.tag === tag)\n .map((s) => s.id)\n for (const id of victims) {\n await this.unsubscribe(id)\n }\n return victims.length\n }\n\n /** Tear down every subscription + decoder session. Idempotent. */\n async dispose(): Promise<void> {\n if (this.disposed) return\n this.disposed = true\n this.subscriptions.clear()\n const sessions = [...this.sessions.values()]\n this.sessions.clear()\n await Promise.all(sessions.map((s) => this.destroySession(s)))\n }\n\n // ── Internal ───────────────────────────────────────────────────────\n\n /**\n * Ensure a `frameSink: 'shm'` decoder session for `format` exists and add\n * `subscriptionId` to its reader set. Reuses the session when one already\n * runs for that format.\n *\n * When a session is created fresh, its reader set is seeded with EVERY\n * subscription currently reading `format`, not just `subscriptionId` — a\n * deferred-arm pass (`armPendingSessions`) may create the session long\n * after several subscriptions of that format have registered, and the\n * session is reference-counted by `subscriberIds`. Seeding only the one id\n * would let an `unsubscribe` of that id tear the session down while its\n * sibling subscriptions still read it.\n */\n private async ensureSession(\n format: FrameHandleFormat,\n subscriptionId: string,\n ): Promise<void> {\n const existing = this.sessions.get(format)\n if (existing) {\n existing.subscriberIds.add(subscriptionId)\n return\n }\n\n const subscriberIds = new Set<string>([subscriptionId])\n for (const subscription of this.subscriptions.values()) {\n if (subscription.format === format) subscriberIds.add(subscription.id)\n }\n const session: FormatSession = {\n format,\n proxy: null,\n nodeId: null,\n subscriberIds,\n }\n const started = await this.startSessionDecoder(session)\n if (started) {\n this.sessions.set(format, session)\n }\n }\n\n /**\n * Create the `frameSink: 'shm'` decoder for a `FormatSession` and start\n * draining its handle queue. Mutates `session.proxy` / `session.nodeId` in\n * place so a `rotate()` can rebuild a registered session without disturbing\n * its `subscriberIds`. Returns `false` when no stream / unsupported codec\n * means no decoder could be created.\n */\n private async startSessionDecoder(session: FormatSession): Promise<boolean> {\n const { format } = session\n const info = this.resolveStreamInfo()\n if (!info) {\n // No stream yet — the subscription is registered but receives no handles\n // until a session exists. The plane self-arms: `armPendingSessions`\n // (driven off the `pushPacket` feed) re-attempts this once the broker\n // starts pushing packets, so a lone pending subscriber is not stranded.\n this.logger?.info('frame-handle plane: no stream — session deferred', {\n meta: { format },\n })\n return false\n }\n\n const supported = await this.decoderApi.supportsCodec({ codec: info.codec })\n if (!supported) {\n this.logger?.warn('frame-handle plane: codec unsupported — session skipped', {\n meta: { format, codec: info.codec },\n })\n return false\n }\n\n const { sessionId, nodeId } = await this.decoderApi.createSession({\n codec: info.codec,\n maxFps: 0,\n outputFormat: format,\n scale: 1,\n frameSink: 'shm',\n ...(info.numericDeviceId !== undefined ? { deviceId: info.numericDeviceId } : {}),\n tag: `${info.tag}:shm:${format}`,\n })\n const proxy = new DecoderSessionProxy(this.decoderApi, sessionId)\n session.proxy = proxy\n session.nodeId = nodeId\n\n // Drain the decoder's shm handle queue and fan each handle into every\n // subscription reading this format.\n proxy.startHandlePolling((handle) => {\n this.fanoutHandle(format, handle)\n }).catch((err: unknown) => {\n this.logger?.warn('frame-handle plane: handle polling error', {\n meta: { format, error: errMsg(err) },\n })\n })\n\n if (info.pullModeUrl) {\n proxy.openStream(info.pullModeUrl).catch((err: unknown) => {\n this.logger?.error('frame-handle plane: pull-mode openStream failed', {\n meta: { format, error: errMsg(err) },\n })\n })\n }\n\n this.logger?.info('frame-handle plane: shm decoder session created', {\n meta: { format, codec: info.codec, sessionId, nodeId },\n })\n return true\n }\n\n /** Push one decoded `FrameHandle` to every subscription of `format`. */\n private fanoutHandle(format: FrameHandleFormat, handle: FrameHandle): void {\n // One fanout call == one decoded frame delivered into the plane.\n this.rollDecodeFpsWindow()\n this.framesInWindow += 1\n for (const subscription of this.subscriptions.values()) {\n if (subscription.format !== format) continue\n // Latest-wins: a full queue overwrites its oldest handle, so a slow\n // consumer always pulls the newest frames and silently drops the rest.\n subscription.queue.push(handle)\n }\n }\n\n /** Destroy a decoder session, swallowing teardown errors. */\n private async destroySession(session: FormatSession): Promise<void> {\n const proxy = session.proxy\n session.proxy = null\n session.nodeId = null\n if (!proxy) return\n try {\n await proxy.destroy()\n } catch (err) {\n this.logger?.warn('frame-handle plane: session destroy failed', {\n meta: { format: session.format, error: errMsg(err) },\n })\n }\n }\n}\n","/**\n * `AudioChunkPlane` — the broker's serialisable decoded audio-chunk surface\n * (Phase 5 / D9).\n *\n * This is the poll-based replacement for `StreamBroker.onDecodedAudioChunk`'s\n * live-object callback path. A live callback cannot cross a process boundary;\n * once the `pipeline` group is dissolved (Task 8) the audio consumer\n * (`pipeline-orchestrator`) runs in a different process from the broker, so\n * audio delivery must go over tRPC like the video frame plane.\n *\n * ## Why no shared memory\n *\n * Unlike video frames (~6 MB at 1080p RGB), a decoded audio chunk is tiny — a\n * ~500ms `AudioCodecSession` window is a few KB of Float32 PCM. Tiny payloads\n * serialise over tRPC cheaply, so audio ships its bytes INLINE on the RPC\n * wire; there is no shm ring.\n *\n * ## FIFO / no-drop queue policy\n *\n * Audio wants FIFO arrival order with no latest-wins drop: a missing chunk is\n * an audible gap and breaks the audio analyzer's fixed-length analysis\n * window. Each subscription therefore owns a generously-sized FIFO queue\n * (`AUDIO_QUEUE_CAPACITY`). Chunks arrive roughly every\n * `AudioCodecSession`-window (~500ms), so the queue buffers tens of seconds\n * of audio — a healthy poll loop never fills it. The queue only drops its\n * OLDEST chunk if a truly stalled consumer lets it overflow (a bounded safety\n * valve, not the steady-state behaviour).\n */\nimport { randomUUID } from 'node:crypto'\n\nimport { RingBuffer } from '@camstack/types'\nimport type { DecodedAudioChunk, IScopedLogger } from '@camstack/types'\n\n/**\n * Per-subscription FIFO capacity. A chunk is a few KB and arrives ~every\n * 500ms, so 64 buffers ~30s of audio between two `pullAudioChunks` polls\n * without unbounded growth.\n */\nconst AUDIO_QUEUE_CAPACITY = 64\n\n/** A consumer's decoded audio-chunk subscription. */\ninterface AudioChunkSubscription {\n readonly id: string\n /** Short caller-identity tag for diagnostics (`audio-analyzer`, …). */\n readonly tag: string\n /** Ms epoch the subscription was opened. */\n readonly subscribedAt: number\n /** Bounded FIFO chunk queue drained by `pullAudioChunks`. */\n readonly queue: RingBuffer<DecodedAudioChunk>\n chunksDelivered: number\n}\n\n/** A diagnostic row for one audio-chunk subscription (mirrors `BrokerAudioClient`). */\nexport interface AudioChunkSubscriberInfo {\n readonly tag: string\n readonly subscribedAt: number\n readonly chunksDelivered: number\n}\n\n/**\n * Owns the broker's decoded audio-chunk subscriptions. The broker feeds every\n * decoded `DecodedAudioChunk` into `fanout`; each subscription's poll loop\n * drains its own FIFO queue via `pull`.\n */\nexport class AudioChunkPlane {\n private readonly logger: IScopedLogger | undefined\n private readonly subscriptions = new Map<string, AudioChunkSubscription>()\n\n constructor(logger?: IScopedLogger) {\n this.logger = logger\n }\n\n /** Number of active audio-chunk subscriptions — surfaced as audio demand. */\n get subscriberCount(): number {\n return this.subscriptions.size\n }\n\n /**\n * Register an audio-chunk subscription. The returned `subscriptionId` is\n * the handle the consumer passes to `pull` / `unsubscribe`.\n */\n subscribe(input?: { readonly tag?: string }): { subscriptionId: string } {\n const subscriptionId = `ac-${randomUUID()}`\n const subscription: AudioChunkSubscription = {\n id: subscriptionId,\n tag: input?.tag ?? 'unknown',\n subscribedAt: Date.now(),\n queue: new RingBuffer<DecodedAudioChunk>(AUDIO_QUEUE_CAPACITY),\n chunksDelivered: 0,\n }\n this.subscriptions.set(subscriptionId, subscription)\n this.logger?.info('audio-chunk subscription added', {\n meta: { subscriptionId, tag: subscription.tag },\n })\n return { subscriptionId }\n }\n\n /**\n * Drain up to `maxCount` `DecodedAudioChunk`s for a subscription, in FIFO\n * arrival order. An unknown subscription id returns `[]` (the consumer may\n * have been torn down concurrently).\n */\n pull(subscriptionId: string, maxCount: number): readonly DecodedAudioChunk[] {\n const subscription = this.subscriptions.get(subscriptionId)\n if (!subscription) return []\n const chunks = subscription.queue.drain(maxCount)\n subscription.chunksDelivered += chunks.length\n return chunks\n }\n\n /** Release a subscription. Returns `true` when a known subscription was released. */\n unsubscribe(subscriptionId: string): boolean {\n const existed = this.subscriptions.delete(subscriptionId)\n if (existed) {\n this.logger?.info('audio-chunk subscription removed', {\n meta: { subscriptionId },\n })\n }\n return existed\n }\n\n /**\n * Fan one decoded `DecodedAudioChunk` into every active subscription's FIFO\n * queue. A full queue drops its oldest chunk (bounded safety valve for a\n * stalled consumer). No-op when there are no subscriptions.\n */\n fanout(chunk: DecodedAudioChunk): void {\n for (const subscription of this.subscriptions.values()) {\n subscription.queue.push(chunk)\n }\n }\n\n /** Diagnostic snapshot of every active audio-chunk subscription. */\n listSubscribers(): readonly AudioChunkSubscriberInfo[] {\n return [...this.subscriptions.values()].map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n chunksDelivered: s.chunksDelivered,\n }))\n }\n\n /**\n * Force-release every subscription whose tag matches `tag`. Returns the\n * number released.\n */\n killByTag(tag: string): number {\n const victims = [...this.subscriptions.values()]\n .filter((s) => s.tag === tag)\n .map((s) => s.id)\n for (const id of victims) {\n this.subscriptions.delete(id)\n }\n return victims.length\n }\n\n /** Tear down every subscription. Idempotent. */\n dispose(): void {\n this.subscriptions.clear()\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// Audio chunks are emitted at the codec's native frame cadence (~21\n// to 128 ms depending on codec) — no accumulator window here. This\n// path used to coalesce into 500 ms chunks to match YAMNet/Apple\n// SoundAnalysis input windows; the side-effect was ~500 ms added\n// latency on every WebRTC playback (cam 15 / Reolink AAC chain).\n// Realtime consumers (WebRTC PCMU egress) now see audio with sub-\n// 100 ms decode latency. ML analyzers (YAMNet 480 ms hop / Apple SA\n// 960 ms window) accumulate at their own pipeline side; the\n// AudioChunkPlane is pull-based so they can buffer however many\n// small chunks they need before invoking `classify()`.\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 // No accumulator: each decoded PCM frame is emitted as one\n // DecodedAudioChunk immediately for minimum WebRTC latency. ML\n // consumers downstream apply their own window aggregation.\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 // No chunk-byte target — emit happens per codec frame.\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 // No accumulator to flush — each PCM frame was emitted as it\n // came back from `pullPcm`. close() proceeds directly to the\n // upstream session teardown.\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 * Emit a freshly-pulled PCM frame as a DecodedAudioChunk immediately.\n * No accumulator: realtime WebRTC consumers get audio at the codec's\n * native cadence (~21-128ms per frame). ML analyzers that need a\n * fixed-window input (YAMNet, Apple SA) aggregate at their own\n * pipeline side from the pull-based AudioChunkPlane subscription.\n *\n * `pcm.pts` is a codec-relative PTS (often starts at 0), not a\n * wall-clock; downstream consumers interpret the chunk timestamp as\n * ms-since-epoch, so we stamp with `Date.now()` to match the\n * `AudioRtpDecoder` path.\n */\n private appendPcm(pcm: { data: Uint8Array; sampleRate: number; channels: number; pts: number }): void {\n if (pcm.data.byteLength === 0) return\n // Buffer.from(uint8array) deep-copies — safe to keep across the\n // RPC tick that owns the source buffer.\n this.emit({\n data: Buffer.from(pcm.data),\n sampleRate: pcm.sampleRate,\n channels: pcm.channels,\n timestamp: Date.now(),\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 // No accumulator to drain — each PCM frame from the reaped\n // session was already emitted at pull time.\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","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","/**\n * Egress audio SDP grafting.\n *\n * The push-mode restreamer SDP (built by `buildSdp`) is video-only: one\n * `m=video` track on payload 96, `a=control:trackID=0`. To carry transcoded\n * audio we let ffmpeg's RTP muxer emit the authoritative audio SDP (`-sdp_file`,\n * correct fmtp/config even for AAC), extract its `m=audio` block, and graft it\n * onto the video SDP as a second track (`trackID=1`).\n *\n * Two rewrites keep the grafted block consistent with the server SDP:\n * - the media port is forced to 0 (RTSP uses interleaved TCP, the port is\n * irrelevant and ffmpeg writes its own UDP port);\n * - a *dynamic* payload type (>= 96) is remapped to 97 so it never collides\n * with the video track's 96. Static types (PCMU=0, PCMA=8) are left intact.\n *\n * Pure + side-effect-free so the grafting is unit-testable without ffmpeg.\n */\n\nconst VIDEO_PAYLOAD_TYPE = 96\nconst AUDIO_DYNAMIC_PAYLOAD_TYPE = 97\n\ninterface ExtractOptions {\n /** trackID to assign to the grafted audio track. Defaults to 1. */\n readonly trackId?: number\n}\n\n/**\n * Extract the `m=audio` media block from an ffmpeg-generated SDP, normalised\n * for grafting (port 0, non-colliding payload type, own `a=control`, explicit\n * connection line). Returns `null` when the SDP has no audio media section.\n */\nexport function extractAudioMediaBlock(ffmpegSdp: string, options: ExtractOptions = {}): string | null {\n const trackId = options.trackId ?? 1\n const lines = ffmpegSdp.split(/\\r?\\n/)\n const startIdx = lines.findIndex((l) => l.startsWith('m=audio'))\n if (startIdx < 0) return null\n\n // Collect the audio media block: from `m=audio` to the next `m=` or EOF.\n const block: string[] = []\n for (let i = startIdx; i < lines.length; i++) {\n const line = lines[i]!\n if (i > startIdx && line.startsWith('m=')) break\n if (line.trim().length === 0) continue\n if (line.startsWith('a=control:')) continue // we add our own\n block.push(line)\n }\n\n // Parse the original payload type from `m=audio <port> RTP/AVP <pt>`.\n const mLine = block[0]!\n const mParts = mLine.split(/\\s+/)\n const origPt = Number.parseInt(mParts[3] ?? '', 10)\n if (!Number.isInteger(origPt)) return null\n const newPt = origPt >= 96 ? AUDIO_DYNAMIC_PAYLOAD_TYPE : origPt\n\n const remapped = block.map((line) => {\n if (line.startsWith('m=audio')) {\n return `m=audio 0 ${mParts.slice(2).join(' ').replace(String(origPt), String(newPt))}`\n }\n if (origPt !== newPt && (line.startsWith('a=rtpmap:') || line.startsWith('a=fmtp:'))) {\n return line.replace(`:${origPt} `, `:${newPt} `)\n }\n return line\n })\n\n // Ensure a connection line so the block is self-contained (ffmpeg puts c= at\n // session level). Insert right after the m= line if absent.\n if (!remapped.some((l) => l.startsWith('c='))) {\n remapped.splice(1, 0, 'c=IN IP4 0.0.0.0')\n }\n\n remapped.push(`a=control:trackID=${trackId}`)\n return remapped.join('\\r\\n')\n}\n\n/**\n * Append a grafted audio media block to a video-only SDP, preserving CRLF\n * framing and the trailing blank line.\n */\nexport function appendAudioMediaBlock(videoSdp: string, audioBlock: string): string {\n const trimmed = videoSdp.replace(/[\\r\\n]+$/, '')\n return `${trimmed}\\r\\n${audioBlock}\\r\\n`\n}\n\nexport { VIDEO_PAYLOAD_TYPE, AUDIO_DYNAMIC_PAYLOAD_TYPE }\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 { appendAudioMediaBlock } from './egress-audio-sdp.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 * Grafted audio `m=audio` block for transcode-egress audio (set by the\n * egress from ffmpeg's `-sdp_file`). Appended to the push-mode video SDP as\n * trackID=1. Muted sessions still get the video-only SDP.\n */\n private egressAudioBlock: string | 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 this.egressAudioBlock = null\n }\n\n /**\n * Graft (or clear) a transcode-egress audio track onto the push-mode SDP.\n * Safe to call before or after the video codec params are detected — the SDP\n * is recomposed whenever both are available.\n */\n setEgressAudioBlock(block: string | null): void {\n this.egressAudioBlock = block\n if (this.codecParams) this.rebuildSdp()\n }\n\n /** Recompose the push-mode SDP from current codec params + egress audio block. */\n private rebuildSdp(): void {\n if (!this.codecParams) return\n const { codec, parameterSets } = this.codecParams\n const videoSdp = buildSdp({ codec, parameterSets, streamName: this.streamName })\n this.sdp = this.egressAudioBlock ? appendAudioMediaBlock(videoSdp, this.egressAudioBlock) : videoSdp\n // Muted sessions remain video-only.\n this.mutedSdp = buildSdp({ codec, parameterSets, streamName: `${this.streamName} (muted)` })\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.rebuildSdp()\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` — the standard RFC 4571 length-prefixed\n * RTP-over-TCP wire format.\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 * 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 * RTMP client with the following adaptations from the original implementation:\n * - `console` replaced with project `IScopedLogger`.\n * - Inlined `readLength` helper (no external dependency).\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 ──\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/+/wGgClGk1MvAN9VJ6BIUWVMODsABAPgACACEiJKqGeGhQqwRGuD0AAYoXquoGIBbe/gWsgEpqcCgPRV6f7/tuZ7kEjdqDyQOaD0b0opPh4ZntwudgChUAgBWE/I8AAYkAGAVEv9aOCaoOfkLoEDDg+wVRCyEzMH/47AEQAayAAR6Q5aqJwZJPKiFgcIGQwKLx3gGA/YUFyppIB6j9xlWJQlzF8AIR0tGJhUX5SyHgEeZDUxcwVTWB4Mhp2eFARY1zoCOhp+Q6EC81gYGownlbiDaOEev9m6a2TT4oI8tUGheSeAZ2wBSQx3By0gcR7ATMD52cJQBSw7AAaQAAgBDwr4jSJP+D78Smc2YeABjq29EmwAKbIkxv0IjvgWGqYq9G5MlTBwwAdKU7iOhZJzRwZE7A1p+RoRfc5GbQmylVm76/b2bbxhNYoe9uCgBB4d8ABgUF3NjrYxnooRAfUhy/+tDhwTrnkAGr/QLuwDeijTe1eucm2iJMaolgqbh9iNSAISYVWzNzwopGPTybBCbFBQ5JPK2BCZFhQ5L3nDDenskjAMgMJBh//HYAgAQQsOA94lm1yVgZeY2cNwH3IA0lEGnefYEY7RC8pKgDWP11gofhtuOSAcqzslqMti6KBV/dO4/AuNc3VWMubLGAjpk2J8hUdv5MUFiom+G2LF4fZLU1smnooE+7gSv6RoQfZJixhcL6DAD/O0XaOYuqDsAB/BKpBxAu+mztAehC/2HABDIIcjMWcJIi4QAAbADgJlkEBgACAfc/0jmOxh4W454cgMAAQD7gAGC3JTNpByAwABAPuEAAQcdcHYAPAEAAU4A9ku2WFlBraIzQdgA6I89A0GF0ZefeAugfNPfhxkXTG9/dO4/hgbwFvygbK0JCH34AWUpSYgW0uCBv790yayNGLqn9Ejz4GgwuTLz7x2AAjIzIBDuwAEARuvvAUjhDgrkrdwOAAbADgJlgAUcGcPaePJPARkACYAAQELkrdyVu5hwBMAAICFzDgCYAAQELg78DMBGicZPcWI2EZwBSHIMAlVeYmQkdwgAA2AHATLADl6pChRdKfTGUD/7/DpHcw6R3MAZTHJlf1vCVmBBRTNFBLwOwBEQAceXMmdPjz4SymUiET4AHnSHvKNSiQkY3/f/TCXVheWRrXYj1rC8w3iShymRpDMpRNUvSzBiRfohpwSKP/3/jsABGAlUbH44AOAAEAGS+83AYQAO54I4aWUITZHVIAAJIAAICtwgAA2AHA+XzhwACSAACArchwACSAACArcnMdgAIBgzwPWjWI94CnTFwGENkADOLBHDXyDiTwEyAIgA24QAAbADgJlhwADYAcBMvAJNAAboxQABAQR6vQ4EQAbchwIgA25kARjLmHAjGXB//jT00//HYAEADAxRXdwAGJAUEjIBwADYAcBMsAIzZMAG7GCpDRHYcAA2AAIDBmnC4OvluAEe9Xg///jsABABjUSAAps2wkVCYFveHWDFwAHDXw0UMV8rsCT4AFFoMWey5MIAS3YsiA5KKNFb1/Px+m2u4BULtQ1oBSzAAesDedhpYQLHd/wYFlrxiiU4/Lv4EMVnQQSnBA4ICQpuLjScwigQKPgw4/AEBA4O0KspzCvHCBLT3kFUS2uMVoPFADMCCoTybAQE+KMf08myAAIA2lAxdzwOHCgk2rtAgHWGP6psYVYZgFqpgTbim6ablNNACVP2HAE1//7DILXP1RMBAvb47AEAPgAOAkinIIqsBw96Y6QfAGyvTkaaD0p4BK7I4GsEmnwFX907gAERtMxa8oQAAbADgJly2yZGR1XiwADYAAgMGQwVgFaoRzZPAK24AH5Amzi6sAyq6t8IzwucBsAogwf4/ABwIdwM+020dDpYaemIWkz+ADqCKwVJAxd31eLAANAACAyZ8gAAmAIA25hwACYAgDbmOwAE2wP8AS2nHNDAAObANAOAAbADgJlhwADYAcBMsAiIGwKWoo8Ahj1eLAANgACAwZDgAGwA4CZYcAA2AHATLHAANgACAwZHAANgACAwZIAAMADAVcw4ABgAYCrn47AB8L8hh1IACAD9V94OR6UXAAmMiGikKOI5fJEwxsQAAVAIC7hAABsAOAmWAEfYyuOlBxbSQdkAEBgCLmHAAKgEBdyHAAKgEBdzDgEBgCLmHAIDAEXB34GYCNE4ye4sRsIzgCkOQYBKq8xMhI7hAABsAOAmWAHL1SFCi6U+mMoH/3+HSO5h0juYAymOTK/reErMCCimaKCXgdgCIgA48uZM6fHnwllMpEInwAPOkPeUalEhIxv+/+mEurC8sjWuxHrWF5hvElDlMjSGZSiapelmDEi/RDTgkUf/v/HYACMBKo2PxwAcAAIAMl95uAwgAdzwRw0soQmyOqQAASQAAQFbhAABsAOB8vnDgAEkAAEBW5DgAEkAAEBW5OY7AAQIIChUpoTlx1ji2YnYJRI+ADsJx/w5hpafAAexE5beQAmBJX9ufszMGXuFS9sADGUszZCyvgAAJ2DFKkkA3dmAcwNxxNVWfNEYcaRsKPq4SfBgPj5ZLCAxkGA4dgCADAcfAmKkDuKaaR4AklDUBE5ITBqmbSAJTmxAZacChOftXsBINVifsKfe1LxuLMRXACuOjrfPCiiMenjtABeADAKkn5NhGyLCB6WecpEREETMNylBh//HYAwABNwABAgc/AwcEJsScUwJQO0gDSCDTvPdQIx0QvqlagC2O1pAOhNuENAMV5umu9dgy+7jt+gVnG1pGaVYJEn3+Bo1Y6EBfQuggbutXrbBgAGaF/5AYuTQKP4DTmIN2a2xI1Ge1FT8vrwA3hxx5dddddddddddddddddddddddddddddddddddddddddddddddcdgAuFDBzgAIhvaQDe4hevgA/IE6B1lHFifV7LAANAACAyZBwADQAAgMmYC5EMroYwcZ/OisMgAA0AHARLDgAGgA4HS/H4ADAABAGFAtUJHLEWUdvNCv7yNwBd27nkBUgNf+wG3ooaEQJvQIA+VMNErfFAADdtAAEBad6AOARXzBRgv4PEAAGwAwHywKfu51mgkU0QMXsOgCBF1TAAbeAcZ1jrsyAATAV91KgFcSLlpGTE+Uzlbh3AOd2yALQLNDv8AACBBAY44BxsyCmxoH5bDAFGx6cIhkuLP4AO9wBI5UcC2HHHxIAAmAAIBdz/7jSVKSCQABMAAQDbgZQAAmAAIBtz8dgAITADeHcdCk6dPaGtxFKngAPERBEDsU8Wk+ZN5AABsAOAmWHAANgBwEy9EQZA5KSRo576vfLAANgACAwZHAANgACAwZx+AAmgB4NdF2UXRVlF/4APkDIFx5rjAMT74fBkKUAg8knzfHYAsAAnASQDgrEP88/wqPjMGT46q84Bj1VIBteOgAFgBpkDg8I4ADEmARnv4FAACwAlQBmeEMAAxIkEZ4HFWEwykHKfzADEVCgT0ywx+ABEcjOsiMy8MP/AB2MIotEMLbu0E2ycNHRPh2AIAAKAsQAFxD0G+RADWZCgXdUQoAAQBAWLD88CIHfdABBEIL3z3CJAAzQAFz0lAAEAQFmATnvDgBjkqkAqV7hG4AbQAK1XCCi9gBDJSe1PL8dgCACc4AKCNGDqDq8PpgAGGSgy84YYJgAC6eAATPaMeSgAFU1AAme0YBW3eAAYZCTt72jA7DwEpQBA7JYog6goBtZ4pQIuveIwAAgCh5kOz3/iHAAEAcPEB2e8dAY5UIBDXV/hDAEgAHCRqvzo0D/X3Qregu6QHsaeOZSD4xtsvHE9p3lG2DAnhEFms+WCSAm//caB809+HGRdMb3907gMDeAt+UDZWhIQ+/fwAPKUpMQLaXBA39+6ZNZGjF1QB7GnjmUg+MbbLxxPad5Rth2AIAMCiC7DbbRcwWMtGTBj//9Ejz4GgwuTLz7+NA+ae/DjIumM7+53H+/jszH/CovoqIf+AQ4P4FGlz4c2WUwT5y+GSw1Z8QFvmhA7t+D8AAhFEqsuWinhG/8AB50h7yjUokJGt/39MJdWF5ZHtdiPWsLzDeJKEJWYE4gmijPj/KYl2cylE1S9LMGJF+iGnBIo//WGrPiAt80IHdvwY7AAQDHc9z3ue5z3PYz//4AHnSHvKNSiQka3/f47AAQRQAIkCcZEKGK6ljQvYf8ADAUgANEU6ADgX/9RoADfuUAAQEE+r3x+JDTBtvbb409NPHYACMwB4AK9oBdsCVhzc4EWBwADYAcBMsACjgzh7Tx5J4CIAjDcRHv54MlAAJgNEgnPDgAGwABAYM1LBnRUAECinq9/yYAAmAlwH56IRKxWPnh24AJyAAYCsMsoOogIirks1hgU5Cr3wgIjLghxgAkRWe/8gIjLgjBgAkzWeMwE0sMI58ICnJVa5/64/AHaaMyaRkZF//4APhodyOYIf6fAVf3TnH9Zl1oKYszA7Mx/wqL6KiH/gEOD+BRpc+HNllME+cvhksNWfEBb5oQO7fg/AAIRRKrLlop4Rv/AAedIe8o1KJCRrf9/TCXVheWR7XYj1rC8w3iShCVmBOIJooz4/ymJdnMpRNUvSzBiRfohpwSKP/1hqz4gLfNCB3b8GOwAEAx3Pc97nuc9z2M//+AB50h7yjUokJGt/3+OwBAABaA4CW4gtCt2QwABpACi659/XpOAAIB4BbkMzwUHgjBWoMqvECDHuP0AA0AFugOpcQIMe5JAABANALUB2ehGABkcAUfTCH/nlQABgALEgdDiBBj3MarwxC2QkQRAgx7g7ABwAGAgwQ9RokTFiwdI3bt1//4HARjbmABopxlo0RkAAGgAwCJY/AAQnc7AAEA8ZqqrRNkaNF///8DgAGgAoHXAAHiIRCB2KJGoPmTagABsDAk2Mtsy6666666666666666666666666666666666666666666666647AEAARnue5735/4AD5kMjgjG4gkeY5xGYiMCLXqHBJreHH4AiKAGGmzZzbQ++E8pmJJI8ZWus7fsE4kVky0YQPj/3LRGmJ8pFdgSnI4nsPwo/boxLs5lKNqm/+BzkLiB7Q8IHfv3x34yAAEBEEAWmqVIDzAtRmIKDVoiDj2PBB9HST4PBobWFQcZNsh0A2FPI5Dyphw8gCs5QNkwEhD78ABdJgihBE7/VGvX/28YAC3lwAKBkj4EEOioQXPM19lcZEMroIzEkpYodgAIBDPB32vtnj58ErgnUyN/wAfmEUMQkkYO++rxAABoAOAiWJAANAoabGHeYsAA0AAIDJkHAANAACAyZIAANABwESw4ABoAOAiW+OwAH2AAIBgAOGlyFtDJwM1ArO//7wnA5BxSlvKr36kmgw4rk8lzgFpV7IQQBDK3H/7dNcAAYBqmAPAAcRhMPjhrL3q94EE0MjDoQcU0gHBkAAGABAKucaDq1Ah4WSvE4BsOAAYAEAq4QAAbAEA24OwBBYAA44RfMRbgqUp0QT8QRAGwK8h5i52D0WCSKOdkIoPGHf4YAFBbEFncqDiQPaAC53nNEfg7gOdf+ocRgUog0UtV6zKBOWRQMAXjvDiAHIAMAuKfkuR2Iomg82HVcHYACABgUkLAwr846nzA1209AxcAC2QVL76YBuHk3wLAK2CPLXBKxYTOwsge/jx3xbzMAfXqABsPEbZQWDUiDX34FBy1A1TAAlP/3nPbManL1FStbsQIgpozHeawvIwLtTegHKP2BO6RkFSPy8C3WqQk2IZBuYl/7lJEJDMLjkKAKEAQeARwBJQABAADgvfTxvRgUqZCsLhrSTBa9TOcymHivOCApywW4MJcyzF7+/4gi3qCTtQPDx7hYG+uYC3uSYAntFpkaMXFYGDBLvjmycYqhU//6mgF4i/gJ4U+5gS6vPTjR7BHAUAIGBtjo8jM6KKkQM6Azf/caeIngsTXD7Eyn840000+0WsjmL9MB2nwfIjPANSNRiohW2lQBzPbrfIAltEGnu+sGoQuxYUNyTwNQQuRQUNyzxlaMLzxnTMLTwIYACAAMOaCQPepKNsFYGfuTnRuAAZNRZu+HWKlf4CYZqgaE7BoB/ncF8imPonfFvM9j6rg8Imzg9J8YEPvwGjlNg2TG+AJTN3xbZnsfVbXoAklFGnOfmtFaKXtIUAYt+uwPiVE3Vj8mChuUiJITZCIzbjSVKSAx3giBhisDTe9pMi+UikMKJ9DRk55AABcAUBNz8gAAuAKAm5hwAC4AoCbngAzEGMRtHiSCB/gIbRIAE79CACgRcdgAIGY5zHMcwAAgZAAP3rQHBb4YE8Igs1nsAhkhR/+kAAQDAKuYeHAAgGAVchwAIBgFXB34ATAAMAwJTJs2AF4hoHFgpSxmFnAWvKBrsJFP/bACylKTECz7ggd+/ZotZGA06/9//9WNPHMph8Y+2VjiJErVSbsMwAngmHvEXY2JIXf++QAAaADgIlgAYOCMHqJOJOJHYACIzIgGM7d573Mf/4YEj82/IAANABwESwAMFgjB6qziTix+AAgAMCCSrDKzJQqUKtEipD/AAIRRKrLlop4Rv/QlZgTiCaKM+MGmvtNDvwACRAABASFSYMjBW1YPyZ4m6YS6sKjCPaAnEetYXmG8SUOUxLs5qwmqADZjkAd6gACApXO/+g1Z8QFFdIAF99sQAATUAAQFLjAABAAAQDbgATRMCKEERbVgqNMuQAAaADgdLx2AAgEDvA5aFIj7UgUSoVf3/wAMJYMwaps8k4qAE8wAJu5AABART6vEAAGgA4CJYkAA0UcFjGuYfhUtH/uZOZ+cyczj8ACIDgDDFZmgAemBQTMv//AByNAAyoQUAVz1eLAANgACAwZAhNGgAaoQbgwR0IAANgBgEyx+AICC44ABBItz5kMgXdYtANAJ0VSAwOQ4G9/tZxpe4glKCZpAT0AnuQVxMiB9/wfwALDsw+rI7iaDxR94YAFhS0bGP2zLBh99tXrbP1vmwKcAwuRCngFrdrMVMG+5WoApntb5AF7RBpzf5h+AAgDDAR0Tb1G1mN0KurvL+AB4qo+qI/iIDRe4NQIC/FiOWngMiMAuTFCuV7zIB+GLueMwBnCHxPAPHDHDZceTh/N+4D/t5QABAMsrwMFBdQpXrEFSjm/U8MMMcHYAogABAXoAChhRWMYgS1ESUlAajL0DFo2CbGttWQmAP/4HAgABwAkLIgP5Q+4oQMbfv/0fwBYpUBhMzAefRgE6hibjSUAAIAWmAECBGW5/jsABDZncVjO72tYxv/+ABVIwysMqklrc4gAA0AHARLHYAEH1zdzf/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/73QAGSw1BulYBuWZgRLcAAICP4GC1SAiBDKIFC8EHLUFQ4unmAPwBAQApVh7gt9NLRK0nnlCdgi2nDBGTfEAAFABAOl19AzyMAMr9zyoABgAAQAFidgOQEjAQOp3gIUQDGTEsVpC+BiQTEsUAGtPeoDxni8BMPHq7B/El4DUU0ZaNI0gSHYAhnAFDCtah1IsWJy1OHxeA2C7ztIArg1rvfy0jTE+Uiu2Eg4WvhhsHS31/hBgyyziht56xgPSmIB1S8Bw+BlcEOtYTAUR/1bhcHhvNIAOQ8/v/HYACAYQFDKvjnJwSkjDNiBOI3wAOxAmDmcs/hImlQAPFLR1kFQ8SKfv/528HB2oEyIJAFr36kdkEUSSw5T+Bw5aK000kafepwFKPbfcAIXvw7AUCADUAFLtiIyjV4Ve2JAzfEAAGwA4CZYBA7YOoo2CRAgPToZlKNrHikF6oa5gsAQvfv/uR+YfpggAX/fqQrh0PKt5hyyN9AhTjSAsf+DAqn05GmjwQwCW2mwM8sBw/947AAQRSEKY5igACCiAAICmt7AB0HngAU0jTE5wiN88gAA2AHATLIAAQYfc/DgAIMPuYcABBh9zHYAOw1AqdCKrljDJAACiYAmDgAGwA4CZYcAA2AHATLAXBBUoCRREnzFgAGwABAYMhwADYAcBMsDQADNgAUBMJ4OAAbAAEBgyIQB6KAAIClzwsAAoAAIBUMHAAKAACAVD/HYEgACAIDAzYijHmYnu3mKnBIYGWBwsqBwD8d4CRTnbxNQKUD2jkfxFav8CHgUqCP4AFn6QWbWrj5CxZ/3hgAUhKTZI4OZRgE57gIsoAAgJ/vW64MYT6PSg31uhC7NEHPHYACMBKo2PxwA4AArJfeIx6gAdzwRw0swQmyNqQAAQYAAQE7hAABsAOAmWAI77ZC95DIIQlyHAAIMAAICdyHAAIMAAICdzDkIS5hyEJcX47AARGRGAh3aAGgpdfeA1HTwAXmMRpRECFWx/XEAAGwA4CZZAABiAACAvc/DgAGIAAIC9zDgAGIAAIC9zHYB0AAQCrtQAwAAgxSAAe0HAANgBwEyw4ABsAOAmWA6AAbsnAAEBateLAANgACAwZDgAGwA4CZYcAA2AHATLHAANgACAwZHAANgACAwZIkVzDyK5+PwAcnYAN7FsBZo2EKyEgIfgAPkY2fHMEE9togQAAaADAIl0ZBPgqiTHnerxYABoAAQGTP/x+ADsAEoBzn28DB0DVJh9N4IGABSRCQHQhpqgs+7IAAJgKH3CAADYAcBMsOAAbADgJlgFGYTjY8aw96vFgAGwABAYMw4ABMBQ+5DgAEwFD7hAAQHAdcw4AQHAdcx+AHAIKKZIETF7TM03zvbBwke+QAsQJcZ0ngGjsQAR9RMg5HxgZEGDdMYWv+9RGEAZuaU0Av6ff8DtGQMnICqKJL47AEYgPEie9OTlmUZKvPwVCIBvkNxPjC2EuL8cyMEXOCLLIICSZ4sR4YB+GkH7/gzSjBKKKus3800OJYVgoqbJB8CEAT2HUGbBQDx8GMvu47eB7CW24wCNeAg/+9dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcdhQBhQwGciOyiPdmrAbgMcBDM4l5eKwiAAICQA0DTyTwzgiJ8SZ5AABEBAu4BchWiZ4rEQAAQFABpGrWFYDQA0gEI1L3l4gAAiAgXcJCFobPBQeEcMnj6rwa4/AAQAIAUQyVVJK6Z4A2iCh9JEJY2BHDCVH/+e+AxLUZbJkemR6QzXgYkew/7DPwNkfgKgHFf3UiILEycSKTF/tXrbfH/gGEcBTxcgUjGAEARi7Bh1YEg8R4Ir5b4NQXNBwYW19oE+MiACAAEAaWHgAgABAGlgGgRVQ40Xr7QL8Dw8RzQD/LfAh5wCtopwK6v4LDBVXPgHbOuf9jiwCtogaa6v7dNd6H6eDpkFqaPIEJiHfgAQGD9m1WZVYUAfoCMnFQGPwOK16UkApQax/z/zjd69H+AkPoLOklZJMg0SdhmEpPK4BNCiv52i1kPKTu36ToNCHhTHPHYACACFwAYKYGZnuKWfipKolK0AHkryw4GILAs/dBYClsGk1ZIBY/4sXAVN5KgDFFEBef4F5rgQxZpAUfeIAAIKAAICtwAHUjDUglJWc29+Y+pe7gOYB2BIcgkA9e/B3IlnC31EACv78QAAQUAAQFbmC4HAAIKAAICtwf8GAoTR8LCRhxFG4koi2JHBgAnJpIgolJI73+wdwFIFh3kAHr36w3aewgFILAwf+8MAH5vwMwIeWEH3sHTymUgtKac/AtnTF1J2cuBFvDWnCwBH9+8HC6C8t1tccHYAIzfd3f9///4DYV5GiHlYPx2AAkgAuEoPUmqjMADKbUCjAB8QIKRjzBZMVT4gAA2AHATLw4ABsAOAmWBoABn8AcC5zwPggqUhIojT5iwADYAAgMGfjgAGwABAYMlQAC8lAAEBaZ58dgAIBgACAC0SFdrAzuVXBUy2yV8AAp6ISnfsMhq8JE1AYWxcVzX5Vsw/RUrSn2GoRS5cBeFAIZhwE24f6XIgpd/KRCTEI4CM5Tw6QPEthAKAV0XZLUZAyJ0AAEAr7GCPsFJHdk4hbAVGvyVa4jwL5lNvogAQwu4BnAvb2QGbJIDhZuuuOwDQAAQC5aAAIT4KiAA1VbgBQAA3JfAAoHzAIAANgBwEy8OAAbADgJlhwADYAcBMsdAAN23gACAtOvFgAGwABAYM/HAANgACAwZHAANgACAwZx+AAmgB4MSQACZpJoAxicHP8AH5gwhPkGiiQonxAABoAMAiX4egwilBAqiT5iwADQAAgMmfrKpXHYdASBKA2I/G4dgL7xDGpjggd5AABQBQVcBghyCh4HL8RwePxe/e1EAAQC5bnvDgAFAFBVwnEP0+eAKq8MQtSRin8GI6AAEAmy57w6bGcP2HXnrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrjsABAQO0DFoUiOtXiaX9P9/8AHMGQJlnkmEJ9AB3JgAiKUQAX31eIAANABwESxIABoU0FjHeYfgAOAxiKeJYYnOUmaz////wJBfAFkad0WeAgqwA/1MGiTCawiP1Np5egIA64mwhLTeHYACAgdIEiNDASkWeJ0f+kGruACNtvimyQBtHoguXhxgGQ/OoQ76ugWLhAAEAwDrlDASWFVTjBR97wWG59u4AQQ0ofu8MAOhSxAU04IlMQGACDhSJ5QYwFLLEAAQDAOuEAAggm5hwAQQTcx2AAwDsGl0Pl0vdD//gA/IGEI/DRZLyfYENpIACVfmAFAioY/AATAAwDiEunA4GNlrXPtZzHiAD7sgGBenAkdA7GLFUW3JKAP+4IgADaYF6FgHf+X9MJZWOhRtWzit2wj4wFQedA89YUT2G8QWP6fUgycoESce9lMTifGmNLIIEqGwqJ41RRJYikOGuZxYByfYfgAtgACAEgBfABdr2Ahxyfv+bBBIL6CQQGTtgGfURIcn5zo7wABAs+T8hEw4YgHT3GQABOwBgPjPA1BiTAACAZuzwEDhgYrLjxX1WOwACOigIBcg8xPyasDwrPsOwBBB9CVTxIbDOAUkjnCbZoKQvVAFCJyp7YDIBUgf96AxsRQm3cF8iRj6IbkKjR1choJziQhpgHxO4GKcYSF3/t++2AGq3gACAYWApEFF2rKZYaGwVDFb79IYAwDV4GRAACDi7l0MlwbPssAT/fg4ACDi7lsdA1tS8kHmExwQwBGUAKFmaCSNGcHCSYyBgGUEhNbOKJTRO9/gOGZUdCA4mSxwwYTAOULJCCADl78QAAaADAIlhwADQAYBEv8CzfIkx9EKRbGYwlKYcxiQgQ1qD1Hg41A/4/AB9AAEAcaj7xIGzpneuFbV8ACdEDZwT1nYTYWQAAbADgJlvr1tgWKSjLLMkgSL/CoDYgXJIAF3vkHDtgyyjSBp94d+MEAqQe7OtgaJiH9OGD1IAAQttGSssFH9fbOtLeAHKLM37f5AAhT7gAYeO2lMDGlhz/8JAANHCgsY3zaTGRghOfop9QYb4EZM2hNlKrMQAAaADgIljsABCZwb+C7rLrrEgAJ7gRf4AhBhEJCBVGnzFgAGgABAZMiwADQKCxgnxEAAGgA4CJYOAAaAAEBkyDgAGgABAZMhwADQAcBEsYAAYACgInY7AAQQA8wAKbEp/57gd+5YlTsUAngRB1QNkBn+GV7qFq3eaAJaiyrwBUDslKGYqXLAAMSAjSwoKAb31z2zbMwEA/7o50Fl4HAKoX5YsBqFswXbgYbMwjDglRCRtSRb63GkgCfTAACAacpIkhJEIjMQABOAAICNwdgA4EBXgcxKkwm3IFUqFWg///gAcywZg0XIlNhBQArwAJL0AAEBParxAABoAOAiWJAANKUAOAjuYfhVa/+pkpn5TJTOOwAEB0QFwXssROPC8+rTb/ABxjIGI4kaN/er3gQbIRAz1sEpOljgeAD4xCsK0Sq0lu0QAAaADgIljsAKAACAXLYAGAAEGOIADWv8AUAANS3AACAvOvFgAGgABAZMiQADXUAOAiuYgAA0AHARLBwADQAAgMmQcAA0AAIDJkOAAaADgIlhwADQAcBEsfgAIMhAAOlKEAUJM2dLJkz//wAowAGuRQABASyq9ApMAB/I2ACAbgHYACIAGoBjnQ8DSooUQtydt//8ACujEoMhNcsLPYAB1EGw+uGMv+rxYABoAAQGTKgABoUcbGH+YgAA0AHARLH4AFgGEFMlShq9oHY5x3v4AG8RGDL2DKJJIRPfLM9fIAYkEB0TGmsT3rPMYB27pKwDOr8wBD/bkdPKFgwDvuMAxDy3w7AERwAKBIXwTEZOfAIkjavcFUDgH5YMqVQWEj8JJxgbADi6ry+AbMMlzIFFxYLdwLbHJwIQ5wk/d5gBLAABARXPJzICIECV9qCksZmyCxgE4U5+OVRCQzBCXIYDAyACUAAQEbmHAJQABARua66666666666666666666666666666666666666666666666666666666666777/fff7/f779+/313hv3333hrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrw=',\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/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 DecodedAudioChunk,\n BrokerStatus,\n BrokerStats,\n Unsubscribe,\n IRestreamer,\n IScopedLogger,\n FrameHandle,\n SubscribeFramesInput,\n SubscribeFramesResult,\n BrokerConsumerAttribution,\n BrokerEncodedClient,\n} from '@camstack/types'\n\ninterface EncodedSubscriberEntry {\n readonly id: string\n readonly channel: 'annexb' | 'rtp'\n attribution: BrokerConsumerAttribution\n readonly subscribedAt: number\n packetsDelivered: number\n}\n\nconst UNKNOWN_ATTRIBUTION: BrokerConsumerAttribution = { kind: 'unknown' }\n\nimport type {\n SubscribeAudioChunksResult,\n} from '@camstack/types'\n\nimport type { DecoderCapApi } from './decoder-session-proxy.js'\nimport { FrameHandlePlane, type FrameHandlePlaneStreamInfo } from './frame-handle-plane.js'\nimport { AudioChunkPlane } from './audio-chunk-plane.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 { 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'\n\nconst INITIAL_RECONNECT_DELAY_MS = 1_000\nconst MAX_RECONNECT_DELAY_MS = 30_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\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 — the closure re-resolves a\n * fresh URL on every dial. `null` for tests and stand-alone callers;\n * falls back to the source passed 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 /**\n * The shared-memory frame-handle surface (Phase 5 / D9) — the broker's sole\n * decoded-frame path. Lazily created on the first `subscribeFrameHandles`\n * call; owns the per-format `frameSink: 'shm'` decoder sessions a handle\n * consumer reads from. `null` until a consumer subscribes.\n */\n private frameHandlePlane: FrameHandlePlane | 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 /** Flag-only depacketizer for the `push-rtp` source-RTP pre-buffer path\n * (sets keyframe/param-set flags without re-emitting AnnexB packets). */\n private pushRtpFlagDepacketizer: 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\n /**\n * Rolling source-RTP activity watchdog. Distinct from the first-video\n * watchdog: this one ARMS on the first RTP packet (video or audio) and\n * RESETS on every subsequent packet. If no packet arrives within\n * `RTP_ACTIVITY_TIMEOUT_MS`, the reader is torn down and reconnected.\n *\n * Wi-Fi-attached IP cameras silently stall the RTSP/TCP source under\n * packet loss: ICE/DTLS stay healthy, `rtpPacketsSent` keeps advancing\n * (replaying the GOP cache), but the source-side audio/video time\n * progresses only in tiny bursts. The user sees a frozen viewport with\n * no recovery because the existing code path never noticed the stall.\n *\n * The watchdog resets on every RTP packet and kills+restarts the reader\n * when no data arrives within `RTP_ACTIVITY_TIMEOUT_MS` (~10s).\n *\n * Reused across the native RTSP and RFC 4571 readers — both flow through\n * `buildRtpStreamCallbacks` and call `kickRtpActivityWatchdog()` per\n * packet. push-rtp / push (provider-driven) and RTMP sources sit out\n * because they have no native reader to tear down.\n */\n private rtpActivityWatchdog: ReturnType<typeof setTimeout> | undefined\n /** Number of times the activity watchdog fired in this broker's lifetime\n * (incremented on expiry, NOT on arm/cancel). Surfaced via the test\n * seam so unit tests can assert recovery behavior without inspecting\n * private timer handles. */\n private rtpActivityWatchdogFiredCount = 0\n /** Activity watchdog timeout. Matches `FIRST_VIDEO_TIMEOUT_MS` so a\n * healthy live source comfortably stays ahead (PCMU audio is 50pps,\n * H.264 video is 30fps → 80pps minimum on a working stream) while a\n * real stall is detected before Chrome's ~15s ICE-disconnect timer\n * has any chance to fire. */\n private static readonly RTP_ACTIVITY_TIMEOUT_MS = 8_000\n private manualStop = false\n private stopping = false\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 Map<\n (packet: EncodedPacket) => void,\n EncodedSubscriberEntry\n >()\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 Map<\n (rtpData: Buffer) => void,\n EncodedSubscriberEntry\n >()\n private subscriberIdCounter = 0\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 /**\n * Decoded audio-chunk plane (Phase 5 / D9) — the poll-based, tRPC-reachable\n * replacement for the live-object `onDecodedAudioChunk` callback path.\n * Holds the per-subscription FIFO chunk queues. Assigned in the constructor\n * so it gets a scoped logger child.\n */\n private readonly audioChunkPlane: AudioChunkPlane\n /**\n * Gate for feeding encoded packets to the decoder: H.264/H.265 decoders\n * need the SPS/PPS carried in the first keyframe to initialise, so the\n * broker withholds packets from the shm frame plane until the first\n * keyframe arrives.\n */\n private firstKeyframeReceived = false\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 /** 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\n /** Tracking flags set synchronously by the RTP depacketizer callback. */\n private _lastNalKeyframe = false\n private _lastNalParamSet = false\n\n /**\n * Source-RTP pre-buffer: a ring of raw source RTP packets trimmed to\n * begin at the most recent keyframe access unit — i.e. the current GOP so\n * far. A late-joining WebRTC viewer on the repacketizer path replays this\n * on connect (`getRtpPreBuffer`) so its decoder initialises immediately\n * from a keyframe instead of waiting for the camera's next IDR (which can\n * be many seconds away on a long-GOP 4K stream). This is the RTP-level\n * counterpart to the AnnexB `preBuffer`, kept separately because the\n * repacketizer path needs the original RTP wire packets, not AnnexB.\n * Only populated for RTP sources (`isRtpSource()`).\n */\n private rtpRing: Buffer[] = []\n private rtpRingCurAuStart = 0\n private rtpRingPrevMarker = true\n private rtpRingHasKeyframe = false\n private rtpRingBytes = 0\n /** Memory ceiling for the RTP ring. A GOP that exceeds this (pathological\n * long-GOP high-bitrate stream) drops the bootstrap rather than balloon\n * memory — late joiners fall back to waiting for the next keyframe. */\n private static readonly RTP_RING_MAX_BYTES = 24 * 1024 * 1024\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 this.audioChunkPlane = new AudioChunkPlane(logger?.child('audio-chunk-plane'))\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 — keeping the loopback open\n // 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 this.cancelRtpActivityWatchdog()\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.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.frameHandlePlane) {\n const plane = this.frameHandlePlane\n this.frameHandlePlane = null\n await plane.dispose()\n }\n\n await this.pipeServer.stop()\n this.rtspRestreamer.destroy()\n this.preBuffer.clear()\n\n this.pendingParamNals = []\n this.firstKeyframeReceived = false\n this.encodedCallbacks.clear()\n this.audioChunkPlane.dispose()\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 // Frame-handle subscribers (Phase 5 / D9 shm plane) count as demand —\n // their decoder sessions need a live encoded-packet feed.\n if ((this.frameHandlePlane?.subscriberCount ?? 0) > 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.audioChunkPlane.subscriberCount > 0) return true\n if (this.rtspRestreamer.getSessionCount() > 0) return true\n if (this.pipeServer.getClientCount() > 0) return true\n // `_preBufferEnabled=true` keeps the stream warm even with zero\n // active subscribers. Earlier the gate also required\n // `preBuffer.getDuration() > 0` — meaning the broker could only\n // STAY warm AFTER it had already received its first packets. On\n // `suspend()` the pre-buffer gets cleared (intentional, to drop\n // stale GOPs across the idle gap), so a momentarily empty buffer\n // immediately voided demand again and the broker collapsed back\n // to idle on the very next `checkDemand` poll. The natural read\n // of the flag is \"keep the stream alive permanently\", so drop the\n // duration gate: a camera that wants always-on stays always-on,\n // and a battery camera explicitly sets `setPreBufferEnabled(false)`.\n if (this._preBufferEnabled) 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 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 // Activity watchdog has no purpose when the broker is intentionally\n // idle — let `start()` re-arm it on the next packet.\n this.cancelRtpActivityWatchdog()\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 (late-join).\n // ONE pre-buffer per source: RTP sources (RTSP) use the raw source-RTP\n // ring (`captureRtpForPreBuffer` / `getRtpPreBuffer`) for the repacketizer\n // path, so the AnnexB pre-buffer would be a redundant second copy of the\n // same GOP — its video is never flushed for RTP sources (the WebRTC\n // AnnexB flush is gated on push sources) and profile derivation reads SDP\n // first. So only AnnexB/push sources populate this one.\n if (packet.type === 'video' && !this.isRtpSource()) {\n this.preBuffer.push(packet)\n }\n\n for (const [cb, entry] of this.encodedCallbacks) {\n cb(packet)\n entry.packetsDelivered += 1\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 the decoder.\n // H264/H265 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 }\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 the decoder after the first keyframe — the decoder needs\n // SPS/PPS to start. Without this, streams that start mid-GOP silently\n // produce 0 decoded frames. Feed the shm frame-handle plane's per-format\n // decoder sessions the encoded packet (Phase 5 / D9) — a no-op when no\n // consumer has subscribed; the plane ignores audio packets internally.\n if (packet.type === 'video' && this.firstKeyframeReceived) {\n this.frameHandlePlane?.pushPacket(packet)\n }\n }\n\n onEncodedData(\n callback: (packet: EncodedPacket) => void,\n attribution: BrokerConsumerAttribution = UNKNOWN_ATTRIBUTION,\n ): Unsubscribe {\n this.subscriberIdCounter += 1\n const entry: EncodedSubscriberEntry = {\n id: `enc-${this.deviceId}-${this.subscriberIdCounter}`,\n channel: 'annexb',\n attribution,\n subscribedAt: Date.now(),\n packetsDelivered: 0,\n }\n this.encodedCallbacks.set(callback, entry)\n if (this.streamingDebug) {\n this.logger?.info('encoded subscriber added', {\n meta: {\n total: this.encodedCallbacks.size,\n subscriberId: entry.id,\n kind: attribution.kind,\n label: attribution.label,\n },\n })\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', {\n meta: { total: this.encodedCallbacks.size, subscriberId: entry.id },\n })\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.keys()) 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(\n callback: (rtpData: Buffer) => void,\n attribution: BrokerConsumerAttribution = UNKNOWN_ATTRIBUTION,\n ): Unsubscribe {\n this.subscriberIdCounter += 1\n const entry: EncodedSubscriberEntry = {\n id: `rtp-${this.deviceId}-${this.subscriberIdCounter}`,\n channel: 'rtp',\n attribution,\n subscribedAt: Date.now(),\n packetsDelivered: 0,\n }\n this.rtpVideoCallbacks.set(callback, entry)\n if (this.streamingDebug) {\n this.logger?.info('rtp video subscriber added', {\n meta: {\n total: this.rtpVideoCallbacks.size,\n subscriberId: entry.id,\n kind: attribution.kind,\n label: attribution.label,\n },\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, subscriberId: entry.id },\n })\n }\n this.checkDemand()\n }\n }\n\n /**\n * Re-tag every subscriber whose attribution carries `sessionId` with the\n * supplied patch (e.g. a remote address discovered once ICE connects, or a\n * User-Agent resolved at the signaling edge). Both the audio + video rows of\n * a WebRTC session share one `sessionId`, so a single call updates both.\n *\n * The stored attribution is never mutated — the entry's pointer is swapped\n * to a freshly merged object. Returns true when at least one entry matched.\n */\n updateConsumerAttributionBySession(\n sessionId: string,\n patch: Partial<BrokerConsumerAttribution>,\n ): boolean {\n let matched = false\n for (const entry of this.encodedCallbacks.values()) {\n if (entry.attribution.sessionId === sessionId) {\n entry.attribution = { ...entry.attribution, ...patch }\n matched = true\n }\n }\n for (const entry of this.rtpVideoCallbacks.values()) {\n if (entry.attribution.sessionId === sessionId) {\n entry.attribution = { ...entry.attribution, ...patch }\n matched = true\n }\n }\n return matched\n }\n\n getSdpParameterSets(): ReadonlyArray<Buffer> | null {\n return this.sdpParameterSets\n }\n\n /**\n * Append a source RTP packet to the pre-buffer ring, trimming it to begin\n * at the most recent keyframe access unit. Access-unit boundaries are\n * detected via the RTP marker bit (set on the last packet of a frame);\n * keyframe-ness comes from the depacketizer flags set just before this\n * call. The ring therefore always holds `[current keyframe AU start .. now]`.\n */\n private captureRtpForPreBuffer(rtpData: Buffer): void {\n // marker bit: RTP header byte 1, bit 7 — last packet of an access unit.\n const marker = rtpData.length > 1 && (rtpData[1]! & 0x80) !== 0\n const auStart = this.rtpRingPrevMarker || this.rtpRing.length === 0\n if (auStart) this.rtpRingCurAuStart = this.rtpRing.length\n this.rtpRing.push(rtpData)\n this.rtpRingBytes += rtpData.length\n\n // This packet's NAL began/contained keyframe data → the current AU is a\n // keyframe AU. Trim the ring to start at its first packet so a fresh\n // viewer gets the minimal current GOP.\n if (this._lastNalParamSet || this._lastNalKeyframe) {\n if (this.rtpRingCurAuStart > 0) {\n const dropped = this.rtpRing.splice(0, this.rtpRingCurAuStart)\n for (const b of dropped) this.rtpRingBytes -= b.length\n this.rtpRingCurAuStart = 0\n }\n this.rtpRingHasKeyframe = true\n }\n\n this.rtpRingPrevMarker = marker\n\n // Memory ceiling: a GOP longer than the cap drops the bootstrap entirely\n // (late joiners wait for the next keyframe) rather than ballooning memory.\n if (this.rtpRingBytes > StreamBroker.RTP_RING_MAX_BYTES) {\n this.resetRtpPreBuffer()\n }\n }\n\n /**\n * Reset the source-RTP pre-buffer ring to its empty/cold state. Called on\n * every reader teardown (`resetRtpPipeline`) so a stale GOP from a prior\n * session can NEVER leak across an on-demand idle window — and on cap\n * overflow. Without this, a redial that begins mid-GOP (no IDR yet) would\n * replay the previous session's stale `rtpRingHasKeyframe`-true ring to a\n * fresh viewer (the `1 packet, sent=0` cold-start symptom): a degenerate,\n * non-decodable slice instead of waiting for the new dial's first keyframe.\n */\n private resetRtpPreBuffer(): void {\n this.rtpRing = []\n this.rtpRingBytes = 0\n this.rtpRingCurAuStart = 0\n this.rtpRingPrevMarker = true\n this.rtpRingHasKeyframe = false\n }\n\n /**\n * Snapshot of the source-RTP pre-buffer (the current GOP from its\n * keyframe) for a late-joining repacketizer-path viewer to replay on\n * connect — instant decoder start. Empty until a keyframe has been seen\n * (or after a cap overflow / reader teardown), in which case the viewer\n * falls back to waiting for the camera's next keyframe.\n */\n getRtpPreBuffer(): readonly Buffer[] {\n return this.rtpRingHasKeyframe ? this.rtpRing.slice() : []\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. Captures into\n * the source-RTP pre-buffer (so a late-joining repacketizer-path viewer\n * can instant-start from the current GOP) and fans out to every\n * `onVideoRtp` subscriber.\n *\n * `push-rtp` providers deliver AnnexB-equivalents separately via\n * `pushEncodedPacket()` (see `IStreamBroker` contract) — so this path\n * does NOT re-emit EncodedPackets; it only derives the per-packet\n * keyframe/param-set flags the pre-buffer capture needs (via a\n * flag-only depacketizer that never touches the AnnexB plane), keeping\n * the AnnexB consumers (decoder, restreamer) single-fed by the provider.\n *\n * The pre-buffer is captured even with zero subscribers attached — a\n * viewer that connects mid-GOP must be able to replay the GOP that\n * accumulated before it arrived. The fan-out is what no-ops without\n * subscribers, so providers can still fire-and-forget.\n */\n pushVideoRtp(rtpData: Buffer): void {\n if (this.stopping) return\n\n // Rolling activity watchdog: every source-RTP packet (push-rtp,\n // native RTSP, rfc4571) resets the silence timer. Mirrors the\n // `buildRtpStreamCallbacks.onVideoRtp` arm so a push-rtp source\n // benefits from the same stall detection even though its expiry\n // is informational-only (no reader to tear down).\n this.kickRtpActivityWatchdog()\n\n // Derive the keyframe/param-set flags the capture reads. Reset\n // per-packet so each capture sees only this packet's flags.\n this._lastNalKeyframe = false\n this._lastNalParamSet = false\n this.ensurePushRtpFlagDepacketizer().processPacket(rtpData)\n\n // Capture into the source-RTP pre-buffer (current GOP) BEFORE fan-out\n // so a subscriber bootstrapping from it sees this packet as the tail —\n // exactly as the native reader does (`buildRtpStreamCallbacks.onVideoRtp`).\n this.captureRtpForPreBuffer(rtpData)\n\n if (this.rtpVideoCallbacks.size === 0) return\n for (const [cb, entry] of this.rtpVideoCallbacks) {\n try {\n cb(rtpData)\n entry.packetsDelivered += 1\n } catch (err) {\n this.logger?.warn('rtp video subscriber threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n }\n\n /**\n * Lazily build a flag-only RTP depacketizer for the `push-rtp` source\n * path. Unlike the native reader's depacketizer (which emits AnnexB\n * EncodedPackets via `handleDepacketizedNal`), this callback ONLY sets\n * `_lastNalKeyframe`/`_lastNalParamSet` — the push provider owns the\n * AnnexB plane through `pushEncodedPacket`, so re-emitting here would\n * double-feed the decoder/restreamer. push-rtp has no SDP `onVideoTrack`,\n * so the codec comes from the source declaration (`detectedCodec`, seeded\n * in `start()`). Dropped on teardown (`resetRtpPipeline`) so a redial\n * starts with clean FU reassembly state.\n */\n private ensurePushRtpFlagDepacketizer(): RtpDepacketizer {\n if (!this.pushRtpFlagDepacketizer) {\n const codec = this.detectedCodec === 'h265' ? 'h265' : 'h264'\n this.pushRtpFlagDepacketizer = new RtpDepacketizer(codec, ({ keyframe, parameterSet }) => {\n if (keyframe) this._lastNalKeyframe = true\n if (parameterSet) this._lastNalParamSet = true\n })\n }\n return this.pushRtpFlagDepacketizer\n }\n\n /** Toggle the per-device streaming debug flag. */\n setStreamingDebug(enabled: boolean): void {\n this.streamingDebug = enabled\n }\n\n /**\n * Test seam exposing the source-RTP activity watchdog state. Production\n * code MUST NOT read this — it exists so unit tests can assert recovery\n * behavior without inspecting private timer handles (which can't be\n * compared by reference, and tracking them externally would race with\n * arm/cancel paths). Mirrors the `firstVideoWatchdog` behavior but\n * fires on every-packet activity rather than first-packet only.\n */\n getRtpActivityWatchdogStatus(): { armed: boolean; firedCount: number } {\n return {\n armed: this.rtpActivityWatchdog !== undefined,\n firedCount: this.rtpActivityWatchdogFiredCount,\n }\n }\n\n // ── Decoded audio-chunk plane (Phase 5 / D9) ─────────────────────────\n\n /**\n * Open a decoded audio-chunk subscription — the poll-based, tRPC-reachable\n * replacement for the live-object `onDecodedAudioChunk` callback. The\n * broker registers a per-subscription FIFO queue and returns a\n * `subscriptionId` the consumer drains via `pullAudioChunks`.\n */\n subscribeAudioChunks(options?: { readonly tag?: string }): SubscribeAudioChunksResult {\n const tag = options?.tag\n if (!tag) {\n this.logger?.warn(`subscribeAudioChunks called without tag — listClients will show 'unknown'`)\n }\n const result = this.audioChunkPlane.subscribe({ tag })\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 return result\n }\n\n /** Drain up to `maxCount` `DecodedAudioChunk`s for an audio-chunk subscription. */\n pullAudioChunks(subscriptionId: string, maxCount: number): readonly DecodedAudioChunk[] {\n return this.audioChunkPlane.pull(subscriptionId, maxCount)\n }\n\n /** Release an audio-chunk subscription. Returns `true` when it existed. */\n unsubscribeAudioChunks(subscriptionId: string): boolean {\n const released = this.audioChunkPlane.unsubscribe(subscriptionId)\n if (released) this.checkDemand()\n return released\n }\n\n // ── Shared-memory frame-handle plane (Phase 5 / D9) ──────────────────\n\n /**\n * Resolve the stream descriptor the `FrameHandlePlane` needs to create a\n * `frameSink: 'shm'` decoder session — codec, numeric device id, log tag,\n * and (for a pull-mode decoder) the local pipe URL. Returns `null` before\n * the broker has a source.\n */\n private resolveFrameHandleStreamInfo(): FrameHandlePlaneStreamInfo | null {\n const codec = this.detectedCodec ?? this.source?.videoCodec\n if (!codec || !this.source) return null\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 // Push-mode decoders (the node-av default) consume the broker's\n // `pushPacket` feed; only a pull-mode decoder needs the local pipe URL.\n // The plane decides per-session via this value.\n return {\n codec,\n ...(Number.isFinite(numericDeviceId) ? { numericDeviceId } : {}),\n tag: `broker:${this.deviceId}`,\n // push-mode is the node-av default today; pull-mode URL wiring is deferred to a later Phase-5 task.\n pullModeUrl: null,\n }\n }\n\n /** Lazily construct the frame-handle plane, returning `null` with no decoder. */\n private ensureFrameHandlePlane(): FrameHandlePlane | null {\n if (this.frameHandlePlane) return this.frameHandlePlane\n if (!this.decoderApi) {\n this.logger?.warn('subscribeFrameHandles: no decoder API — frame handles unavailable')\n return null\n }\n this.frameHandlePlane = new FrameHandlePlane({\n decoderApi: this.decoderApi,\n logger: this.logger?.child('frame-handle-plane'),\n resolveStreamInfo: () => this.resolveFrameHandleStreamInfo(),\n })\n return this.frameHandlePlane\n }\n\n /**\n * Subscribe to this broker's shared-memory frame-handle stream — the broker's\n * decoded-frame surface. The broker spins up (or reuses) a `frameSink: 'shm'`\n * decoder session producing `input.format` and returns a `subscriptionId`\n * the consumer polls via `pullFrameHandles`. fps throttling is implicit\n * (latest-wins ring reads); `input.maxFps` is echoed as a cadence hint.\n */\n async subscribeFrameHandles(\n input: Omit<SubscribeFramesInput, 'brokerId'>,\n ): Promise<SubscribeFramesResult> {\n const plane = this.ensureFrameHandlePlane()\n if (!plane) {\n throw new Error('stream-broker: no decoder available for frame-handle subscription')\n }\n this.checkDemand()\n const { subscriptionId, maxFps } = await plane.subscribe({\n format: input.format,\n maxFps: input.maxFps,\n tag: input.tag,\n })\n return { subscriptionId, maxFps }\n }\n\n /** Drain up to `maxCount` `FrameHandle`s for a frame-handle subscription. */\n pullFrameHandles(subscriptionId: string, maxCount: number): readonly FrameHandle[] {\n return this.frameHandlePlane?.pullHandles(subscriptionId, maxCount) ?? []\n }\n\n /** Release a frame-handle subscription. Returns `true` when it existed. */\n async unsubscribeFrameHandles(subscriptionId: string): Promise<boolean> {\n const plane = this.frameHandlePlane\n if (!plane) return false\n const released = await plane.unsubscribe(subscriptionId)\n if (released && plane.subscriberCount === 0) {\n this.checkDemand()\n }\n return released\n }\n\n getStats(): BrokerStats {\n // Phase 5 / D9: decoded frames flow through the shm frame plane's\n // per-format decoder sessions. `decodeFps` is the plane's aggregate\n // decoded-frame rate; `inputFps` stays from the encoded-packet counter.\n return {\n status: this._suspended ? 'idle' : this._status,\n inputFps: this.encodedInputFps,\n decodeFps: this.frameHandlePlane?.decodeFps() ?? 0,\n encodedSubscribers: this.encodedCallbacks.size + this.rtpVideoCallbacks.size,\n decodedSubscribers: this.frameHandlePlane?.subscriberCount ?? 0,\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.frameHandlePlane?.decoderNodeIds()[0] ?? null,\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 encoded: readonly BrokerEncodedClient[]\n readonly pipeClients: number\n readonly encodedSubscribers: number\n } {\n const encoded: BrokerEncodedClient[] = []\n for (const entry of this.encodedCallbacks.values()) {\n encoded.push({\n id: entry.id,\n channel: entry.channel,\n attribution: entry.attribution,\n subscribedAt: entry.subscribedAt,\n packetsDelivered: entry.packetsDelivered,\n })\n }\n for (const entry of this.rtpVideoCallbacks.values()) {\n encoded.push({\n id: entry.id,\n channel: entry.channel,\n attribution: entry.attribution,\n subscribedAt: entry.subscribedAt,\n packetsDelivered: entry.packetsDelivered,\n })\n }\n\n return {\n rtsp: this.rtspRestreamer.listSessionInfos(),\n // Phase 5 / D9: the `decoded` channel is the shm frame-handle plane's\n // subscriber set. `framesDropped` is always 0 — a slow consumer's\n // dropped frames are recycled silently at the ring, not counted here.\n decoded: (this.frameHandlePlane?.listSubscribers() ?? []).map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n maxFps: s.maxFps,\n framesDelivered: s.framesDelivered,\n framesDropped: 0,\n })),\n audio: this.audioChunkPlane.listSubscribers().map((s) => ({\n tag: s.tag,\n subscribedAt: s.subscribedAt,\n chunksDelivered: s.chunksDelivered,\n })),\n encoded,\n pipeClients: this.pipeServer.getClientCount(),\n encodedSubscribers: this.encodedCallbacks.size + this.rtpVideoCallbacks.size,\n }\n }\n\n /**\n * Force-disconnect a single consumer. Matches by channel:\n * - `rtsp`: by `sessionId` (calls `RtspRestreamer.killSession`).\n * - `decoded`: by `tag` — releases every shm frame-handle subscription\n * whose tag matches (the plane winds the decoder session down once its\n * last subscriber leaves).\n * - `audio`: by `tag` — drops every audio subscriber whose tag matches,\n * then re-evaluates demand.\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 if (channel === 'decoded') {\n const plane = this.frameHandlePlane\n if (!plane) return false\n const matched = plane.listSubscribers().some((s) => s.tag === handle)\n if (!matched) return false\n // Plane unsubscribe is async (decoder-session teardown); fire it in the\n // background — the caller only needs the synchronous \"matched\" verdict.\n plane.killByTag(handle).then((killed) => {\n this.logger?.info('frame-handle subscribers force-disconnected', {\n meta: { handle, killed },\n })\n this.checkDemand()\n }).catch((err: unknown) => {\n this.logger?.warn('frame-handle killByTag failed', {\n meta: { handle, error: errMsg(err) },\n })\n })\n return true\n }\n const killed = this.audioChunkPlane.killByTag(handle)\n if (killed === 0) return false\n this.logger?.info('audio subscribers force-disconnected', {\n meta: { handle, killed, remaining: this.audioChunkPlane.subscriberCount },\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\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 // Rolling activity watchdog: reset on every packet. Wi-Fi\n // packet-loss stalls on the TCP socket (audio+video share\n // the interleaved channel) show up here as the per-packet\n // gap exceeding RTP_ACTIVITY_TIMEOUT_MS; the expiry tears\n // the reader down so scheduleReconnect rebuilds the source.\n this.kickRtpActivityWatchdog()\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 // Capture into the source-RTP pre-buffer (current GOP) BEFORE the\n // fan-out, so a subscriber that bootstraps from it sees the current\n // packet as the ring's tail. Keyframe flags were set by the\n // depacketizer above.\n this.captureRtpForPreBuffer(rtpData)\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, entry] of this.rtpVideoCallbacks) {\n try {\n cb(rtpData)\n entry.packetsDelivered += 1\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 this.audioChunkPlane.fanout(chunk)\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 // Audio shares the same TCP interleaved socket as video — an\n // audio packet is equally good proof the source is alive, so\n // it also resets the rolling activity watchdog.\n this.kickRtpActivityWatchdog()\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, entry] of this.encodedCallbacks) {\n cb(packet)\n entry.packetsDelivered += 1\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 // ── Rolling RTP activity watchdog ─────────────────────────────────\n\n /**\n * Kick the rolling source-RTP activity watchdog: cancel any prior arm\n * and schedule a fresh expiry. Called per RTP packet from the native\n * RTSP reader, the RFC 4571 reader, AND `pushVideoRtp` so push-rtp\n * sources stay covered by the same arm/cancel lifecycle (the expiry\n * path is reader-aware — it does nothing when no reader is alive).\n *\n * Cheap (clearTimeout + setTimeout); called at ~50-100Hz on a healthy\n * stream — the overhead is negligible compared to the depacketize and\n * fan-out work each packet already does.\n */\n private kickRtpActivityWatchdog(): void {\n if (this.manualStop || this.stopping || this._suspended) return\n if (this.rtpActivityWatchdog) {\n clearTimeout(this.rtpActivityWatchdog)\n }\n this.rtpActivityWatchdog = setTimeout(() => {\n this.onRtpActivityWatchdogExpired()\n }, StreamBroker.RTP_ACTIVITY_TIMEOUT_MS)\n }\n\n /** Cancel the rolling RTP activity watchdog. Idempotent. */\n private cancelRtpActivityWatchdog(): void {\n if (!this.rtpActivityWatchdog) return\n clearTimeout(this.rtpActivityWatchdog)\n this.rtpActivityWatchdog = undefined\n }\n\n /**\n * Activity watchdog expiry: source RTP went silent for longer than\n * `RTP_ACTIVITY_TIMEOUT_MS`. The TCP socket is still open as far as\n * the OS knows but no data has arrived — typical Wi-Fi packet-loss\n * stall. Tear the reader down (\"activity watchdog fired: stream\n * inactivity\") so `scheduleReconnect()` rebuilds the source.\n *\n * Only fires for sources that actually have a reader to tear down:\n * native RTSP and RFC 4571. push / push-rtp / RTMP sources are\n * provider-driven and don't go through this lifecycle — they have\n * their own `PUSH_STALL_TIMEOUT_MS` path.\n */\n private onRtpActivityWatchdogExpired(): void {\n this.rtpActivityWatchdog = undefined\n this.rtpActivityWatchdogFiredCount++\n if (this.manualStop || this.stopping || this._suspended) return\n\n const meta = {\n timeoutMs: StreamBroker.RTP_ACTIVITY_TIMEOUT_MS,\n ...this.sourceMetaForLog(),\n }\n this.logger?.warn(\n 'source-RTP activity watchdog: no packets within window — forcing reconnect',\n { meta },\n )\n\n if (this.nativeClient) {\n if (this.streamingDebug) {\n this.logger?.info('activity watchdog: destroying native RTSP reader', { meta })\n }\n this.destroyNativeClient()\n this._status = 'error'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n this.scheduleReconnect()\n return\n }\n\n if (this.rfc4571Reader) {\n if (this.streamingDebug) {\n this.logger?.info('activity watchdog: destroying rfc4571 reader', { meta })\n }\n this.destroyRfc4571Reader()\n this._status = 'error'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n // The lib's loopback may have idle-rotated — ask the publisher to\n // refresh before the next dial picks up a stale URL.\n this.notifySourceRefresh()\n this.scheduleReconnect()\n return\n }\n\n // No reader to tear down (push / push-rtp / RTMP) — log only.\n // The provider owns the lifecycle on those paths.\n if (this.streamingDebug) {\n this.logger?.info(\n 'activity watchdog: no reader to tear down (push/RTMP source) — log only',\n { meta },\n )\n }\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 /**\n * Moleculer nodeID of a decoder provider hosting one of this broker's shm\n * frame-plane sessions. `null` when the plane has no session. Used by the\n * broker-manager's `rotateDecodersOnNode` to drop only the sessions\n * affected by a per-agent event (e.g. an hwaccel override change).\n */\n getDecoderNodeId(): string | null {\n return this.frameHandlePlane?.decoderNodeIds()[0] ?? null\n }\n\n /**\n * Force this broker's shm frame-plane decoder sessions hosted on\n * `agentNodeId` to rotate — destroy and rebuild so they re-pull the latest\n * per-agent preferences (hwaccel backend, …). A no-op when the broker has\n * no frame plane. Subscriptions survive the rotation.\n */\n async rotateDecoderSession(reason: string, agentNodeId?: string): Promise<void> {\n const plane = this.frameHandlePlane\n if (!plane) return\n const target = agentNodeId\n ?? (plane.decoderNodeIds()[0]?.split('/')[0])\n if (!target) return\n this.logger?.info('rotating shm decoder sessions', { meta: { reason, agentNodeId: target } })\n await plane.rotate(target, reason)\n }\n\n\n private destroyNativeClient(): void {\n if (this.nativeClient) {\n this.nativeClient.destroy()\n this.nativeClient = null\n }\n this.resetRtpPipeline()\n // Reader is gone — stop the rolling activity watchdog so it can't\n // fire against a destroyed client. The next reconnect re-arms it\n // on the first RTP packet of the fresh stream.\n this.cancelRtpActivityWatchdog()\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 // Same reasoning for the rolling activity watchdog: reader gone,\n // no source to monitor. The next reconnect re-arms on first RTP.\n this.cancelRtpActivityWatchdog()\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.pushRtpFlagDepacketizer) {\n this.pushRtpFlagDepacketizer.reset()\n this.pushRtpFlagDepacketizer = null\n }\n // Drop the source-RTP pre-buffer with the reader — the GOP it holds\n // belongs to the connection being torn down. Keeping it across an\n // on-demand idle/redial leaks a stale GOP into the next session's\n // instant-start replay (root cause of the device-97 cold-start stutter).\n this.resetRtpPreBuffer()\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 /**\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 the audio-chunk plane unchanged.\n this.audioChunkPlane.fanout(chunk)\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, entry] of this.encodedCallbacks) {\n cb(packet)\n entry.packetsDelivered += 1\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 * Closure that wakes the broker (subscribes a synthetic consumer to its\n * encoded-data fan-out, which trips `checkDemand` → upstream dial) and\n * returns a teardown handle. Released by the listen server when the RTSP\n * session ends, so the broker can idle-tear-down normally when no real\n * consumers remain. Without this, an RTSP client doing DESCRIBE on a\n * cold broker gets back a 200 with empty SDP (broker never dialed\n * because nothing subscribed) — `ffprobe`, recording sidecars, and any\n * external RTSP integration are effectively broken until something else\n * happens to be actively consuming the broker.\n */\nexport type BrokerWarmUp = () => () => void\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 /** brokerId → warm-up closure registered alongside the restreamer */\n private readonly brokerWarmUps = new Map<string, BrokerWarmUp>()\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(\n brokerId: string,\n restreamer: RtspRestreamer,\n warmUp?: BrokerWarmUp,\n existingToken?: string,\n ): 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 if (warmUp) this.brokerWarmUps.set(brokerId, warmUp)\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 this.brokerWarmUps.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 void this.dispatch(socket, requestBuffer)\n }\n\n socket.on('data', onData)\n socket.on('error', () => {})\n }\n\n /**\n * Async dispatch path for the first complete RTSP request. Resolves the\n * restreamer, wakes the broker via its `warmUp` closure, awaits the SDP\n * (broker only populates `restreamer.sdp` after dialing the upstream and\n * parsing `onVideoTrack`), and then hands the socket off to a\n * `RtspSession`. The warm-up handle stays alive for the entire socket\n * lifetime so `checkDemand` on the broker keeps the dial up; we release\n * it on `close` / `error`. Without this hop, `DESCRIBE` would race the\n * cold broker and return 200 with empty SDP.\n */\n private async dispatch(socket: net.Socket, requestBuffer: string): Promise<void> {\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 // Acquire the broker warm-up consumer (no-op when the broker is\n // already streaming — `checkDemand` is monotonic in subscriber count).\n // The release handle is held until the socket closes so the dial stays\n // up for the entire RTSP session, never seeing a zero-consumer dip\n // that would tear down the upstream pull mid-PLAY.\n const warmUp = brokerId ? this.brokerWarmUps.get(brokerId) : undefined\n const releaseWarmUp = warmUp ? warmUp() : null\n let released = false\n const releaseOnce = (): void => {\n if (released) return\n released = true\n try { releaseWarmUp?.() } catch { /* swallow — listener teardown */ }\n }\n socket.once('close', releaseOnce)\n socket.once('error', releaseOnce)\n\n // Wait briefly for the broker to dial upstream and parse the SDP.\n // RTSP DESCRIBE is the operation that fails worst with empty SDP, so\n // we block here up to ~3 s. Bounded: a dead upstream camera shouldn't\n // hang the connection forever — we fall through to whatever SDP the\n // restreamer has (likely empty) and let the client get a normal 200\n // with whatever it can parse.\n const sdpReady = await this.waitForSdp(restreamer, isMuted, 3000)\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 releaseOnce()\n }, isMuted)\n restreamer.addSession(session)\n\n // Diagnostic-only: surface that we waited (or timed out) so an\n // operator chasing \"ffprobe got empty SDP\" sees the gap.\n void sdpReady\n\n // Re-inject the buffered data so the session processes the first request\n socket.unshift(Buffer.from(requestBuffer, 'ascii'))\n }\n\n /**\n * Poll the restreamer's SDP cache every 50 ms up to `timeoutMs`. The\n * cache is populated by the broker's `onVideoTrack` callback once the\n * RTSP DESCRIBE/SETUP/PLAY against the upstream camera completes — for\n * a cold broker that's typically ~200-1000 ms. Resolves `true` if the\n * SDP becomes non-empty within the budget, `false` on timeout.\n */\n private async waitForSdp(\n restreamer: RtspRestreamer,\n isMuted: boolean,\n timeoutMs: number,\n ): Promise<boolean> {\n const start = Date.now()\n const have = (): boolean => {\n const s = isMuted ? (restreamer.getMutedSdp() ?? restreamer.getSdp()) : restreamer.getSdp()\n return !!s && s.length > 0\n }\n if (have()) return true\n return new Promise<boolean>((resolve) => {\n const interval = setInterval(() => {\n if (have()) {\n clearInterval(interval)\n resolve(true)\n return\n }\n if (Date.now() - start > timeoutMs) {\n clearInterval(interval)\n resolve(false)\n }\n }, 50)\n })\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\nimport { parseProfileBrokerId } 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 // Skip profile-keyed alias tokens (see getEntriesForDevice).\n if (parseProfileBrokerId(brokerId)) 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 /**\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 // Skip profile-keyed alias tokens (`${deviceId}/high|mid|low`) — they\n // are an internal exporter convergence mechanism, NOT raw camStream\n // restreams. The raw/live-view path must surface source camStreams only;\n // profile entries are served via getProfileRtspEntries / getEntry.\n if (parseProfileBrokerId(brokerId)) 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 * Low-level ffmpeg argument fragments shared, byte-for-byte, across the\n * broker's ffmpeg sites (transcode egress, the WebRTC transcode feed, the\n * mjpeg decoder session). These are the pieces that were genuinely identical;\n * the higher-level builders differ (RTSP vs stdin input, hw vs sw encoders,\n * scale-filter shapes) and stay separate. Pure + side-effect-free.\n */\n\n/** Audio encoders addressable by both the preset model and the EncodeProfile model. */\nexport type FfmpegAudioCodec = 'opus' | 'aac' | 'pcmu' | 'pcma' | 'copy'\n\nexport interface AudioEncoderArgsOptions {\n readonly bitrateKbps?: number\n readonly sampleRateHz?: number\n readonly channels?: number\n}\n\nconst AUDIO_ENCODER_BY_CODEC: Record<FfmpegAudioCodec, string> = {\n opus: 'libopus',\n aac: 'aac',\n pcmu: 'pcm_mulaw',\n pcma: 'pcm_alaw',\n copy: 'copy',\n}\n\n/**\n * Build the audio-encode args `-c:a <enc> [-b:a Nk] [-ar N] [-ac N]`. Each knob\n * is emitted only when provided, in this fixed order — matching what every\n * broker site already emitted by hand.\n */\nexport function audioEncoderArgs(codec: FfmpegAudioCodec, opts: AudioEncoderArgsOptions = {}): string[] {\n const args = ['-c:a', AUDIO_ENCODER_BY_CODEC[codec]]\n if (opts.bitrateKbps !== undefined) args.push('-b:a', `${opts.bitrateKbps}k`)\n if (opts.sampleRateHz !== undefined) args.push('-ar', String(opts.sampleRateHz))\n if (opts.channels !== undefined) args.push('-ac', String(opts.channels))\n return args\n}\n\n/** The `-hide_banner -loglevel <level>` preamble every broker ffmpeg site opens with. */\nexport function logBannerArgs(level: 'error' | 'warning' | 'info'): string[] {\n return ['-hide_banner', '-loglevel', level]\n}\n","/**\n * Shared, side-effect-free ffmpeg invocation builder for the stream-broker.\n *\n * One place that embeds hardware acceleration (decode `-hwaccel` from the\n * platform probe + encoder selection), scale, thread count, audio, consumer\n * `outputArgs` and a discriminated output sink. Every broker ffmpeg site builds\n * its argument list here so hw-accel and the system-configured ffmpeg behaviour\n * stay consistent across the transcode pipeline (today) and the webrtc-feed /\n * decoder-session paths (follow-up).\n *\n * The builder returns the ARGUMENT list only — the binary path comes from the\n * `system-config` `ffmpeg.binaryPath` section and is passed to `spawn`\n * separately, keeping this function pure.\n */\nimport type { HardwareEncoders, HardwareEncoderId, AudioCodecTarget } from '@camstack/types'\nimport { audioEncoderArgs, logBannerArgs } from './ffmpeg-args-common.js'\n\n/** The `system-config` `ffmpeg.hwAccel` user preference. */\nexport type FfmpegHwAccelSetting =\n | 'auto'\n | 'none'\n | 'videotoolbox'\n | 'vaapi'\n | 'qsv'\n | 'cuda'\n\n/** The `prefer` value passed to `platformProbe.resolveHwAccel` (no `'auto'`). */\nexport type FfmpegHwAccelPrefer = Exclude<FfmpegHwAccelSetting, 'auto'>\n\n/** Resolved `system-config` `ffmpeg` section the broker spawns against. */\nexport interface FfmpegSystemConfig {\n /** `ffmpeg.binaryPath` — passed to `spawn`, never into the arg list. */\n readonly binaryPath: string\n /** `ffmpeg.hwAccel` — drives decode `prefer` + encoder forcing. */\n readonly hwAccel: FfmpegHwAccelSetting\n /** `ffmpeg.threadCount` — `0` = auto (let ffmpeg decide). */\n readonly threadCount: number\n}\n\n/** Where the transcoded output goes. */\nexport type FfmpegSink =\n | { readonly kind: 'rtsp-listen'; readonly url: string }\n | { readonly kind: 'stdout'; readonly container: 'h264' | 'hevc' | 'mpegts' }\n\n/** Audio codecs that can be muxed into an RTP sidecar output. */\nexport type AudioSidecarCodec = Extract<AudioCodecTarget, 'aac' | 'opus' | 'pcmu'>\n\n/** Narrow an audio target to a sidecar-capable codec, or `null` (copy/none). */\nexport function toAudioSidecarCodec(audio: AudioCodecTarget): AudioSidecarCodec | null {\n return audio === 'aac' || audio === 'opus' || audio === 'pcmu' ? audio : null\n}\n\n/**\n * Optional second ffmpeg output that maps the source audio to its own RTP\n * stream (over UDP) alongside an elementary video stdout sink. ffmpeg writes\n * the authoritative SDP for this stream to `sdpFile` (`-sdp_file`), which the\n * egress grafts onto the restreamer's video-only SDP.\n */\nexport interface FfmpegAudioSidecar {\n readonly codec: AudioSidecarCodec\n /** `rtp://127.0.0.1:<port>` the egress listens on. */\n readonly rtpUrl: string\n /** Path ffmpeg writes the RTP stream's SDP to. */\n readonly sdpFile: string\n}\n\nexport interface FfmpegTranscodeInvocation {\n readonly sourceUrl: string\n readonly targetCodec: 'H264' | 'H265'\n readonly encoder: HardwareEncoderId\n /** Decode hw-accel backend; `null`/`'none'` ⇒ software decode (no `-hwaccel`). */\n readonly decodeHwAccel: string | null\n readonly scale: { readonly width: number; readonly height: number } | null\n readonly audio: AudioCodecTarget\n /** `0` = auto (omit `-threads`). */\n readonly threadCount: number\n readonly outputArgs: readonly string[]\n readonly sink: FfmpegSink\n /**\n * When set, append a second RTP audio output. Only meaningful with an\n * elementary video sink (which is `-an`); the audio rides the sidecar.\n */\n readonly audioSidecar?: FfmpegAudioSidecar | null\n}\n\n/**\n * Map the `system-config` `ffmpeg.hwAccel` preference to the `prefer` argument\n * of `platformProbe.resolveHwAccel`: `'auto'` lets the resolver rank backends,\n * `'none'` forces software, a specific backend pins it.\n */\nexport function hwAccelSettingToPrefer(setting: FfmpegHwAccelSetting): FfmpegHwAccelPrefer | null {\n if (setting === 'auto') return null\n return setting\n}\n\n/**\n * Pick the decode `-hwaccel` backend, gated by what the configured ffmpeg\n * binary actually supports (`platformProbe.getHardwareDecodeAccels`).\n *\n * `preferred` entries are literal `-hwaccel` names (kernel-resolved), so they\n * intersect directly with the probed method list. Conservative by design:\n * - empty `preferred` → `null` (software decode);\n * - empty `supportedMethods` (probe miss / build reports none) → keep the top\n * preference and let the egress's per-stream software fallback cover failures;\n * - otherwise return the first preference the build supports, or `null` when it\n * supports none of them (positive evidence to skip a doomed hw-decode spawn).\n */\nexport function pickDecodeHwAccel(\n preferred: readonly string[],\n supportedMethods: readonly string[],\n): string | null {\n if (preferred.length === 0) return null\n if (supportedMethods.length === 0) return preferred[0] ?? null\n const supported = new Set(supportedMethods.map((m) => m.toLowerCase()))\n return preferred.find((name) => supported.has(name.toLowerCase())) ?? null\n}\n\n/**\n * Pick the video encoder, honouring the `hwAccel` preference: `'none'` forces\n * the software encoder; otherwise the probe's default (hw when available) is\n * used, falling back to software when no probe result is present.\n */\nexport function pickVideoEncoder(\n target: 'H264' | 'H265',\n encoders: HardwareEncoders | null,\n hwAccel: FfmpegHwAccelSetting,\n): HardwareEncoderId {\n const software: HardwareEncoderId = target === 'H264' ? 'libx264' : 'libx265'\n if (hwAccel === 'none' || !encoders) return software\n return target === 'H264' ? encoders.defaultH264 : encoders.defaultH265\n}\n\n/** Audio encoder args for a transcode target (preset values over the shared low-level builder). */\nexport function pickAudioEncoderArgs(audio: AudioCodecTarget): readonly string[] {\n switch (audio) {\n case 'aac':\n return audioEncoderArgs('aac', { bitrateKbps: 128, sampleRateHz: 48000, channels: 2 })\n case 'opus':\n return audioEncoderArgs('opus', { bitrateKbps: 64, sampleRateHz: 48000, channels: 2 })\n case 'pcmu':\n return audioEncoderArgs('pcmu', { sampleRateHz: 8000, channels: 1 })\n case 'copy':\n return audioEncoderArgs('copy')\n case 'none':\n return ['-an']\n default:\n return audioEncoderArgs('aac', { bitrateKbps: 128 })\n }\n}\n\n/**\n * Build the `-vf` filter args. A consumer `-vf` in `outputArgs` always wins (we\n * never emit a duplicate `-vf`); otherwise an explicit scale is applied;\n * otherwise no filter (native source resolution).\n */\nfunction buildVideoFilterArgs(\n scale: { readonly width: number; readonly height: number } | null,\n outputArgs: readonly string[],\n): string[] {\n if (outputArgs.some((a) => a === '-vf')) return []\n if (scale) return ['-vf', `scale=${scale.width}:${scale.height}`]\n return []\n}\n\n/** Build the output-sink args (terminal — the output target is last). */\nfunction buildSinkArgs(sink: FfmpegSink): string[] {\n if (sink.kind === 'stdout') {\n return ['-f', sink.container, 'pipe:1']\n }\n return ['-f', 'rtsp', '-rtsp_transport', 'tcp', '-rtsp_flags', 'listen', sink.url]\n}\n\n/**\n * Build a second output that maps the source audio to RTP-over-UDP. `0:a:0?`\n * makes the audio stream optional — a source with no audio just skips it\n * instead of failing the whole ffmpeg invocation.\n */\nfunction buildAudioSidecarArgs(sidecar: FfmpegAudioSidecar): string[] {\n return [\n '-map', '0:a:0?',\n ...pickAudioEncoderArgs(sidecar.codec),\n '-f', 'rtp',\n '-sdp_file', sidecar.sdpFile,\n sidecar.rtpUrl,\n ]\n}\n\n/**\n * Assemble the full ffmpeg argument list for a transcode invocation. Layout:\n *\n * -hide_banner -loglevel warning\n * [-hwaccel <decode>] (before -i, when set and not 'none')\n * -rtsp_transport tcp -i <src>\n * [-vf scale=WxH] (unless a consumer -vf is present)\n * -c:v <encoder>\n * [-threads <N>] (when N > 0)\n * <audio args>\n * <consumer outputArgs verbatim>\n * <sink args> (terminal: output target last)\n */\nexport function buildFfmpegArgs(inv: FfmpegTranscodeInvocation): string[] {\n const decodeArgs =\n inv.decodeHwAccel && inv.decodeHwAccel !== 'none' ? ['-hwaccel', inv.decodeHwAccel] : []\n const threadArgs = inv.threadCount > 0 ? ['-threads', String(inv.threadCount)] : []\n\n // An elementary stdout sink (raw h264/hevc) cannot carry audio — force `-an`\n // so ffmpeg doesn't waste cycles encoding an audio stream it can't mux.\n const isElementaryVideoSink =\n inv.sink.kind === 'stdout' && (inv.sink.container === 'h264' || inv.sink.container === 'hevc')\n const audioArgs = isElementaryVideoSink ? ['-an'] : pickAudioEncoderArgs(inv.audio)\n\n return [\n ...logBannerArgs('warning'),\n ...decodeArgs,\n '-rtsp_transport', 'tcp',\n '-i', inv.sourceUrl,\n ...buildVideoFilterArgs(inv.scale, inv.outputArgs),\n '-c:v', inv.encoder,\n ...threadArgs,\n ...audioArgs,\n ...inv.outputArgs,\n ...buildSinkArgs(inv.sink),\n ...(inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []),\n ]\n}\n","import { createSocket, type Socket } from 'node:dgram'\n\n/**\n * Receives RTP audio datagrams ffmpeg pushes to a local UDP port (the\n * transcode-egress audio sidecar). The egress forwards each datagram verbatim\n * to the restreamer's `pushAudioRtpPassthrough`.\n */\nexport interface AudioRtpReceiver {\n /** Bind a UDP socket; resolves with the `rtp://` URL ffmpeg should target. */\n start(onPacket: (rtp: Buffer) => void): Promise<{ rtpUrl: string }>\n stop(): void\n}\n\nexport class UdpAudioRtpReceiver implements AudioRtpReceiver {\n private readonly socket: Socket = createSocket('udp4')\n private stopped = false\n\n start(onPacket: (rtp: Buffer) => void): Promise<{ rtpUrl: string }> {\n return new Promise<{ rtpUrl: string }>((resolve, reject) => {\n this.socket.once('error', reject)\n this.socket.on('message', (msg) => {\n if (!this.stopped) onPacket(msg)\n })\n this.socket.bind(0, '127.0.0.1', () => {\n const addr = this.socket.address()\n resolve({ rtpUrl: `rtp://127.0.0.1:${addr.port}` })\n })\n })\n }\n\n stop(): void {\n if (this.stopped) return\n this.stopped = true\n try {\n this.socket.close()\n } catch {\n /* already closed */\n }\n }\n}\n","/**\n * TranscodeEgress — the egress half of `getStreamWithCodec`.\n *\n * The broker's RTSP listen server is restream-only (no ANNOUNCE/RECORD), so an\n * ffmpeg that *publishes* to it 404s. ffmpeg's `-rtsp_flags listen` server mode\n * is unreliable across builds (it attempts to connect, not listen). So we use\n * the pattern already proven in this codebase (the WebRTC transcode feed):\n * ffmpeg writes the transcoded **Annex-B elementary stream to stdout**, and we\n * push those bytes into a `RtspRestreamer` via `pushPacket` (which deframes +\n * repacketizes). The consumer gets back a normal broker token URL.\n *\n * VIDEO-ONLY: `pushPacket` handles video; audio in the restreamer travels a\n * separate RTP path, so the transcode egress drops audio (matching the WebRTC\n * transcode feed). Audio-in-transcode is a future enhancement.\n *\n * Decode hw-accel is tried first (big CPU win) but is fragile for some HEVC\n * streams (VideoToolbox can fail to decode). If ffmpeg dies before producing any\n * output and a decode backend was set, we respawn once with software decode.\n *\n * Every collaborator is injected so the wiring is unit-testable without real\n * ffmpeg or sockets.\n */\nimport type { ChildProcess, spawn as nodeSpawn } from 'node:child_process'\nimport { readFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport { errMsg } from '@camstack/types'\nimport type { IScopedLogger, EncodedPacket } from '@camstack/types'\nimport {\n buildFfmpegArgs,\n type FfmpegTranscodeInvocation,\n type FfmpegAudioSidecar,\n type AudioSidecarCodec,\n} from './ffmpeg-invocation.js'\nimport { extractAudioMediaBlock } from '../rtsp/egress-audio-sdp.js'\nimport { UdpAudioRtpReceiver, type AudioRtpReceiver } from '../rtsp/udp-audio-rtp-receiver.js'\n\n/** Minimal surface of RtspRestreamer the egress feeds (Annex-B chunks + optional audio). */\nexport interface RtspRestreamerLike {\n pushPacket(packet: EncodedPacket): void\n /** Forward an RTP audio datagram (transcode-egress audio sidecar). */\n pushAudioRtpPassthrough?(rtp: Buffer): void\n /** Graft the egress audio track onto the push-mode SDP. */\n setEgressAudioBlock?(block: string | null): void\n}\n\n/**\n * A registered egress channel: a restreamer already registered on the broker's\n * main listen server, its shareable consumer URL, and a release hook that\n * unregisters + destroys it. Created by production code that holds the concrete\n * `RtspRestreamer` type, so the egress only ever sees the structural `*Like`.\n */\nexport interface EgressChannel {\n readonly restreamer: RtspRestreamerLike\n readonly consumerUrl: string\n release(): void\n}\n\nexport interface TranscodeEgressDeps {\n readonly logger: IScopedLogger\n readonly ffmpegBinaryPath: string\n readonly spawnFn: typeof nodeSpawn\n /** Create + register a restreamer for this brokerId on the main listen server. */\n readonly acquireChannel: (brokerId: string) => EgressChannel\n /** How long to wait for ffmpeg's first output byte before declaring it dead. */\n readonly firstDataTimeoutMs?: number\n /** Audio-sidecar collaborators — injected so the wiring is testable. */\n readonly audioReceiverFactory?: () => AudioRtpReceiver\n readonly readSdpFile?: (path: string) => Promise<string>\n readonly makeSdpFilePath?: () => string\n}\n\nexport interface TranscodeEgressArgs {\n readonly brokerId: string\n /** Invocation minus the sink + audioSidecar — the egress fills those in. */\n readonly invocation: Omit<FfmpegTranscodeInvocation, 'sink' | 'audioSidecar'>\n /**\n * Opt-in audio sidecar. When set, the egress maps the source audio to an RTP\n * output (UDP) and forwards it to the restreamer, grafting ffmpeg's audio SDP\n * onto the push-mode video SDP. Off by default — the default egress is the\n * live-verified video-only path. NOT live-verified end-to-end (no A+V\n * consumer drives it yet).\n */\n readonly audio?: { readonly codec: AudioSidecarCodec }\n}\n\nconst DEFAULT_FIRST_DATA_TIMEOUT_MS = 8_000\n\nexport class TranscodeEgress {\n private child: ChildProcess | null = null\n private channel: EgressChannel | null = null\n private stopped = false\n private startedAtMs = 0\n private audioReceiver: AudioRtpReceiver | null = null\n private audioSidecar: FfmpegAudioSidecar | null = null\n private audioGrafted = false\n\n constructor(\n private readonly deps: TranscodeEgressDeps,\n private readonly args: TranscodeEgressArgs,\n ) {}\n\n /** Spawn ffmpeg, stream its stdout into the restreamer. Returns the consumer URL. */\n async start(): Promise<string> {\n const channel = this.deps.acquireChannel(this.args.brokerId)\n this.channel = channel\n try {\n // Only the audio path needs the async UDP bind before spawning; the\n // default video-only egress spawns synchronously.\n if (this.args.audio) {\n await this.setupAudioSidecar(channel.restreamer)\n }\n await this.spawnWithDecodeFallback(channel.restreamer)\n } catch (err) {\n // ffmpeg never produced output — don't leak the process or channel.\n await this.stop()\n throw err\n }\n this.deps.logger.info('transcode egress ready', { meta: { brokerId: this.args.brokerId } })\n return channel.consumerUrl\n }\n\n /** Tear down ffmpeg and release the channel registration. */\n async stop(): Promise<void> {\n if (this.stopped) return\n this.stopped = true\n this.killChild()\n if (this.audioReceiver) {\n this.audioReceiver.stop()\n this.audioReceiver = null\n }\n if (this.channel) {\n this.channel.release()\n this.channel = null\n }\n }\n\n /**\n * Set up the audio sidecar (opt-in): bind a UDP RTP receiver that forwards\n * each datagram to the restreamer, and allocate the `-sdp_file` path. ffmpeg\n * is later told to map source audio to `rtpUrl`. No-op when audio isn't\n * requested. NOT live-verified end-to-end (no A+V consumer drives it yet).\n */\n private async setupAudioSidecar(restreamer: RtspRestreamerLike): Promise<void> {\n const audio = this.args.audio\n if (!audio) return\n const factory = this.deps.audioReceiverFactory ?? ((): AudioRtpReceiver => new UdpAudioRtpReceiver())\n const makePath = this.deps.makeSdpFilePath\n ?? ((): string => join(tmpdir(), `camstack-egress-${randomUUID()}.sdp`))\n const receiver = factory()\n this.audioReceiver = receiver\n const { rtpUrl } = await receiver.start((rtp) => restreamer.pushAudioRtpPassthrough?.(rtp))\n this.audioSidecar = { codec: audio.codec, rtpUrl, sdpFile: makePath() }\n this.deps.logger.info('transcode egress: audio sidecar listening', {\n meta: { brokerId: this.args.brokerId, rtpUrl, codec: audio.codec },\n })\n }\n\n /**\n * Read ffmpeg's `-sdp_file` (written when the RTP audio output opens), extract\n * the `m=audio` block and graft it onto the restreamer SDP. Best-effort: a\n * miss just means the consumer negotiates video only.\n */\n private async graftAudioSdp(restreamer: RtspRestreamerLike, sdpFile: string): Promise<void> {\n const read = this.deps.readSdpFile ?? ((p: string): Promise<string> => readFile(p, 'utf8'))\n try {\n const sdpText = await read(sdpFile)\n const block = extractAudioMediaBlock(sdpText)\n if (block) {\n restreamer.setEgressAudioBlock?.(block)\n this.deps.logger.info('transcode egress: audio track grafted onto SDP', {\n meta: { brokerId: this.args.brokerId },\n })\n } else {\n this.deps.logger.warn('transcode egress: ffmpeg SDP had no audio media block', {\n meta: { brokerId: this.args.brokerId },\n })\n }\n } catch (err) {\n this.deps.logger.warn('transcode egress: failed to graft audio SDP', {\n meta: { brokerId: this.args.brokerId, error: errMsg(err) },\n })\n }\n }\n\n /**\n * Try decode hw-accel first; if ffmpeg dies before producing output and a\n * decode backend was requested, respawn once with software decode.\n */\n private async spawnWithDecodeFallback(restreamer: RtspRestreamerLike): Promise<void> {\n const decode = this.args.invocation.decodeHwAccel\n try {\n await this.spawnAttempt(restreamer, decode)\n } catch (err) {\n if (this.stopped) throw err\n if (decode && decode !== 'none') {\n this.deps.logger.warn('transcode egress: hw decode produced no output — retrying software decode', {\n meta: { brokerId: this.args.brokerId, decodeHwAccel: decode, error: errMsg(err) },\n })\n await this.spawnAttempt(restreamer, null)\n return\n }\n throw err\n }\n }\n\n /**\n * Spawn ffmpeg writing the transcoded Annex-B stream to stdout, piping each\n * chunk into the restreamer. Resolves on the FIRST output byte (the pipeline\n * is live); rejects if ffmpeg exits / errors / is silent before then.\n */\n private spawnAttempt(restreamer: RtspRestreamerLike, decodeHwAccel: string | null): Promise<void> {\n const targetCodec = this.args.invocation.targetCodec\n const container: 'h264' | 'hevc' = targetCodec === 'H264' ? 'h264' : 'hevc'\n const packetCodec = targetCodec === 'H264' ? 'h264' : 'h265'\n const args = buildFfmpegArgs({\n ...this.args.invocation,\n decodeHwAccel,\n sink: { kind: 'stdout', container },\n audioSidecar: this.audioSidecar,\n })\n\n return new Promise<void>((resolve, reject) => {\n const child = this.deps.spawnFn(this.deps.ffmpegBinaryPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n this.child = child\n this.startedAtMs = Date.now()\n let settled = false\n\n const timeoutMs = this.deps.firstDataTimeoutMs ?? DEFAULT_FIRST_DATA_TIMEOUT_MS\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n this.killChild()\n reject(new Error(`transcode egress: no output within ${timeoutMs}ms`))\n }, timeoutMs)\n\n child.stdout?.on('data', (chunk: Buffer) => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n resolve()\n // Pipeline is live: graft the audio SDP once (ffmpeg has opened the\n // RTP output and written -sdp_file by now).\n if (this.audioSidecar && !this.audioGrafted) {\n this.audioGrafted = true\n void this.graftAudioSdp(restreamer, this.audioSidecar.sdpFile)\n }\n }\n const ptsMs = Date.now() - this.startedAtMs\n restreamer.pushPacket({\n type: 'video',\n data: chunk,\n pts: ptsMs,\n dts: ptsMs,\n keyframe: false, // the restreamer's deframer detects real keyframes\n codec: packetCodec,\n })\n })\n\n child.stderr?.setEncoding('utf8')\n child.stderr?.on('data', (line: string) => {\n this.deps.logger.debug('transcode egress ffmpeg', {\n meta: { brokerId: this.args.brokerId, line: line.trim() },\n })\n })\n\n child.once('error', (err: Error) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(err)\n })\n\n child.once('exit', (code, signal) => {\n if (settled) return // exited after going live → data just stops\n settled = true\n clearTimeout(timer)\n reject(new Error(`transcode egress: ffmpeg exited before output (code=${code} signal=${signal})`))\n })\n })\n }\n\n private killChild(): void {\n const child = this.child\n this.child = null\n if (child && !child.killed) {\n try {\n child.kill('SIGTERM')\n } catch (err) {\n this.deps.logger.warn('transcode egress ffmpeg kill error', {\n meta: { brokerId: this.args.brokerId, error: errMsg(err) },\n })\n }\n }\n }\n}\n","/**\n * Transcode-pipeline manager — backs `streamBroker.getStreamWithCodec`.\n *\n * Responsibilities:\n * 1. Source-select: profile-driven only. With `profile` the assigned\n * camStreamId IS the source; without it the first published stream.\n * 2. Passthrough (no ffmpeg, `-c copy`): when `video:'copy'`, OR when the\n * source codec already matches the requested codec AND `audio:'copy'`\n * AND no `outputArgs`. Returns the broker restream URL.\n * 3. Spawn + refcount transcode pipelines otherwise: one `TranscodeEgress`\n * per unique (deviceId, source, videoCodec, audioCodec, resolution,\n * outputArgs) tuple, multi-consumer via refcount. Stops the egress N\n * seconds after the refcount drops to 0 (`releaseGraceMs`).\n * 4. Pick the right encoder + decode hw-accel: consults\n * `platform-probe.getHardwareEncoders` / `resolveHwAccel` (typed, cached)\n * and the system `ffmpeg` config (binaryPath / hwAccel / threadCount),\n * falling back to `libx264` / `libx265` and software decode.\n *\n * Egress: ffmpeg runs as an RTSP *server* (`-rtsp_flags listen`) on an internal\n * The transcoded Annex-B stream is read from ffmpeg's stdout and pushed into a\n * restreamer registered under a consumer token (see `TranscodeEgress`). The\n * broker's RTSP server is restream-only, so the old \"ffmpeg publishes to the\n * broker\" path 404'd, and ffmpeg's RTSP listen mode is unreliable — the stdout\n * pipe is the pattern proven by the WebRTC transcode feed.\n */\nimport { spawn } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { errMsg, makeSourceBrokerId } from '@camstack/types'\nimport type {\n CameraStream,\n CamProfile,\n IScopedLogger,\n AddonApi,\n GetStreamWithCodecInput,\n RtpSource,\n AudioCodecTarget,\n HardwareEncoders,\n} from '@camstack/types'\nimport {\n pickVideoEncoder,\n pickDecodeHwAccel,\n toAudioSidecarCodec,\n hwAccelSettingToPrefer,\n type FfmpegSystemConfig,\n type FfmpegHwAccelPrefer,\n} from './ffmpeg-invocation.js'\nimport {\n TranscodeEgress,\n type EgressChannel,\n} from './transcode-egress.js'\n\ntype CameraStreamLookup = (deviceId: number) => readonly CameraStream[]\ntype RtspEntryLookup = (brokerId: string) => { url: string } | null\n/** Resolve the camStreamId assigned to a device's profile slot, or null. */\ntype ProfileSourceLookup = (deviceId: number, profile: CamProfile) => string | null\n/** Create + register an egress channel (restreamer + token URL) for a brokerId. */\ntype AcquireChannelFn = (brokerId: string) => EgressChannel\n\ninterface PipelineEntry {\n readonly key: string\n readonly source: RtpSource\n readonly egress: TranscodeEgress | null\n refcount: number\n releaseTimer: NodeJS.Timeout | null\n}\n\nconst DEFAULT_FFMPEG_CONFIG: FfmpegSystemConfig = {\n binaryPath: 'ffmpeg',\n hwAccel: 'auto',\n threadCount: 0,\n}\n\nexport interface TranscodePipelineManagerOptions {\n readonly logger: IScopedLogger\n readonly api: AddonApi | null\n readonly cameraStreamLookup: CameraStreamLookup\n readonly rtspEntryLookup: RtspEntryLookup\n /** Resolve a device profile to its assigned source camStreamId. When\n * omitted, `getStreamWithCodec` cannot honour a `profile` target. */\n readonly resolveProfileSource?: ProfileSourceLookup\n readonly releaseGraceMs?: number\n /** Live accessor for the system `ffmpeg` config (binaryPath / hwAccel / threadCount). */\n readonly ffmpegConfig?: () => FfmpegSystemConfig\n /** Injectable for tests; defaults to `node:child_process` spawn. */\n readonly spawnFn?: typeof import('node:child_process').spawn\n /** Create + register the egress restreamer on the broker's main listen\n * server. Required for the transcode path (the manager is decoupled from\n * the listen server); when omitted, a transcode request throws. */\n readonly acquireChannel?: AcquireChannelFn\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 videoTargetToCodec(v: 'h264' | 'h265'): 'H264' | 'H265' {\n return v === 'h264' ? 'H264' : 'H265'\n}\n\nfunction pipelineKeyFor(\n deviceId: number,\n camStreamId: string,\n videoCodec: 'H264' | 'H265' | 'copy',\n audioCodec: AudioCodecTarget,\n outResolution: { width: number; height: number },\n outputArgs: readonly string[],\n audioEgress: boolean,\n): string {\n const ae = audioEgress ? '|ae=1' : ''\n const base = `${deviceId}|src=${camStreamId}|v=${videoCodec}|a=${audioCodec}|r=${outResolution.width}x${outResolution.height}${ae}`\n return outputArgs.length > 0 ? `${base}|o=${JSON.stringify(outputArgs)}` : base\n}\n\nexport class TranscodePipelineManager {\n private readonly entries = new Map<string, PipelineEntry>()\n /** In-flight transcode starts, keyed by pipeline key — single-flight the\n * async egress start so concurrent acquires share one egress. */\n private readonly pending = new Map<string, Promise<RtpSource>>()\n private readonly logger: IScopedLogger\n private readonly api: AddonApi | null\n private readonly cameraStreamLookup: CameraStreamLookup\n private readonly rtspEntryLookup: RtspEntryLookup\n private readonly resolveProfileSource: ProfileSourceLookup | null\n private readonly releaseGraceMs: number\n private readonly ffmpegConfig: () => FfmpegSystemConfig\n private readonly spawnFn: typeof import('node:child_process').spawn\n private readonly acquireChannel: AcquireChannelFn | null\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.resolveProfileSource = opts.resolveProfileSource ?? null\n this.releaseGraceMs = opts.releaseGraceMs ?? 5_000\n this.ffmpegConfig = opts.ffmpegConfig ?? (() => DEFAULT_FFMPEG_CONFIG)\n this.spawnFn = opts.spawnFn ?? spawn\n this.acquireChannel = opts.acquireChannel ?? null\n }\n\n async acquire(input: GetStreamWithCodecInput): Promise<RtpSource> {\n const wantedAudio: AudioCodecTarget = input.audio ?? 'aac'\n const outputArgs = input.outputArgs ?? []\n const audioEgress = input.audioEgress === true\n\n // Source selection precedence:\n // - `profile` → the profile's assigned camStreamId IS the source.\n // - else `targetResolution` → the published source closest by area.\n // - else → the first published stream.\n const sourceStream = input.profile !== undefined\n ? this.resolveTargetedStream(input.deviceId, input.profile)\n : input.targetResolution !== undefined\n ? (this.pickClosestSource(input.deviceId, input.targetResolution) ?? this.firstPublished(input.deviceId))\n : this.firstPublished(input.deviceId)\n if (input.profile === undefined && !sourceStream) {\n throw new Error(`getStreamWithCodec: no published stream for device ${input.deviceId}`)\n }\n if (!sourceStream || !sourceStream.url) {\n throw new Error(`getStreamWithCodec: no assigned source for device ${input.deviceId}`)\n }\n\n const sourceCodec = normaliseCodec(sourceStream.codec)\n // Effective output resolution: explicit target wins, else native source.\n const outResolution = input.targetResolution ?? sourceStream.resolution ?? { width: 1280, height: 720 }\n\n // A `copy` cannot be rescaled, so `targetResolution` is ignored for copy\n // SCALING (still honoured for source selection above). The codec-match\n // passthrough branch additionally requires that no rescale is needed.\n const sourceRes = sourceStream.resolution\n const resMatchesTarget =\n input.targetResolution === undefined ||\n (sourceRes !== undefined &&\n sourceRes.width === input.targetResolution.width &&\n sourceRes.height === input.targetResolution.height)\n\n // Passthrough (no ffmpeg): explicit `copy`, OR the source codec already\n // matches the requested codec AND audio is copy AND no extra output args\n // AND no rescale is needed.\n const wantsPassthrough =\n input.video === 'copy' ||\n (sourceCodec !== null &&\n videoTargetToCodec(input.video) === sourceCodec &&\n wantedAudio === 'copy' &&\n outputArgs.length === 0 &&\n resMatchesTarget)\n if (wantsPassthrough) {\n if (sourceCodec === null) {\n this.logger.warn('copy-passthrough requested but source codec is unknown — reporting H264', {\n meta: { deviceId: input.deviceId, camStreamId: sourceStream.camStreamId },\n })\n }\n const passVideoCodec: 'H264' | 'H265' = sourceCodec ?? 'H264'\n const url = this.resolveSourceUrl(input.deviceId, sourceStream.camStreamId)\n const key = pipelineKeyFor(\n input.deviceId,\n sourceStream.camStreamId,\n 'copy',\n wantedAudio,\n outResolution,\n outputArgs,\n false,\n )\n return this.shareOrCreatePassthrough(key, () => ({\n url,\n videoCodec: passVideoCodec,\n audioCodec: 'passthrough',\n resolution: outResolution,\n transcoded: false,\n encoder: 'copy',\n pipelineKey: key,\n }))\n }\n\n const targetCodec: 'H264' | 'H265' = videoTargetToCodec(\n input.video === 'copy' ? 'h264' : input.video,\n )\n const key = pipelineKeyFor(\n input.deviceId,\n sourceStream.camStreamId,\n targetCodec,\n wantedAudio,\n outResolution,\n outputArgs,\n audioEgress,\n )\n\n const existing = this.entries.get(key)\n if (existing) {\n return this.shareEntry(existing).source\n }\n\n // Single-flight: a concurrent acquire for the same key shares the in-flight\n // egress start rather than spawning a second ffmpeg.\n const inflight = this.pending.get(key)\n if (inflight) {\n await inflight\n const after = this.entries.get(key)\n if (after) return this.shareEntry(after).source\n // creation failed — fall through and retry below.\n }\n\n const start = this.startTranscode({\n key,\n deviceId: input.deviceId,\n camStreamId: sourceStream.camStreamId,\n targetCodec,\n audio: wantedAudio,\n audioEgress,\n scale: input.targetResolution ?? null,\n outResolution,\n outputArgs,\n })\n this.pending.set(key, start)\n try {\n return await start\n } finally {\n this.pending.delete(key)\n }\n }\n\n private async startTranscode(opts: {\n key: string\n deviceId: number\n camStreamId: string\n targetCodec: 'H264' | 'H265'\n audio: AudioCodecTarget\n audioEgress: boolean\n scale: { width: number; height: number } | null\n outResolution: { width: number; height: number }\n outputArgs: readonly string[]\n }): Promise<RtpSource> {\n if (!this.acquireChannel) {\n throw new Error('getStreamWithCodec: transcode egress not wired (acquireChannel missing)')\n }\n const cfg = this.ffmpegConfig()\n const encoders = await this.resolveHardwareEncoders()\n const encoder = pickVideoEncoder(opts.targetCodec, encoders, cfg.hwAccel)\n const decodeHwAccel = await this.resolveDecodeHwAccel(hwAccelSettingToPrefer(cfg.hwAccel))\n const sourceUrl = this.resolveSourceUrl(opts.deviceId, opts.camStreamId)\n const brokerId = `transcode:${opts.deviceId}:${randomUUID().slice(0, 8)}`\n\n // Opt-in audio sidecar: only for a re-encoded, sidecar-capable audio target.\n const sidecarCodec = opts.audioEgress ? toAudioSidecarCodec(opts.audio) : null\n\n const egress = new TranscodeEgress(\n {\n logger: this.logger.child('egress'),\n ffmpegBinaryPath: cfg.binaryPath,\n spawnFn: this.spawnFn,\n acquireChannel: this.acquireChannel,\n },\n {\n brokerId,\n invocation: {\n sourceUrl,\n targetCodec: opts.targetCodec,\n encoder,\n decodeHwAccel,\n scale: opts.scale,\n audio: opts.audio,\n threadCount: cfg.threadCount,\n outputArgs: opts.outputArgs,\n },\n audio: sidecarCodec ? { codec: sidecarCodec } : undefined,\n },\n )\n\n const url = await egress.start()\n\n const source: RtpSource = {\n url,\n videoCodec: opts.targetCodec,\n audioCodec: opts.audio,\n resolution: opts.outResolution,\n transcoded: true,\n encoder,\n pipelineKey: opts.key,\n }\n const entry: PipelineEntry = { key: opts.key, source, egress, refcount: 1, releaseTimer: null }\n this.entries.set(opts.key, entry)\n this.logger.info('Transcode pipeline started', {\n meta: { key: opts.key, encoder, decodeHwAccel, sourceUrl, url },\n })\n return source\n }\n\n private firstPublished(deviceId: number): CameraStream | null {\n return this.cameraStreamLookup(deviceId).find((s) => !!s.url) ?? null\n }\n\n /**\n * Pick the published source whose pixel area is closest to `target`. Only\n * considers streams that have a `url`. Returns null when none qualify (the\n * caller falls back to `firstPublished`).\n */\n private pickClosestSource(deviceId: number, target: { width: number; height: number }): CameraStream | null {\n const targetArea = target.width * target.height\n let best: CameraStream | null = null\n let bestDelta = Number.MAX_SAFE_INTEGER\n for (const s of this.cameraStreamLookup(deviceId)) {\n if (!s.url) continue\n const w = s.resolution?.width ?? 1280\n const h = s.resolution?.height ?? 720\n const delta = Math.abs(w * h - targetArea)\n if (delta < bestDelta) { bestDelta = delta; best = s }\n }\n return best\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 void 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 void this.shutdownEntry(entry)\n }\n this.entries.clear()\n }\n\n /**\n * Resolve a source stream to its broker restream URL\n * (`${deviceId}/${camStreamId}`), so every consumer rides the broker's one\n * source dial. Throws when no restream entry exists — a targeted source\n * must be dialed before it can be consumed.\n */\n private resolveSourceUrl(deviceId: number, camStreamId: string): string {\n const brokerId = makeSourceBrokerId(deviceId, camStreamId)\n const entry = this.rtspEntryLookup(brokerId)\n if (entry?.url) return entry.url\n throw new Error(`getStreamWithCodec: no broker restream for ${brokerId} (source not dialed)`)\n }\n\n /**\n * Resolve a profile target to its assigned, published source stream.\n * Throws when the profile has no assignment or the assigned source is not\n * currently published — a targeted request must not silently fall back to\n * a different source.\n */\n private resolveTargetedStream(deviceId: number, profile: CamProfile): CameraStream {\n const camStreamId = this.resolveProfileSource?.(deviceId, profile) ?? null\n if (!camStreamId) {\n throw new Error(\n `getStreamWithCodec: profile \"${profile}\" has no assigned source for device ${deviceId}`,\n )\n }\n const stream = this.cameraStreamLookup(deviceId).find(\n (s) => s.camStreamId === camStreamId && !!s.url,\n )\n if (!stream) {\n throw new Error(\n `getStreamWithCodec: profile \"${profile}\" source \"${camStreamId}\" not published for device ${deviceId}`,\n )\n }\n return stream\n }\n\n /** Query the typed, cached hardware-encoder probe; null on any failure. */\n private async resolveHardwareEncoders(): Promise<HardwareEncoders | null> {\n if (!this.api) return null\n try {\n return await this.api.platformProbe.getHardwareEncoders.query()\n } catch (err) {\n this.logger.warn('platformProbe.getHardwareEncoders failed — software encode fallback', {\n meta: { error: errMsg(err) },\n })\n return null\n }\n }\n\n /**\n * Resolve the preferred decode hw-accel backend; null = software decode.\n * Gated by what the configured ffmpeg binary can actually decode\n * (`getHardwareDecodeAccels`), so we never spawn a doomed hw-decode attempt\n * on a backend this build lacks. Per-stream fragility (e.g. VideoToolbox\n * HEVC) is still caught by the egress's software fallback.\n */\n private async resolveDecodeHwAccel(prefer: FfmpegHwAccelPrefer | null): Promise<string | null> {\n if (!this.api) return null\n try {\n const res = await this.api.platformProbe.resolveHwAccel.query({ prefer })\n const supported = await this.resolveDecodeAccelMethods()\n const picked = pickDecodeHwAccel(res.preferred, supported)\n if (picked === null && res.preferred.length > 0) {\n this.logger.info('decode hw-accel: configured ffmpeg supports none of the preferred backends — software decode', {\n meta: { preferred: res.preferred, supported },\n })\n }\n return picked\n } catch (err) {\n this.logger.warn('platformProbe.resolveHwAccel failed — software decode fallback', {\n meta: { error: errMsg(err) },\n })\n return null\n }\n }\n\n /**\n * The `-hwaccel` methods the configured ffmpeg supports; `[]` on any failure\n * (treated as \"unknown\" by {@link pickDecodeHwAccel}, which then keeps the\n * top preference rather than filtering it out).\n */\n private async resolveDecodeAccelMethods(): Promise<readonly string[]> {\n if (!this.api) return []\n try {\n const res = await this.api.platformProbe.getHardwareDecodeAccels.query()\n return res.methods\n } catch (err) {\n this.logger.warn('platformProbe.getHardwareDecodeAccels failed — decode gate skipped', {\n meta: { error: errMsg(err) },\n })\n return []\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 shareOrCreatePassthrough(key: string, factory: () => RtpSource): 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, egress: null, refcount: 1, releaseTimer: null }\n this.entries.set(key, entry)\n return source\n }\n\n private async shutdownEntry(entry: PipelineEntry): Promise<void> {\n if (entry.releaseTimer) {\n clearTimeout(entry.releaseTimer)\n entry.releaseTimer = null\n }\n this.entries.delete(entry.key)\n if (entry.egress) {\n try {\n await entry.egress.stop()\n } catch (err) {\n this.logger.warn('transcode egress shutdown error', {\n meta: { key: entry.key, error: errMsg(err) },\n })\n }\n }\n }\n}\n","import {\n CAM_PROFILE_ORDER,\n makeProfileBrokerId,\n makeSourceBrokerId,\n type CamProfile,\n type ProfileRtspEntry,\n} from '@camstack/types'\n\nexport interface ProfileRestreamerRegistration {\n readonly profileBrokerId: string\n readonly camStreamId: string\n}\n\nexport interface ProfileRestreamerActions {\n readonly register: readonly ProfileRestreamerRegistration[]\n readonly unregister: readonly string[]\n}\n\nexport interface ProfileRestreamerActionsParams {\n readonly deviceId: number\n readonly assignmentMap: Partial<Record<CamProfile, string>>\n readonly sourceBrokerExists: (camStreamId: string) => boolean\n readonly currentBindings: ReadonlyMap<string, string>\n}\n\n/**\n * Diff the desired profile→source aliasing against the current\n * bindings. Pure: the manager supplies `sourceBrokerExists` (does a\n * live `StreamBroker` exist for this camStream?) and the current\n * `profileBrokerId → sourceBrokerId` bindings. Only emits work when a\n * profile's backing source actually changed, so an unchanged read\n * leaves the existing RTSP token (and any in-flight HAP session) alone.\n */\nexport function computeProfileRestreamerActions(\n params: ProfileRestreamerActionsParams,\n): ProfileRestreamerActions {\n const { deviceId, assignmentMap, sourceBrokerExists, currentBindings } = params\n const register: ProfileRestreamerRegistration[] = []\n const unregister: string[] = []\n for (const profile of CAM_PROFILE_ORDER) {\n const profileBrokerId = makeProfileBrokerId(deviceId, profile)\n const currentSource = currentBindings.get(profileBrokerId) ?? null\n const camStreamId = assignmentMap[profile]\n if (camStreamId === undefined) {\n // Unassigned profile — drop any token that was aliasing it.\n if (currentSource !== null) unregister.push(profileBrokerId)\n continue\n }\n // Past this guard `camStreamId` narrows to `string`, so the register\n // push needs no extra defined-check or cast.\n const desiredSource = sourceBrokerExists(camStreamId)\n ? makeSourceBrokerId(deviceId, camStreamId)\n : null\n if (desiredSource === currentSource) continue\n if (currentSource !== null) unregister.push(profileBrokerId)\n if (desiredSource !== null) register.push({ profileBrokerId, camStreamId })\n }\n return { register, unregister }\n}\n\n/** Minted restream URL pair for a profile-keyed brokerId. */\nexport interface ProfileRestreamUrl {\n readonly url: string\n readonly mutedUrl: string\n readonly enabled: boolean\n}\n\n/** Source-camStream codec/resolution used to enrich a profile entry. */\nexport interface ProfileSourceMeta {\n readonly codec?: string\n readonly resolution?: { readonly width: number; readonly height: number }\n}\n\nexport interface BuildProfileRtspEntriesParams {\n readonly deviceId: number\n readonly assignmentMap: Partial<Record<CamProfile, string>>\n readonly resolveEntry: (profileBrokerId: string) => ProfileRestreamUrl | null\n readonly resolveMeta: (camStreamId: string) => ProfileSourceMeta\n}\n\n/**\n * Map a device's assigned profiles → `ProfileRtspEntry[]`. Pure: the\n * manager supplies `resolveEntry` (profileBrokerId → minted restream\n * URL or null) and `resolveMeta` (camStreamId → codec/resolution).\n * Profiles with no assignment or no mintable entry are skipped.\n */\nexport function buildProfileRtspEntries(\n params: BuildProfileRtspEntriesParams,\n): ProfileRtspEntry[] {\n const { deviceId, assignmentMap, resolveEntry, resolveMeta } = params\n const result: ProfileRtspEntry[] = []\n for (const profile of CAM_PROFILE_ORDER) {\n const camStreamId = assignmentMap[profile]\n if (camStreamId === undefined) continue\n const profileBrokerId = makeProfileBrokerId(deviceId, profile)\n const entry = resolveEntry(profileBrokerId)\n if (!entry) continue\n const meta = resolveMeta(camStreamId)\n result.push({\n profile,\n brokerId: profileBrokerId,\n url: entry.url,\n mutedUrl: entry.mutedUrl,\n enabled: entry.enabled,\n ...(meta.codec !== undefined ? { codec: meta.codec } : {}),\n ...(meta.resolution !== undefined ? { resolution: meta.resolution } : {}),\n })\n }\n return result\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 SubscribeFramesInput, SubscribeFramesResult, FrameHandle,\n SubscribeAudioChunksInput, SubscribeAudioChunksResult, DecodedAudioChunk,\n CamStreamDescriptor,\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 { RtspRestreamer } from '../rtsp/rtsp-restreamer.js'\nimport { RtspRestreamProviderImpl, type RtspRestreamEntry } from '../rtsp/rtsp-restream-provider.js'\nimport { TranscodePipelineManager } from './transcode-pipeline.js'\nimport type { EgressChannel } from './transcode-egress.js'\nimport type { FfmpegSystemConfig } from './ffmpeg-invocation.js'\nimport { CAM_PROFILE_ORDER, DeviceFeature, DeviceType, EventCategory, errMsg, RingBuffer, EncodeProfileSchema, makeSourceBrokerId, makeProfileBrokerId, type EncodeProfile, type VideoEncode, type ProfileRtspEntry } from '@camstack/types'\nimport {\n computeProfileRestreamerActions,\n buildProfileRtspEntries,\n} from './profile-restreamers.js'\nimport { z } from 'zod'\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\nexport interface 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 * Per-device \"Advanced WebRTC debug logging\" flag. When true, EVERY\n * WebRTC `AdaptiveSession` created for this device gets `debug = true`,\n * which turns on the full per-step lifecycle logging in `session.ts`\n * (local answer ICE candidates + types, remote offer candidates, DTLS\n * state transitions, ICE nomination, codec negotiation, first\n * video/audio RTP, audio codec/clock). Distinct from `streamingDebug`\n * (broker/repacketizer verbosity) so an operator can isolate the\n * WebRTC negotiation path alone. Off by default.\n */\n webrtcDebug?: boolean\n /**\n * Operator-defined derived streams. Each entry is materialised\n * into a live broker by `loadPersistedDeviceOverrides` on hub\n * boot via publishCameraStream({kind:'derived', …}).\n */\n derivedStreams?: Record<string, DerivedStreamEntry>\n}\n\ninterface DerivedStreamEntry {\n readonly sourceCamStreamId: string\n readonly encodeProfile: EncodeProfile\n readonly preBuffer?: StreamPreBuffer\n readonly rtspEnabled?: boolean\n}\n\nconst StreamPreBufferSchema = z.object({\n enabled: z.boolean(),\n seconds: z.number(),\n})\n\nconst DerivedStreamSchema = z.object({\n // Empty string = an unconfigured placeholder: the UI creates a derived\n // stream (name + encode params) BEFORE the operator picks a source. That\n // state must persist so the half-built stream survives a restart — a\n // `min(1)` here rejected the whole device-overrides blob on save (one\n // placeholder blocked EVERY device). Runtime materialization skips empty\n // sources, so an unconfigured entry is harmless until a source is set.\n sourceCamStreamId: z.string(),\n encodeProfile: EncodeProfileSchema,\n preBuffer: StreamPreBufferSchema.optional(),\n rtspEnabled: z.boolean().optional(),\n})\n\nconst DeviceOverrideSchema = z.object({\n preBufferSecOverride: z.number().optional(),\n preBuffer: z.record(z.string(), StreamPreBufferSchema).optional(),\n streamingDebug: z.boolean().optional(),\n webrtcDebug: z.boolean().optional(),\n derivedStreams: z.record(z.string(), DerivedStreamSchema).optional(),\n})\n\nexport function serializeDeviceOverride(o: DeviceOverride): string {\n return JSON.stringify(o)\n}\n\nexport function parseDeviceOverride(s: string): DeviceOverride {\n return DeviceOverrideSchema.parse(JSON.parse(s))\n}\n\n/** Whole-blob schema for the persisted `deviceOverrides` store key:\n * numeric-string deviceId → DeviceOverride (includes `derivedStreams`). */\nexport const DeviceOverridesMapSchema = z.record(z.string(), DeviceOverrideSchema)\nexport type DeviceOverridesMap = z.infer<typeof DeviceOverridesMapSchema>\n\n/** Persisted store-key schemas for the broker's other durable blobs.\n * `rtspTokens`: camStreamKey → token hex. `rtspEnabled`: brokerId →\n * enabled. `profileMap`: numeric-string deviceId → {map, auto}. */\nexport const RtspTokensSchema = z.record(z.string(), z.string())\nexport const RtspEnabledSchema = z.record(z.string(), z.boolean())\nexport const ProfileMapSchema = z.record(\n z.string(),\n z.object({\n map: z.record(z.string(), z.string()),\n auto: z.boolean(),\n }),\n)\nexport type RtspTokensBlob = z.infer<typeof RtspTokensSchema>\nexport type RtspEnabledBlob = z.infer<typeof RtspEnabledSchema>\nexport type ProfileMapBlob = z.infer<typeof ProfileMapSchema>\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 SOURCE broker id from a `(deviceId, camStreamId)` tuple. Thin\n * wrapper over the canonical `makeSourceBrokerId` — single source of truth. */\nfunction brokerIdFor(deviceId: number, camStreamId: string): string {\n return makeSourceBrokerId(deviceId, camStreamId)\n}\n\n/**\n * Inverse of `brokerIdFor`: split a `<deviceId>/<camStreamId>` broker id back\n * into its tuple. Splits on the FIRST `/` (the deviceId is a leading integer;\n * the camStreamId may itself contain no `/` today, but be liberal). Returns\n * null when the leading segment isn't a non-negative integer.\n */\nfunction parseBrokerId(brokerId: string): { deviceId: number; camStreamId: string } | null {\n const slash = brokerId.indexOf('/')\n if (slash <= 0) return null\n const deviceId = Number(brokerId.slice(0, slash))\n const camStreamId = brokerId.slice(slash + 1)\n if (!Number.isInteger(deviceId) || deviceId < 0 || camStreamId.length === 0) return null\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 /** profileBrokerId (`${deviceId}/${profile}`) → sourceBrokerId currently aliased. */\n private readonly profileRestreamerBindings = new Map<string, string>()\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 /**\n * Routes a frame-handle `subscriptionId` (Phase 5 / D9) back to its owning\n * brokerId so `pullFrameHandles` / `unsubscribeFrames` resolve in O(1)\n * without scanning every broker.\n */\n private readonly frameSubscriptionBroker = new Map<string, string>()\n /**\n * Routes an audio-chunk `subscriptionId` (Phase 5 / D9) back to its owning\n * brokerId so `pullAudioChunks` / `unsubscribeAudioChunks` resolve in O(1)\n * without scanning every broker. Mirrors `frameSubscriptionBroker`.\n */\n private readonly audioSubscriptionBroker = new Map<string, string>()\n /**\n * Derived streams that could not be materialised at boot because their\n * source broker had not yet been registered. Keyed by the source brokerId\n * (`${deviceId}/${sourceCamStreamId}`). Drained by `publishCameraStream`\n * when the matching source is registered.\n */\n private readonly pendingDerivedStreams = new Map<string, Array<{ deviceId: number; name: string; def: NonNullable<DeviceOverride['derivedStreams']>[string] }>>()\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 /** Public accessor for same-process callers (e.g. `CameraStreamsProvider.pickStream`)\n * that want to emit diagnostics through the manager's logger. Internal callers\n * use `this.logger` directly. */\n getLogger(): IScopedLogger { return this.logger }\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 * Snapshot of the system `ffmpeg` config section (binaryPath / hwAccel /\n * threadCount). Refreshed by the host addon via `setFfmpegConfig`; the\n * transcode manager reads it live per spawn. Defaults are safe (PATH ffmpeg,\n * auto hw-accel, auto threads) until the addon loads the section.\n */\n private ffmpegConfig: FfmpegSystemConfig = { binaryPath: 'ffmpeg', hwAccel: 'auto', threadCount: 0 }\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 resolveProfileSource: (deviceId, profile) =>\n this.assignments.get(deviceId)?.map[profile] ?? null,\n ffmpegConfig: () => this.ffmpegConfig,\n // Create + register the egress restreamer on the main listen server.\n // Holds the concrete `RtspRestreamer` here so no cast is needed at the\n // listen-server boundary; the egress only sees the structural `*Like`.\n acquireChannel: (brokerId): EgressChannel => {\n const restreamer = new RtspRestreamer(brokerId)\n const token = this.rtspServer.registerRestreamer(brokerId, restreamer)\n return {\n restreamer,\n consumerUrl: `rtsp://127.0.0.1:${this.rtspProvider.getRtspPort()}/${token}`,\n release: () => {\n this.rtspServer.unregisterRestreamer(brokerId)\n restreamer.destroy()\n },\n }\n },\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 pullHandles: { query: (input: { sessionId: string; maxCount: number }) => Promise<readonly import('@camstack/types').FrameHandle[]> }\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 pullHandles: async (input) => {\n const proxy = apiDecoder()\n if (proxy) return proxy.pullHandles.query(input)\n if (!localApi) return []\n return localApi.pullHandles(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 // The in-process `staticDecoders` fallback has no shm sink — its\n // `IDecoderSession` exposes only `onFrame` (pixels), not\n // `onFrameHandle`. The shared-memory frame plane (Phase 5 / D9) runs\n // through the real `decoder` cap (the node-av addon, which owns the\n // `frameSink: 'shm'` ring writer). `pullHandles` therefore returns\n // nothing here — a `frameSink: 'shm'` request degrades to no frames\n // rather than crashing the broker.\n pullHandles: async () => [],\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 /** Update the system `ffmpeg` config snapshot used by the transcode egress. */\n setFfmpegConfig(cfg: FfmpegSystemConfig): void {\n this.ffmpegConfig = cfg\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 // Wake-up window: between the Baichuan wake-up issue and the\n // camera's first dialed-back RTP packet. Surfaces as a labelled\n // \"WAKING UP\" tile (`'waking'` placeholder reason) on encoded-path\n // subscribers (H.264 sessions, derived-broker pipelines). The\n // H.265 RTP repacketizer path still drops placeholders to avoid\n // freezing Chrome's HEVC decoder — see `BrokerWebrtcServer`'s\n // useRtpRepacketizer guard. The reason snaps back via the\n // subsequent `BatteryOnStatusChanged { sleeping: false }` event,\n // or implicitly once the first real packet flips the broker's\n // status to `'streaming'`.\n bus.subscribe({ category: EventCategory.BatteryOnWakeStarted }, (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, 'waking')\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 // Back-register every broker created BEFORE the server was wired. Addon\n // activation runs `reconcileAllCatalogsAtStartup()` (which creates brokers\n // via ensureBroker → fanOutRegistration) BEFORE this call, so those\n // registrations hit the `if (this.webrtcServer)` guard while it was still\n // undefined and were silently dropped. They live only in `this.brokers`;\n // ensureBroker's `this.brokers.has` short-circuit means no later reconcile\n // ever repairs them — so the WebRTC server would never learn about them and\n // every session for those devices fails with \"No broker for stream …\".\n // Re-registering here also makes a server swap self-healing.\n for (const [brokerId, broker] of this.brokers) {\n server.registerBroker(brokerId, broker)\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 /** Upsert a single SOURCE (non-derived) cam-stream into the registry\n * WITHOUT ranking or broker creation. Returns the prior entry (if any)\n * so callers can detect url refresh / already-existed. */\n private upsertSourceCameraStream(\n deviceId: number,\n input: {\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 deviceFeatures?: readonly string[]\n autoEligible?: boolean\n metadata?: Readonly<Record<string, unknown>>\n },\n ): CameraStreamInternal | undefined {\n const { camStreamId, kind, url, codec, resolution, fps, label, metadata } = input\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 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 return existing\n }\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 /**\n * Required when `kind === 'derived'`. The `camStreamId` of the source\n * stream (within the same device) whose encoded plane this derived\n * stream re-encodes.\n */\n sourceCamStreamId?: string\n /**\n * Required when `kind === 'derived'`. The encode parameters that the\n * broker-spawned transcode pipeline will apply to the source plane.\n */\n encodeProfile?: EncodeProfile\n }): Promise<{ success: true }> {\n const { deviceId, camStreamId, kind, codec, resolution, label } = input\n\n // ── derived kind: register metadata + validate source, no broker spawn ──\n if (kind === 'derived') {\n const { sourceCamStreamId, encodeProfile } = input\n if (!sourceCamStreamId || !encodeProfile) {\n throw new Error(\"kind: 'derived' requires sourceCamStreamId + encodeProfile\")\n }\n\n // Single-flight: if a derived entry with this name already exists,\n // return immediately — callers should use retractCameraStream +\n // republish to change encode parameters.\n const existingDerived = this.cameraStreams.get(deviceId)?.get(camStreamId)\n if (existingDerived) {\n return { success: true as const }\n }\n\n // Validate that the source stream is registered for this device.\n const sourceBrokerId = brokerIdFor(deviceId, sourceCamStreamId)\n if (!this.brokers.has(sourceBrokerId)) {\n throw new Error(`SOURCE_NOT_FOUND: ${sourceBrokerId}`)\n }\n\n // Register the derived stream metadata so getCameraStreamsForDevice\n // surfaces it. The encode profile fields are mapped onto the standard\n // CameraStreamInternal shape for UI / assignment purposes.\n let derivedStreamMap = this.cameraStreams.get(deviceId)\n if (!derivedStreamMap) {\n derivedStreamMap = new Map()\n this.cameraStreams.set(deviceId, derivedStreamMap)\n }\n const order = this.nextOrder(deviceId)\n const deviceFeaturesArr: string[] = input.deviceFeatures ? [...input.deviceFeatures] : []\n const derivedCam: CameraStreamInternal = {\n camStreamId,\n deviceId,\n kind: 'derived',\n ...(label !== undefined ? { label } : {}),\n ...(codec !== undefined ? { codec } : encodeProfile.video.codec !== 'copy' ? { codec: encodeProfile.video.codec } : {}),\n ...(resolution !== undefined\n ? { resolution }\n : encodeProfile.video.width !== undefined && encodeProfile.video.height !== undefined\n ? { resolution: { width: encodeProfile.video.width, height: encodeProfile.video.height } }\n : {}),\n ...(encodeProfile.video.fps !== undefined ? { fps: encodeProfile.video.fps } : {}),\n order,\n deviceFeatures: deviceFeaturesArr,\n autoEligible: input.autoEligible ?? false,\n }\n derivedStreamMap.set(camStreamId, derivedCam)\n this.emitCamStreamsChanged(deviceId)\n // TODO(Task N): spawn/attach ffmpeg transcode pipeline via\n // buildTranscodeFfmpegArgs(encodeProfile) and wire source broker's\n // onEncodedData → ffmpeg stdin → derived broker's pushEncodedPacket.\n return { success: true as const }\n }\n\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. The upsert\n // overwrites the feature snapshot too — battery detection can happen\n // post-creation (live probe), so the latest signal wins.\n const existing = this.upsertSourceCameraStream(deviceId, input)\n const urlRefreshed = existing !== undefined\n && input.url !== undefined\n && existing.url !== input.url\n\n // Capture push-demand state BEFORE the rank so a re-published,\n // already-assigned push stream re-triggers demand afterward.\n const prePublishProfile = PUSH_KINDS.has(kind)\n ? findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId)\n : null\n const wasAlreadyAssigned = prePublishProfile !== null && existing !== undefined\n\n await this.reconcileAutoAssignment(deviceId, '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 // Drain any derived streams that were queued in pendingDerivedStreams\n // because their source wasn't registered when loadPersistedDeviceOverrides\n // ran. Now that the source broker has landed, materialise each pending\n // derived in order. Only non-derived publishes (i.e. real source streams)\n // can unblock pending deriveds — derived-on-derived is not supported.\n const justRegisteredBrokerId = brokerIdFor(deviceId, camStreamId)\n const pending = this.pendingDerivedStreams.get(justRegisteredBrokerId)\n if (pending) {\n this.pendingDerivedStreams.delete(justRegisteredBrokerId)\n for (const p of pending) {\n await this.publishCameraStream({\n deviceId: p.deviceId,\n camStreamId: `derived:${p.name}`,\n kind: 'derived',\n sourceCamStreamId: p.def.sourceCamStreamId,\n encodeProfile: p.def.encodeProfile,\n })\n }\n }\n\n this.emitCamStreamsChanged(deviceId)\n return { success: true as const }\n }\n\n /**\n * Publish a device's FULL stream catalog atomically: upsert every\n * source descriptor, rank the profiles ONCE over the complete set,\n * then create each broker. Deterministic counterpart to looping\n * `publishCameraStream` per descriptor (which persisted + emitted an\n * intermediate, wrong assignment between each publish → the transient\n * high/mid/low reshuffle during catalog republish). Derived descriptors\n * (which need their source broker to already exist) fall back to the\n * per-stream path AFTER the sources are live.\n */\n private async publishCatalog(\n deviceId: number,\n descriptors: readonly CamStreamDescriptor[],\n ): Promise<void> {\n const sources = descriptors.filter((d) => d.kind !== 'derived')\n const derived = descriptors.filter((d) => d.kind === 'derived')\n\n const streamMapBefore = this.cameraStreams.get(deviceId)\n const reEmitDemandFor: string[] = []\n for (const d of sources) {\n const existed = streamMapBefore?.get(d.camStreamId) !== undefined\n const wasAssigned = PUSH_KINDS.has(d.kind)\n && findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, d.camStreamId) !== null\n if (existed && wasAssigned) reEmitDemandFor.push(d.camStreamId)\n }\n const urlBefore = new Map<string, string | undefined>()\n for (const d of sources) urlBefore.set(d.camStreamId, streamMapBefore?.get(d.camStreamId)?.url)\n\n // (1) Upsert every source descriptor (no rank, no broker yet).\n for (const d of sources) {\n this.upsertSourceCameraStream(deviceId, d)\n }\n\n // (2) Rank ONCE over the complete set.\n await this.reconcileAutoAssignment(deviceId, 'publish')\n\n // (3) Re-emit demand for already-assigned push streams.\n for (const camStreamId of reEmitDemandFor) {\n const profile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId)\n if (profile !== null) this.emitCamStreamDemand(deviceId, camStreamId, profile)\n }\n\n // (4) Ensure a broker per source (+ url-refresh reconnect).\n for (const d of sources) {\n await this.ensureBroker(deviceId, d.camStreamId)\n const prevUrl = urlBefore.get(d.camStreamId)\n if (d.url !== undefined && prevUrl !== undefined && prevUrl !== d.url) {\n this.brokers.get(brokerIdFor(deviceId, d.camStreamId))?.kickReconnect()\n }\n }\n\n // (5) Derived descriptors: per-stream (source brokers now exist).\n for (const d of derived) {\n await this.publishCameraStream({ deviceId, ...d })\n }\n\n this.emitCamStreamsChanged(deviceId)\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 // ── Catalog reconcile (PULL) ─────────────────────────────────────────\n //\n // The broker is the authority for its own cam-stream registry: it PULLS\n // each camera's `stream-catalog` (via DeviceProxy) and reconciles, rather\n // than relying on providers to push. This makes the broker self-heal after\n // a restart — it re-derives the full registry from the providers. Driven by\n // the host addon on start + a configurable poll + device / stream-params\n // events.\n\n /**\n * Race a catalog pull against a timeout so a wedged provider handler\n * can't leave the reconcile pending forever. Rejects (caught upstream →\n * retry next tick) if the provider hasn't answered in time.\n */\n private async withCatalogTimeout<T>(promise: Promise<T>, deviceId: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`stream-catalog.getCatalog timed out for device ${String(deviceId)}`)),\n 12_000,\n )\n })\n try {\n return await Promise.race([promise, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n }\n\n /**\n * Pull one camera's `stream-catalog` and reconcile its cam-streams: upsert\n * every descriptor, retract any cam-stream no longer in the catalog. A pull\n * that throws (provider mid-restart, no `stream-catalog` cap) is skipped —\n * the poll retries. An EMPTY catalog is also skipped rather than tearing\n * down (a provider probe can transiently return none); genuine removal flows\n * through the device-unregistered path.\n */\n async reconcileDeviceCatalog(deviceId: number): Promise<void> {\n if (!this.api) return\n let descriptors: readonly CamStreamDescriptor[]\n try {\n // Device-scoped cap called from the broker (system context): pass the\n // deviceId explicitly — the SDK link chain routes to the owning\n // provider, hub-local or cross-node, transparently.\n //\n // Bounded with a timeout: a provider whose getCatalog handler wedges\n // (e.g. a session-exhausted Reolink camera that never answers the\n // Baichuan stream-options query) would otherwise leave this await\n // pending forever, permanently stalling the reconcile and leaving the\n // device's cam-stream registry — and its device-settings tab — empty.\n // On timeout we fall into the catch and retry on the next poll tick.\n descriptors = (await this.withCatalogTimeout(\n this.api.streamCatalog.getCatalog.query({ deviceId }),\n deviceId,\n )) ?? []\n } catch (err) {\n this.logger.debug('reconcileDeviceCatalog: stream-catalog pull failed — will retry', {\n tags: { deviceId }, meta: { error: errMsg(err) },\n })\n return\n }\n if (descriptors.length === 0) return\n\n const keep = new Set(descriptors.map((d) => d.camStreamId))\n // Atomic: upsert all + rank ONCE (no transient reshuffle). Replaces the\n // per-descriptor publishCameraStream loop, which persisted an\n // intermediate assignment between each stream.\n await this.publishCatalog(deviceId, descriptors)\n const existing = this.cameraStreams.get(deviceId)\n if (existing) {\n for (const camStreamId of [...existing.keys()]) {\n if (!keep.has(camStreamId)) {\n await this.retractCameraStream({ deviceId, camStreamId })\n }\n }\n }\n }\n\n /**\n * Enumerate every camera and reconcile its catalog. The reconcile backstop\n * (poll cadence) and the start-up fetch. Per-device failures are isolated so\n * one unreachable camera can't block the rest.\n */\n async reconcileAllCatalogs(): Promise<void> {\n if (!this.api) return\n let devices: readonly { id: number; type: string; isCamera: boolean; disabled: boolean }[]\n try {\n devices = await this.api.deviceManager.listAll.query({})\n } catch (err) {\n this.logger.debug('reconcileAllCatalogs: deviceManager.listAll failed — will retry', {\n meta: { error: errMsg(err) },\n })\n return\n }\n const cameras = devices.filter((d) => d.isCamera || d.type === DeviceType.Camera)\n await Promise.allSettled(cameras.map((d) => this.reconcileDeviceCatalog(d.id)))\n }\n\n /**\n * Startup catalog pull: run a full reconcile, then a bounded number of extra\n * passes while any camera still has an empty registry (a cold/slow provider\n * may answer on a later attempt). Bounded so a genuinely-offline camera can't\n * loop forever — the 30s poll remains the ongoing backstop.\n */\n async reconcileAllCatalogsAtStartup(maxPasses = 3, gapMs = 1500): Promise<void> {\n for (let pass = 0; pass < maxPasses; pass++) {\n await this.reconcileAllCatalogs()\n if (!(await this.hasAnyCameraWithEmptyCatalog())) return\n if (pass < maxPasses - 1) await new Promise((r) => setTimeout(r, gapMs))\n }\n }\n\n private async hasAnyCameraWithEmptyCatalog(): Promise<boolean> {\n if (!this.api) return false\n let devices: readonly { id: number; type: string; isCamera: boolean; disabled: boolean }[]\n try {\n devices = await this.api.deviceManager.listAll.query({})\n } catch {\n return false\n }\n for (const d of devices) {\n if ((d.isCamera || d.type === DeviceType.Camera) && !d.disabled) {\n const streams = this.cameraStreams.get(d.id)\n if (!streams || streams.size === 0) return true\n }\n }\n return false\n }\n\n /**\n * Drop a device's cam-streams entirely — fired on `device.unregistered`.\n */\n async retractDevice(deviceId: number): Promise<void> {\n const existing = this.cameraStreams.get(deviceId)\n if (!existing) return\n for (const camStreamId of [...existing.keys()]) {\n await this.retractCameraStream({ deviceId, camStreamId })\n }\n }\n\n /**\n * Lazily (re)create the broker instance for a `brokerId` if its cam-stream\n * is known. The audio/frame subscribe paths call this so a consumer's\n * retry RESURRECTS the broker after a restart — the video path already\n * `ensureBroker`s, but audio/frame subscribers previously assumed an\n * existing instance and failed forever once the process respawned.\n * No-op when the brokerId is malformed or its cam-stream isn't (yet)\n * reconciled into the registry (`ensureBroker` itself guards on that).\n */\n private async ensureBrokerForId(brokerId: string): Promise<void> {\n if (this.brokers.has(brokerId)) return\n const parsed = parseBrokerId(brokerId)\n if (!parsed) return\n await this.ensureBroker(parsed.deviceId, parsed.camStreamId)\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 // ── Internal: live broker lookup (not a cap method) ─────────────────\n\n /**\n * Resolve the live `StreamBroker` instance for a broker id. In-process\n * only — a `StreamBroker` is not tRPC-serialisable, so this is reachable\n * solely from same-process callers (device-scoped providers, the WebRTC\n * server). Cross-process frame access goes through the shm `subscribeFrames`\n * surface; cross-process stats through `getBrokerStats` / `listClients`.\n */\n async getBroker(input: { brokerId: string }): Promise<IStreamBroker | null> {\n return this.brokers.get(input.brokerId) ?? null\n }\n\n // ── Cap methods: broker runtime (stats + client inventory) ──────────\n\n // ── Cap methods: shared-memory frame-handle plane (Phase 5 / D9) ─────\n\n /**\n * Open a `FrameHandle` subscription on a broker — the handle-based\n * replacement for the live-object `onDecodedFrame` callback. Routed over\n * tRPC, so a consumer in a different process can subscribe; the consumer\n * then polls `pullFrameHandles` and feeds each handle to a\n * `FrameRingReader`.\n */\n async subscribeFrames(input: SubscribeFramesInput): Promise<SubscribeFramesResult> {\n await this.ensureBrokerForId(input.brokerId)\n const broker = this.brokers.get(input.brokerId)\n if (!broker) {\n throw new Error(`stream-broker: no broker for \"${input.brokerId}\"`)\n }\n const result = await broker.subscribeFrameHandles({\n format: input.format,\n maxFps: input.maxFps,\n tag: input.tag,\n })\n this.frameSubscriptionBroker.set(result.subscriptionId, input.brokerId)\n return result\n }\n\n /** Drain `FrameHandle`s for a subscription opened via `subscribeFrames`. */\n async pullFrameHandles(input: { subscriptionId: string; maxCount: number }): Promise<readonly FrameHandle[]> {\n const brokerId = this.frameSubscriptionBroker.get(input.subscriptionId)\n if (!brokerId) return []\n const broker = this.brokers.get(brokerId)\n if (!broker) return []\n return broker.pullFrameHandles(input.subscriptionId, input.maxCount)\n }\n\n /**\n * Drop every `frameSubscriptionBroker` + `audioSubscriptionBroker` entry\n * pointing at `brokerId`. Called on broker teardown so a destroyed broker\n * leaves no dangling `subscriptionId → brokerId` rows (a slow consumer that\n * never calls `unsubscribeFrames` / `unsubscribeAudioChunks` would otherwise\n * leak a map entry per stream).\n */\n private sweepFrameSubscriptions(brokerId: string): void {\n for (const [subscriptionId, mappedBrokerId] of this.frameSubscriptionBroker) {\n if (mappedBrokerId === brokerId) {\n this.frameSubscriptionBroker.delete(subscriptionId)\n }\n }\n for (const [subscriptionId, mappedBrokerId] of this.audioSubscriptionBroker) {\n if (mappedBrokerId === brokerId) {\n this.audioSubscriptionBroker.delete(subscriptionId)\n }\n }\n }\n\n /** Release a frame-handle subscription. */\n async unsubscribeFrames(input: { subscriptionId: string }): Promise<{ released: boolean }> {\n const brokerId = this.frameSubscriptionBroker.get(input.subscriptionId)\n if (!brokerId) return { released: false }\n this.frameSubscriptionBroker.delete(input.subscriptionId)\n const broker = this.brokers.get(brokerId)\n if (!broker) return { released: false }\n const released = await broker.unsubscribeFrameHandles(input.subscriptionId)\n return { released }\n }\n\n // ── Cap methods: decoded audio-chunk plane (Phase 5 / D9) ────────────\n\n /**\n * Open a decoded audio-chunk subscription on a broker — the poll-based,\n * tRPC-reachable replacement for the live-object `onDecodedAudioChunk`\n * callback. The consumer then polls `pullAudioChunks` and feeds each\n * `DecodedAudioChunk` to its downstream audio logic. Audio chunks are tiny\n * so their bytes travel inline on the RPC wire — no shared memory.\n */\n async subscribeAudioChunks(input: SubscribeAudioChunksInput): Promise<SubscribeAudioChunksResult> {\n await this.ensureBrokerForId(input.brokerId)\n const broker = this.brokers.get(input.brokerId)\n if (!broker) {\n throw new Error(`stream-broker: no broker for \"${input.brokerId}\"`)\n }\n const result = broker.subscribeAudioChunks({ tag: input.tag })\n this.audioSubscriptionBroker.set(result.subscriptionId, input.brokerId)\n return result\n }\n\n /** Drain `DecodedAudioChunk`s for a subscription opened via `subscribeAudioChunks`. */\n async pullAudioChunks(\n input: { subscriptionId: string; maxCount: number },\n ): Promise<readonly DecodedAudioChunk[]> {\n const brokerId = this.audioSubscriptionBroker.get(input.subscriptionId)\n if (!brokerId) return []\n const broker = this.brokers.get(brokerId)\n if (!broker) return []\n return broker.pullAudioChunks(input.subscriptionId, input.maxCount)\n }\n\n /** Release an audio-chunk subscription. */\n async unsubscribeAudioChunks(input: { subscriptionId: string }): Promise<{ released: boolean }> {\n const brokerId = this.audioSubscriptionBroker.get(input.subscriptionId)\n if (!brokerId) return { released: false }\n this.audioSubscriptionBroker.delete(input.subscriptionId)\n const broker = this.brokers.get(brokerId)\n if (!broker) return { released: false }\n const released = broker.unsubscribeAudioChunks(input.subscriptionId)\n return { released }\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: [], encoded: [], 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 // Only touch brokers with a shm frame-plane decoder session on this\n // agent — the broker rotates every matching per-format session itself.\n const onNode = broker.getDecoderNodeId()?.split('/')[0] === agentNodeId\n if (!onNode) continue\n try {\n await broker.rotateDecoderSession(reason, agentNodeId)\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 this.frameSubscriptionBroker.clear()\n this.audioSubscriptionBroker.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).map((e) => this.enrichRtspEntry(e))\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).map((e) => this.enrichRtspEntry(e))\n }\n\n /** Device-scoped PROFILE RTSP entry fetch — backs `cameraStreams.getProfileRtspEntries`. */\n getProfileRtspEntriesForDevice(deviceId: number, hostname?: string): readonly ProfileRtspEntry[] {\n if (!this.assignments.has(deviceId)) return []\n // Ensure the profile→source aliasing is current before reading URLs.\n this.syncProfileRestreamers(deviceId)\n const assignmentMap = this.assignments.get(deviceId)?.map ?? {}\n const streamMap = this.cameraStreams.get(deviceId)\n return buildProfileRtspEntries({\n deviceId,\n assignmentMap,\n resolveEntry: (profileBrokerId) => this.rtspProvider.getEntry(profileBrokerId, hostname),\n resolveMeta: (camStreamId) => {\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n const cam = streamMap?.get(camStreamId)\n const codec = broker?.getStats()?.codec ?? cam?.codec\n return {\n ...(codec !== undefined ? { codec } : {}),\n ...(cam?.resolution !== undefined ? { resolution: cam.resolution } : {}),\n }\n },\n })\n }\n\n /**\n * Keep one RTSP restreamer per assigned profile slot, aliasing the\n * assigned source broker's `RtspRestreamer` + a warm-up that drives\n * the source broker's single on-demand pull. So a profile consumer\n * (HAP/Alexa/recording) and a WebRTC consumer of the same profile\n * converge on ONE physical camera pull. Diff-based + idempotent: only\n * (re)registers when a profile's backing source changes, so a stable\n * token survives across reads (no mid-session token rotation).\n */\n private syncProfileRestreamers(deviceId: number): void {\n const assignmentMap = this.assignments.get(deviceId)?.map ?? {}\n const actions = computeProfileRestreamerActions({\n deviceId,\n assignmentMap,\n sourceBrokerExists: (camStreamId) => this.brokers.has(brokerIdFor(deviceId, camStreamId)),\n currentBindings: this.profileRestreamerBindings,\n })\n if (actions.register.length === 0 && actions.unregister.length === 0) return\n for (const profileBrokerId of actions.unregister) {\n this.rtspServer.unregisterRestreamer(profileBrokerId)\n this.profileRestreamerBindings.delete(profileBrokerId)\n }\n for (const { profileBrokerId, camStreamId } of actions.register) {\n const sourceBrokerId = brokerIdFor(deviceId, camStreamId)\n const sourceBroker = this.brokers.get(sourceBrokerId)\n if (!sourceBroker) continue\n const existingToken = this.persistedTokens.get(profileBrokerId)\n // Warm-up: subscribing a no-op encoded consumer to the SOURCE\n // broker keeps it dialed for the lifetime of an RTSP session, so\n // the profile DESCRIBE doesn't race a cold broker.\n const warmUp = (): (() => void) => sourceBroker.onEncodedData(() => { /* warm-up no-op */ })\n this.rtspServer.registerRestreamer(\n profileBrokerId,\n sourceBroker.getRtspRestreamer(),\n warmUp,\n existingToken,\n )\n this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId)\n if (!existingToken) this.rtspProvider.persistTokens()\n }\n }\n\n /**\n * Enrich a bare RTSP entry with the source camStream's `codec` /\n * `resolution`. The provider doesn't know — it only owns tokens + URL\n * minting — but the consumer-side pickers\n * (`pickPreferredRtspEntry`, the exporter's `resolveSourceParams`)\n * need that shape to resolve `streamPreference: 'auto'` to the slot\n * whose source is closest to the consumer's target. Falls back to\n * the bare entry when the brokerId points at a stream the manager\n * never registered (legacy + race scenarios) — the picker then\n * silently degrades to first-enabled.\n */\n private enrichRtspEntry(entry: RtspRestreamEntry): RtspRestreamEntry {\n const parsed = parseBrokerId(entry.brokerId)\n if (!parsed) return entry\n const stream = this.cameraStreams.get(parsed.deviceId)?.get(parsed.camStreamId)\n if (!stream) return entry\n return {\n ...entry,\n ...(stream.codec !== undefined ? { codec: stream.codec } : {}),\n ...(stream.resolution !== undefined ? { resolution: stream.resolution } : {}),\n }\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 // Queue any persisted derived streams for boot-time hydration.\n // publishCameraStream({kind:'derived'}) requires the source broker to\n // already be registered, which is not guaranteed at boot. For each\n // derived definition whose source is already up, we attempt the publish\n // immediately (inside the IIFE). If SOURCE_NOT_FOUND is thrown, the\n // definition is recorded in pendingDerivedStreams, keyed by the source\n // brokerId. publishCameraStream drains the queue when the source lands.\n void (async () => {\n for (const [deviceId, override] of overrides) {\n if (!override.derivedStreams) continue\n for (const [name, def] of Object.entries(override.derivedStreams)) {\n // Unconfigured placeholder (no source yet) — keep it persisted but\n // don't try to materialize a broker for it.\n if (!def.sourceCamStreamId) continue\n try {\n await this.publishCameraStream({\n deviceId,\n camStreamId: `derived:${name}`,\n kind: 'derived',\n sourceCamStreamId: def.sourceCamStreamId,\n encodeProfile: def.encodeProfile,\n })\n } catch (err) {\n if (err instanceof Error && err.message.includes('SOURCE_NOT_FOUND')) {\n const sourceBrokerId = brokerIdFor(deviceId, def.sourceCamStreamId)\n const list = this.pendingDerivedStreams.get(sourceBrokerId) ?? []\n list.push({ deviceId, name, def })\n this.pendingDerivedStreams.set(sourceBrokerId, list)\n }\n }\n }\n }\n })()\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 /**\n * Per-device \"Advanced WebRTC debug logging\" flag — when true, every\n * WebRTC session created for this device runs with `debug = true` so\n * the full negotiation/media lifecycle is logged. Off by default.\n * Independent of `isStreamingDebug`.\n */\n isWebrtcDebug(deviceId: number): boolean {\n return this.deviceOverrides.get(deviceId)?.webrtcDebug === true\n }\n\n /**\n * Returns an EncodeProfile-shaped snapshot of the camStream's *probed*\n * source parameters — the codec / resolution / fps the publisher\n * advertised when it called `publishCameraStream`, optionally enriched\n * with live broker stats (bitrateKbps) when available.\n *\n * Used as `widgetConfig.sourceParams` so the FFmpeg parameters widget\n * can render a read-only \"Source (probed)\" chips card next to the\n * override editor. Returns `undefined` when the camStream isn't\n * registered or has no advertised media shape (the widget then\n * omits the card rather than showing fictitious zeros).\n *\n * Note: audio is reported as `'passthrough'` because the broker's\n * source side doesn't probe audio shape today — the widget's audio\n * chip will read \"passthrough\" rather than guess a sample rate.\n */\n getCameraStreamProbedParams(deviceId: number, camStreamId: string): EncodeProfile | undefined {\n const stream = this.cameraStreams.get(deviceId)?.get(camStreamId)\n if (stream === undefined) return undefined\n const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId))\n const stats = broker?.getStats()\n const rawCodec = (stream.codec ?? stats?.codec ?? '').toLowerCase()\n const codec: VideoEncode['codec'] = rawCodec === 'h265' ? 'h265' : 'h264'\n const video: VideoEncode = {\n codec,\n ...(stream.resolution?.width !== undefined ? { width: stream.resolution.width } : {}),\n ...(stream.resolution?.height !== undefined ? { height: stream.resolution.height } : {}),\n ...(stream.fps !== undefined ? { fps: stream.fps } : {}),\n ...(stats?.bitrateKbps !== undefined && stats.bitrateKbps > 0 ? { bitrateKbps: Math.round(stats.bitrateKbps) } : {}),\n }\n // Nothing meaningful was advertised — omit so the widget hides the\n // probed card instead of showing only a codec chip.\n if (video.width === undefined && video.height === undefined && video.fps === undefined) {\n return undefined\n }\n return { video, audio: 'passthrough' as const }\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 \"General\" — three profile→stream selectors + derived stream names ──────\n const generalTab: ConfigSubTabDefinitionWithValue = {\n id: 'general',\n label: 'General',\n fields: [\n ...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 type: 'text' as const,\n key: 'derivedStreamNames',\n label: 'Derived stream names',\n description: 'Add a name to create a new derived stream; configure its source and encode params in its tab below.',\n placeholder: 'e.g. web-720p',\n span: 2 as const,\n value: Object.keys(override?.derivedStreams ?? {}),\n multiple: {\n min: 0,\n max: 10,\n addLabel: 'Add derived stream',\n itemLabel: 'Stream ${n}',\n },\n },\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 sourceStreamOptions = streams.map((s) => ({ value: s.camStreamId, label: s.label ?? s.camStreamId }))\n\n const derivedTabs: ConfigSubTabDefinitionWithValue[] = Object.entries(override?.derivedStreams ?? {}).map(([name, def]) => ({\n id: `derived:${name}`,\n label: name,\n fields: [\n {\n type: 'select' as const,\n key: `derivedSource:${name}`,\n label: 'Source stream',\n description: 'Which source camStream this derived stream reads from.',\n options: sourceStreamOptions,\n span: 2 as const,\n value: def.sourceCamStreamId,\n },\n {\n type: 'widget' as const,\n key: `derivedEncodeWidget:${name}`,\n label: 'Encode parameters',\n span: 2 as const,\n widgetId: 'stream-broker/ffmpeg-params',\n widgetConfig: {\n fieldKey: `derivedProfile:${name}`,\n label: 'Encode parameters',\n // The derived stream's \"source\" is its upstream camStream —\n // surface that camStream's probed shape so the operator sees\n // what the transcode pipeline is consuming.\n sourceParams: this.getCameraStreamProbedParams(deviceId, def.sourceCamStreamId),\n initialValue: def.encodeProfile,\n writerCapName: 'stream-broker',\n writerAddonId: 'stream-broker',\n },\n },\n // Toggle pair: Pre-buffer + RTSP restream render side by side\n // (span 1 each). Order matters — adjacent span-1 fields share a\n // row in the two-column form grid.\n {\n type: 'boolean' as const,\n key: `derivedPrebufferEnabled:${name}`,\n label: 'Pre-buffer',\n style: 'switch' as const,\n span: 1 as const,\n default: false,\n value: def.preBuffer?.enabled ?? false,\n },\n {\n type: 'boolean' as const,\n key: `derivedRtspEnabled:${name}`,\n label: 'RTSP restream',\n style: 'switch' as const,\n span: 1 as const,\n default: false,\n value: def.rtspEnabled ?? false,\n },\n // Full-width duration slider, shown only when Pre-buffer is on —\n // no orphan slider when the feature is disabled.\n {\n type: 'slider' as const,\n key: `derivedPrebufferSec:${name}`,\n label: 'Pre-buffer Duration',\n min: 0,\n max: 30,\n step: 1,\n unit: 's',\n showValue: true,\n span: 2 as const,\n showWhen: { field: `derivedPrebufferEnabled:${name}`, equals: true },\n default: this.defaultPreBufferSec,\n value: def.preBuffer?.seconds ?? this.defaultPreBufferSec,\n },\n ],\n }))\n\n const streamBrokerSection: ConfigSectionWithValues = {\n id: 'stream-broker-streams',\n title: '',\n tab: 'stream-broker',\n // Hoist out of the Config tab into a dedicated device-detail\n // top-level \"Stream Broker\" tab — DeviceDetail discovers the tab\n // from `location: 'top-tab'` sections, the way recording/zones/ptz\n // do. Without this the section nests as a Config inner-tab.\n location: 'top-tab',\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: [generalTab, ...streamTabs, ...derivedTabs],\n },\n ],\n }\n\n const diagnosticsSection: ConfigSectionWithValues = {\n id: 'stream-broker-diagnostics',\n title: 'Diagnostics',\n tab: 'stream-broker',\n // Same top-tab as the streams section (sections sharing a tab id\n // stack in `order` sequence inside that one tab body).\n location: 'top-tab',\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 type: 'boolean' as const,\n style: 'switch' as const,\n key: 'webrtcDebug',\n label: 'Advanced WebRTC debug logging',\n description:\n 'Log EVERY step of the WebRTC pipeline for this device: the local ICE candidates we put in the answer (with type host/srflx/relay + address), the remote offer candidates, DTLS state transitions, ICE nomination, codec negotiation (offered vs negotiated), and first video/audio RTP. Use to diagnose hard cases (Alexa streaming, A/V sync). Off by default — turn on only while debugging.',\n default: false,\n value: override?.webrtcDebug === 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 if (fieldKey === 'webrtcDebug') {\n overrideDirty = true\n const next = value === null || value === undefined ? undefined : Boolean(value)\n if (next === undefined) {\n delete nextOverride.webrtcDebug\n } else {\n nextOverride.webrtcDebug = next\n }\n // No live broker push needed: each new WebRTC session reads\n // `isWebrtcDebug(deviceId)` at creation (see\n // `device-scoped-providers.ts`), so the next session for this\n // device picks the flag up. Existing sessions keep their flag.\n continue\n }\n\n if (fieldKey === 'derivedStreamNames' && Array.isArray(value)) {\n overrideDirty = true\n const names = value.filter((v): v is string => typeof v === 'string' && v.length > 0)\n const next = { ...(nextOverride.derivedStreams ?? {}) }\n // Add missing entries with empty profile placeholders\n for (const n of names) {\n if (!next[n]) {\n next[n] = {\n sourceCamStreamId: '',\n encodeProfile: { video: { codec: 'h264' as const }, audio: 'passthrough' as const },\n }\n }\n }\n // Remove names no longer in the list\n for (const existing of Object.keys(next)) {\n if (!names.includes(existing)) delete next[existing]\n }\n nextOverride.derivedStreams = next\n // Tear down derived brokers that were removed from the list\n for (const existing of Object.keys(nextOverride.derivedStreams === next ? {} : (nextOverride.derivedStreams ?? {}))) {\n if (!names.includes(existing)) {\n void this.retractCameraStream({ deviceId, camStreamId: `derived:${existing}` })\n }\n }\n // Materialise any derived streams whose source is already available\n for (const n of names) {\n const def = next[n]\n if (!def || !def.sourceCamStreamId) continue\n const sourceBrokerId = brokerIdFor(deviceId, def.sourceCamStreamId)\n if (!this.brokers.has(sourceBrokerId)) continue\n void this.publishCameraStream({\n deviceId,\n camStreamId: `derived:${n}`,\n kind: 'derived',\n sourceCamStreamId: def.sourceCamStreamId,\n encodeProfile: def.encodeProfile,\n }).catch((err: unknown) => {\n if (err instanceof Error && err.message.includes('SOURCE_NOT_FOUND')) return\n this.logger.warn('applyDeviceSettingsPatch: derivedStreamNames materialise failed', {\n tags: { deviceId }, meta: { name: n, error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n continue\n }\n\n if (fieldKey.startsWith('derivedSource:')) {\n const name = fieldKey.slice('derivedSource:'.length)\n overrideDirty = true\n const next = { ...(nextOverride.derivedStreams ?? {}) }\n if (!next[name]) {\n next[name] = { sourceCamStreamId: '', encodeProfile: { video: { codec: 'h264' as const }, audio: 'passthrough' as const } }\n }\n next[name] = { ...next[name], sourceCamStreamId: typeof value === 'string' ? value : '' }\n nextOverride.derivedStreams = next\n continue\n }\n\n if (fieldKey.startsWith('derivedProfile:')) {\n const name = fieldKey.slice('derivedProfile:'.length)\n overrideDirty = true\n const next = { ...(nextOverride.derivedStreams ?? {}) }\n if (!next[name]) {\n next[name] = { sourceCamStreamId: '', encodeProfile: { video: { codec: 'h264' as const }, audio: 'passthrough' as const } }\n }\n next[name] = { ...next[name], encodeProfile: EncodeProfileSchema.parse(value) }\n nextOverride.derivedStreams = next\n continue\n }\n\n if (fieldKey.startsWith('derivedPrebufferEnabled:')) {\n const name = fieldKey.slice('derivedPrebufferEnabled:'.length)\n overrideDirty = true\n const next = { ...(nextOverride.derivedStreams ?? {}) }\n if (next[name]) {\n next[name] = {\n ...next[name],\n preBuffer: { ...(next[name].preBuffer ?? { enabled: false, seconds: this.defaultPreBufferSec }), enabled: Boolean(value) },\n }\n nextOverride.derivedStreams = next\n }\n continue\n }\n\n if (fieldKey.startsWith('derivedPrebufferSec:')) {\n const name = fieldKey.slice('derivedPrebufferSec:'.length)\n overrideDirty = true\n const next = { ...(nextOverride.derivedStreams ?? {}) }\n if (next[name] && typeof value === 'number') {\n next[name] = {\n ...next[name],\n preBuffer: { ...(next[name].preBuffer ?? { enabled: false, seconds: 0 }), seconds: value },\n }\n nextOverride.derivedStreams = next\n }\n continue\n }\n\n if (fieldKey.startsWith('derivedRtspEnabled:')) {\n const name = fieldKey.slice('derivedRtspEnabled:'.length)\n overrideDirty = true\n const next = { ...(nextOverride.derivedStreams ?? {}) }\n if (next[name]) {\n next[name] = { ...next[name], rtspEnabled: Boolean(value) }\n nextOverride.derivedStreams = next\n }\n continue\n }\n\n }\n\n if (overrideDirty) {\n const hasContent =\n nextOverride.preBufferSecOverride !== undefined ||\n (nextOverride.preBuffer !== undefined && Object.keys(nextOverride.preBuffer).length > 0) ||\n nextOverride.streamingDebug !== undefined ||\n nextOverride.webrtcDebug !== undefined ||\n (nextOverride.derivedStreams !== undefined && Object.keys(nextOverride.derivedStreams).length > 0)\n if (!hasContent) {\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 /**\n * Recompute the auto profile assignment for a device over its CURRENT\n * complete cam-stream set and apply it once. Extracted from\n * `publishCameraStream` so a multi-stream catalog publish can upsert\n * every stream first and rank ONCE (no transient intermediate\n * assignment). When the device's assignment is operator-locked\n * (`auto === false`) the existing map is re-applied unchanged.\n */\n private async reconcileAutoAssignment(\n deviceId: number,\n reason: 'publish' | 'retract',\n ): Promise<void> {\n const current = this.assignments.get(deviceId)\n const streams = [...(this.cameraStreams.get(deviceId)?.values() ?? [])]\n .sort((a, b) => a.order - b.order)\n const isAuto = current?.auto ?? true\n const newMap: Partial<Record<CamProfile, string>> =\n !current || (isAuto && this.wouldChangeAutoMap(current.map, streams))\n ? this.computeInitialAssignment(streams)\n : current.map\n await this.applyAssignmentUpdate(deviceId, newMap, { auto: isAuto, reason })\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 // Re-point profile-keyed restreamers at their (possibly new) sources.\n // On a FIRST publish this runs before ensureBroker creates the source\n // broker, so profiles whose source isn't live yet are skipped here and\n // wired by the syncProfileRestreamers call inside the publish path once\n // the broker exists — don't remove that second call thinking it redundant.\n this.syncProfileRestreamers(deviceId)\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 case 'derived':\n // Derived streams bypass ensureBroker — their source plane is the\n // transcode pipeline, not a pull/push transport. This branch is\n // unreachable in practice (publishCameraStream short-circuits before\n // ensureBroker is called for derived entries) but satisfies the\n // exhaustive switch so the type-checker accepts the full CamStreamKind\n // union.\n throw new Error(`buildSourceFromCamStream called for derived stream '${cam.camStreamId}' — derived streams must not reach ensureBroker`)\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 — the\n // source provider closure re-resolves a fresh URL on every dial.\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 // Pass a warm-up closure that subscribes a no-op encoded-data\n // consumer for the duration of an RTSP restream session. Triggers\n // `checkDemand` → upstream dial when an external client (ffprobe,\n // recording sidecar, NVR) connects, so the DESCRIBE doesn't race a\n // cold broker and return an empty SDP. The handle is released when\n // the RTSP socket closes, letting the broker idle-suspend normally.\n const warmUp = (): (() => void) => broker.onEncodedData(() => { /* warm-up no-op */ })\n this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken)\n if (!existingToken) this.rtspProvider.persistTokens()\n\n await this.fanOutRegistration(deviceId, brokerId, cam, broker)\n // A new source broker may back one or more assigned profiles — wire\n // their profile-keyed restreamers now that the source exists.\n this.syncProfileRestreamers(deviceId)\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.sweepFrameSubscriptions(brokerId)\n this.rtspProvider.persistTokens()\n // The source for one or more profiles just disappeared — drop the\n // now-dangling profile-keyed restreamers.\n this.syncProfileRestreamers(deviceId)\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 // Default OFF for every camera (battery or AC) when the operator\n // hasn't toggled the per-stream pre-buffer setting. The broker\n // stays strictly on-demand: it dials upstream only when a consumer\n // (WebRTC session, RTSP restream client, recording subscriber,\n // detection plane) attaches, and idle-suspends after the consumer\n // count drops back to zero. Operators opt in to \"always warm\"\n // explicitly per stream via the Streams settings panel. Reverts an\n // earlier always-on default that was surprising — every camera\n // started dialing upstream at boot even when nothing was watching.\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) : makeProfileBrokerId(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","import type { EncodeProfile } from '@camstack/types'\n\n/**\n * Resolve an EncodeProfile from an optional persisted override and a\n * consumer-baked default.\n *\n * override → consumerDefault (by reference)\n *\n * Pure function. The override-or-not decision is local to the caller.\n * In practice `withSourceResolution` adapts a base `EncodeProfile` to a\n * probed source resolution; the WebRTC path passes\n * `WEBRTC_FALLBACK_PROFILE` as that base. The preset library is\n * intentionally NOT a resolver parameter — the snapshot-semantics rule\n * means a supplied override carries its own materialised profile.\n */\nexport function resolveEncodeProfile(\n override: EncodeProfile | undefined,\n consumerDefault: EncodeProfile,\n): EncodeProfile {\n return override ?? consumerDefault\n}\n\n/**\n * Adapt a consumer's baked default to a publisher-probed source shape.\n *\n * When the probed source advertises both width and height, the returned\n * profile overrides `consumerDefault.video.{width,height}` so the\n * forced-transcode path scales to the source's real resolution instead\n * of the baked 720p — a 4K H.265 camera served to a no-HW-HEVC browser\n * stays 4K H.264 on the wire instead of dropping to 720p.\n *\n * Returns the consumer default by reference when the probed source is\n * absent or carries no dimensions, so callers can rely on identity for\n * \"no smart fallback applied\" checks.\n *\n * Other video fields (codec, profile, fps, bitrate, GOP, preset, tune)\n * are NOT touched — those encode the consumer's protocol contract, not\n * the source. Audio is left untouched for the same reason.\n *\n * Pure function. The decision to apply this fallback (only when the\n * operator hasn't set an explicit override) lives at the call site —\n * see `WebrtcSessionProvider.resolveTranscodeProfile`.\n */\nexport function withSourceResolution(\n consumerDefault: EncodeProfile,\n probedSource: EncodeProfile | undefined,\n): EncodeProfile {\n const probedWidth = probedSource?.video.width\n const probedHeight = probedSource?.video.height\n if (probedWidth === undefined || probedHeight === undefined) return consumerDefault\n return {\n ...consumerDefault,\n video: { ...consumerDefault.video, width: probedWidth, height: probedHeight },\n }\n}\n","import type { EncodeProfile } from '@camstack/types'\n\n/**\n * Hardcoded fallback encode profile for the WebRTC viewer path. Used only\n * when the broker must spawn ffmpeg (source codec ∉ the browser-accepted\n * set, e.g. H.265 source on an H.264-only browser). Replaces the former\n * operator-configurable `streamWebrtcParams` override + `WEBRTC_BAKED_DEFAULT`\n * preset. Bit-identical to the old `web-default` profile.\n */\nexport const WEBRTC_FALLBACK_PROFILE: EncodeProfile = {\n video: { codec: 'h264', profile: 'baseline', width: 1280, height: 720, fps: 25, bitrateKbps: 3000, gopFrames: 30, bf: 0, preset: 'ultrafast', tune: 'zerolatency' },\n audio: 'passthrough',\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 * True when an H.264 `profile-level-id` (6 hex chars: profile_idc +\n * constraint-set flags + level_idc) is Baseline / Constrained Baseline,\n * i.e. profile_idc 0x42 (66). iOS Safari/WebKit WebRTC decodes ONLY this\n * profile for H.264 RECEIVE — Main (0x4d) and High (0x64) negotiate but\n * never decode (endless PLI storm, black view). The test is on\n * profile_idc (the first byte) alone: the constraint flags and level\n * don't change Baseline decodability on iOS.\n */\nexport function isBaselineProfileLevelId(profileLevelId: string): boolean {\n return profileLevelId.length >= 2 && profileLevelId.slice(0, 2).toLowerCase() === \"42\";\n}\n\n/**\n * Group a flat list of H.264 NAL units into access units (one decoded\n * picture each), per the boundary rules of H.264 §7.4.1.2.4. A new access\n * unit starts at:\n * - an Access Unit Delimiter (type 9), or\n * - a VCL slice (types 1–5) whose `first_mb_in_slice == 0` (a new\n * primary coded picture — detected by the high bit of the byte after\n * the NAL header), or\n * - an SPS (7) / PPS (8) / SEI (6) that follows a completed picture.\n * AUD NALs are dropped from the output (they're delimiters only).\n *\n * This restores correct frame delimiting for the transcode feed: each\n * returned access unit is sent with exactly ONE RTP timestamp and a\n * single trailing marker bit, so the receiver can assemble and decode\n * each picture. (ffmpeg's raw-H.264 stdout is a bytestream with no frame\n * boundaries, so we recover them here.)\n */\nexport function groupNalsIntoAccessUnits(nals: readonly Buffer[]): Buffer[][] {\n const accessUnits: Buffer[][] = [];\n let current: Buffer[] = [];\n let currentHasVcl = false;\n\n const isVcl = (type: number): boolean => type >= 1 && type <= 5;\n const startsNewAccessUnit = (type: number, nal: Buffer): boolean => {\n // A VCL NAL begins a new picture when first_mb_in_slice == 0, which\n // encodes as a leading '1' bit → high bit of the first slice-header byte.\n if (isVcl(type)) return nal.length >= 2 && (nal[1]! & 0x80) === 0x80;\n // AUD / SPS / PPS / SEI before the next picture belong to that picture.\n return type === 9 || type === 7 || type === 8 || type === 6;\n };\n\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const type = nal[0]! & 0x1f;\n if (currentHasVcl && startsNewAccessUnit(type, nal)) {\n accessUnits.push(current);\n current = [];\n currentHasVcl = false;\n }\n if (type === 9) continue; // drop the Access Unit Delimiter\n current.push(nal);\n if (isVcl(type)) currentHasVcl = true;\n }\n if (current.length > 0) accessUnits.push(current);\n return accessUnits;\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 // Cap each lookup hard. A `.local` from a REMOTE peer (cellular/Tailscale)\n // is unresolvable here, and the OS resolver (getaddrinfo) blocks ~5s before\n // giving up — that delay stalls `setRemoteDescription` and breaks the whole\n // ICE flow even when the answer ALSO carries usable srflx/relay candidates\n // (the remote case). A same-LAN peer's `.local` resolves via Bonjour/Avahi\n // in a few ms, so a short cap keeps the LAN benefit while never blocking ICE\n // on the (pointless-for-remote) host candidates.\n const MDNS_LOOKUP_TIMEOUT_MS = 400\n const replacements = await Promise.all(\n [...hosts].map(async (host) => {\n try {\n const address = await Promise.race([\n dnsLookup(host, { family: 4 }).then((r) => r.address),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(`mDNS lookup timed out after ${MDNS_LOOKUP_TIMEOUT_MS}ms`)), MDNS_LOOKUP_TIMEOUT_MS).unref?.(),\n ),\n ])\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 (dropping host candidate, srflx/relay still used)', { 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 * 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 * 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 * Re-fragments H.264 RTP packets coming straight off the camera so they\n * fit 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), and\n * SPS/PPS are aggregated into a STAP-A sent before every IDR.\n *\n * This is the H.264 sibling of `H265Repacketizer`. Forwarding source RTP\n * through it — instead of the \"depacketize → Annex-B → re-packetize from\n * scratch\" `writeVideoNals` path — is what makes Main/High passthrough\n * decodable on iOS (WebKit), whose H.264 depacketizer is as strict as its\n * HEVC one. Desktop Chrome tolerates the synthesized shape; iOS does not.\n */\nimport { isNextSequenceNumber, JitterBuffer, RtpPacket } from \"./jitter-buffer.js\";\n\n// https://yumichan.net/video-processing/video-compression/introduction-to-h264-nal-unit/\nexport const NAL_TYPE_STAP_A = 24;\nexport const NAL_TYPE_FU_A = 28;\nexport const NAL_TYPE_NON_IDR = 1;\nexport const NAL_TYPE_IDR = 5;\nexport const NAL_TYPE_SEI = 6;\nexport const NAL_TYPE_SPS = 7;\nexport const NAL_TYPE_PPS = 8;\nexport const NAL_TYPE_DELIMITER = 9;\n\nconst NAL_HEADER_SIZE = 1;\nconst FU_A_HEADER_SIZE = 2;\nconst LENGTH_FIELD_SIZE = 2;\nconst STAP_A_HEADER_SIZE = NAL_HEADER_SIZE + LENGTH_FIELD_SIZE;\n\n// A STAP-A packet aggregates multiple NALs. Split it back into them.\nexport function depacketizeStapA(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 ret.push(data.subarray(lastPos));\n return ret;\n}\n\nexport function splitH264NaluStartCode(data: Buffer): Buffer[] {\n const ret: Buffer[] = [];\n let previous = 0;\n let offset = 0;\n const maybeAddSlice = (): void => {\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 H264CodecInfo {\n sps?: Buffer;\n pps?: Buffer;\n // SEI may only apply to a number/time range of frames; resending it with\n // every keyframe is a best-effort to keep decoders that need the SEI in sync.\n sei?: Buffer;\n}\n\ninterface FuaFragmentOptions {\n payload: Buffer;\n noStart: boolean;\n noEnd: boolean;\n marker: boolean;\n}\n\nexport class H264Repacketizer {\n extraPackets = 0;\n fuaMax!: number;\n pendingFuA: RtpPacket[] | undefined;\n // the stapa packet that will be sent before an idr frame.\n stapa: RtpPacket | undefined;\n fuaMin!: number;\n\n constructor(public console: Console, private maxPacketSize: number, public codecInfo?: H264CodecInfo, public jitterBuffer = new JitterBuffer(console, 4)) {\n this.setMaxPacketSize(maxPacketSize);\n }\n\n setMaxPacketSize(maxPacketSize: number): void {\n this.maxPacketSize = maxPacketSize;\n // 12 is the rtp/srtp header size.\n this.fuaMax = maxPacketSize - FU_A_HEADER_SIZE;\n this.fuaMin = Math.round(maxPacketSize * .8);\n }\n\n ensureCodecInfo(): H264CodecInfo {\n if (!this.codecInfo) {\n this.codecInfo = {};\n }\n return this.codecInfo;\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 updateSei(sei: Buffer): void {\n this.ensureCodecInfo().sei = sei;\n }\n\n shouldFilter(_nalType: number): boolean {\n // Currently nothing is filtered, but this could be customized.\n return false;\n }\n\n // a fragmentation unit (fua) is a NAL unit broken into multiple fragments.\n // https://datatracker.ietf.org/doc/html/rfc6184#section-5.8\n packetizeFuA(data: Buffer, noStart?: boolean, noEnd?: boolean): Buffer[] {\n // handle both normal packets and fua packets.\n // a fua packet can be fragmented easily into smaller packets, as\n // it is already a fragment, and splitting segments is trivial.\n\n const initialNalType = data[0]! & 0x1f;\n\n if (initialNalType === NAL_TYPE_FU_A) {\n const fnri = data[0]! & (0x80 | 0x60);\n const originalNalType = data[1]! & 0x1f;\n const isFuStart = !!(data[1]! & 0x80);\n const isFuEnd = !!(data[1]! & 0x40);\n const isFuMiddle = !isFuStart && !isFuEnd;\n\n const originalNalHeader = Buffer.from([fnri | originalNalType]);\n data = Buffer.concat([originalNalHeader, data.subarray(FU_A_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 const fnri = data[0]! & (0x80 | 0x60);\n const nalType = data[0]! & 0x1F;\n\n const fuIndicator = fnri | NAL_TYPE_FU_A;\n\n const fuHeaderMiddle = Buffer.from([fuIndicator, nalType]);\n const fuHeaderStart = noStart ? fuHeaderMiddle : Buffer.from([fuIndicator, nalType | 0x80]);\n const fuHeaderEnd = noEnd ? fuHeaderMiddle : Buffer.from([fuIndicator, nalType | 0x40]);\n let fuHeader = fuHeaderStart;\n\n const packages: Buffer[] = [];\n let offset = NAL_HEADER_SIZE;\n\n while (offset < data.length) {\n const packageSize = Math.min(this.fuaMax, data.length - offset);\n const 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 // https://datatracker.ietf.org/doc/html/rfc6184#section-5.7.1\n packetizeOneStapA(datas: Buffer[]): Buffer {\n const payload: Buffer[] = [];\n\n if (!datas.length)\n throw new Error('packetizeOneStapA requires at least one NAL');\n\n let counter = 0;\n let availableSize = this.maxPacketSize - STAP_A_HEADER_SIZE;\n\n // h264/rtp spec: https://datatracker.ietf.org/doc/html/rfc6184#section-5.6\n // homekit does not want NRI aggregation in the sps/pps stap-a.\n const stapHeader = NAL_TYPE_STAP_A;\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 packed = Buffer.alloc(2);\n packed.writeUInt16BE(nalu.length, 0);\n payload.push(packed, nalu);\n }\n\n // when a stapa packet has a p frame inside it, it may exceed the max packet size.\n // it needs to be returned as is to be turned into a fua packet.\n if (counter === 0)\n return datas.shift()!;\n\n // a single nalu stapa is unnecessary, return the nalu itself.\n if (counter === 1) {\n return payload[1]!;\n }\n\n payload.unshift(Buffer.from([stapHeader]));\n return Buffer.concat(payload);\n }\n\n packetizeStapA(datas: Buffer[]): Buffer[] {\n const ret: Buffer[] = [];\n while (datas.length) {\n const nalu = this.packetizeOneStapA(datas);\n if (nalu.length < this.maxPacketSize) {\n ret.push(nalu);\n continue;\n }\n const fuas = this.packetizeFuA(nalu);\n ret.push(...fuas);\n }\n return ret;\n }\n\n createPacket(rtp: RtpPacket, data: Buffer, marker: boolean): RtpPacket {\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 return ret;\n }\n\n flushPendingFuA(ret: RtpPacket[]): void {\n const pending = this.pendingFuA;\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[1]! & 0x1f;\n const hasFuStart = !!(first.payload[1]! & 0x80);\n const hasFuEnd = !!(last.payload[1]! & 0x40);\n\n const fnri = first.payload[0]! & (0x80 | 0x60);\n const originalNalHeader = Buffer.from([fnri | originalNalType]);\n\n const getDefragmentedPendingFua = (): Buffer => {\n const originalFragments = pending.map(packet => packet.payload.subarray(FU_A_HEADER_SIZE));\n originalFragments.unshift(originalNalHeader);\n return Buffer.concat(originalFragments);\n };\n\n // have seen cameras that toss sps/pps/idr into a fua, delimited by start codes.\n // so the fua packet looks like: sps | start code | pps | start code | idr\n if (originalNalType === NAL_TYPE_SPS) {\n const defragmented = getDefragmentedPendingFua();\n\n const splits = splitH264NaluStartCode(defragmented);\n while (splits.length) {\n const split = splits.shift()!;\n const splitNaluType = split[0]! & 0x1f;\n 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 if (splitNaluType === NAL_TYPE_IDR)\n this.maybeSendStapACodecInfo(first, ret);\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 while (pending.length) {\n const fua = pending[0]!;\n if (fua.payload.length > this.maxPacketSize || fua.payload.length < this.fuaMin)\n break;\n pending.shift();\n ret.push(this.createPacket(fua, fua.payload, fua.header.marker));\n }\n\n if (!pending.length) {\n this.pendingFuA = undefined;\n return;\n }\n\n const refragFirst = pending[0]!;\n const refragLast = pending[pending.length - 1]!;\n const refragHasFuStart = !!(refragFirst.payload[1]! & 0x80);\n const refragHasFuEnd = !!(refragLast.payload[1]! & 0x40);\n\n const defragmented = getDefragmentedPendingFua();\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.pendingFuA = undefined;\n }\n\n createRtpPackets(packet: RtpPacket, nalus: Buffer[], ret: RtpPacket[], hadMarker = packet.header.marker): void {\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 maybeSendStapACodecInfo(packet: RtpPacket, ret: RtpPacket[]): void {\n if (this.stapa) {\n // stapa with codec information was sent recently, no need to send codec info.\n this.stapa = undefined;\n return;\n }\n\n if (!this.codecInfo?.sps || !this.codecInfo?.pps)\n return;\n\n const agg = [this.codecInfo.sps, this.codecInfo.pps];\n if (this.codecInfo?.sei)\n agg.push(this.codecInfo.sei);\n const aggregates = this.packetizeStapA(agg);\n if (aggregates.length !== 1) {\n this.console.error('expected only 1 packet for sps/pps stapa');\n return;\n }\n // this stapa only contains sps and pps (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 // given the packet, fragment it into multiple packets as needed.\n // a fragment of a payload may be provided via fuaOptions.\n fragment(packet: RtpPacket, ret: RtpPacket[], fuaOptions: FuaFragmentOptions = {\n payload: packet.payload,\n noStart: false,\n noEnd: false,\n marker: packet.header.marker,\n }): void {\n const { payload, noStart, noEnd, marker } = fuaOptions;\n if (payload.length > this.maxPacketSize || noStart || noEnd) {\n const fragments = this.packetizeFuA(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[]): void {\n // empty packets are apparently valid from webrtc. filter those out.\n if (!packet.payload.length) {\n this.flushPendingFuA(ret);\n this.extraPackets--;\n return;\n }\n\n const nalType = packet.payload[0]! & 0x1F;\n\n // fragmented packets must share a timestamp\n if (this.pendingFuA && this.pendingFuA[0]!.header.timestamp !== packet.header.timestamp) {\n this.flushPendingFuA(ret);\n }\n\n if (nalType === NAL_TYPE_FU_A) {\n // ideally send the packets through as is from the upstream source.\n // refragment only if the incoming fua packet is larger than the max packet size.\n const data = packet.payload;\n const originalNalType = data[1]! & 0x1f;\n\n if (this.shouldFilter(originalNalType)) {\n this.extraPackets--;\n return;\n }\n\n const isFuStart = !!(data[1]! & 0x80);\n\n if (isFuStart) {\n if (this.pendingFuA)\n this.console.error('fua restarted. skipping refragmentation of previous fua.', originalNalType);\n\n this.pendingFuA = undefined;\n\n // if this is an idr frame, but no sps has been sent via a stapa, dummy one up.\n // the stream may not contain codec information in stapa or may be sending it\n // in separate sps/pps packets.\n if (originalNalType === NAL_TYPE_IDR) {\n this.maybeSendStapACodecInfo(packet, ret);\n }\n }\n else {\n if (this.pendingFuA) {\n // check if packets were missing earlier from the previously queued fua packets.\n // if so, don't queue the current packet. all further fua packets are dropped\n // until a later fua start is received.\n const last = this.pendingFuA[this.pendingFuA.length - 1]!;\n if (!isNextSequenceNumber(last.header.sequenceNumber, packet.header.sequenceNumber)) {\n this.console.error('fua packet missing. skipping refragmentation.', originalNalType);\n return;\n }\n }\n }\n\n if (!this.pendingFuA)\n this.pendingFuA = [];\n\n this.pendingFuA.push(packet);\n\n const isFuEnd = !!(packet.payload[1]! & 0x40);\n if (isFuEnd) {\n this.flushPendingFuA(ret);\n }\n else if (this.pendingFuA.reduce((p, c) => p + c.payload.length - FU_A_HEADER_SIZE, NAL_HEADER_SIZE) > this.maxPacketSize) {\n // refragment fua packets as they are received, saving the last undersized packet for\n // the next fua packet.\n const last = this.pendingFuA[this.pendingFuA.length - 1]!.clone();\n const partial: RtpPacket[] = [];\n this.flushPendingFuA(partial);\n // retain a fua packet to validate subsequent fua packets.\n const retain = partial.pop();\n if (retain)\n last.payload = retain.payload;\n this.pendingFuA = [last];\n ret.push(...partial);\n }\n }\n else if (nalType === NAL_TYPE_STAP_A) {\n this.flushPendingFuA(ret);\n\n let hasSps = false;\n let hasPps = false;\n\n // break the aggregated packet up to update codec information.\n const depacketized = depacketizeStapA(packet.payload);\n depacketized.forEach(payload => {\n const stapNalType = payload[0]! & 0x1F;\n if (stapNalType === NAL_TYPE_SPS) {\n hasSps = true;\n this.updateSps(payload);\n }\n else if (stapNalType === NAL_TYPE_PPS) {\n hasPps = true;\n this.updatePps(payload);\n }\n else if (stapNalType === NAL_TYPE_SEI) {\n this.updateSei(payload);\n }\n // NAL_TYPE_DELIMITER / NON_IDR / IDR / 0 inside a stapa are uncommon\n // but harmless — re-aggregated below as-is.\n });\n\n // log that a stapa with codec info was sent\n if (hasSps && hasPps)\n this.stapa = packet;\n\n const stapa = this.packetizeStapA(depacketized);\n this.createRtpPackets(packet, stapa, ret);\n }\n else if (nalType >= 1 && nalType < 24) {\n this.flushPendingFuA(ret);\n\n if (this.shouldFilter(nalType)) {\n this.extraPackets--;\n return;\n }\n\n // codec information should be aggregated into a stapa. usually around 50 bytes total.\n 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) {\n this.extraPackets--;\n this.updateSei(packet.payload);\n return;\n }\n\n if (nalType === NAL_TYPE_IDR) {\n // if this is an idr frame, but no sps has been sent, dummy one up.\n this.maybeSendStapACodecInfo(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}\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, groupNalsIntoAccessUnits } from \"./h264-utils.js\";\nimport { convertH265ToAnnexB, isH265IrapAccessUnit } from \"./h265-utils.js\";\nimport { resolveMdnsCandidatesInSdp } from \"./mdns-resolve.js\";\nimport { networkInterfaces } from \"node:os\";\nimport { H265Repacketizer, type H265CodecInfo } from \"./h265-repacketizer.js\";\nimport { H264Repacketizer } from \"./h264-repacketizer.js\";\nimport type {\n WeriftModule,\n WeriftPeerConnection,\n WeriftMediaStreamTrack,\n WeriftRtpSender,\n WeriftTransceiver,\n WeriftPcOptions,\n WeriftIceServer,\n WeriftReceiverReport,\n WeriftIceCandidate,\n} from \"./werift-types.js\";\n\n/** A trickle ICE candidate exchanged over the signaling channel. */\nexport interface IceCandidatePayload {\n candidate: string;\n sdpMid: string | null;\n sdpMLineIndex: number | null;\n}\nimport { errMsg, type EncodeProfile, type WebrtcStreamTarget } from '@camstack/types'\nimport { WEBRTC_FALLBACK_PROFILE } from './webrtc-fallback-profile.js'\nimport { audioEncoderArgs, logBannerArgs } from '../stream-broker/ffmpeg-args-common.js'\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 // werift is patched in place via patches/werift+0.22.9.patch (repo-root patch-package):\n // under iceTransportPolicy:'relay' it now emits a relay-only SDP (forceTurn candidate-leak fix)\n // so remote/NAT viewers don't nominate a dead host candidate. Do not bump/replace werift\n // without re-vendoring that patch — see docs/superpowers/specs/2026-05-25-werift-turn-nat-fix-design.md\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// Tailscale host-candidate detection\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal shape of an `os.networkInterfaces()` entry we rely on. Kept\n * structural (not the full `os.NetworkInterfaceInfo`) so the detector\n * can be unit-tested with a hand-rolled map and no `os` mock.\n */\nexport interface NetworkInterfaceAddress {\n readonly address: string;\n readonly family: string | number;\n readonly internal: boolean;\n}\n\n/** Map shape returned by `os.networkInterfaces()` (subset we consume). */\nexport type NetworkInterfaceMap = Readonly<\n Record<string, readonly NetworkInterfaceAddress[] | undefined>\n>;\n\n/** True for an IPv4 address inside the Tailscale CGNAT range 100.64.0.0/10. */\nfunction isTailscaleIpv4(address: string): boolean {\n const parts = address.split(\".\");\n if (parts.length !== 4) return false;\n const a = Number.parseInt(parts[0] ?? \"\", 10);\n const b = Number.parseInt(parts[1] ?? \"\", 10);\n if (Number.isNaN(a) || Number.isNaN(b)) return false;\n // 100.64.0.0 – 100.127.255.255\n return a === 100 && b >= 64 && b <= 127;\n}\n\n/** True for an IPv6 address inside the Tailscale ULA range fd7a::/16. */\nfunction isTailscaleIpv6(address: string): boolean {\n // Strip a zone id (`fd7a::1%utun4`) before the prefix check.\n const pct = address.indexOf(\"%\");\n const clean = (pct === -1 ? address : address.slice(0, pct)).toLowerCase();\n return clean.startsWith(\"fd7a:\");\n}\n\n/**\n * Enumerate the host's network interfaces and return any Tailscale\n * overlay addresses — IPv4 in 100.64.0.0/10 (CGNAT) and IPv6 in\n * fd7a::/16 (ULA). Loopback/internal interfaces are skipped.\n *\n * werift gathers ICE candidates from its own socket binds and (in\n * 0.22.9) skips point-to-point `utun` interfaces, so a Tailscale host\n * candidate is never offered by default. Feeding the detected\n * address(es) into `pcOptions.iceAdditionalHostAddresses` makes werift\n * advertise them explicitly, so a remote Tailscale viewer can form a\n * direct host↔host pair (native H.264, no relay, no re-encode).\n *\n * The address is detected dynamically — never hardcoded — so it follows\n * the host's actual Tailscale assignment.\n *\n * @param interfaces injected for testing; defaults to `os.networkInterfaces()`.\n */\nexport function getTailscaleHostAddresses(\n interfaces: NetworkInterfaceMap = networkInterfaces(),\n): readonly string[] {\n const found = new Set<string>();\n for (const entries of Object.values(interfaces)) {\n if (!entries) continue;\n for (const entry of entries) {\n if (entry.internal) continue;\n const fam = entry.family;\n const isV4 = fam === \"IPv4\" || fam === 4;\n const isV6 = fam === \"IPv6\" || fam === 6;\n if (isV4 && isTailscaleIpv4(entry.address)) found.add(entry.address);\n else if (isV6 && isTailscaleIpv6(entry.address)) {\n // Drop any zone id so werift sees a bare literal.\n const pct = entry.address.indexOf(\"%\");\n found.add(pct === -1 ? entry.address : entry.address.slice(0, pct));\n }\n }\n }\n return [...found];\n}\n\n// ---------------------------------------------------------------------------\n// Video transcode decision\n// ---------------------------------------------------------------------------\n\nexport interface VideoTranscodeInput {\n /** Camera source video codec. */\n readonly sourceCodec: 'H264' | 'H265';\n /** Codec the browser selected in its SDP answer. */\n readonly negotiatedCodec: 'H264' | 'H265';\n /**\n * True when the H.264 source profile is non-Baseline (Main/High) so the\n * egress must be re-encoded to Constrained Baseline for iOS. Decided by\n * the broker from the camera's SPS; irrelevant for H.265 sources.\n */\n readonly transcodeToBaseline: boolean;\n}\n\n/**\n * Whether the session must run ffmpeg to produce a browser-decodable\n * egress instead of forwarding the camera's native bytes:\n *\n * - H.265 source + H.264-only browser → transcode (pre-existing).\n * - H.264 Main/High source + H.264 browser → transcode to Baseline (iOS fix).\n * - Baseline H.264, or H.265↔H.265 → passthrough (zero re-encode).\n */\nexport function resolveVideoTranscode(input: VideoTranscodeInput): boolean {\n const { sourceCodec, negotiatedCodec, transcodeToBaseline } = input;\n if (sourceCodec === 'H265' && negotiatedCodec === 'H264') return true;\n if (sourceCodec === 'H264' && negotiatedCodec === 'H264' && transcodeToBaseline) return true;\n return false;\n}\n\n/**\n * Read the video codec the peer settled on from a negotiated SDP — the\n * browser's answer (server-offer flow) or the server's answer to a client\n * offer (client-offer flow). Returns the first `H264`/`H265` rtpmap codec\n * (the primary payload type listed first on the `m=video` line), or\n * undefined when the SDP carries no video. Drives `negotiatedCodec`, which\n * `needsTranscode` reads to decide passthrough vs ffmpeg.\n */\nexport function detectNegotiatedVideoCodec(\n sdp: string,\n preferred?: 'H264' | 'H265',\n): 'H264' | 'H265' | undefined {\n // The werift client-offer answer lists H.264 BEFORE H.265 (it preserves the\n // browser's offer order, which puts the 10+ H.264 PTs ahead of the two\n // H.265 PTs). The raw \"first match\" would then return H.264 even when both\n // codecs were negotiated and we configured H.265 as preferred. With a\n // `preferred` hint: if that codec appears anywhere in the SDP, return it;\n // otherwise fall back to the first-match behaviour. Callers thread\n // `this.sourceCodec` so an H.265 source ends up on the H.265 wire path.\n if (preferred && new RegExp(`a=rtpmap:\\\\d+ ${preferred}/90000`, 'i').test(sdp)) {\n return preferred;\n }\n const m = sdp.match(/a=rtpmap:\\d+ (H264|H265)\\/90000/i)?.[1]?.toUpperCase();\n return m === 'H264' || m === 'H265' ? m : undefined;\n}\n\n/** Parsed view of one SDP `a=candidate:` line — the fields a debug log needs. */\nexport interface CandidateSummary {\n /** ICE candidate type: `host` | `srflx` | `prflx` | `relay` | `unknown`. */\n readonly type: string;\n /** `address:port` (the connection address advertised in the candidate). */\n readonly address: string;\n /** `udp` | `tcp` | the raw transport token. */\n readonly protocol: string;\n}\n\n/**\n * Parse a single SDP candidate line into `{ type, address, protocol }`.\n * The grammar (RFC 5245 §15.1) is:\n * `candidate:<foundation> <component> <transport> <priority> <addr> <port> typ <type> ...`\n * possibly prefixed with `a=`. Returns an `unknown`-typed summary when the\n * line doesn't match — never throws, so it's safe in a debug log path.\n */\nexport function parseCandidateLine(line: string): CandidateSummary {\n const body = line.startsWith('a=') ? line.slice(2) : line;\n const parts = body.trim().split(/\\s+/);\n // parts: ['candidate:F', comp, transport, prio, addr, port, 'typ', type, ...]\n const protocol = (parts[2] ?? 'unknown').toLowerCase();\n const addr = parts[4];\n const port = parts[5];\n const typIdx = parts.indexOf('typ');\n const type = typIdx >= 0 ? parts[typIdx + 1] ?? 'unknown' : 'unknown';\n const address = addr !== undefined && port !== undefined ? `${addr}:${port}` : 'unknown';\n return { type, address, protocol };\n}\n\n/**\n * Extract every `a=candidate:` line from an SDP and summarise each as\n * `{ type, address, protocol }`. Used by the advanced WebRTC debug log to\n * SHOW exactly which local candidates we put in the answer (the suspected\n * unreachable Tailscale/private host candidates on the Alexa path) and which\n * remote candidates the peer offered.\n */\nexport function summarizeCandidates(sdp: string): readonly CandidateSummary[] {\n return sdp\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.startsWith('a=candidate:'))\n .map(parseCandidateLine);\n}\n\n/**\n * Extract the `a=fmtp:` and `a=rtpmap:` lines from an SDP — the codec\n * parameter lines carrying `profile-level-id` and the codec/clock mapping.\n * Used by the advanced WebRTC debug log to compare offered vs negotiated\n * codec profiles (the iOS/Alexa H.264 passthrough discriminator).\n */\nexport function extractFmtpLines(sdp: string): readonly string[] {\n return sdp\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.startsWith('a=fmtp:') || l.startsWith('a=rtpmap:'));\n}\n\n/**\n * ffmpeg `-f` input demuxer for the transcode feed, keyed on the source\n * codec: H.265 Annex-B → `hevc`, H.264 Annex-B → `h264`. Output is always\n * Constrained Baseline H.264 (see `startTranscodeFeed`).\n */\nexport function transcodeInputFormat(sourceCodec: 'H264' | 'H265'): 'hevc' | 'h264' {\n return sourceCodec === 'H265' ? 'hevc' : 'h264';\n}\n\n// ---------------------------------------------------------------------------\n// A/V sync diagnostic timeline math\n// ---------------------------------------------------------------------------\n\n/**\n * One track's sampled clock state at the instant the diagnostic runs: the\n * last RTP timestamp we sent on that track, that track's RTP clock rate\n * (90000 for video, 8000 for PCMU audio), and the wall-clock elapsed since\n * the session was created. Pure input — no werift, no timers.\n */\nexport interface AvSyncTrackSample {\n /** Last RTP timestamp sent on this track (RTP ticks at `rate`). */\n readonly rtpTs: number;\n /** RTP clock rate in Hz (90000 video / 8000 PCMU audio). */\n readonly rate: number;\n /** Wall-clock ms elapsed since the session was created. */\n readonly elapsedMs: number;\n}\n\n/** Per-track derived timeline figures for the av-sync diagnostic. */\nexport interface AvSyncTrackResult {\n /** Media-time = rtpTs / rate, in seconds. */\n readonly mediaSeconds: number;\n /** Skew = media-time − wall-clock-elapsed (seconds). Negative ⇒ the RTP\n * clock has fallen behind real time. */\n readonly skewSeconds: number;\n}\n\n/** Result of the av-sync timeline computation for one diagnostic tick. */\nexport interface AvSyncSkewResult {\n /** Video track figures, or null when video is absent / rate is invalid. */\n readonly video: AvSyncTrackResult | null;\n /** Audio track figures, or null when audio is absent / rate is invalid. */\n readonly audio: AvSyncTrackResult | null;\n /**\n * Inter-track skew = audio media-seconds − video media-seconds, in seconds.\n * Negative ⇒ the audio media clock lags the video media clock (the suspected\n * \"audio plays ~0.5s behind video\" signature). null when either track is\n * absent / has an invalid clock rate, so no comparison is possible.\n */\n readonly interTrackSkewSeconds: number | null;\n}\n\n/** Convert one sampled track to media-seconds + per-track skew, or null when\n * the clock rate is non-positive (would divide by zero). */\nfunction deriveAvSyncTrack(sample: AvSyncTrackSample | null): AvSyncTrackResult | null {\n if (!sample || sample.rate <= 0) return null;\n const mediaSeconds = sample.rtpTs / sample.rate;\n const skewSeconds = mediaSeconds - sample.elapsedMs / 1000;\n return { mediaSeconds, skewSeconds };\n}\n\n/**\n * Compute the per-track and inter-track timeline skew for the av-sync\n * diagnostic. Given the last sent RTP timestamp, clock rate, and wall-clock\n * elapsed for each track, derive media-time (rtpTs/rate) and how far each\n * track's media clock has drifted from real time, plus the inter-track skew\n * (audio media-seconds − video media-seconds) that quantifies the observed\n * \"audio behind video\" lag. A missing track or an invalid (≤0) clock rate\n * yields null for that track and null for the inter-track figure. Pure.\n */\nexport function computeAvSyncSkew(input: {\n readonly video: AvSyncTrackSample | null;\n readonly audio: AvSyncTrackSample | null;\n}): AvSyncSkewResult {\n const video = deriveAvSyncTrack(input.video);\n const audio = deriveAvSyncTrack(input.audio);\n const interTrackSkewSeconds =\n video && audio ? audio.mediaSeconds - video.mediaSeconds : null;\n return { video, audio, interTrackSkewSeconds };\n}\n\n/**\n * Input to `computeAudioSilencePadPackets` — the pure decision of how many\n * PCMU silence packets to pre-stream BEFORE the first real audio packet so the\n * audio first-send wall-clock aligns to the video first-send wall-clock. Used\n * by `AdaptiveSession.writeAudio` once per session, on the very first audio\n * frame, to close the constant startup gap seen on transcoded sources (e.g.\n * Reolink AAC → PCMU runs ~400 ms behind video → browser inflates audio jitter\n * buffer → user perceives ~1 s of audio-late lag).\n */\nexport interface AudioSilencePadInput {\n /** Wall-clock at the moment the first real audio frame is about to send (ms). */\n readonly nowMs: number;\n /** Wall-clock at which the FIRST video RTP packet was sent (ms), or null\n * when video hasn't sent yet — no anchor → emit nothing. */\n readonly firstVideoSendWallMs: number | null;\n /** PCMU frame size in samples per packet (160 = 20 ms at 8 kHz). */\n readonly framesizeSamples: number;\n /** Audio sample rate (Hz). PCMU is 8 kHz. */\n readonly rateHz: number;\n /** Upper bound on the silence pre-stream duration so a runaway gap can't\n * blast tens of seconds of silence onto the wire. */\n readonly maxPadMs: number;\n}\n\n/**\n * Decide how many PCMU silence packets to pre-stream BEFORE the first real\n * audio packet so audio `firstSendWallMs` aligns to video `firstSendWallMs`.\n * Pure — no I/O, no clock reads, no side effects.\n *\n * Returns 0 when there's nothing to align to (no video anchor, gap ≤ 0,\n * sub-packet gap), and caps the count at `ceil(maxPadMs / frameMs)` to bound\n * the worst case. For positive gaps of one packet or more it returns\n * `ceil(gap / frameMs)` so the silence FULLY covers the measured gap.\n *\n * frameMs = `framesizeSamples / rateHz * 1000`. With PCMU 8 kHz / 160 samples\n * that's 20 ms — so a 401 ms gap returns 21 packets, and a 30 s gap saturates\n * at 100 packets (the 2000 ms cap / 20 ms-per-packet).\n */\nexport function computeAudioSilencePadPackets(input: AudioSilencePadInput): number {\n if (input.firstVideoSendWallMs === null) return 0;\n if (input.framesizeSamples <= 0 || input.rateHz <= 0) return 0;\n if (input.maxPadMs <= 0) return 0;\n const gapMs = input.nowMs - input.firstVideoSendWallMs;\n if (gapMs <= 0) return 0;\n const frameMs = (input.framesizeSamples / input.rateHz) * 1000;\n if (gapMs < frameMs) return 0;\n const desired = Math.ceil(gapMs / frameMs);\n const cap = Math.ceil(input.maxPadMs / frameMs);\n return Math.min(desired, cap);\n}\n\n/** One scheduled silence packet on the audio pre-stream plan — what\n * `planAudioSilencePadPackets` emits and the writer turns into a werift\n * `RtpPacket` (PCMU payload of `framesizeSamples` × `0xFF`). */\nexport interface AudioSilencePadPacketPlan {\n /** RTP timestamp at the audio clock rate (already `>>> 0`-normalised). */\n readonly rtpTs: number;\n /** Sequence number (`& 0xffff`-normalised), contiguous from the prior\n * `audioSeqNum` and ending one BEFORE the real packet's seq. */\n readonly seq: number;\n}\n\n/** Result of `planAudioSilencePadPackets` — the silence packets to emit + the\n * updated `audioSeqNum` to carry forward into the real packet's seq. The plan\n * is empty when `computeAudioSilencePadPackets` returns 0. */\nexport interface AudioSilencePadPlan {\n readonly packets: readonly AudioSilencePadPacketPlan[];\n /** The sequence number of the LAST silence packet emitted, OR the input\n * `audioSeqNum` when no packets were emitted. The next real packet's seq\n * is `(nextAudioSeqNum + 1) & 0xffff`. */\n readonly nextAudioSeqNum: number;\n}\n\n/** Input to `planAudioSilencePadPackets` — same parameters as\n * `computeAudioSilencePadPackets` plus the real first audio packet's `rtpTs`\n * (so silence timestamps anchor contiguously to it) and the current\n * `audioSeqNum` (so silence seqs continue from it). */\nexport interface AudioSilencePadPlanInput extends AudioSilencePadInput {\n /** RTP timestamp the FIRST real audio packet would carry (already\n * `>>> 0`-normalised). Silence packets occupy `[realRtpTs − N·160, realRtpTs)`. */\n readonly realRtpTs: number;\n /** Current `audioSeqNum` BEFORE any silence packet. The first silence\n * packet's seq is `(audioSeqNum + 1) & 0xffff`. */\n readonly audioSeqNum: number;\n}\n\n/**\n * Pure plan-builder for the PCMU silence pre-stream — turns\n * `computeAudioSilencePadPackets`'s count into the concrete (rtpTs, seq) list\n * the audio writer emits, plus the updated `audioSeqNum` to carry forward into\n * the real packet's seq.\n *\n * Silence timestamps run `realRtpTs − N·framesizeSamples … realRtpTs −\n * framesizeSamples`, then the real packet at `realRtpTs` — fully contiguous in\n * both ts and seq (32-bit/16-bit wrap-safe via `>>> 0` / `& 0xffff`). No I/O,\n * no clock reads, no side effects — safe to unit-test in isolation.\n */\nexport function planAudioSilencePadPackets(input: AudioSilencePadPlanInput): AudioSilencePadPlan {\n const packetCount = computeAudioSilencePadPackets(input);\n if (packetCount <= 0) {\n return { packets: [], nextAudioSeqNum: input.audioSeqNum };\n }\n const frameSize = input.framesizeSamples;\n const firstSilenceRtpTs = (input.realRtpTs - packetCount * frameSize) >>> 0;\n const packets: AudioSilencePadPacketPlan[] = [];\n let seq = input.audioSeqNum;\n for (let i = 0; i < packetCount; i++) {\n seq = (seq + 1) & 0xffff;\n packets.push({\n rtpTs: (firstSilenceRtpTs + i * frameSize) >>> 0,\n seq,\n });\n }\n return { packets, nextAudioSeqNum: seq };\n}\n\n/**\n * Build the ffmpeg argv for a transcode of `inputFormat` (h264 / hevc)\n * source bytes into the consumer profile's video/audio codecs.\n *\n * Layout:\n * [global flags] [profile.inputArgs] -i pipe:0\n * [video encode block] [audio encode block] [profile.outputArgs]\n * -f h264 pipe:1\n *\n * Smart video copy:\n * The operator's preset is the CONSUMER's requirement (\"I need h264\n * output\"). When the source already speaks the requested codec, the\n * broker elides the encode block entirely — no `-vf` / `-preset` /\n * `-r` / `-maxrate` — and runs ffmpeg as a re-muxer on the video\n * plane. Width / height / fps / bitrate constraints in the profile\n * are treated as a downstream BUDGET, not a forced rescale: pick a\n * source stream slot that already fits and let `-c:v copy` carry\n * it. Operators who genuinely want a forced re-encode (rebrand,\n * bitrate trim, GOP knobs, …) pick \"Custom\" in the widget — that\n * path lands here with codec === source codec and STILL copies,\n * because the broker cannot know which non-codec knob was the\n * reason for opening the editor. If a forced re-encode at the\n * source codec is ever required we'll add an explicit `force: true`\n * flag to `VideoEncode`.\n *\n * The explicit `codec: 'copy'` sentinel keeps working — it's the\n * widget's \"Passthrough\" preset and the wire payload for legacy\n * contributions. Both paths emit `-c:v copy` identically.\n *\n * - `audio === 'passthrough'` appends `-an`. Otherwise the audio\n * encode block (`-c:a`, `-b:a`, `-ar`, `-ac`) lands before any\n * outputArgs. Audio smart-copy needs the source audio codec\n * plumbed alongside `inputFormat` and isn't here yet — for now the\n * audio plane honours the profile literally.\n */\nexport function buildTranscodeFfmpegArgs(\n inputFormat: 'hevc' | 'h264',\n profile: EncodeProfile,\n decodeHwAccel?: string | null,\n): string[] {\n const args: string[] = [...logBannerArgs('error')]\n if (profile.inputArgs?.length) args.push(...profile.inputArgs)\n // Hardware decode (`-hwaccel`) is an input option — emit it right before the\n // input. `null`/`'none'` ⇒ software decode (current default).\n if (decodeHwAccel && decodeHwAccel !== 'none') args.push('-hwaccel', decodeHwAccel)\n args.push('-f', inputFormat, '-i', 'pipe:0')\n\n const v = profile.video\n const sourceCodec: 'h264' | 'h265' = inputFormat === 'hevc' ? 'h265' : 'h264'\n const shouldCopyVideo = v.codec === 'copy' || v.codec === sourceCodec\n if (shouldCopyVideo) {\n args.push('-c:v', 'copy')\n } else {\n if (v.width !== undefined && v.height !== undefined) {\n args.push('-vf', `scale='min(${v.width},iw)':'min(${v.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2`)\n }\n const encoder = v.codec === 'h265' ? 'libx265' : 'libx264'\n args.push('-c:v', encoder)\n if (v.preset) args.push('-preset', v.preset)\n if (v.tune) args.push('-tune', v.tune)\n if (v.profile) args.push('-profile:v', v.profile)\n if (v.fps !== undefined) args.push('-r', String(v.fps))\n if (v.gopFrames !== undefined) args.push('-g', String(v.gopFrames))\n if (v.bf !== undefined) args.push('-bf', String(v.bf))\n if (v.bitrateKbps !== undefined) {\n args.push('-maxrate', `${v.bitrateKbps}k`, '-bufsize', `${v.bitrateKbps * 2}k`)\n }\n }\n\n if (profile.audio === 'passthrough') {\n args.push('-an')\n } else {\n const a = profile.audio\n args.push(...audioEncoderArgs(a.codec, {\n bitrateKbps: a.bitrateKbps,\n sampleRateHz: a.sampleRateHz,\n channels: a.channels,\n }))\n }\n\n if (profile.outputArgs?.length) args.push(...profile.outputArgs)\n args.push('-f', 'h264', 'pipe:1')\n return args\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 /**\n * Owning device id (parsed from the brokerId `${deviceId}/${camStreamId}`).\n * Threaded into every session log's `meta` so device-scoped operator\n * log filters don't drop session lifecycle lines as `dev=?`. The\n * `logger` passed in is ALSO expected to be device-tagged\n * (`withTags({ deviceId })`) so the structured tag column is populated;\n * this field mirrors it into `meta` for the diagnostic logs. `-1` for\n * the bare-deviceKey shape with no parseable id.\n */\n deviceId: number;\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 * Suppress IPv6 host gathering and filter IPv6 entries out of\n * `additionalHostAddresses`. Default false. Used for peers that\n * document IPv4-only ICE (Alexa RTC). See `webrtc-session.cap.ts`.\n */\n disableIpv6?: boolean;\n };\n /** Callback for RTCP stats (called every ~3s). */\n onStats?: (stats: SessionStats) => void;\n /**\n * Source-RTP pre-buffer accessor (broker's current GOP). On the\n * repacketizer path, the session replays this on its first forwarded\n * packet so the viewer's decoder starts immediately from a keyframe\n * instead of waiting for the camera's next IDR. Returns [] when no\n * bootstrap is available (non-RTP source, no keyframe yet, cap overflow).\n */\n rtpBootstrap?: () => readonly Buffer[];\n /** Enable verbose frame/RTP logging. */\n debug?: boolean;\n /** Source video codec. Determines SDP negotiation + RTP packetization. */\n sourceCodec?: 'H264' | 'H265';\n /**\n * H.264 source whose profile is non-Baseline (Main/High) — the egress\n * must be re-encoded to Constrained Baseline so iOS (WebKit) can decode\n * it. Decided by the broker from the camera's SPS. Ignored for H.265\n * sources (their transcode is driven by codec negotiation). Default false.\n */\n transcodeToBaseline?: boolean;\n /**\n * EncodeProfile this session uses for its in-process transcode\n * (when the source codec or client capabilities require it).\n * Resolved from `WEBRTC_FALLBACK_PROFILE`, smart-adapted to the\n * probed source resolution (raw fallback when no probed dimensions).\n */\n transcodeProfile?: EncodeProfile;\n /**\n * Decode `-hwaccel` backend for the in-process transcode feed (e.g.\n * `'videotoolbox'`). `null`/absent ⇒ software decode. Probe-gated upstream;\n * the feed falls back to software if hw decode produces no output.\n */\n transcodeDecodeHwAccel?: string | null;\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 readonly deviceId: number;\n private source: FrameSource;\n private readonly logger: Logger;\n private readonly intercom: AdaptiveSessionOptions[\"intercom\"];\n private readonly iceConfig: AdaptiveSessionOptions[\"iceConfig\"];\n /** Force TURN-relay-only ICE — set for client-offer (Alexa) sessions whose\n * peer is a cloud media server unreachable via host/srflx behind NAT. */\n private forceRelayOnly = false;\n /** Latest RTCP Receiver Report from the remote viewer (jitter/loss),\n * captured via sender.onRtcp. null until the first RR arrives. */\n private lastRr: WeriftReceiverReport | null = null;\n /** Monotonic base (ms, performance.now) for send-time RTP timestamps. */\n private videoRtpClockBaseMs: number | null = null;\n /** Log the camera's actual H.264 profile-level-id once per session. */\n private profileLogged = false;\n private readonly onStats: AdaptiveSessionOptions[\"onStats\"];\n debug: boolean;\n private readonly sourceCodec: 'H264' | 'H265';\n /**\n * H.264 source profile is non-Baseline (Main/High) → egress must be\n * re-encoded to Constrained Baseline for iOS. Set by the broker from the\n * camera SPS; ignored for H.265 sources. Default false.\n */\n private readonly transcodeToBaseline: boolean;\n /** EncodeProfile used when startTranscodeFeed spawns ffmpeg. */\n private readonly transcodeProfile: EncodeProfile;\n /** Decode hw-accel backend for the transcode feed; null = software decode. */\n private readonly transcodeDecodeHwAccel: string | null;\n /** Codec actually negotiated with the browser after SDP answer. */\n private negotiatedCodec: 'H264' | 'H265' = 'H264';\n /**\n * True when the egress can't be the camera's native bytes: H.265 source\n * with an H.264-only browser, or an H.264 Main/High source that must be\n * forced to Constrained Baseline for iOS. See `resolveVideoTranscode`.\n */\n get needsTranscode(): boolean {\n return resolveVideoTranscode({\n sourceCodec: this.sourceCodec,\n negotiatedCodec: this.negotiatedCodec,\n transcodeToBaseline: this.transcodeToBaseline,\n });\n }\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 /**\n * Fires once ICE reaches connected; the server reads the nominated pair to\n * tag the broker subscription with the remote address.\n */\n onConnected: (() => void) | null = null;\n\n /** RTP sequence number counter (must increment per packet). */\n private videoSeqNum = 0;\n private audioSeqNum = 0;\n /**\n * Last RTP timestamp actually written on each track (RTP ticks at the\n * track's clock rate — 90 kHz video, 8 kHz PCMU audio). Captured purely\n * for the debug-only av-sync diagnostic, which converts them to media-time\n * to expose whether the audio RTP clock runs behind video's and by how much.\n * null until the first packet is sent on that track. Diagnostic-only — no\n * send-path behaviour reads these.\n */\n private lastVideoRtpTs: number | null = null;\n private lastAudioRtpTs: number | null = null;\n /**\n * First RTP timestamp seen on each track, captured to make the av-sync diag\n * media-time SESSION-RELATIVE. Video is H.264 PASSTHROUGH, so its RTP ts is\n * the camera's free-running source clock (a huge arbitrary value, hours of\n * media); audio is a sample counter starting near 0. Subtracting the per-track\n * first ts puts both on a common session-zero base so interTrackSkew is\n * meaningful. null until the first packet on that track. Diagnostic-only.\n */\n private firstVideoRtpTs: number | null = null;\n private firstAudioRtpTs: number | null = null;\n /**\n * Wall-clock (Date.now(), ms) at the FIRST packet sent on each track. The gap\n * `firstAudioSendWallMs − firstVideoSendWallMs` is the constant startup offset\n * between the two tracks — when video forwards with ~0 latency (passthrough)\n * but audio is delayed by its decode/transcode pipeline, this gap ≈ the steady\n * perceived audio-behind-video lag. Per-track media-vs-wall skew then tells us\n * if it's a constant offset (no drift) vs a growing one. Diagnostic-only.\n */\n private firstVideoSendWallMs: number | null = null;\n private firstAudioSendWallMs: number | null = null;\n /**\n * Most recent AUDIO source-frame capture timestamp (the incoming frame's\n * `timestampMicros`, in µs) and the send-time wall-clock (Date.now(), ms)\n * captured at the instant we wrote that frame's RTP. Their relationship is\n * the transcode/buffer latency the av-sync diagnostic surfaces — the prime\n * suspect for a constant audio lag (esp. the AAC→PCMU transcode path).\n * Diagnostic-only; null until the first audio frame is written.\n */\n private lastAudioSrcCaptureMicros: number | null = null;\n private lastAudioSendWallMs: number | null = null;\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 /** 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. 1100 keeps the full wire packet\n * (payload + RTP hdr + hdr-extensions + SRTP/GCM tag + UDP + IPv4/IPv6)\n * comfortably under the Tailscale/WireGuard overlay MTU (1280) — at the\n * old 1180/1200 a packet is ~1266 B, which fits a plain 1500-MTU LAN but\n * is at/over the 1280 overlay limit, so keyframe packets silently drop\n * over Tailscale → the keyframe never reassembles → endless PLI storm\n * (LAN-OK / remote-fail). 1100 leaves ~90 B of headroom for IPv6.\n * Both codecs share the live `repacketizerMtu` (default = the H264 static\n * below) so the adaptive ladder can shrink packets for either codec. */\n /**\n * Per-session H.264 RTP repacketizer (lazy-init). H.264 RTSP sources\n * forward source RTP through this — same Tailscale-safe MTU and the same\n * reason as H.265: iOS (WebKit) rejects the `writeVideoNals` synthesized\n * RTP shape for Main/High passthrough; the repacketizer preserves the\n * native RTP layout (STAP-A SPS/PPS + FU-A). See `forwardSourceRtpVideo`.\n */\n private h264Repacketizer: H264Repacketizer | null = null;\n static readonly H264_REPACKETIZER_MTU = 1100;\n /** Reduced RTP payload MTU engaged by the adaptive ladder at the lowest\n * tier when loss persists — smaller packets survive lossy links better.\n * Must stay below H264_REPACKETIZER_MTU (1100). */\n static readonly ADAPTIVE_LOW_MTU = 900;\n /** Broker source-RTP pre-buffer accessor + one-shot replay guard. */\n private readonly rtpBootstrap?: () => readonly Buffer[];\n private rtpBootstrapDone = false;\n /**\n * Trickle ICE: the server's locally-gathered candidates, buffered as werift\n * surfaces them via `onIceCandidate`. The client polls `getIceCandidates`\n * and adds each. Lets `handleOffer` return the answer IMMEDIATELY (no\n * gathering wait) — candidates flow afterwards → ~0s connect.\n */\n private readonly localIceCandidates: IceCandidatePayload[] = [];\n private iceGatheringComplete = false;\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 /** Set by the broker server when a tier change needs a client re-offer.\n * Polled via the session state. null when no renegotiation is pending. */\n pendingRenegotiation: { target: WebrtcStreamTarget; epoch: number } | null = null;\n\n /** Sink for RTCP Receiver Report loss samples (adaptive controller). */\n private rrSink: ((s: { packetsLost: number; highestSequence: number }) => void) | null = null;\n\n /** Register a sink for RTCP Receiver Report loss samples (adaptive controller). */\n onReceiverReport(cb: (s: { packetsLost: number; highestSequence: number }) => void): void {\n this.rrSink = cb;\n }\n\n /** Live repacketizer MTU; read when (re)building the repacketizer so a\n * mid-session change applies on the next rebuild. Defaults to the static\n * H264 MTU so current behaviour is identical until an override is set. */\n private repacketizerMtu: number = AdaptiveSession.H264_REPACKETIZER_MTU;\n\n /** Override the repacketizer MTU at runtime (adaptive ladder lowest tier). */\n setRepacketizerMtu(mtu: number): void {\n this.repacketizerMtu = mtu;\n }\n\n /** Drop the repacketizer + bootstrap/sync state so a newly bound source\n * stream re-seeds its SPS/PPS and replays instant-start cleanly. */\n resetRepacketizer(): void {\n this.h264Repacketizer = null;\n this.h265Repacketizer = null;\n this.videoRtpSynced = false;\n this.rtpBootstrapDone = false;\n this.sourceVideoSsrc = null;\n }\n\n constructor(options: AdaptiveSessionOptions) {\n this.sessionId = options.sessionId;\n this.deviceId = options.deviceId;\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.transcodeToBaseline = options.transcodeToBaseline ?? false;\n this.transcodeProfile = options.transcodeProfile ?? WEBRTC_FALLBACK_PROFILE;\n this.transcodeDecodeHwAccel = options.transcodeDecodeHwAccel ?? null;\n this.rtpBootstrap = options.rtpBootstrap;\n this.createdAt = Date.now();\n }\n\n /**\n * Force TURN-relay-only ICE for this session. MUST be called before\n * `createOffer()` (which builds the PeerConnection via\n * `buildPcOptions`) — once the PC exists the policy is fixed.\n *\n * Set by the broker for remote (non-LAN) viewers: with the patched\n * werift, `iceTransportPolicy:'relay'` produces a genuinely relay-only\n * SDP, so a CGNAT client (which can only offer a relay candidate) gets\n * a clean relay↔relay media path instead of werift nominating a dead\n * host/hairpin-srflx pair. The Alexa/WHEP `handleOffer` path sets this\n * internally; this setter is the server-offer (browser live-view) path.\n */\n setForceRelayOnly(value: boolean): void {\n this.forceRelayOnly = value;\n }\n\n /**\n * Wait for ICE gathering to yield a usable candidate, then return — do NOT\n * block on full gathering \"complete\". The direct LAN/Tailscale path only\n * needs a host candidate (present in <1s); waiting for STUN/TURN gathering\n * to finish cost many seconds of dead startup latency before the offer/\n * answer could be sent. srflx/relay keep gathering into the local SDP; we\n * just don't wait on them. A small floor (600ms) lets a fast srflx also\n * land in the SDP. Hard cap (2.5s) bounds a stalled agent — e.g. a\n * remote-only peer that genuinely needs a slow relay.\n */\n private async waitForIceGatheringFast(): Promise<void> {\n if (!this.pc) return;\n if (this.pc.iceGatheringState === \"complete\") return;\n await new Promise<void>((resolve) => {\n let settled = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n const finish = (): void => {\n if (settled) return;\n settled = true;\n if (poll) clearInterval(poll);\n resolve();\n };\n this.pc?.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") finish();\n });\n const start = Date.now();\n poll = setInterval(() => {\n const sdp = this.pc?.localDescription?.sdp ?? \"\";\n if (/ typ host/.test(sdp) && Date.now() - start >= 600) finish();\n }, 100);\n setTimeout(finish, 2500);\n });\n }\n\n /**\n * Wait for ICE gathering to COMPLETE (not just yield a host candidate).\n * Used by the NON-TRICKLE answer path (Alexa): unlike the browser path,\n * a non-trickle peer never polls `getIceCandidates` nor trickles back, so\n * the answer SDP must already carry the server's candidates. The Alexa\n * path is `relayOnly` — there are NO host candidates, so the host-floor\n * heuristic in `waitForIceGatheringFast` would never fire; we wait on the\n * gathering-complete signal instead. werift fires `onIceCandidate(undefined)`\n * on completion (which sets `iceGatheringComplete`), and also emits\n * `iceGatheringStateChange('complete')`. A hard cap (`capMs`, default 5s)\n * bounds a stalled relay so we never block forever — the answer is still\n * returned with whatever candidates landed.\n */\n private async waitForIceGatheringComplete(capMs = 5000): Promise<void> {\n if (!this.pc) return;\n if (this.iceGatheringComplete || this.pc.iceGatheringState === \"complete\") return;\n await new Promise<void>((resolve) => {\n let settled = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n const finish = (): void => {\n if (settled) return;\n settled = true;\n if (poll) clearInterval(poll);\n resolve();\n };\n this.pc?.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") finish();\n });\n // The `onIceCandidate(undefined)` handler set up in `handleOffer` flips\n // `iceGatheringComplete`; poll that flag so completion is observed even\n // if the state-change event is missed.\n poll = setInterval(() => {\n if (this.iceGatheringComplete || this.pc?.iceGatheringState === \"complete\") finish();\n }, 100);\n setTimeout(finish, capMs);\n });\n }\n\n /**\n * Read the nominated (selected) ICE candidate pair off the live\n * werift connection, via the video sender's DTLS → ICE transport.\n * Returns null until ICE has nominated a pair (or if werift hasn't\n * wired the transport chain yet). Used by the media diagnostic to\n * confirm — on a remote retest — whether werift converged on a\n * relay↔relay pair.\n */\n public readNominatedPair(): {\n localType: string; localAddr: string;\n remoteType: string; remoteAddr: string;\n } | null {\n const conn = this.videoSender?.dtlsTransport?.iceTransport?.connection;\n const pair = conn?.nominated;\n if (!pair) return null;\n const lc = pair.localCandidate;\n const rc = pair.remoteCandidate;\n return {\n localType: lc.type,\n localAddr: `${lc.host}:${lc.port}`,\n remoteType: rc.type,\n remoteAddr: `${rc.host}:${rc.port}`,\n };\n }\n\n /**\n * Log the nominated ICE candidate pair once ICE connects. werift may\n * populate `connection.nominated` a tick AFTER the \"connected\" event\n * fires, so retry briefly before giving up. This is the decisive\n * one-shot line for a remote retest: a `relay/…` ↔ `relay/…` pair\n * means the relay-only fix took effect; anything else (or null) on a\n * remote session is the dead-pair symptom.\n */\n private logNominatedPair(attempt = 0): void {\n if (this.closed) return;\n const pair = this.readNominatedPair();\n if (!pair) {\n if (attempt < 5) {\n setTimeout(() => this.logNominatedPair(attempt + 1), 200);\n return;\n }\n this.logger.info('ICE nominated pair', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n forceRelayOnly: this.forceRelayOnly, nominated: 'none',\n },\n });\n return;\n }\n this.logger.info('ICE nominated pair', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n forceRelayOnly: this.forceRelayOnly,\n selectedLocalType: pair.localType,\n selectedLocalAddr: pair.localAddr,\n selectedRemoteType: pair.remoteType,\n selectedRemoteAddr: pair.remoteAddr,\n },\n });\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 // ICE servers — STUN/TURN. werift's `setLocalDescription` blocks on\n // `gatherCandidates()` until every STUN/TURN server responds or hits the\n // per-server gather timeout — which the werift patch caps short (see\n // patches/werift+0.22.9.patch) so a slow/unreachable server can't stall\n // startup. We keep the full set so a mobile viewer (iOS Safari does NOT\n // offer a Tailscale candidate → no direct path) can use a TURN relay.\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 // NOTE: we no longer add extra public STUN servers (stun.cloudflare /\n // stun.l.google). From this hub they timed out (no srflx was ever\n // produced) and — because werift only finalises the answer SDP when ICE\n // gathering COMPLETES (peerConnection.js sets the candidates on the\n // end-of-gathering signal, never incrementally) — every dead/slow server\n // delays the answer. The turn-provider cap already supplies the servers\n // we want (Cloudflare STUN+TURN); keeping the answer gated only on fast\n // servers is what lets gathering complete (and the answer return) quickly.\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 // Constrained Baseline 3.1. iOS Safari WebRTC ONLY negotiates Constrained\n // Baseline for H.264 RECEIVE — offering Main (4d0033) or High (640034)\n // makes werift's handleAnswer throw \"negotiate codecs failed\" because iOS\n // answers Baseline and werift can't reconcile. So Baseline is the only\n // value that negotiates with iOS; the catch is iOS then decodes ONLY\n // Baseline, so the CAMERA must emit Baseline for the picture to render\n // (set the camera/substream to a low/baseline profile). Desktop Chrome\n // decodes any profile regardless. For a Main/High camera that can't be\n // set to Baseline, the only werift-passthrough route to iOS is H.265\n // (iOS negotiates + HW-decodes HEVC natively); H.264 needs transcoding.\n parameters: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\",\n rtcpFeedback,\n });\n // H.265 fmtp — using the H.264-shaped fmtp string for H.265 works\n // in practice: 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 this known-good fmtp value.\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 — KEEP `sdes:mid` only (required for BUNDLE\n // demux; without it browsers can't route incoming RTP to the right\n // m-line). DROP `transport-wide-cc` + `abs-send-time`: werift emits\n // unreliable timing values on those, which a Chrome receiver feeds into\n // its jitter buffer. On a ~zero-jitter LAN the bad timing is harmless,\n // but over a real remote path (Tailscale/4G jitter+RTT) the jitter\n // buffer discards frames as \"too late\" → never decodes → PLI storm,\n // even though every packet arrives (browser sends RR, sends NO NACK).\n // Dropping them lets Chrome time playout off its own receive clock.\n // (Distinct from the earlier mistaken full-disable that also removed\n // sdes:mid and regressed LAN demux.)\n headerExtensions: {\n video: [\n { uri: \"urn:ietf:params:rtp-hdrext:sdes:mid\" },\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.forceRelayOnly) pcOptions.iceTransportPolicy = \"relay\";\n // Amazon's Alexa.RTCSessionController spec: \"For ICE candidates, you can\n // use either UDP or TCP but you must use IPv4.\" Echo's media gateway\n // silently ignores IPv6 candidates AND counts them against the\n // documented 6-second SDP processing budget — so emitting our 4 native\n // GUA + 1 Tailscale fd7a:: candidates wastes the gateway's budget and\n // can starve the host/srflx pair of validation time. With this set,\n // werift skips IPv6 host gathering entirely (see\n // node_modules/werift/lib/ice/src/iceBase.d.ts iceUseIpv6).\n if (this.iceConfig?.disableIpv6) pcOptions.iceUseIpv6 = false;\n // Per-candidate-pair logging is DEBUG-only — it floods (dozens of pairs\n // per session). The nominated pair (the decisive one) is logged at info\n // unconditionally via `logNominatedPair`. Deduped to avoid spam.\n if (this.debug) {\n const loggedPairs = new Set<string>();\n pcOptions.iceFilterCandidatePair = (pair) => {\n try {\n const lc = pair.localCandidate;\n const rc = pair.remoteCandidate;\n const key = `${lc?.type}:${lc?.host}:${lc?.port}|${rc?.type}:${rc?.host}:${rc?.port}`;\n if (!loggedPairs.has(key)) {\n loggedPairs.add(key);\n this.logger.info('ICE candidate pair', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n local: `${lc?.type ?? '?'}/${lc?.host ?? '?'}:${lc?.port ?? '?'}`,\n remote: `${rc?.type ?? '?'}/${rc?.host ?? '?'}:${rc?.port ?? '?'}`,\n proto: pair?.protocol?.type,\n },\n });\n }\n } catch {\n // never break ICE on a logging error\n }\n return true;\n };\n }\n if (this.iceConfig?.portRange) pcOptions.icePortRange = this.iceConfig.portRange;\n\n // Merge any explicitly-configured additional host addresses with the\n // dynamically-detected Tailscale overlay address(es). werift skips the\n // point-to-point utun interface in its own gathering, so without this\n // the broker never offers a Tailscale-reachable host candidate and a\n // remote Tailscale viewer is stuck on the (broken) relay path. With it,\n // both peers sit on the 100.x / fd7a:: overlay and a direct host↔host\n // pair wins ICE — native H.264, no re-encode. De-dupe; never clobber.\n const tailscaleHosts = getTailscaleHostAddresses();\n let mergedHostAddrs = [\n ...new Set<string>([\n ...(this.iceConfig?.additionalHostAddresses ?? []),\n ...tailscaleHosts,\n ]),\n ];\n // Drop IPv6 addresses (anything containing a colon) when the caller\n // banned IPv6 — Tailscale exposes both an IPv4 100.x and an IPv6\n // fd7a:: overlay address; only the IPv4 one is useful for an\n // IPv4-only peer like Alexa.\n if (this.iceConfig?.disableIpv6) {\n mergedHostAddrs = mergedHostAddrs.filter((addr) => !addr.includes(':'));\n }\n if (mergedHostAddrs.length > 0) {\n pcOptions.iceAdditionalHostAddresses = mergedHostAddrs;\n if (tailscaleHosts.length > 0) {\n this.logger.info(\"offering Tailscale host candidate(s)\", {\n meta: {\n phase: \"session\",\n sessionId: this.sessionId,\n tailscaleHosts: tailscaleHosts.join(\",\"),\n additionalHostAddresses: mergedHostAddrs.join(\",\"),\n },\n });\n }\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, deviceId: this.deviceId, state, forceRelayOnly: this.forceRelayOnly } });\n if (state === \"connected\") {\n this.state = \"connected\";\n this.logNominatedPair();\n // Best-effort: the hook reads the nominated pair to tag the broker\n // subscription. A throwing hook must never break the ICE path.\n try { this.onConnected?.(); } catch { /* swallow — the session must not care */ }\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 // Include candidates in the offer, but don't block on full gathering —\n // resolve as soon as a host candidate is present (see\n // `waitForIceGatheringFast`). Cuts startup from ~5-10s to <1s on the\n // LAN/Tailscale direct path.\n await this.waitForIceGatheringFast();\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. Thread `sourceCodec`\n // as the preferred hint so an answer SDP that lists H.264 ahead of H.265\n // doesn't wrongly downgrade an H.265-capable session to H.264 (see\n // detectNegotiatedVideoCodec docstring).\n const answerVideoCodec = detectNegotiatedVideoCodec(resolvedSdp, this.sourceCodec)\n if (answerVideoCodec) {\n this.negotiatedCodec = answerVideoCodec\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 if (this.debug) {\n // Offered (our offer) vs negotiated (browser answer) profile-level-ids —\n // the decisive evidence for an iOS H.264 passthrough that \"negotiates but\n // shows black\" because the camera emits a higher profile than the\n // browser will decode. See the iOS H.264 passthrough notes.\n this.logger.info('Codec negotiation detail', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n offeredFmtp: extractFmtpLines(this.pc.localDescription?.sdp ?? ''),\n negotiatedFmtp: extractFmtpLines(resolvedSdp),\n },\n })\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 // Track DTLS state transitions while we poll — werift's typed surface\n // exposes only `state` (no onStateChange), so the wait loop doubles as\n // the transition observer. Each change is logged when debug is on.\n let prevDtls = dtlsTransport.state;\n while (dtlsTransport.state !== \"connected\" && Date.now() < deadline) {\n if (this.debug && dtlsTransport.state !== prevDtls) {\n this.logger.info('DTLS state', {\n meta: { phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId, from: prevDtls, to: dtlsTransport.state },\n });\n prevDtls = dtlsTransport.state;\n }\n await new Promise<void>((r) => setTimeout(r, 100));\n }\n if (this.debug && dtlsTransport.state !== prevDtls) {\n this.logger.info('DTLS state', {\n meta: { phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId, from: prevDtls, to: dtlsTransport.state },\n });\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(\n clientOffer: { sdp: string; type: \"offer\" },\n opts?: { nonTrickle?: boolean },\n ): Promise<{ sdp: string; type: \"answer\" }> {\n // `forceRelayOnly` is set by the caller via `setForceRelayOnly` BEFORE\n // this runs (the broker does it from the cap's server-injected\n // `relayOnly` flag): true for a cloud peer like Alexa's\n // RTCSessionController (never reachable via our host/srflx behind NAT),\n // false for a LAN/Tailscale browser viewer doing client-offer\n // passthrough (a direct host pair carries full native quality, and\n // forcing relay would defeat it). No hardcoded assumption here.\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n // ICE state at INFO (the server-offer path does the same) — for the\n // client-offer flow Alexa uses, \"stuck at checking\" is the canonical\n // black-view symptom and we need to see the transition without a debug flag.\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.info('ICE state', { meta: { phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId, state, forceRelayOnly: this.forceRelayOnly } });\n if (state === \"connected\") {\n this.state = \"connected\";\n this.logNominatedPair();\n // Best-effort: the hook reads the nominated pair to tag the broker\n // subscription. A throwing hook must never break the ICE path.\n try { this.onConnected?.(); } catch { /* swallow — the session must not care */ }\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 // Trickle ICE: buffer each local candidate werift gathers so the client\n // can poll it (`getIceCandidates`) AFTER we return the answer. werift\n // fires `undefined` when gathering completes. This is what lets the answer\n // go back immediately instead of blocking on full gathering.\n this.pc.onIceCandidate.subscribe((c: WeriftIceCandidate | undefined) => {\n if (!c || !c.candidate) { this.iceGatheringComplete = true; return; }\n this.localIceCandidates.push({\n candidate: c.candidate,\n sdpMid: c.sdpMid ?? null,\n sdpMLineIndex: c.sdpMLineIndex ?? null,\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 // Count of REMOTE candidates the peer offered (full list only at debug).\n const offerCandidates = resolvedOfferSdp\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.startsWith('a=candidate:'))\n if (this.debug) {\n this.logger.info('client offer REMOTE ICE candidates', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n count: offerCandidates.length,\n candidateTypes: summarizeCandidates(resolvedOfferSdp),\n candidates: offerCandidates,\n },\n })\n }\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\n // tracks. CRITICAL: save the transceiver senders — the feed loop pushes\n // RTP through `this.videoSender`/`this.audioSender`, and without these\n // assignments every send path bails out with \"writeVideoNals: no sender\"\n // (the server-offer path in `start()` does the same at line ~333).\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 // CRITICAL: force `sendonly`. The browser offers each transceiver\n // `recvonly` (it only wants to receive). Without this, werift answers\n // `inactive` and marks the m-line rejected (port 0), which empties\n // the `a=group:BUNDLE` line → Chrome/iOS rejects the whole answer with\n // \"A BUNDLE group contains a MID='' matching no m= section\" and the\n // session never connects. The server-offer path adds its transceivers\n // with `{direction:'sendonly'}` for the same reason.\n t.setDirection(\"sendonly\");\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n await t.sender.replaceTrack(this.videoTrack);\n this.videoSender = t.sender;\n // When the source is H.265, override the sender's codec with the\n // H.265 entry from the transceiver's negotiated codec list.\n // Without this, werift's createAnswer settles `sender.codec` on\n // the FIRST codec the browser offered — and browsers list 10+\n // H.264 PTs before the 2 H.265 PTs in their offer order, so the\n // sender ends up tagged H.264 even though our pcOptions preferred\n // H.265 and the answer SDP advertised both. The outgoing RTP\n // packets then carry PT=H.264 in the header but payload=H.265\n // NAL units; Chrome's depacketizer never initialises an HEVC\n // decoder, and `hasReceiverReport` stays false even as packets\n // keep flowing. Reassigning here before `createAnswer` propagates\n // the right PT through werift's sender path. Symmetrical for\n // H.264 sources is not required: pcOptions only advertises\n // H.264 in that case, so there is no PT ambiguity.\n if (this.sourceCodec === 'H265') {\n const h265 = t.codecs?.find((c) => c.mimeType === 'video/H265')\n if (h265) {\n t.sender.codec = h265\n this.logger.info('forced H.265 codec on video sender', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n payloadType: h265.payloadType,\n },\n })\n } else {\n this.logger.warn('sourceCodec=H265 but no H.265 codec in transceiver', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n availableMimeTypes: t.codecs?.map((c) => c.mimeType) ?? [],\n },\n })\n }\n }\n } else if (kind === \"audio\" && !this.audioTrack) {\n t.setDirection(\"sendonly\");\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n await t.sender.replaceTrack(this.audioTrack);\n this.audioSender = t.sender;\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 const videoTransceiver = this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n this.videoSender = videoTransceiver.sender;\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 const audioTransceiver = this.pc.addTransceiver(this.audioTrack, { direction: \"sendonly\" });\n this.audioSender = audioTransceiver.sender;\n }\n\n const answerDesc = await this.pc.createAnswer();\n let finalSdp: string;\n if (opts?.nonTrickle === true) {\n // NON-TRICKLE ICE (Alexa): the peer never polls `getIceCandidates` nor\n // trickles back, so the answer SDP must already carry the server's\n // candidates. Await `setLocalDescription` then WAIT for ICE gathering to\n // COMPLETE, then read the populated `localDescription.sdp` (now with\n // `a=candidate` lines). The Alexa path is relayOnly (no host candidates),\n // so we wait on gathering-complete — bounded to ~5s — rather than the\n // host-floor heuristic of the trickle path; a relay/srflx candidate lands\n // in the SDP without blocking forever.\n await this.pc.setLocalDescription(answerDesc).catch((e) => {\n this.logger.warn('setLocalDescription failed (non-trickle)', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(e) } });\n });\n await this.waitForIceGatheringComplete();\n finalSdp = this.pc?.localDescription?.sdp ?? answerDesc.sdp;\n } else {\n // TRICKLE ICE (tempo-0 model): do NOT await setLocalDescription\n // and do NOT wait for gathering. werift's setLocalDescription resolves only\n // after FULL ICE gathering completes (~10s with TURN); awaiting it — or\n // waiting for candidates to land in the answer SDP — is the entire startup\n // latency. Instead fire it, return the BARE answer (ufrag/pwd/fingerprint/\n // setup, no candidates) IMMEDIATELY, and let the server's candidates flow to\n // the client afterwards via `getIceCandidates` (buffered by the\n // onIceCandidate subscription above). The client likewise trickles its\n // candidates back via `addIceCandidate`. Connection forms in ~0s.\n void this.pc.setLocalDescription(answerDesc).catch((e) => {\n this.logger.warn('setLocalDescription failed', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(e) } });\n });\n\n // Bare answer — candidates trickle separately. `localDescription` may not\n // be populated yet (not awaited), so fall back to the createAnswer SDP.\n finalSdp = this.pc?.localDescription?.sdp ?? answerDesc.sdp;\n }\n this.state = \"connecting\";\n\n // Detect the codec werift settled on in OUR answer. In the client-offer\n // flow the browser offers its full decode caps (incl. H.264 High on\n // iOS 18) and werift echoes the matching codec — so `negotiatedCodec`\n // must be read from the answer here, exactly as `handleAnswer` reads it\n // from the browser's answer in the server-offer flow. Without this it\n // stays at the 'H264' default and `needsTranscode` mis-decides for an\n // H.265 source. Set BEFORE `startFeedingFrames()` so the feed picks the\n // right path on the first frame. Thread `sourceCodec` as the preferred\n // hint: werift orders H.264 ahead of H.265 in the answer (mirroring the\n // browser offer), so the first-match regex would wrongly choose H.264 for\n // an H.265-capable browser — see cam 99 freeze diagnosis.\n const answerVideoCodec = detectNegotiatedVideoCodec(finalSdp, this.sourceCodec)\n if (answerVideoCodec) {\n this.negotiatedCodec = answerVideoCodec\n }\n this.logger.info('Codec negotiated (client-offer)', {\n meta: { phase: 'session', sessionId: this.sessionId, source: this.sourceCodec, negotiated: this.negotiatedCodec, needsTranscode: this.needsTranscode },\n })\n\n if (this.debug) {\n // Offered = the client's offer (Alexa/WHEP/browser); negotiated = OUR\n // answer. Surfaces the profile-level-id the peer asked for vs the one we\n // echoed back — the iOS/Alexa passthrough discriminator.\n this.logger.info('Codec negotiation detail (client-offer)', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n offeredFmtp: extractFmtpLines(resolvedOfferSdp),\n negotiatedFmtp: extractFmtpLines(finalSdp),\n },\n })\n }\n\n if (this.debug) {\n // CRITICAL: SHOW the FULL set of LOCAL ICE candidates we put in the\n // ANSWER — parsed into type (host/srflx/relay) + address — not just the\n // count. On the Alexa (non-trickle, relay-only) path the answer must\n // already carry the candidates; if it carries an unreachable\n // Tailscale/private `host` candidate the peer can't pair with, this is\n // the line that proves it. Raw lines are kept too for full fidelity.\n const gatheredCandidates = finalSdp\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.startsWith('a=candidate:'));\n this.logger.info('WHEP answer LOCAL ICE candidates', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n nonTrickle: opts?.nonTrickle === true, forceRelayOnly: this.forceRelayOnly,\n candidateCount: gatheredCandidates.length,\n candidateTypes: summarizeCandidates(finalSdp),\n candidates: gatheredCandidates,\n },\n });\n }\n\n // CRITICAL: do NOT feed synchronously here. In the client-offer flow the\n // answer must be RETURNED before the browser can complete DTLS — so at\n // this point the transport is never connected yet, and `sendRtp` silently\n // drops everything until DTLS comes up (see `handleAnswer`'s DTLS wait).\n // Feeding now blasts the FIRST keyframe into a dead transport; the browser\n // then has only P-frames, can't initialise its decoder, and shows black\n // (no loss/PLI on our side because nothing was actually transmitted).\n // Defer feeding until DTLS connects — the dual of the server-offer path.\n void this.startFeedingWhenDtlsReady();\n\n return { sdp: finalSdp, type: \"answer\" };\n }\n\n /**\n * Wait for the video sender's DTLS transport to connect, then start\n * feeding. Used by the client-offer (`handleOffer`) path, which must\n * return its SDP answer before the browser performs the DTLS handshake —\n * so feeding can only safely begin once the transport is up. Mirrors the\n * inline DTLS wait `handleAnswer` does for the server-offer path. Bounded\n * (10s) so a never-connecting session doesn't leak the wait forever.\n */\n private async startFeedingWhenDtlsReady(): Promise<void> {\n const dtlsTransport = this.videoSender?.dtlsTransport;\n if (dtlsTransport && dtlsTransport.state !== \"connected\") {\n const deadline = Date.now() + 10_000;\n // Observe DTLS transitions while polling (werift exposes only `state`).\n let prevDtls = dtlsTransport.state;\n while (\n dtlsTransport.state !== \"connected\" &&\n Date.now() < deadline &&\n !this.closed\n ) {\n if (this.debug && dtlsTransport.state !== prevDtls) {\n this.logger.info('DTLS state (client-offer)', {\n meta: { phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId, from: prevDtls, to: dtlsTransport.state },\n });\n prevDtls = dtlsTransport.state;\n }\n await new Promise<void>((r) => setTimeout(r, 100));\n }\n if (this.debug && dtlsTransport.state !== prevDtls) {\n this.logger.info('DTLS state (client-offer)', {\n meta: { phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId, from: prevDtls, to: dtlsTransport.state },\n });\n }\n this.logger.debug('client-offer DTLS wait complete', {\n meta: { phase: 'session', sessionId: this.sessionId, state: dtlsTransport.state },\n });\n }\n if (this.closed) return;\n // If DTLS never reached \"connected\" within the deadline, sendRtp would\n // silently drop every packet and the session would sit forever as a\n // zombie — the broker keeps reading source RTP, the feed loop keeps\n // pushing frames in, the transport drops them all, and nothing ever\n // tears the session down. Observed today: a Hikvision session sat\n // for 16+ minutes burning CPU on audio frames going nowhere because\n // Echo's ICE check failed and there was no cleanup. Tear it down so\n // the broker releases the source subscription too.\n const dtlsState = this.videoSender?.dtlsTransport?.state;\n if (dtlsState !== \"connected\") {\n this.logger.warn('client-offer: DTLS never connected — closing session', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n deviceId: this.deviceId,\n dtls: dtlsState ?? 'unknown',\n forceRelayOnly: this.forceRelayOnly,\n },\n });\n void this.close();\n return;\n }\n this.startFeedingFrames();\n }\n\n /** Add a remote (client) ICE candidate — trickle ICE client→server. */\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 * Snapshot of the server's locally-gathered ICE candidates for trickle\n * polling (server→client). The client polls this after receiving the bare\n * answer and adds each candidate; `done` flips true when gathering finishes\n * so the client can stop polling.\n */\n getIceCandidatesSnapshot(): { candidates: IceCandidatePayload[]; done: boolean } {\n return { candidates: [...this.localIceCandidates], done: this.iceGatheringComplete };\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 // DO NOT gate on `this.negotiatedCodec === 'H265'` here. The broker calls\n // this at session-create time (`broker-webrtc-server.ts:719`), BEFORE\n // `handleOffer`/`handleAnswer` has had a chance to populate\n // `negotiatedCodec` from the answer SDP — at that point the field is\n // still the constructor default `'H264'` and the seed would be silently\n // dropped. Without the seed, `H265Repacketizer.codecInfo` stays empty\n // and `maybeSendAPCodecInfo` never emits the VPS+SPS+PPS aggregation\n // packet that Chrome's HEVC depacketizer needs to initialise → freeze.\n // The broker already gates the call by `sessionCodec === 'H265'`, so\n // seeding here is a no-op for non-H.265 sessions.\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 this.repacketizerMtu,\n codecInfo,\n );\n }\n\n /**\n * Seed the H.264 repacketizer's SPS/PPS from SDP-published parameter sets\n * so the STAP-A codec packet can precede the first IDR even when the\n * camera doesn't emit param sets in-band. H.264 sibling of\n * `seedH265CodecInfoFromSdp`. No-op on non-H.264 sessions.\n */\n seedH264CodecInfoFromSdp(parameterSets: ReadonlyArray<Buffer>): void {\n // Mirror of `seedH265CodecInfoFromSdp`: do NOT gate on negotiatedCodec.\n // The broker discriminates by `sessionCodec === 'H264'` before calling\n // (broker-webrtc-server.ts ~720), and seeding the H.264 repacketizer's\n // SPS/PPS is a no-op for non-H.264 sessions because the repacketizer is\n // only instantiated when `forwardSourceRtpVideo` runs with `codec='H264'`.\n if (!parameterSets.length) return;\n this.ensureH264Repacketizer();\n const rep = this.h264Repacketizer!;\n for (const ps of parameterSets) {\n const nalType = ps[0]! & 0x1f;\n if (nalType === 7) rep.updateSps(Buffer.from(ps));\n else if (nalType === 8) rep.updatePps(Buffer.from(ps));\n }\n if (this.debug) {\n this.logger.info('seeded H.264 repacketizer codecInfo from SDP', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n psCount: parameterSets.length,\n hasSps: !!rep.codecInfo?.sps,\n hasPps: !!rep.codecInfo?.pps,\n },\n });\n }\n }\n\n private ensureH264Repacketizer(): void {\n if (this.h264Repacketizer) return;\n this.h264Repacketizer = new H264Repacketizer(\n console,\n this.repacketizerMtu,\n );\n }\n\n /**\n * Forward a source RTP video packet (raw on-wire bytes) through the\n * codec's repacketizer to the browser. Used by the broker's RTSP\n * direct-RTP subscription for BOTH H.265 and H.264 — bypasses the\n * AnnexB→writeVideoNals path entirely so the native RTP layout (which\n * iOS's strict depacketizer requires) is preserved.\n *\n * Drops everything until the SDP answer has been negotiated\n * (`negotiatedCodec`/`videoSender` populated).\n */\n forwardSourceRtpVideo(rtpData: Buffer): void {\n if (this.closed) return;\n const codec = this.negotiatedCodec;\n if (codec !== 'H265' && codec !== 'H264') return;\n if (!this.videoSender || !_werift) return;\n\n // Wait for DTLS. `sendRtp` silently drops before the transport is\n // connected — and `handleOffer` returns its answer (so feeding can begin)\n // BEFORE the browser performs DTLS. Without this gate the pre-buffer\n // bootstrap fires the instant `videoSender` exists (mid-negotiation,\n // pre-ICE), gets entirely dropped, and the viewer still waits a full GOP\n // for the next live keyframe — the \"instant start\" never lands. Gating\n // here means the bootstrap replays on the FIRST packet AFTER DTLS, when\n // the pre-buffer = the current GOP and the packets actually transmit.\n if (this.videoSender.dtlsTransport?.state !== 'connected') return;\n\n // Instant start: on the FIRST packet forwarded after the session is\n // ready, replay the broker's source-RTP pre-buffer (the current GOP from\n // its keyframe) so the decoder initialises immediately instead of waiting\n // for the camera's next IDR. The pre-buffer is captured in the broker's\n // dispatch BEFORE fan-out, so `rtpData` is its tail — it's included in the\n // replay and must not be forwarded again, hence the early return.\n if (!this.rtpBootstrapDone) {\n this.rtpBootstrapDone = true;\n const bootstrap = this.rtpBootstrap?.() ?? [];\n if (bootstrap.length > 0) {\n let sent = 0;\n for (const b of bootstrap) {\n if (this.sendSourceRtpPacket(b, codec)) sent++;\n }\n this.logger.info('replayed source-RTP pre-buffer (instant start)', {\n meta: { phase: 'session', sessionId: this.sessionId, codec, packets: bootstrap.length, sent },\n });\n return;\n }\n }\n\n this.sendSourceRtpPacket(rtpData, codec);\n }\n\n /**\n * Repacketize one source RTP packet and send it on the video sender.\n * Shared by the live forward path and the pre-buffer bootstrap replay.\n * Returns true when at least one output packet was sent.\n */\n private sendSourceRtpPacket(rtpData: Buffer, codec: 'H264' | 'H265'): boolean {\n if (!this.videoSender || !_werift) return false;\n const werift = _werift;\n\n let srcPkt;\n try {\n srcPkt = werift.RtpPacket.deSerialize(rtpData);\n } catch (err) {\n this.logger.warn('source RTP deserialize failed', {\n meta: { phase: 'session', sessionId: this.sessionId, codec, error: errMsg(err), len: rtpData.length },\n });\n return false;\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 ?? (codec === 'H265' ? 97 : 96);\n\n let outPkts;\n try {\n if (codec === 'H265') {\n this.ensureH265Repacketizer();\n outPkts = this.h265Repacketizer!.repacketize(srcPkt);\n } else {\n this.ensureH264Repacketizer();\n outPkts = this.h264Repacketizer!.repacketize(srcPkt);\n }\n } catch (err) {\n this.logger.warn('repacketize failed', {\n meta: { phase: 'session', sessionId: this.sessionId, codec, error: errMsg(err) },\n });\n return false;\n }\n\n let sent = false;\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 // Record the forwarded packet's RTP timestamp (90 kHz) for the\n // av-sync diagnostic. Diagnostic-only.\n this.lastVideoRtpTs = pkt.header.timestamp >>> 0;\n if (this.firstVideoRtpTs === null) {\n this.firstVideoRtpTs = this.lastVideoRtpTs;\n this.firstVideoSendWallMs = Date.now();\n }\n this.rtpPacketsSent++;\n sent = true;\n this.logFirstVideoRtp('source-rtp', codec);\n if (this.debug && (this.rtpPacketsSent === 1 || this.rtpPacketsSent % 500 === 0)) {\n this.logger.info('source-RTP forwarded', {\n meta: { phase: 'session', sessionId: this.sessionId, codec, count: this.rtpPacketsSent },\n });\n }\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error('sendRtp (source forward) error', {\n meta: { phase: 'session', sessionId: this.sessionId, codec, error: errMsg(err) },\n });\n }\n }\n }\n return sent;\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 // Capture the remote receiver's RTCP Receiver Reports (jitter / loss).\n // werift parses RR but never persists it, so the media-diag fields were\n // always empty. Stash the latest RR matching our ssrc for diagnostics —\n // jitter (90kHz units) and fractionLost tell us whether a remote viewer\n // is seeing network jitter (timing) vs actual packet loss.\n if (sender.onRtcp) {\n sender.onRtcp.subscribe((rtcp) => {\n const reports = rtcp?.reports;\n if (!reports || reports.length === 0) return;\n const mine = reports.find((r) => r.ssrc === sender.ssrc) ?? reports[0];\n if (mine) this.lastRr = mine;\n if (this.rrSink && mine) {\n this.rrSink({\n packetsLost: mine.packetsLost ?? mine.cumulativeLost ?? 0,\n highestSequence: mine.highestSequence ?? 0,\n });\n }\n });\n }\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 const now = Date.now();\n if (now - this.lastPliResendAt < AdaptiveSession.PLI_RESEND_COOLDOWN_MS) return;\n this.lastPliResendAt = now;\n\n // Source-RTP path (RTSP H.264 / H.265 — current cam 127, cam 99 H.265):\n // replay the broker's source-RTP pre-buffer. The ring buffer contains\n // the current GOP starting from the most recent keyframe — exactly\n // what the browser is asking for after losing reference frames. This\n // is the only recovery path that doesn't require an out-of-band\n // keyframe-request to the camera; the keyframe is already in our\n // hands, we just need to retransmit. Same mechanism as the first-\n // packet instant-start replay, but on-demand for PLI recovery.\n // Without this, an on-Wi-Fi camera that loses a packet from the\n // initial IDR (or mid-GOP) freezes the viewer with no recovery —\n // the browser sends PLI, our cached-keyframe path bails because\n // `lastKeyframeNals` is only populated by `writeVideoNals` (the\n // AnnexB path), not by `forwardSourceRtpVideo` (the source-RTP path).\n if (this.rtpBootstrap) {\n const codec = this.negotiatedCodec;\n if (codec === 'H264' || codec === 'H265') {\n const bootstrap = this.rtpBootstrap();\n if (bootstrap.length > 0) {\n let sent = 0;\n for (const b of bootstrap) {\n if (this.sendSourceRtpPacket(b, codec)) sent++;\n }\n this.logger.info('PLI received — replayed source-RTP pre-buffer', {\n meta: { phase: 'session', sessionId: this.sessionId, codec, packets: bootstrap.length, sent },\n });\n return;\n }\n }\n // Falls through to the AnnexB cached-keyframe path when the\n // pre-buffer is empty (broker just rebuilt the reader, hasn't seen\n // a keyframe yet) AND the AnnexB feed has populated lastKeyframeNals.\n }\n\n // AnnexB push-mode path (Reolink Baichuan, Frigate, push-rtp): re-send\n // the last cached keyframe via writeVideoNals. Same packetization the\n // feed loop uses for keyframes (single-NAL packets for VPS/SPS/PPS,\n // FU fragmentation for IDR slice).\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 // Stamp with a CURRENT send-time RTP timestamp (not the cached\n // keyframe's old ts): a resend carrying a past timestamp arrives\n // out-of-order and the receiver discards it, which would perpetuate\n // the PLI storm we're trying to break.\n this.writeVideoNals(this.lastKeyframeNals, this.sendRtpTimestamp(), 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 // Sample-count accumulator for PCMU/PCMA audio RTP timestamps:\n // 1 byte = 1 sample @ codec clock (PCMU 8 kHz mono). The counter\n // advances exactly with the audio data we've forwarded, so RTP\n // timestamps stay clock-coherent with the codec even when the\n // source delivers below real-time. (A µs-derived alternative was\n // tried but the broker doesn't seem to populate\n // `frame.timestampMicros` with a usable source-clock value for\n // audio — debug log gated on `this.debug` below dumps the raw\n // value so we can confirm what the broker is passing through.)\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 // Send-time RTP timestamp (see sendRtpTimestamp): capture-time\n // stamps caused ~80ms receiver jitter over Tailscale → PLI storm.\n const rtpTs = this.sendRtpTimestamp();\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. 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 } 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) {\n this.lastSps = Buffer.from(n);\n if (!this.profileLogged && n.length >= 4) {\n this.profileLogged = true;\n const plid = `${n[1]!.toString(16).padStart(2, '0')}${n[2]!.toString(16).padStart(2, '0')}${n[3]!.toString(16).padStart(2, '0')}`;\n this.logger.info('camera H.264 SPS profile-level-id', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n actualProfileLevelId: plid, advertisedProfileLevelId: '42e01f',\n match: plid === '42e01f',\n profileIdc: n[1], levelIdc: n[3],\n },\n });\n }\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 }\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 // Derive RTP timestamp from the SOURCE frame's capture\n // time (now in honest µs after the broker-side packet.pts\n // ms→µs conversion in broker-webrtc-server.ts). Lets\n // werift's RTCP Sender Report carry an honest NTP↔RTP\n // mapping anchored on the source clock — the browser can\n // apply its internal drift compensation instead of sync-\n // gating the MediaStream into a freeze. PCMU clock is\n // 8 kHz: 1 µs = 0.008 ticks → `µs × 8 / 1000`, u32-wrapped.\n const rtpTs = (Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1_000_000)) >>> 0;\n // `audioTimestampBase` reference kept satisfied for tsc —\n // see the loop-local declaration above; not used now.\n void audioTimestampBase;\n\n if (this.debug) {\n this.logger.info('audio frame in (direct-feed)', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n timestampMicros: frame.timestampMicros,\n dataLen: frame.data.length,\n computedRtpTs: rtpTs,\n codec: frame.codec,\n },\n });\n }\n\n // Audio pipeline latency: stash the SOURCE frame's capture\n // timestamp (`timestampMicros`) and the send-time wall-clock at the\n // instant we write it. The av-sync diagnostic compares them to\n // surface transcode/buffer latency — the prime suspect for the\n // constant ~0.5s audio lag (esp. the AAC→PCMU transcode path).\n // Diagnostic-only; no effect on the RTP we send.\n this.recordAudioSourceLatency(frame.timestampMicros);\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 /**\n * Transcode feed — re-encode the source to Constrained Baseline H.264:\n * H.265→H.264 (browser lacks HEVC) or H.264 Main/High→Baseline (iOS only\n * decodes Baseline). Pipes the source Annex-B to ffmpeg stdin, reads\n * Baseline H.264 from stdout, and re-frames it into access units so each\n * picture is sent with one RTP timestamp (see the stdout handler).\n */\n private startTranscodeFeed(signal: AbortSignal): void {\n const { spawn } = require('node:child_process') as typeof import('node:child_process')\n\n // Sample-count accumulator for the audio passthrough branch\n // below — same role as `audioTimestampBase` in the direct-feed\n // loop. Kept loop-local so it resets cleanly on every transcode\n // restart.\n let transcodeAudioTimestampBase: number | null = null\n\n const inputFormat = transcodeInputFormat(this.sourceCodec)\n this.logger.info(`Starting ${this.sourceCodec}→H.264 Baseline transcode feed`, {\n meta: { phase: 'session', sessionId: this.sessionId, sourceCodec: this.sourceCodec, inputFormat },\n })\n\n // Hardware decode is tried first (CPU win) but is fragile for some HEVC\n // streams (VideoToolbox can fail to decode). If ffmpeg dies before\n // producing any output and a decode backend was set, we respawn once with\n // software decode. The source keeps flowing into the new child, which\n // re-syncs at the next keyframe (the broker drives a PLI). `proc.ff` is the\n // current child so the stdin loop can retarget without restarting the\n // source iterator.\n let producedOutput = false\n let fellBackToSoftware = false\n\n const spawnFfmpeg = (decodeHwAccel: string | null): ReturnType<typeof spawn> => {\n const ffmpegArgs = buildTranscodeFfmpegArgs(inputFormat, this.transcodeProfile, decodeHwAccel)\n const child = spawn('ffmpeg', ffmpegArgs, { stdio: ['pipe', 'pipe', 'pipe'] })\n if (decodeHwAccel) {\n this.logger.info('Transcode feed using hardware decode', {\n meta: { phase: 'session', sessionId: this.sessionId, decodeHwAccel },\n })\n }\n\n child.on('exit', (code) => {\n this.logger.info('Transcode ffmpeg exited', { meta: { phase: 'session', sessionId: this.sessionId, code } })\n if (!producedOutput && decodeHwAccel && !fellBackToSoftware && !signal.aborted && !this.closed) {\n fellBackToSoftware = true\n this.logger.warn('Transcode hw decode produced no output — retrying software decode', {\n meta: { phase: 'session', sessionId: this.sessionId, decodeHwAccel },\n })\n proc.ff = spawnFfmpeg(null)\n }\n })\n child.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 // Re-frame ffmpeg's raw-H.264 bytestream into access units and send each\n // as ONE RTP-timestamped picture. ffmpeg stdout carries no frame\n // boundaries, so we split into NALs up to the last complete start code,\n // carry the trailing (still-growing) access unit across chunks, and emit\n // every COMPLETE access unit with a fresh send-time RTP timestamp. The\n // reframing state is per-child so a software-fallback respawn starts clean.\n let pendingBuf = Buffer.alloc(0)\n let carryNals: Buffer[] = []\n\n child.stdout?.on('data', (chunk: Buffer) => {\n producedOutput = true\n pendingBuf = Buffer.concat([pendingBuf, chunk])\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 nals = carryNals.concat(splitAnnexBToNals(complete))\n const accessUnits = groupNalsIntoAccessUnits(nals)\n // The last access unit may still be growing — hold it back until the\n // next picture boundary proves it complete.\n if (accessUnits.length <= 1) { carryNals = nals; return }\n carryNals = accessUnits[accessUnits.length - 1]!\n for (let i = 0; i < accessUnits.length - 1; i++) {\n // Drop SEI; AUDs were already removed by the grouping. Keep SPS/PPS/IDR/slices.\n const auNals = accessUnits[i]!.filter((n) => (n[0]! & 0x1f) !== 6)\n if (auNals.length > 0) this.writeVideoNals(auNals, this.sendRtpTimestamp(), 'H264')\n }\n })\n return child\n }\n\n const proc = { ff: spawnFfmpeg(this.transcodeDecodeHwAccel) }\n\n signal.addEventListener('abort', () => {\n if (!proc.ff.killed) proc.ff.kill('SIGTERM')\n })\n\n // Feed the source Annex-B 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 (proc.ff.stdin && !proc.ff.stdin.destroyed) {\n proc.ff.stdin.write(mediaFrame.frame.data)\n }\n } else if (mediaFrame.type === 'audio') {\n // Audio passthrough (no transcode needed). Same µs-derived\n // RTP timestamp as the direct-feed branch — see comment\n // there for the RTCP SR / NTP↔RTP rationale.\n const frame = mediaFrame.frame\n const rtpTs = (Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1_000_000)) >>> 0\n void transcodeAudioTimestampBase;\n\n if (this.debug) {\n this.logger.info('audio frame in (transcode passthrough)', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n timestampMicros: frame.timestampMicros,\n dataLen: frame.data.length,\n computedRtpTs: rtpTs,\n codec: frame.codec,\n },\n })\n }\n // Audio pipeline latency probe for the av-sync diagnostic — see the\n // direct-feed audio branch. Diagnostic-only.\n this.recordAudioSourceLatency(frame.timestampMicros)\n this.writeAudio(frame.data, rtpTs, 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 (proc.ff.stdin && !proc.ff.stdin.destroyed) proc.ff.stdin.end()\n if (!this.closed) void this.close()\n }\n })()\n }\n\n // 1100 (not 1200): keep the full wire packet under the Tailscale/WireGuard\n // overlay MTU (1280). See H264_REPACKETIZER_MTU for the rationale — at 1200\n // a keyframe's RTP packets are ~1266 B and silently drop over the overlay,\n // causing a never-decoding PLI storm that works fine on a 1500-MTU LAN.\n private static readonly MAX_RTP_PAYLOAD = 1100;\n\n private rtpPacketsSent = 0;\n private rtpLogCounter = 0;\n /** One-shot guards for the \"first video/audio RTP\" debug markers. */\n private firstVideoRtpLogged = false;\n private firstAudioRtpLogged = false;\n /**\n * One-shot guard for the audio-silence pre-stream. When the source audio\n * pipeline (e.g. AAC → PCMU transcode) lags video by a CONSTANT startup\n * delay, the first real audio frame arrives N ms AFTER the first video\n * frame went out → audio `firstSendWallMs` − video `firstSendWallMs` is a\n * persistent positive gap, the browser inflates its audio jitter buffer\n * over that gap, and the user perceives ~1 s of audio-late lag.\n *\n * Right before the very first real audio packet is written we emit N PCMU\n * silence packets (0xFF bytes, 160 samples each = 20 ms) so the audio\n * sender's first wall-clock instant aligns with the video sender's first\n * wall-clock instant, then the real packet continues seamlessly. See\n * `prestreamAudioSilenceIfNeeded` + `computeAudioSilencePadPackets`.\n */\n private firstAudioFramePadded = false;\n /** PCMU frame size in samples per packet — 160 = 20 ms at 8 kHz. */\n private static readonly PCMU_FRAME_SAMPLES = 160;\n /** PCMU silence byte. PCMU's µ-law representation of digital zero is 0xFF. */\n private static readonly PCMU_SILENCE_BYTE = 0xff;\n /** Upper bound on the audio silence pre-stream duration (ms) — guards a\n * runaway from blasting tens of seconds of silence onto the wire. */\n private static readonly AUDIO_SILENCE_MAX_PAD_MS = 2_000;\n\n /**\n * Emit the one-shot \"first video RTP sent\" debug marker. Both egress\n * paths (`writeVideoNals` synthesis + `sendSourceRtpPacket` repacketizer\n * forward) funnel here so the marker fires exactly once regardless of\n * which path the session took. Gated on `debug`.\n */\n private logFirstVideoRtp(path: 'writeVideoNals' | 'source-rtp', codec: string): void {\n if (!this.debug || this.firstVideoRtpLogged) return;\n this.firstVideoRtpLogged = true;\n this.logger.info('first video RTP sent', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n path, codec, negotiated: this.negotiatedCodec,\n sinceCreatedMs: Date.now() - this.createdAt,\n },\n });\n }\n\n /**\n * Emit the one-shot \"first audio RTP sent\" debug marker, including the\n * source audio codec and the negotiated sender clock rate — the data\n * needed to diagnose A/V sync and \"no audio\" cases. Gated on `debug`.\n */\n private logFirstAudioRtp(sourceCodec: string | undefined, senderPt: number): void {\n if (!this.debug || this.firstAudioRtpLogged) return;\n this.firstAudioRtpLogged = true;\n const senderCodec = this.audioSender?.codec;\n this.logger.info('first audio RTP sent', {\n meta: {\n phase: 'session', sessionId: this.sessionId, deviceId: this.deviceId,\n sourceCodec: sourceCodec ?? 'unknown',\n senderMime: senderCodec?.mimeType ?? 'unknown',\n senderPayloadType: senderPt,\n sinceCreatedMs: Date.now() - this.createdAt,\n },\n });\n }\n\n /**\n * RTP timestamp (90 kHz) derived from the hub's MONOTONIC clock at the\n * moment of sending — NOT the camera capture time.\n *\n * Forwarding live media with capture-time RTP timestamps creates a cadence\n * mismatch vs the actual send time: the pipeline/decoder adds variable\n * latency between capture (`frame.timestampMicros`) and the instant we push\n * the packets out. A remote receiver measures that mismatch as high RTCP\n * jitter (~80 ms observed over Tailscale, with ZERO packet loss) — its\n * jitter buffer can't lock onto a stable clock, so frames are discarded and\n * it PLI-storms. A ~0-RTT LAN absorbs it; a real remote path does not.\n *\n * Stamping off send time makes the RTP-timestamp cadence equal the actual\n * transmit cadence, so arrival cadence matches and the receiver's jitter\n * collapses to true network jitter. Same effect ffmpeg/libwebrtc achieve by\n * pacing output. All video send paths (live feed + PLI keyframe resend)\n * share this one clock so timestamps stay monotonic.\n */\n private sendRtpTimestamp(): number {\n const now = performance.now();\n if (this.videoRtpClockBaseMs === null) this.videoRtpClockBaseMs = now;\n return Math.floor((now - this.videoRtpClockBaseMs) * 90) >>> 0;\n }\n\n /**\n * Record the latest audio SOURCE frame's capture timestamp and the send-time\n * wall-clock at the moment we write it. Diagnostic-only — read by the\n * debug \"av-sync diag\" log to estimate audio transcode/buffer latency. Does\n * not touch any send path. A non-finite capture timestamp is ignored so a\n * source that doesn't stamp frames can't poison the figure.\n */\n private recordAudioSourceLatency(captureMicros: number): void {\n if (!Number.isFinite(captureMicros)) return;\n this.lastAudioSrcCaptureMicros = captureMicros;\n this.lastAudioSendWallMs = Date.now();\n }\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 // Record the last video RTP timestamp for the av-sync diagnostic\n // (90 kHz). Diagnostic-only — does not influence packetization.\n this.lastVideoRtpTs = rtpTs >>> 0;\n if (this.firstVideoRtpTs === null) {\n this.firstVideoRtpTs = this.lastVideoRtpTs;\n this.firstVideoSendWallMs = Date.now();\n }\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 via werift'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 this.logFirstVideoRtp('writeVideoNals', codec);\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 // ONE-SHOT: pre-stream PCMU silence so audio first-send wall-clock\n // aligns to video first-send wall-clock. Runs only on the very first\n // real audio packet; idempotent on every subsequent call.\n this.prestreamAudioSilenceIfNeeded(rtpTs >>> 0, pt);\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 // Record the last audio RTP timestamp (PCMU 8 kHz) for the av-sync\n // diagnostic. Diagnostic-only.\n this.lastAudioRtpTs = rtpTs >>> 0;\n if (this.firstAudioRtpTs === null) {\n this.firstAudioRtpTs = this.lastAudioRtpTs;\n this.firstAudioSendWallMs = Date.now();\n }\n this.logFirstAudioRtp(codec, pt);\n } catch (err) {\n this.logger.debug('Audio write error', { meta: { phase: 'session', sessionId: this.sessionId, error: errMsg(err) } });\n }\n }\n\n /**\n * Pre-stream N PCMU silence packets (160 bytes of 0xFF each, 20 ms / 8 kHz)\n * BEFORE the first real audio packet so the audio sender's first wall-clock\n * instant aligns with the video sender's first wall-clock instant. One-shot\n * per session, gated on `firstAudioFramePadded`. The packet count comes from\n * the pure `computeAudioSilencePadPackets` helper (gap clamp, sub-frame\n * floor, maxPad cap). Silence packets occupy RTP timestamps\n * `[realRtpTs − N·160, realRtpTs)` so the real packet's own timestamp stays\n * exactly what the caller computed and continuity holds across the boundary\n * (no gap, no overlap). Sequence numbers continue from `audioSeqNum`.\n *\n * After emitting the silence, `firstAudioRtpTs` / `firstAudioSendWallMs`\n * reference the FIRST silence packet (so `firstSendGapMs` in the av-sync\n * diag reads ~0). Any failure on a single `sendRtp` is logged and the loop\n * continues — partial alignment is better than aborting.\n */\n private prestreamAudioSilenceIfNeeded(realRtpTs: number, payloadType: number): void {\n if (this.firstAudioFramePadded) return;\n this.firstAudioFramePadded = true;\n if (!this.audioSender || !_werift) return;\n const werift = _werift;\n const nowMs = Date.now();\n const plan = planAudioSilencePadPackets({\n nowMs,\n firstVideoSendWallMs: this.firstVideoSendWallMs,\n framesizeSamples: AdaptiveSession.PCMU_FRAME_SAMPLES,\n rateHz: AdaptiveSession.PCMU_RTP_CLOCK_HZ,\n maxPadMs: AdaptiveSession.AUDIO_SILENCE_MAX_PAD_MS,\n realRtpTs,\n audioSeqNum: this.audioSeqNum,\n });\n if (plan.packets.length === 0) return;\n const silencePayload = Buffer.alloc(\n AdaptiveSession.PCMU_FRAME_SAMPLES,\n AdaptiveSession.PCMU_SILENCE_BYTE,\n );\n for (let i = 0; i < plan.packets.length; i++) {\n const planned = plan.packets[i]!;\n try {\n const header = new werift.RtpHeader();\n header.payloadType = payloadType;\n header.timestamp = planned.rtpTs;\n header.marker = true;\n header.sequenceNumber = planned.seq;\n const pkt = new werift.RtpPacket(header, silencePayload);\n this.audioSender.sendRtp(pkt);\n this.lastAudioRtpTs = planned.rtpTs;\n if (this.firstAudioRtpTs === null) {\n this.firstAudioRtpTs = planned.rtpTs;\n // Anchor to the VIDEO first-send wall-clock so the av-sync diag's\n // `firstSendGapMs = firstAudioSendWallMs − firstVideoSendWallMs`\n // reads ~0 after this fix. Falls back to `nowMs` defensively.\n this.firstAudioSendWallMs = this.firstVideoSendWallMs ?? nowMs;\n }\n } catch (err) {\n this.logger.debug('Audio silence pre-stream error', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n error: errMsg(err),\n packetIndex: i,\n totalPackets: plan.packets.length,\n },\n });\n }\n }\n // Carry forward the seqNum past the silence packets so the real packet\n // (which does `this.audioSeqNum = (this.audioSeqNum + 1) & 0xffff`) lands\n // on the next contiguous seq, matching the plan.\n this.audioSeqNum = plan.nextAudioSeqNum;\n if (this.debug) {\n this.logger.info('audio silence pre-stream emitted', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n deviceId: this.deviceId,\n packets: plan.packets.length,\n gapMs: this.firstVideoSendWallMs === null ? null : nowMs - this.firstVideoSendWallMs,\n firstSilenceRtpTs: plan.packets[0]?.rtpTs ?? null,\n realRtpTs,\n frameSize: AdaptiveSession.PCMU_FRAME_SAMPLES,\n },\n });\n }\n }\n\n // -----------------------------------------------------------------------\n // RTCP stats collection\n // -----------------------------------------------------------------------\n\n private startStatsCollection(): void {\n // Runs regardless of `onStats` so the diagnostic log below always fires —\n // it's the decisive evidence for \"hub sends RTP but browser loses it\" vs\n // \"hub never sends\" on a remote (WAN) session.\n if (this.statsTimer) 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) return;\n\n // Receiver Report captured live from sender.onRtcp (werift never persists\n // lastReceiverReport, so the old getSenders()-based read was always empty).\n const report: WeriftReceiverReport | undefined = this.lastRr ?? undefined;\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 // Decisive diagnostic: is the hub actually emitting RTP, and does the\n // browser's RTCP receiver-report show loss? `rtpPacketsSent` climbing +\n // a receiver-report with loss ⇒ packets sent but dropped on hub→browser\n // (path/relay loss). `rtpPacketsSent` flat ⇒ werift isn't sending at all.\n //\n // The nominated-pair fields are the smoking gun for the remote-NAT\n // case: on a 4G/CGNAT retest we expect `selectedLocalType:'relay'` +\n // `selectedRemoteType:'relay'` once relay-only is forced. A\n // `host`/`srflx` local with a `relay` remote (or no pair) on a\n // remote session is the dead-pair symptom this fix targets.\n const nominated = this.readNominatedPair();\n if (this.debug) {\n this.logger.info('webrtc media diag', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n deviceId: this.deviceId,\n rtpPacketsSent: this.rtpPacketsSent,\n dtls: this.videoSender?.dtlsTransport?.state ?? 'unknown',\n ice: this.state,\n forceRelayOnly: this.forceRelayOnly,\n hasReceiverReport: !!report,\n fractionLostPct: report ? Math.round((fractionLost / 256) * 100) : -1,\n packetsLost,\n // video RTP clock is 90 kHz → jitter ticks / 90 = milliseconds.\n jitterMs: report ? Math.round(jitter / 90) : -1,\n rttMs: Math.round(rtt * 1000),\n selectedLocalType: nominated?.localType ?? 'none',\n selectedLocalAddr: nominated?.localAddr ?? 'none',\n selectedRemoteType: nominated?.remoteType ?? 'none',\n selectedRemoteAddr: nominated?.remoteAddr ?? 'none',\n },\n });\n }\n\n if (report && this.onStats) {\n this.onStats({\n sessionId: this.sessionId,\n packetLoss: fractionLost / 256, // Fraction lost is 0–255\n jitterMs: jitter,\n rttMs: rtt * 1000,\n packetsReceived: 0,\n packetsLost,\n timestamp: Date.now(),\n });\n }\n\n // Debug-only A/V lip-sync diagnostic — shares this 3s tick.\n this.emitAvSyncDiag();\n }\n\n /** Negotiated audio RTP clock rate in Hz (PCMU is always 8 kHz here; falls\n * back to the sender's clockRate if werift reports one). Video is 90 kHz. */\n private static readonly VIDEO_RTP_CLOCK_HZ = 90_000;\n private static readonly PCMU_RTP_CLOCK_HZ = 8_000;\n\n /**\n * Emit the periodic \"av-sync diag\" log (debug only, ~every 3s) to gather the\n * evidence for the steady \"audio plays ~0.5s behind video\" lag — WITHOUT\n * changing any audio/video behaviour. For BOTH tracks it reports the last\n * sent RTP timestamp, the clock rate, the media-time (rtpTs/rate), the\n * wall-clock elapsed since session create, and the per-track skew\n * (media-time − wall-clock-elapsed); plus the INTER-TRACK skew (audio\n * media-seconds − video media-seconds) — negative means audio's RTP clock\n * runs behind video's, and by how much. It also surfaces the audio pipeline\n * latency (source capture timestamp vs send wall-clock) — the prime suspect\n * for the constant offset — and documents the werift RTCP-SR limitation.\n */\n private emitAvSyncDiag(): void {\n if (!this.debug) return;\n\n const elapsedMs = Date.now() - this.createdAt;\n // The session only ever offers/negotiates PCMU for audio (8 kHz) — the\n // typed werift sender surface exposes mimeType/payloadType but no\n // clockRate, so the codec-fixed 8 kHz constant is the honest rate here.\n const audioRate = AdaptiveSession.PCMU_RTP_CLOCK_HZ;\n\n // Feed SESSION-RELATIVE rtp timestamps (last − first, 32-bit-wrap-safe via\n // `>>> 0`) so video's huge source clock and audio's near-zero sample counter\n // share a session-zero base — otherwise videoMediaSec is hours and\n // interTrackSkew is garbage.\n const skew = computeAvSyncSkew({\n video:\n this.lastVideoRtpTs === null || this.firstVideoRtpTs === null\n ? null\n : {\n rtpTs: (this.lastVideoRtpTs - this.firstVideoRtpTs) >>> 0,\n rate: AdaptiveSession.VIDEO_RTP_CLOCK_HZ,\n elapsedMs,\n },\n audio:\n this.lastAudioRtpTs === null || this.firstAudioRtpTs === null\n ? null\n : {\n rtpTs: (this.lastAudioRtpTs - this.firstAudioRtpTs) >>> 0,\n rate: audioRate,\n elapsedMs,\n },\n });\n\n // Audio pipeline latency: source-frame capture (µs) vs the send wall-clock\n // captured when we wrote it. We can't subtract a µs capture clock from a\n // ms wall clock to a single absolute latency (the two clocks have unknown\n // offsets) — but the DRIFT of (sendWall − capture) across ticks is the\n // transcode/buffer latency growth. Report both raw anchors so the operator\n // can watch the delta evolve; a steady gap that matches the inter-track\n // skew points the finger at the audio pipeline (esp. AAC→PCMU transcode).\n const audioCaptureMs =\n this.lastAudioSrcCaptureMicros === null ? null : this.lastAudioSrcCaptureMicros / 1000;\n const audioSendWallMs = this.lastAudioSendWallMs;\n const audioPipelineDeltaMs =\n audioCaptureMs !== null && audioSendWallMs !== null\n ? Math.round(audioSendWallMs - audioCaptureMs)\n : null;\n\n // Constant startup offset between tracks: how much LATER (wall-clock) audio\n // first flowed vs video. Positive ⇒ audio started later ⇒ steady audio-late\n // lag of roughly this magnitude (video passthrough ~0 latency, audio behind\n // its decode/transcode pipeline). The decisive number for a CONSTANT lag.\n const firstSendGapMs =\n this.firstAudioSendWallMs !== null && this.firstVideoSendWallMs !== null\n ? this.firstAudioSendWallMs - this.firstVideoSendWallMs\n : null;\n\n // RTCP Sender Report (ntpTime, rtpTimestamp) — NOT reachable from our\n // typed werift surface. werift builds the SR inside `RTCRtpSender.runRtcp`\n // from the PRIVATE fields `ntpTimestamp` / `rtpTimestamp` (set in\n // `sendRtp`); the `.d.ts` declares them `private`, and `onRtcp` only fires\n // on INCOMING RTCP (the remote's RR/SR), never the SR we emit. Surfacing\n // the pair would require an `as any` reach into werift internals, which the\n // project forbids — so we report the limitation instead of the value.\n this.logger.info('av-sync diag', {\n meta: {\n phase: 'session',\n sessionId: this.sessionId,\n deviceId: this.deviceId,\n elapsedMs,\n // Video track.\n videoRtpTs: this.lastVideoRtpTs,\n videoClockRate: AdaptiveSession.VIDEO_RTP_CLOCK_HZ,\n videoMediaSec: skew.video ? Number(skew.video.mediaSeconds.toFixed(3)) : null,\n videoSkewSec: skew.video ? Number(skew.video.skewSeconds.toFixed(3)) : null,\n // Constant startup offset audio-vs-video (ms); ≈ steady perceived lag.\n firstSendGapMs,\n // Audio track (PCMU).\n audioRtpTs: this.lastAudioRtpTs,\n audioClockRate: audioRate,\n audioMediaSec: skew.audio ? Number(skew.audio.mediaSeconds.toFixed(3)) : null,\n audioSkewSec: skew.audio ? Number(skew.audio.skewSeconds.toFixed(3)) : null,\n // The decisive figure: negative ⇒ audio media clock lags video.\n interTrackSkewSec:\n skew.interTrackSkewSeconds === null\n ? null\n : Number(skew.interTrackSkewSeconds.toFixed(3)),\n // Audio pipeline latency anchors (see comment above).\n audioSrcCaptureMs: audioCaptureMs === null ? null : Number(audioCaptureMs.toFixed(1)),\n audioSendWallMs,\n audioPipelineDeltaMs,\n // RTCP SR limitation (see comment above).\n rtcpSr: 'unavailable: werift sender ntpTimestamp/rtpTimestamp are private; onRtcp is incoming-only',\n },\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","export interface LossSample {\n readonly packetsLost: number\n readonly highestSequence: number\n}\n\nexport interface LossTrackerConfig {\n readonly analyzeWindowMs: number\n readonly minFailPercent: number\n readonly waitAfterResetMs: number\n}\n\ninterface StampedSample extends LossSample {\n readonly time: number\n}\n\n/** Delta between two RTCP extended (32-bit) highest-sequence values, with\n * 32-bit wraparound handling. */\nfunction seqDelta(from: number, to: number): number {\n const d = to - from\n return d < 0 ? d + 0x1_0000_0000 : d\n}\n\n/**\n * Windowed packet-loss tracker. Mirrors the adaptive tracker used by\n * reference NVR implementations: loss ratio = ΔpacketsLost / Δsequence\n * across the oldest and newest in-window samples; `isFailing` when that\n * exceeds `minFailPercent`. After a tier change (`markChanged`), samples\n * are ignored for `waitAfterResetMs` so the renegotiation's own transient\n * loss doesn't cascade a second downgrade.\n */\nexport class LossTracker {\n private samples: StampedSample[] = []\n private resetUntil = 0\n\n constructor(private readonly config: LossTrackerConfig) {}\n\n markChanged(now: number): void {\n this.samples = []\n this.resetUntil = now + this.config.waitAfterResetMs\n }\n\n track(sample: LossSample, now: number): void {\n if (now < this.resetUntil) return\n this.samples.push({ ...sample, time: now })\n const cutoff = now - this.config.analyzeWindowMs\n this.samples = this.samples.filter((s) => s.time > cutoff)\n }\n\n /** Current loss ratio over the window, or 0 when fewer than two samples. */\n lossRatio(): number {\n const first = this.samples[0]\n const last = this.samples[this.samples.length - 1]\n if (!first || !last || first === last) return 0\n const seq = seqDelta(first.highestSequence, last.highestSequence)\n if (seq <= 0) return 0\n return (last.packetsLost - first.packetsLost) / seq\n }\n\n isFailing(now: number): boolean {\n if (now < this.resetUntil) return false\n return this.lossRatio() > this.config.minFailPercent\n }\n}\n","import { CAM_PROFILE_ORDER, type CamProfile } from '@camstack/types'\nimport { LossTracker, type LossSample, type LossTrackerConfig } from './loss-tracker.js'\n\n/** ABR tier == the broker profile tier. */\nexport type Tier = CamProfile\nconst TIER_ORDER: readonly Tier[] = CAM_PROFILE_ORDER\n\nexport interface AdaptiveControllerConfig extends LossTrackerConfig {\n readonly upgradeHealthyMs: number\n readonly minTimeBetweenChangesMs: number\n}\n\nexport interface AdaptiveControllerOptions {\n readonly initialTier: Tier\n readonly config: AdaptiveControllerConfig\n readonly onSwitchTier: (tier: Tier) => void\n readonly onReduceMtu: () => void\n readonly onTranscodeDown: () => void\n}\n\n/**\n * Ladder state machine for an adaptive WebRTC session. Fed loss samples\n * (RTCP RR or client-reported) via `onLoss`; `tick` evaluates the ladder.\n * Downgrade order: tier high→mid→low, then MTU reduction at low, then\n * transcode-down. Upgrade one tier after sustained health + hysteresis.\n * Side effects run through injected callbacks (testable without werift).\n */\nexport class AdaptiveController {\n private readonly tracker: LossTracker\n private tier: Tier\n private mtuReduced = false\n private transcodeEngaged = false\n private lastChangeAt = 0\n private healthySince = 0\n\n constructor(private readonly opts: AdaptiveControllerOptions) {\n this.tracker = new LossTracker(opts.config)\n this.tier = opts.initialTier\n }\n\n get currentTier(): Tier { return this.tier }\n\n onLoss(sample: LossSample, now: number): void {\n this.tracker.track(sample, now)\n }\n\n tick(now: number): void {\n const failing = this.tracker.isFailing(now)\n if (failing) { this.healthySince = now; this.handleFailing(now); return }\n this.handleHealthy(now)\n }\n\n private canUpgrade(now: number): boolean {\n return now - this.lastChangeAt >= this.opts.config.minTimeBetweenChangesMs\n || this.lastChangeAt === 0\n }\n\n private commitChange(now: number): void {\n this.lastChangeAt = now\n this.tracker.markChanged(now)\n }\n\n private handleFailing(now: number): void {\n const idx = TIER_ORDER.indexOf(this.tier)\n if (idx < TIER_ORDER.length - 1) {\n this.tier = TIER_ORDER[idx + 1]!\n this.opts.onSwitchTier(this.tier)\n this.commitChange(now)\n return\n }\n // at lowest tier\n if (!this.mtuReduced) {\n this.mtuReduced = true\n this.opts.onReduceMtu()\n this.commitChange(now)\n return\n }\n if (!this.transcodeEngaged) {\n this.transcodeEngaged = true\n this.opts.onTranscodeDown()\n this.commitChange(now)\n }\n }\n\n private handleHealthy(now: number): void {\n if (now - this.healthySince < this.opts.config.upgradeHealthyMs) return\n if (!this.canUpgrade(now)) return\n const idx = TIER_ORDER.indexOf(this.tier)\n if (idx > 0) {\n this.tier = TIER_ORDER[idx - 1]!\n this.mtuReduced = false\n this.transcodeEngaged = false\n this.opts.onSwitchTier(this.tier)\n this.commitChange(now)\n this.healthySince = now\n }\n }\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, EncodeProfile, BrokerConsumerAttribution, WebrtcStreamTarget, CamProfile } from '@camstack/types'\nimport { errMsg } from '@camstack/types'\nimport { AdaptiveSession, type IceServerEntry, type IceCandidatePayload } from './session.js'\nimport { AdaptiveController, type Tier } from './adaptive-controller.js'\nimport type { FrameSource, MediaFrame } from './types.js'\nimport { convertH264ToAnnexB, extractH264ParamSets, isBaselineProfileLevelId } from './h264-utils.js'\nimport { WEBRTC_FALLBACK_PROFILE } from './webrtc-fallback-profile.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 * Optional injected async accessor for a device's LATEST client-reported\n * packet-loss percentage (0–100), or null when no recent client report\n * exists. Polled by the adaptive controller's tick so a downgrade can fire\n * from client telemetry (e.g. the benchmark \"Network Simulation\" presets)\n * even when no RTCP Receiver Report has arrived. The broker synthesizes a\n * monotonic {@link LossSample} from this percentage locally (see\n * `startAdaptive`). Only consulted for sessions created via the\n * `/adaptive` alias.\n */\n readonly clientLossPct?: (deviceId: number) => Promise<number | null>\n /**\n * Optional accessor for the decode `-hwaccel` backend the in-process\n * transcode feed should use (probe-gated upstream). Resolved once and cached.\n * Absent ⇒ software decode (current default).\n */\n readonly resolveTranscodeDecodeHwAccel?: () => Promise<string | null>\n}\n\n// ---------------------------------------------------------------------------\n// Adaptive controller tuning\n// ---------------------------------------------------------------------------\n\n/** How often the adaptive controller re-evaluates the ladder (ms). */\nconst ADAPTIVE_TICK_MS = 2_000\n/** Ladder tuning shared by every adaptive session. */\nconst ADAPTIVE_CONFIG = {\n analyzeWindowMs: 15_000, minFailPercent: 0.05, waitAfterResetMs: 8_000,\n upgradeHealthyMs: 60_000, minTimeBetweenChangesMs: 60_000,\n} as const\n\n/**\n * The adaptive controller's desired tier, carried across a close-and-recreate\n * re-offer so the recreated session resolves to the chosen tier AND re-arms\n * its controller. `forceTranscodeDown` additionally pins the recreated\n * session to a reduced transcode profile (the transcode-down rung).\n */\ninterface AdaptiveIntent {\n readonly prefersTier: Tier\n readonly forceTranscodeDown: boolean\n}\n\ninterface SessionEntry {\n readonly session: AdaptiveSession\n /**\n * Current broker this session is fed from. Fixed for the session's\n * lifetime: an adaptive tier switch is a close-and-recreate re-offer\n * (a NEW SessionEntry on the chosen-tier broker), never an in-place\n * rebind of this field.\n */\n readonly brokerId: string\n /** Unsubscribe from broker's onEncodedData. */\n readonly unsubBroker: Unsubscribe\n /**\n * Unsubscribe from broker's `onVideoRtp` for sessions that forward\n * source RTP through the codec repacketizer (RTP-bearing sources, H.264\n * AND H.265). `null` for AnnexB-only push sessions (which use the\n * AnnexB feed-loop path). Fixed for the session's lifetime — an adaptive\n * tier switch recreates the session rather than rebinding this feed.\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 /** Adaptive ladder controller — present only for sessions created via\n * the `/adaptive` alias. Drives tier switches / MTU / transcode-down. */\n adaptiveController?: AdaptiveController\n /** Interval that polls client loss + ticks the adaptive controller.\n * Cleared in `cleanupSession`. Present only for adaptive sessions. */\n adaptiveTimer?: ReturnType<typeof setInterval>\n /**\n * Remote-address tagging is fire-at-most-once per session. The ICE\n * `connected` transition fires on the INITIAL connect AND on every\n * reconnect after a `disconnected`; without this latch each reconnect\n * would re-arm a fresh retry chain, leaving overlapping untracked\n * timers under ICE flap. Set true once the nominated pair is read +\n * tagged OR the retry budget is exhausted; subsequent `connected`\n * transitions are then no-ops.\n */\n attributionTagged?: boolean\n /** Pending retry-timer handle for the remote-address tagging chain.\n * Cleared in `cleanupSession` so no timer fires after teardown. */\n attributionTimer?: ReturnType<typeof setTimeout>\n}\n\n// ---------------------------------------------------------------------------\n// iOS Baseline transcode decision\n// ---------------------------------------------------------------------------\n\n/**\n * Whether a WebRTC session for this broker must re-encode the source to\n * Constrained Baseline H.264 so iOS (WebKit) can decode it. True only for\n * an H.264 source whose profile-level-id is known AND non-Baseline\n * (Main/High). Unknown profile (e.g. a battery cam before its first\n * keyframe) ⇒ passthrough — desktop still decodes, and a reconnect once the\n * profile is known gets it right. H.265 sources are driven by codec\n * negotiation (`resolveVideoTranscode`), never this flag.\n */\nexport function resolveBrokerTranscodeToBaseline(\n sessionCodec: 'H264' | 'H265',\n sourceProfileLevelId: string | undefined,\n): boolean {\n if (sessionCodec !== 'H264') return false\n if (!sourceProfileLevelId) return false\n return !isBaselineProfileLevelId(sourceProfileLevelId)\n}\n\n/** profile_idc (first byte of a profile-level-id) as a number, e.g.\n * `640c34` → 0x64 (100, High), `4d0033` → 0x4d (77, Main),\n * `42e01f` → 0x42 (66, Constrained Baseline). 0 for malformed input. */\nfunction h264ProfileIdc(profileLevelId: string): number {\n if (profileLevelId.length < 2) return 0\n const idc = Number.parseInt(profileLevelId.slice(0, 2), 16)\n return Number.isNaN(idc) ? 0 : idc\n}\n\n\n/** All H.264 `profile-level-id` values advertised in an SDP (offer or\n * answer), lower-cased. Browsers list one fmtp line per offered H.264\n * profile (e.g. Constrained Baseline + Main + High), so this is the set\n * of profiles the client's decoder can handle. Empty when none is given —\n * per RFC 6184 that implies Constrained Baseline. */\nexport function extractOfferedH264ProfileLevelIds(sdp: string): string[] {\n const ids: string[] = []\n const re = /profile-level-id=([0-9a-fA-F]{6})/g\n let m: RegExpExecArray | null\n while ((m = re.exec(sdp)) !== null) ids.push(m[1]!.toLowerCase())\n return ids\n}\n\n/**\n * Capability-driven transcode decision for the CLIENT-offer flow.\n *\n * Unlike the server-offer path — where the server can only assume iOS\n * decodes Constrained Baseline and so transcodes any Main/High source\n * (`resolveBrokerTranscodeToBaseline`) — a client offer carries the\n * browser's real decode capabilities. iOS 18 (WebKit) offers H.264 High,\n * so a 4K Main/High source passes through untouched (werift echoes the\n * client codec; the native decoder handles it). Only fall back to a\n * Baseline re-encode when the source is non-Baseline AND none of the\n * client's offered profiles can decode it (an older Baseline-only client,\n * or an offer that advertised no H.264 profile at all).\n *\n * \"Can decode\" uses profile_idc ordering: a decoder advertising High\n * (idc 100) decodes Main (77) and Baseline (66); Constrained Baseline\n * (66) decodes only Baseline. So passthrough iff `max(offered idc) >=\n * source idc`. Unknown source profile ⇒ passthrough (desktop still\n * decodes; a reconnect once the profile is known re-decides).\n */\nexport function resolveClientOfferTranscodeToBaseline(\n sessionCodec: 'H264' | 'H265',\n sourceProfileLevelId: string | undefined,\n offeredProfileLevelIds: readonly string[],\n): boolean {\n if (sessionCodec !== 'H264') return false\n if (!sourceProfileLevelId) return false\n if (isBaselineProfileLevelId(sourceProfileLevelId)) return false\n // Capability-driven only: passthrough iff the client offered a\n // profile that decodes the source. Earlier we added a level ≥ 5.0\n // force-transcode (cam 127 main case) on the theory that broadcast\n // Wi-Fi sources lose packets too aggressively for passthrough; but\n // ffmpeg `ultrafast` at the `-maxrate` cap didn't actually shrink\n // the output volumetrically (it just dropped framerate), and the\n // re-encoded keyframes turned out larger and more loss-prone than\n // the source ones. The audio drift that originally appeared to be\n // a passthrough symptom was actually fix #25 (ms → µs broker-side\n // EncodedPacket.pts conversion). Reverted to capability-only.\n const sourceIdc = h264ProfileIdc(sourceProfileLevelId)\n const maxOfferedIdc = offeredProfileLevelIds.reduce(\n (max, plid) => Math.max(max, h264ProfileIdc(plid)),\n 0,\n )\n return maxOfferedIdc < sourceIdc\n}\n\n/**\n * Best-effort extraction of the source H.264 profile-level-id (e.g.\n * `4d0033` Main 5.1, `42e01f` Constrained Baseline) from the camera's SPS.\n * Checks SDP-published parameter sets first (RTSP cameras publish the SPS\n * as a bare NAL in the SDP), then scans the pre-buffer (push/Baichuan\n * cameras emit the SPS inline in keyframes). Returns undefined when no SPS\n * is available yet (e.g. a battery cam before its first keyframe).\n */\nexport function deriveH264ProfileLevelId(\n sdpParameterSets: ReadonlyArray<Buffer> | null,\n preBuffer: readonly EncodedPacket[],\n): string | undefined {\n if (sdpParameterSets) {\n for (const ps of sdpParameterSets) {\n // Bare NAL (no start code); H.264 SPS = type 7. profile_idc /\n // constraints / level_idc are the 3 bytes after the NAL header.\n if (ps.length >= 4 && (ps[0]! & 0x1f) === 7) {\n return Buffer.from([ps[1]!, ps[2]!, ps[3]!]).toString('hex')\n }\n }\n }\n for (const pkt of preBuffer) {\n if (pkt.type !== 'video') continue\n const { profileLevelId } = extractH264ParamSets(convertH264ToAnnexB(pkt.data))\n if (profileLevelId) return profileLevelId\n }\n return undefined\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\n/** Transcode-down ceiling: cap egress height to 360p. */\nconst TRANSCODE_DOWN_MAX_HEIGHT = 360\n/** Transcode-down ceiling: cap egress bitrate to 400 kbps (the `low` LADDER default). */\nconst TRANSCODE_DOWN_MAX_BITRATE_KBPS = 400\n\n/**\n * Derive a reduced transcode profile for the transcode-down rung. Caps the\n * egress to 360p (preserving aspect ratio when both source dims are known)\n * and the bitrate to a low ceiling, forcing an H.264 re-encode so the\n * reduced shape actually reaches the wire. Pure — returns a new profile,\n * never mutates `base`.\n */\nfunction reduceProfileForTranscodeDown(base: EncodeProfile): EncodeProfile {\n const srcWidth = base.video.width\n const srcHeight = base.video.height\n let width = srcWidth\n let height = srcHeight\n if (srcWidth !== undefined && srcHeight !== undefined && srcHeight > TRANSCODE_DOWN_MAX_HEIGHT) {\n height = TRANSCODE_DOWN_MAX_HEIGHT\n // Preserve aspect ratio; force an even width for the encoder.\n const scaled = Math.round((srcWidth * TRANSCODE_DOWN_MAX_HEIGHT) / srcHeight)\n width = scaled % 2 === 0 ? scaled : scaled + 1\n } else if (srcHeight === undefined) {\n height = TRANSCODE_DOWN_MAX_HEIGHT\n }\n const bitrateKbps = base.video.bitrateKbps !== undefined\n ? Math.min(base.video.bitrateKbps, TRANSCODE_DOWN_MAX_BITRATE_KBPS)\n : TRANSCODE_DOWN_MAX_BITRATE_KBPS\n return {\n ...base,\n video: {\n ...base.video,\n // Force a real re-encode so the reduced shape reaches the wire.\n codec: base.video.codec === 'copy' ? 'h264' : base.video.codec,\n ...(width !== undefined ? { width } : {}),\n height,\n bitrateKbps,\n },\n }\n}\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: CamProfile | 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 private readonly clientLossPct: BrokerWebrtcServerOptions['clientLossPct']\n private readonly resolveTranscodeDecodeHwAccelFn: BrokerWebrtcServerOptions['resolveTranscodeDecodeHwAccel']\n /** Cached decode-hwaccel backend for transcode feeds (resolved once). */\n private transcodeDecodeHwAccel: string | null | undefined = 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 (`CamProfile`)\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) => CamProfile | null) | null = null\n /** sessionId → session entry. */\n private readonly sessions = new Map<string, SessionEntry>()\n /**\n * Carries the adaptive controller's desired tier (+ forced transcode-down)\n * across a close-and-recreate re-offer, so the recreated session resolves\n * to the chosen tier AND re-arms its controller. Written by\n * `switchSessionTier`, consumed once by `setupSessionForBroker`, cleared on\n * close.\n */\n private readonly adaptiveIntent = new Map<string, AdaptiveIntent>()\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 this.clientLossPct = options.clientLossPct\n this.resolveTranscodeDecodeHwAccelFn = options.resolveTranscodeDecodeHwAccel\n }\n\n /**\n * Resolve (once, cached) the decode-hwaccel backend for transcode feeds.\n * Never throws — a resolver failure means software decode.\n */\n private async getTranscodeDecodeHwAccel(): Promise<string | null> {\n if (this.transcodeDecodeHwAccel !== undefined) return this.transcodeDecodeHwAccel\n if (!this.resolveTranscodeDecodeHwAccelFn) {\n this.transcodeDecodeHwAccel = null\n return null\n }\n try {\n this.transcodeDecodeHwAccel = await this.resolveTranscodeDecodeHwAccelFn()\n } catch (err) {\n this.logger.warn('resolveTranscodeDecodeHwAccel failed — software decode', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n this.transcodeDecodeHwAccel = null\n }\n return this.transcodeDecodeHwAccel\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) => CamProfile | 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; relayOnly?: boolean; transcodeProfile?: EncodeProfile; consumerAttribution?: BrokerConsumerAttribution } = {},\n ): Promise<{ sessionId: string; sdpOffer: string }> {\n const setup = await this.setupSessionForBroker(streamId, hints, opts)\n try {\n // Force TURN-relay-only ICE for remote (non-LAN) viewers BEFORE\n // `createOffer()` builds the PeerConnection — the patched werift\n // emits a genuinely relay-only SDP under `iceTransportPolicy:'relay'`,\n // so a CGNAT client gets a clean relay↔relay path instead of\n // werift nominating a dead host/hairpin-srflx pair. LAN viewers\n // (`relayOnly` absent/false) keep the direct host/srflx path.\n if (opts.relayOnly === true) {\n setup.session.setForceRelayOnly(true)\n }\n const offer = await setup.session.createOffer()\n setup.sessionLogger.info('WebRTC session created', {\n meta: {\n sessionId: setup.sessionId,\n deviceId: setup.deviceId,\n brokerId: setup.brokerId,\n iceServers: setup.iceServerCount,\n codec: setup.sessionCodec,\n useRtpRepacketizer: setup.useRtpRepacketizer,\n relayOnly: opts.relayOnly === true,\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; relayOnly?: boolean; nonTrickle?: boolean; disableTurn?: boolean; disableIpv6?: boolean; transcodeProfile?: EncodeProfile; consumerAttribution?: BrokerConsumerAttribution } = {},\n ): Promise<{ sessionId: string; sdpAnswer: string }> {\n // Pass the client's offer SDP into setup so the transcode decision is\n // capability-driven: the offered H.264 profiles decide passthrough vs\n // Baseline re-encode (iOS 18 offers High → 4K Main/High passes through).\n const setup = await this.setupSessionForBroker(streamId, hints, { ...opts, clientOfferSdp })\n try {\n // Relay policy must be set BEFORE handleOffer builds the PeerConnection.\n // Cloud peers (Alexa) force TURN-relay-only; LAN/Tailscale browser\n // viewers (relayOnly absent/false) keep the direct host/srflx path.\n if (opts.relayOnly === true) {\n setup.session.setForceRelayOnly(true)\n }\n const answer = await setup.session.handleOffer(\n { sdp: clientOfferSdp, type: 'offer' },\n { nonTrickle: opts.nonTrickle === true },\n )\n setup.sessionLogger.info('WebRTC session created (client-offer)', {\n meta: {\n sessionId: setup.sessionId,\n deviceId: setup.deviceId,\n brokerId: setup.brokerId,\n iceServers: setup.iceServerCount,\n codec: setup.sessionCodec,\n useRtpRepacketizer: setup.useRtpRepacketizer,\n relayOnly: opts.relayOnly === true,\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; relayOnly?: boolean; disableTurn?: boolean; disableIpv6?: boolean; clientOfferSdp?: string; transcodeProfile?: EncodeProfile; consumerAttribution?: BrokerConsumerAttribution },\n ): Promise<{\n sessionId: string\n session: AdaptiveSession\n sessionLogger: IScopedLogger\n deviceId: number\n brokerId: string\n sessionCodec: 'H264' | 'H265'\n useRtpRepacketizer: boolean\n iceServerCount: number\n }> {\n if (this.stopped) throw new Error('Server stopped')\n\n // Adaptive re-offer: a close-and-recreate re-offer carries the same\n // sessionId, so an `adaptiveIntent` entry keyed by it pins the recreated\n // session to the controller's chosen tier (and applies the reduced\n // transcode profile when transcode-down was requested). Only adaptive\n // streams ever record an intent; non-adaptive (profile / cam-stream)\n // sessions never have one and resolve exactly as before.\n const isAdaptiveStream = streamId.endsWith('/adaptive')\n const intent = isAdaptiveStream && opts.sessionId !== undefined\n ? this.adaptiveIntent.get(opts.sessionId)\n : undefined\n // Honour the controller's chosen tier on the recreated broker resolution\n // so `startAdaptive` reads `initialTier` from that broker and resumes at\n // the right rung (BUG #1: upgrade-back was dead because the re-offer\n // landed on a raw cam-stream and never re-armed the controller).\n const resolveHints: ClientHints = intent !== undefined\n ? { ...hints, prefersTier: intent.prefersTier }\n : hints\n const brokerId = this.resolveBrokerId(streamId, resolveHints)\n const broker = this.brokers.get(brokerId)\n if (!broker) throw new Error(`No broker for stream \"${streamId}\" (resolved: \"${brokerId}\")`)\n\n // Transcode-down rung (BUG #4): when the controller forced a transcode-down,\n // the recreated session re-encodes its egress to a reduced (≤360p, low\n // bitrate) profile derived from whatever transcode profile it would\n // otherwise use. The baked default is used when no profile was supplied.\n const baseTranscodeProfile = opts.transcodeProfile ?? WEBRTC_FALLBACK_PROFILE\n const effectiveTranscodeProfile = intent?.forceTranscodeDown === true\n ? reduceProfileForTranscodeDown(baseTranscodeProfile)\n : opts.transcodeProfile\n // Intent is single-use: the recreated session's fresh controller now owns\n // the tier state and writes a new intent on its next switch. Delete to\n // prevent leaks across subsequent re-offers.\n if (intent !== undefined && opts.sessionId !== undefined) {\n this.adaptiveIntent.delete(opts.sessionId)\n }\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 deviceId = deviceIdFromBrokerId(brokerId)\n const deviceTags = {\n deviceId,\n camStreamId: slashIdx > 0 ? brokerId.slice(slashIdx + 1) : brokerId,\n }\n const sessionLogger = this.logger.withTags(deviceTags)\n\n // Wake the broker if it's still cold (RTSP on-demand pull hasn't dialed\n // the camera yet) and wait briefly for the camera SDP. Without this, an\n // on-demand session created against a cold broker reads\n // `getStats().codec === undefined`, defaults to H.264, and locks every\n // downstream decision (PT, codec offer list, repacketizer choice,\n // SDP-seed dispatch) into the wrong codec — every H.265 source then\n // goes black on the viewer (cam 99 freeze diagnosis 2026-05-28). The\n // warm-up `onEncodedData` subscription triggers `checkDemand`→dial and\n // stays alive until the real session subscribers attach below, so the\n // broker can't see zero consumers and teardown mid-wake. Timeout\n // bounded so we never block forever on an unreachable camera; on\n // timeout we fall back to the prior default-H.264 behaviour rather\n // than failing the session entirely.\n let warmUpUnsubscribe: Unsubscribe | null = null\n if (!broker.getStats().codec) {\n warmUpUnsubscribe = broker.onEncodedData(() => { /* warm-up no-op */ }, {\n kind: 'warmup',\n media: 'both',\n })\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n sessionLogger.warn('broker SDP wait timed out — falling back to default codec', {\n meta: { brokerId, currentCodec: broker.getStats().codec ?? null, timeoutMs: 2500 },\n })\n resolve()\n }, 2500)\n const unsubSdp = broker.onSdpParameterSets(() => {\n clearTimeout(timer)\n unsubSdp()\n resolve()\n })\n })\n sessionLogger.info('broker warm-up complete', {\n meta: { brokerId, detectedCodec: broker.getStats().codec ?? null },\n })\n }\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 // Forward source RTP through the codec's repacketizer for ALL RTP-bearing\n // sources (RTSP), H.264 AND H.265 — instead of the AnnexB→writeVideoNals\n // synthesis path. iOS (WebKit) has a strict H.264 depacketizer (like its\n // HEVC one) that rejects the synthesized shape for Main/High passthrough;\n // the repacketizer preserves the native RTP layout. AnnexB-only push\n // brokers (Reolink Baichuan, Frigate) stay on writeVideoNals.\n const useRtpRepacketizer = 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=${useRtpRepacketizer}`,\n { meta: { brokerId, sessionCodec, brokerCodec, sourceType, isRtp, useRtpRepacketizer } },\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 // The real consumer is now attached — release the warm-up ghost\n // subscription. `checkDemand` runs on add+remove; because we add\n // BEFORE removing, the broker never sees zero consumers and won't\n // teardown the dial mid-startup.\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. When the same sessionId arrives twice (Alexa\n // retries InitiateSessionWithOffer with the SAME sessionId when the\n // first Lambda relay didn't deliver the answer back to Echo in time),\n // tear down the stale session first so the retry creates a fresh\n // PeerConnection and Echo receives a usable answer instead of an\n // \"already in use\" error. Resolved BEFORE the subscriptions are\n // created so it can be stamped into the attribution — the\n // `onConnected` hook re-tags subscriptions by this sessionId once ICE\n // discovers the nominated remote address.\n const requestedSessionId = opts.sessionId\n if (requestedSessionId !== undefined && this.sessions.has(requestedSessionId)) {\n const stale = this.sessions.get(requestedSessionId)!\n sessionLogger.warn('Session id reused by client retry — closing stale session before re-creating', {\n meta: { sessionId: requestedSessionId, brokerId: stale.brokerId },\n })\n this.cleanupSession(requestedSessionId)\n await stale.session.close().catch(() => {})\n }\n const sessionId = requestedSessionId ?? crypto.randomUUID()\n const baseAttribution: BrokerConsumerAttribution = {\n ...(opts.consumerAttribution ?? { kind: 'webrtc-browser' }),\n sessionId,\n }\n // Audio path always rides AnnexB; downstream codec depends on the\n // source: when the broker codec is one Echo/HomeKit/browsers can\n // copy (PCMU, Opus) we passthrough, otherwise libav transcodes.\n // The session has the truthy view of the negotiated audio codec\n // in the `audio frame in` log line — we mirror it best-effort here\n // for the widget chip. Source audio codec is whatever the broker's\n // `audioTrackInfo` advertised.\n const sourceAudioCodec = broker.getStats().audio?.codec ?? null\n const targetAudioCodec: string = sourceAudioCodec === 'PCMU' || sourceAudioCodec === 'PCMA' || sourceAudioCodec === 'OPUS'\n ? sourceAudioCodec\n : 'Pcmu'\n const audioTranscoded = sourceAudioCodec !== null && targetAudioCodec.toLowerCase() !== sourceAudioCodec.toLowerCase()\n // The AnnexB subscription serves audio always; for RTP-bearing sources\n // (`useRtpRepacketizer === true`) the video branch early-returns and\n // the actual video runs over the source-RTP fan-out below. Tagging\n // each subscription with its consumed media keeps the widget honest:\n // operators see ONE row per channel (\"audio\" / \"video\"), not two\n // rows that look duplicated.\n const annexbAttribution: BrokerConsumerAttribution = {\n ...baseAttribution,\n media: useRtpRepacketizer ? 'audio' : 'both',\n // When media === 'both' (push-AnnexB source) the dominant channel\n // chip is video: report the video codec as targetCodec for the\n // widget. Audio detail is captured by the audio transport.\n targetCodec: useRtpRepacketizer ? targetAudioCodec : sessionCodec,\n transport: audioTranscoded ? 'transcode' : 'passthrough',\n }\n const rtpAttribution: BrokerConsumerAttribution = {\n ...baseAttribution,\n media: 'video',\n targetCodec: sessionCodec,\n // Source-RTP path through repacketizer preserves frames, only\n // rebuilds headers — that's `'repacketize'`. Baseline transcode\n // (set elsewhere when the cam ships Main/High to a Baseline-only\n // viewer) flips this to `'transcode'`.\n transport: effectiveTranscodeProfile !== undefined ? 'transcode' : 'repacketize',\n }\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 (useRtpRepacketizer) 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 // Broker `EncodedPacket.pts` is wall-clock-relative MS\n // by convention (see stream-broker.ts:2031 et al. and\n // rtsp-restreamer.ts:246 — `pts * 90` to convert to\n // 90kHz). Convert to µs for the frame contract.\n timestampMicros: packet.pts * 1000,\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 // Broker `EncodedPacket.pts` is MS-relative — convert to µs\n // for the frame contract. See the placeholder-branch above.\n timestampMicros: packet.pts * 1000,\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 // Same MS → µs conversion as the video branch above.\n timestampMicros: packet.pts * 1000,\n },\n })\n }\n }, annexbAttribution)\n // Real consumer attached → release the warm-up ghost. The add-then-\n // remove ordering keeps `checkDemand` from ever seeing zero\n // subscribers, so the dial we just woke stays up.\n warmUpUnsubscribe?.()\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 (useRtpRepacketizer) 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 // Pre-buffer pkt.pts is the same MS convention as the\n // live `packet.pts` above — see the comment there.\n timestampMicros: pkt.pts * 1000,\n },\n })\n }\n }\n\n const allIceServers = await this.resolveIceServers()\n // When the caller declares the peer is publicly reachable and TURN is\n // counterproductive (Alexa's cloud media gateway), strip relay entries\n // — keep only STUN. Filter URL-by-URL: a single IceServerEntry may\n // expand into both `stun:` and `turn:` URLs sharing one credential\n // record, and dropping the whole entry would discard the STUN URL too.\n const iceServers: typeof allIceServers = opts.disableTurn === true\n ? allIceServers\n .map((entry) => {\n const urls = Array.isArray(entry.urls) ? entry.urls : [entry.urls]\n const stunOnly = urls.filter((u) => !u.startsWith('turn:') && !u.startsWith('turns:'))\n if (stunOnly.length === 0) return null\n return { ...entry, urls: stunOnly.length === 1 ? stunOnly[0]! : stunOnly }\n })\n .filter((e): e is NonNullable<typeof e> => e !== null)\n : allIceServers\n\n // iOS fix: an H.264 source emitting Main/High can't be decoded by iOS\n // (WebKit) WebRTC, which negotiates Constrained Baseline and decodes\n // ONLY Baseline. Detect the source profile from the camera SPS (SDP\n // params first, then the pre-buffer) and re-encode the egress to\n // Baseline when it isn't already. Baseline H.264 and all H.265 stay\n // passthrough (zero re-encode). See\n // docs/superpowers/specs/2026-05-26-webrtc-ios-baseline-transcode-design.md\n const sourceProfileLevelId = sessionCodec === 'H264'\n ? deriveH264ProfileLevelId(broker.getSdpParameterSets(), broker.getPreBuffer())\n : undefined\n // Client-offer flow: decide from the browser's advertised decode caps\n // (iOS 18 offers H.264 High → 4K Main/High passes through). Server-offer\n // flow: no client offer yet, so fall back to the conservative\n // source-profile-only rule (transcode any Main/High to Baseline for iOS).\n const offeredProfileLevelIds = opts.clientOfferSdp\n ? extractOfferedH264ProfileLevelIds(opts.clientOfferSdp)\n : undefined\n const transcodeToBaseline = offeredProfileLevelIds\n ? resolveClientOfferTranscodeToBaseline(sessionCodec, sourceProfileLevelId, offeredProfileLevelIds)\n : resolveBrokerTranscodeToBaseline(sessionCodec, sourceProfileLevelId)\n if (transcodeToBaseline) {\n sessionLogger.info(\n 'WebRTC: source H.264 is non-Baseline and the client cannot decode it — re-encoding egress to Constrained Baseline',\n { meta: { brokerId, sessionId, sourceProfileLevelId, offeredProfileLevelIds, clientOffer: opts.clientOfferSdp !== undefined } },\n )\n } else if (offeredProfileLevelIds && sourceProfileLevelId && !isBaselineProfileLevelId(sourceProfileLevelId)) {\n sessionLogger.info(\n 'WebRTC: client offered a profile that decodes the non-Baseline H.264 source — passthrough (no re-encode)',\n { meta: { brokerId, sessionId, sourceProfileLevelId, offeredProfileLevelIds } },\n )\n }\n\n const transcodeDecodeHwAccel = await this.getTranscodeDecodeHwAccel()\n\n const session = new AdaptiveSession({\n sessionId,\n source,\n sourceCodec: sessionCodec,\n transcodeToBaseline,\n transcodeProfile: effectiveTranscodeProfile,\n transcodeDecodeHwAccel,\n iceConfig: {\n iceServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n disableIpv6: opts.disableIpv6 === true,\n },\n // Device-tagged logger so every WebRTC session lifecycle line\n // (ICE state, candidate pairs, nominated pair, feed progress)\n // carries `deviceId` — operator log filters scoped to one device\n // no longer drop session logs as `dev=?`.\n logger: sessionLogger,\n deviceId,\n // Source-RTP pre-buffer accessor for instant start on the repacketizer\n // path — the session replays the current GOP on its first forwarded\n // packet. Only meaningful for RTP sources (returns [] otherwise).\n rtpBootstrap: useRtpRepacketizer ? (() => broker.getRtpPreBuffer()) : undefined,\n debug: opts.streamingDebug === true,\n })\n\n // For RTSP (RTP-bearing) sessions, H.264 AND H.265: subscribe to\n // source-RTP and forward through the codec's repacketizer in the\n // session. Also seed the repacketizer codec info from the SDP-derived\n // param sets (H.264 SPS/PPS, H.265 VPS/SPS/PPS) so the STAP-A/AP codec\n // packet can precede the first IDR even when the camera doesn't emit\n // param sets in-band. Push-mode brokers (Reolink Baichuan, etc.) skip\n // this and use the AnnexB feed path.\n const seedFromSdp = (ps: ReadonlyArray<Buffer>): void => {\n if (sessionCodec === 'H265') session.seedH265CodecInfoFromSdp(ps)\n else session.seedH264CodecInfoFromSdp(ps)\n }\n let unsubRtp: Unsubscribe | null = null\n let unsubSdpParams: Unsubscribe | null = null\n if (useRtpRepacketizer) {\n const sdpPs = broker.getSdpParameterSets()\n if (sdpPs && sdpPs.length > 0) {\n seedFromSdp(sdpPs)\n sessionLogger.info('RTP session seeded from SDP at create-time', {\n meta: { sessionId, brokerId, codec: sessionCodec, 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 codec packet, so the\n // browser 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('RTP session deferred: SDP params not ready, subscribing for late delivery', {\n meta: { sessionId, brokerId, codec: sessionCodec },\n })\n unsubSdpParams = broker.onSdpParameterSets((ps) => {\n try {\n seedFromSdp(ps)\n sessionLogger.info('RTP session seeded from SDP late delivery', {\n meta: { sessionId, brokerId, codec: sessionCodec, psCount: ps.length },\n })\n } catch (err) {\n sessionLogger.warn('seedCodecInfoFromSdp threw', {\n meta: { sessionId, codec: sessionCodec, 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('RTP session: first source RTP forwarded to repacketizer', {\n meta: { sessionId, brokerId, codec: sessionCodec, bytes: rtpData.length },\n })\n }\n try {\n session.forwardSourceRtpVideo(rtpData)\n } catch (err) {\n sessionLogger.warn('forwardSourceRtpVideo threw', {\n meta: { sessionId, codec: sessionCodec, error: errMsg(err) },\n })\n }\n }, rtpAttribution)\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 // Once ICE connects, read the nominated pair and tag this session's broker\n // subscriptions (audio + video) with the remote peer address so operators\n // can identify the viewer. Best-effort: werift may populate `nominated` up\n // to ~1s after the \"connected\" event, so retry briefly; any failure is\n // swallowed — this never affects the live session/ICE path. When the\n // selected pair is a relay/srflx candidate the displayed `@addr` is the\n // TURN/reflexive address (the relay's or the NAT-reflexive one), NOT the\n // viewer's real LAN IP — appending the type disambiguates for the operator.\n //\n // Fire-at-most-once: the ICE `connected` transition also fires on every\n // reconnect after a `disconnected`. The `attributionTagged` latch on the\n // session entry ensures a reconnect does NOT re-arm a new retry chain; the\n // pending `attributionTimer` is cleared on teardown (`cleanupSession`) so\n // no timer fires after the session is gone.\n session.onConnected = () => {\n if (entry.attributionTagged) return\n let attempt = 0\n const tag = (): void => {\n // Bail if the session was torn down while a retry was pending — the\n // entry is removed from `this.sessions` in `cleanupSession`. Belt-and-\n // suspenders alongside the timer-clear there.\n if (this.sessions.get(sessionId) !== entry) return\n try {\n const pair = session.readNominatedPair()\n if (!pair) {\n if (attempt++ < 5) {\n entry.attributionTimer = setTimeout(tag, 200)\n } else {\n // Retry budget exhausted — latch so a later reconnect does not\n // re-arm a fresh chain for the same (likely unreadable) pair.\n entry.attributionTagged = true\n }\n return\n }\n const remoteAddr = pair.remoteType === 'host'\n ? pair.remoteAddr\n : `${pair.remoteAddr} (${pair.remoteType})`\n broker.updateConsumerAttributionBySession(sessionId, { remoteAddr })\n entry.attributionTagged = true\n } catch {\n /* best-effort — never break the session path */\n }\n }\n tag()\n }\n\n // Adaptive sessions (created via the `${deviceId}/adaptive` alias) get a\n // ladder controller that can switch the session to a different-tier broker\n // mid-stream. Non-adaptive sessions (pinned profile / cam-stream targets)\n // are left UNCHANGED — they keep the single broker they were created on.\n if (streamId.endsWith('/adaptive')) {\n const initialTier: Tier = this.profileTierResolver?.(brokerId) ?? 'high'\n this.startAdaptive(entry, deviceId, initialTier, sessionId)\n }\n\n return {\n sessionId,\n session,\n sessionLogger,\n deviceId,\n brokerId,\n sessionCodec,\n useRtpRepacketizer,\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 /** Trickle ICE — add a remote (client) candidate to a live session. */\n async addIceCandidate(sessionId: string, candidate: IceCandidatePayload): Promise<void> {\n const entry = this.sessions.get(sessionId)\n if (!entry) return\n await entry.session.addIceCandidate(candidate).catch((err) => {\n this.logger.warn('addIceCandidate threw', { meta: { sessionId, error: errMsg(err) } })\n })\n }\n\n /** Trickle ICE — the server's gathered candidates for a session (poll). */\n getIceCandidates(sessionId: string): { candidates: IceCandidatePayload[]; done: boolean } {\n const entry = this.sessions.get(sessionId)\n if (!entry) return { candidates: [], done: true }\n return entry.session.getIceCandidatesSnapshot()\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', {\n meta: { sessionId, deviceId: deviceIdFromBrokerId(entry.brokerId) },\n })\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 /** Live signaling state for a session — used by the webrtc-session\n * provider's getSessionState. Returns a null pending when unknown. */\n getSessionRenegotiationState(sessionId: string): { pendingRenegotiation: { target: WebrtcStreamTarget; epoch: number } | null } {\n const entry = this.sessions.get(sessionId)\n return { pendingRenegotiation: entry?.session.pendingRenegotiation ?? null }\n }\n\n // ── Private ─────────────────────────────────────────────────────────\n\n /**\n * Wire an {@link AdaptiveController} to a freshly-created adaptive session.\n * The controller is fed loss from two sources — RTCP Receiver Reports\n * (`onReceiverReport`) and, when injected, the device's client-reported\n * packet-loss % polled every {@link ADAPTIVE_TICK_MS} — and drives the\n * ladder via the timer's `tick`. Side effects (tier switch / MTU reduce /\n * transcode-down) run through the controller's callbacks back into this\n * server.\n *\n * The client-loss % is turned into a monotonic {@link LossSample} LOCALLY:\n * a per-controller counter `clientLossN` advances each tick that carries a\n * positive loss %, so consecutive samples have a clean delta whose ratio\n * equals `pct/100` — exactly what the controller's `LossTracker` expects.\n * Both paths feed the same controller; the tracker naturally honours\n * whichever produces the higher loss ratio across its window. The async\n * poll is guarded against overlapping ticks via `tickInFlight`.\n */\n private startAdaptive(entry: SessionEntry, deviceId: number, initialTier: Tier, sessionId: string): void {\n const controller = new AdaptiveController({\n initialTier,\n config: ADAPTIVE_CONFIG,\n onSwitchTier: (tier) => { this.switchSessionTier(entry, deviceId, tier, sessionId) },\n onReduceMtu: () => entry.session.setRepacketizerMtu(AdaptiveSession.ADAPTIVE_LOW_MTU),\n onTranscodeDown: () => { this.switchSessionTier(entry, deviceId, 'low', sessionId, true) },\n })\n entry.session.onReceiverReport((s) => controller.onLoss(s, Date.now()))\n entry.adaptiveController = controller\n\n let clientLossN = 0\n let tickInFlight = false\n const CLIENT_LOSS_SPAN = 1000\n entry.adaptiveTimer = setInterval(() => {\n if (tickInFlight) return\n tickInFlight = true\n void (async () => {\n try {\n const pct = this.clientLossPct ? await this.clientLossPct(deviceId) : null\n const now = Date.now()\n if (pct !== null && pct !== undefined && pct > 0) {\n clientLossN += 1\n controller.onLoss({\n packetsLost: Math.round((pct / 100) * CLIENT_LOSS_SPAN * clientLossN),\n highestSequence: CLIENT_LOSS_SPAN * clientLossN,\n }, now)\n }\n controller.tick(now)\n } finally {\n tickInFlight = false\n }\n })()\n }, ADAPTIVE_TICK_MS)\n }\n\n /**\n * Schedule an adaptive tier switch via a close-and-recreate re-offer.\n *\n * Records the controller's desired tier (+ forced transcode-down) in\n * `adaptiveIntent`, then sets `pendingRenegotiation` with an ADAPTIVE\n * target so the client re-offers under the same sessionId. The re-offer\n * runs through `setupSessionForBroker`, which tears the old session down\n * completely and rebinds whichever feed type the session uses — so we no\n * longer do a live broker rebind here (that produced a dual-feed for the\n * AnnexB path and couldn't recreate the controller). `setupSessionForBroker`\n * consumes the recorded intent to pin the recreated session to `tier` and\n * apply the reduced transcode profile when `forceTranscodeDown` is set.\n */\n private switchSessionTier(entry: SessionEntry, deviceId: number, tier: Tier, sessionId: string, forceTranscodeDown = false): void {\n const prev = this.adaptiveIntent.get(sessionId)\n this.adaptiveIntent.set(sessionId, {\n prefersTier: tier,\n forceTranscodeDown: forceTranscodeDown || (prev?.forceTranscodeDown ?? false),\n })\n const epoch = (entry.session.pendingRenegotiation?.epoch ?? 0) + 1\n entry.session.pendingRenegotiation = { target: { kind: 'adaptive' }, epoch }\n this.logger.info('adaptive: scheduling renegotiation', { meta: { sessionId, deviceId, tier, forceTranscodeDown, epoch } })\n }\n\n private cleanupSession(sessionId: string): void {\n const entry = this.sessions.get(sessionId)\n if (!entry) return\n this.sessions.delete(sessionId)\n // Clear any pending adaptive intent as a leak safety. On the close-and-\n // recreate re-offer the intent is already consumed (read + deleted at the\n // top of `setupSessionForBroker`) before this runs for the stale session,\n // so this is a no-op there; it only catches the permanent-teardown case\n // where a switch was scheduled but the client disconnected before\n // re-offering.\n this.adaptiveIntent.delete(sessionId)\n if (entry.adaptiveTimer) clearInterval(entry.adaptiveTimer)\n // Clear any pending remote-address tagging retry so no timer fires after\n // teardown (and latch it so a stale fire is a guaranteed no-op).\n if (entry.attributionTimer) clearTimeout(entry.attributionTimer)\n entry.attributionTagged = true\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: CamProfile | 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: CamProfile | 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\n/**\n * Parse the numeric deviceId out of a canonical brokerId\n * (`${deviceId}/${camStreamId}`). Returns -1 for the bare-deviceKey\n * shape with no slash. Used so every session/broker log line can carry\n * `deviceId` (operator log filters scoped to one device otherwise drop\n * them as `dev=?`).\n */\nfunction deviceIdFromBrokerId(brokerId: string): number {\n const slashIdx = brokerId.indexOf('/')\n if (slashIdx <= 0) return -1\n const parsed = Number.parseInt(brokerId.slice(0, slashIdx), 10)\n return Number.isNaN(parsed) ? -1 : parsed\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 { makeSourceBrokerId } from '@camstack/types'\nimport type {\n CameraStream, ProfileSlot, CamProfile,\n RtspRestreamEntry, ProfileRtspEntry,\n WebrtcClientHints, WebrtcStreamChoice, WebrtcStreamTarget,\n EncodeProfile,\n PickedCamStream, StreamCodec,\n BrokerConsumerAttribution,\n} from '@camstack/types'\nimport type { StreamBrokerManager } from './stream-broker/stream-broker-manager.js'\nimport { withSourceResolution } from './encode-profile-resolver.js'\nimport { WEBRTC_FALLBACK_PROFILE } from './webrtc/webrtc-fallback-profile.js'\n\n// ── pickStream helpers ───────────────────────────────────────────────\n\n/** Canonicalise a stream codec string against the cap enum. */\nfunction canonicalCodec(raw: string | undefined): StreamCodec | null {\n if (!raw) return null\n const lower = raw.toLowerCase()\n if (lower === 'hevc') return 'hevc'\n if (lower === 'h265') return 'h265'\n if (lower === 'h264') return 'h264'\n if (lower === 'av1') return 'av1'\n if (lower === 'mjpeg') return 'mjpeg'\n if (lower === 'vp8') return 'vp8'\n if (lower === 'vp9') return 'vp9'\n return null\n}\n\n/** Two codecs match if they normalise to the same value OR are h265↔hevc aliases. */\nfunction codecMatches(streamCodec: string | undefined, accept: readonly StreamCodec[]): boolean {\n const c = canonicalCodec(streamCodec)\n if (!c) return false\n if (accept.includes(c)) return true\n // H.265 and HEVC are the same payload, naming varies by SDK / probe.\n if ((c === 'h265' && accept.includes('hevc')) || (c === 'hevc' && accept.includes('h265'))) {\n return true\n }\n return false\n}\n\nconst PROVIDER_PREFIX_DEFAULTS: readonly string[] = ['native:']\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 async getProfileRtspEntries(\n input: { deviceId: number; hostname?: string },\n ): Promise<readonly ProfileRtspEntry[]> {\n return this.manager.getProfileRtspEntriesForDevice(input.deviceId, input.hostname)\n }\n\n /**\n * Pick the best source camStream for the caller's decode constraints.\n * Returns `null` when nothing matches — the caller falls back to its\n * existing path (derived-broker transcode, profile-slot pick, etc).\n *\n * Generalised from Alexa's `tryResolveDirectH264Target`: every broker\n * consumer with codec restrictions (Echo decodes only H.264, HomeKit\n * Secure Video prefers H.264 baseline, browsers without HEVC\n * hardware…) needs the same \"is there a non-transcoded sibling\n * stream I should dial?\" decision, so it lives on the cap.\n */\n async pickStream(input: {\n deviceId: number\n requirements: {\n acceptCodecs?: readonly StreamCodec[]\n minHeight?: number\n minWidth?: number\n excludeDerived?: boolean\n requireSiblingCodec?: readonly StreamCodec[]\n }\n preferences?: {\n preferredProviders?: readonly string[]\n resolutionPreference?: 'highest' | 'lowest'\n }\n }): Promise<PickedCamStream | null> {\n const streams = await this.manager.getCameraStreamsForDevice(input.deviceId)\n if (streams.length === 0) {\n this.manager.getLogger?.()?.info('camera-streams: pickStream — no streams for device', {\n meta: { deviceId: input.deviceId, requirements: input.requirements },\n })\n return null\n }\n\n const requirements = input.requirements\n const preferences = input.preferences\n const excludeDerived = requirements.excludeDerived !== false\n const accept = requirements.acceptCodecs ?? []\n const minHeight = requirements.minHeight ?? 0\n const minWidth = requirements.minWidth ?? 0\n\n const candidates = streams.filter((s) => {\n if (excludeDerived && s.camStreamId.startsWith('derived:')) return false\n if (accept.length > 0 && !codecMatches(s.codec, accept)) return false\n if ((s.resolution?.height ?? 0) < minHeight) return false\n if ((s.resolution?.width ?? 0) < minWidth) return false\n return true\n })\n if (candidates.length === 0) {\n this.manager.getLogger?.()?.info('camera-streams: pickStream — no matching candidates', {\n meta: {\n deviceId: input.deviceId,\n requirements: input.requirements,\n streams: streams.map((s) => ({\n camStreamId: s.camStreamId,\n codec: s.codec,\n resolution: s.resolution,\n })),\n },\n })\n return null\n }\n\n // Sibling-codec gate — only \"pick a bypass\" when the natural pick\n // would have hit one of the rejected codecs. Skips the optimisation\n // for devices that don't need it.\n const requireSibling = requirements.requireSiblingCodec ?? []\n if (requireSibling.length > 0) {\n const hasSibling = streams.some((s) => {\n if (s.camStreamId.startsWith('derived:')) return false\n return codecMatches(s.codec, requireSibling)\n })\n if (!hasSibling) {\n this.manager.getLogger?.()?.info('camera-streams: pickStream — no sibling codec, skipping bypass', {\n meta: {\n deviceId: input.deviceId,\n requireSibling,\n streams: streams.map((s) => ({\n camStreamId: s.camStreamId,\n codec: s.codec,\n })),\n },\n })\n return null\n }\n }\n\n const preferredProviders = preferences?.preferredProviders ?? PROVIDER_PREFIX_DEFAULTS\n const resolutionPreference = preferences?.resolutionPreference ?? 'highest'\n\n // Provider rank: lower is better. Streams whose camStreamId starts\n // with the earliest preferred prefix win. Unmatched streams sit at\n // `preferredProviders.length` (last).\n const providerRank = (camStreamId: string): number => {\n for (let i = 0; i < preferredProviders.length; i += 1) {\n if (camStreamId.startsWith(preferredProviders[i]!)) return i\n }\n return preferredProviders.length\n }\n\n candidates.sort((a, b) => {\n const ra = providerRank(a.camStreamId)\n const rb = providerRank(b.camStreamId)\n if (ra !== rb) return ra - rb\n const ha = a.resolution?.height ?? 0\n const hb = b.resolution?.height ?? 0\n return resolutionPreference === 'highest' ? hb - ha : ha - hb\n })\n\n const winner = candidates[0]!\n const reasonParts: string[] = []\n reasonParts.push(`codec=${winner.codec ?? 'unknown'}`)\n if (winner.resolution) reasonParts.push(`res=${winner.resolution.width}x${winner.resolution.height}`)\n reasonParts.push(`provider=${providerRank(winner.camStreamId)}`)\n if (requireSibling.length > 0) reasonParts.push(`bypassed=${requireSibling.join('|')}`)\n\n return {\n camStreamId: winner.camStreamId,\n codec: winner.codec,\n resolution: winner.resolution,\n reason: reasonParts.join(' '),\n }\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 * Per-device session debug flag passed to `BrokerWebrtcServer` (which\n * forwards it into `AdaptiveSession.debug`). True when EITHER the\n * \"Streaming debug logs\" toggle (`streamingDebug`) OR the dedicated\n * \"Advanced WebRTC debug logging\" toggle (`webrtcDebug`) is on for the\n * device. The latter is the operator switch that, when ON, turns on the\n * full per-step WebRTC lifecycle logging in `session.ts` for every\n * session created for this device. Read once at session creation so the\n * next session for a device picks up a freshly-toggled flag.\n */\n private resolveSessionDebug(deviceId: number): boolean {\n const streamingDebug = typeof this.manager.isStreamingDebug === 'function'\n ? this.manager.isStreamingDebug(deviceId)\n : false\n const webrtcDebug = typeof this.manager.isWebrtcDebug === 'function'\n ? this.manager.isWebrtcDebug(deviceId)\n : false\n return streamingDebug || webrtcDebug\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 makeSourceBrokerId(deviceId, slot.sourceCamStreamId)\n }\n case 'cam-stream':\n return makeSourceBrokerId(deviceId, target.camStreamId)\n }\n }\n\n /**\n * Resolve a `WebrtcStreamTarget` to its concrete camStreamId, or\n * `undefined` for adaptive (no single cam stream is pinned).\n */\n private resolveCamStreamId(deviceId: number, target: WebrtcStreamTarget): string | undefined {\n switch (target.kind) {\n case 'adaptive':\n return undefined\n case 'cam-stream':\n return target.camStreamId\n case 'profile': {\n const slots = this.manager.getProfileSlotsForDevice(deviceId)\n return slots.find((s) => s.profile === target.profile)?.sourceCamStreamId ?? undefined\n }\n }\n }\n\n /**\n * Resolve the `EncodeProfile` to use for a session.\n *\n * 1. The hardcoded WebRTC fallback profile applies, smart-adapted to\n * the source's probed resolution so a 4K H.265 camera served to\n * a no-HW-HEVC browser stays 4K H.264 instead of dropping to\n * the baked 1280×720.\n * 2. Adaptive targets have no concrete camStreamId and always fall\n * back to the raw fallback profile.\n */\n private resolveTranscodeProfile(deviceId: number, camStreamId: string | undefined): EncodeProfile {\n if (camStreamId === undefined) return WEBRTC_FALLBACK_PROFILE\n const probedSource = this.manager.getCameraStreamProbedParams(deviceId, camStreamId)\n return withSourceResolution(WEBRTC_FALLBACK_PROFILE, probedSource)\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\n const camStreamId = slot.sourceCamStreamId\n if (!camStreamId) continue\n const broker = await this.manager.getBroker({ brokerId: makeSourceBrokerId(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: makeSourceBrokerId(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 /**\n * SERVER-INJECTED relay-only flag (set by the hub when the viewer's\n * IP is non-LAN). Forces TURN-relay-only ICE for this session so a\n * CGNAT/4G client gets a clean relay↔relay media path. LAN viewers\n * leave this absent and keep the direct host/srflx path. See the\n * cap definition (`webrtc-session.cap.ts`) for the rationale.\n */\n relayOnly?: boolean\n /**\n * Subscriber attribution surfaced in the stream-broker widget's\n * client list. Hub-router sets `remoteAddr` from the tRPC request\n * context; callers (Alexa addon, browser hooks) provide `kind`,\n * `label`, `userId`, and `sessionId` when known.\n */\n consumerAttribution?: BrokerConsumerAttribution\n }): Promise<{ sessionId: string; sdpOffer: string }> {\n const streamId = this.resolveTargetToStreamId(input.deviceId, input.target)\n const streamingDebug = this.resolveSessionDebug(input.deviceId)\n const camStreamId = this.resolveCamStreamId(input.deviceId, input.target)\n const transcodeProfile = this.resolveTranscodeProfile(input.deviceId, camStreamId)\n return this.webrtcServer.createSession(streamId, input.hints, {\n streamingDebug,\n relayOnly: input.relayOnly === true,\n transcodeProfile,\n ...(input.consumerAttribution !== undefined ? { consumerAttribution: input.consumerAttribution } : {}),\n })\n }\n\n async handleOffer(input: {\n deviceId: number\n target?: WebrtcStreamTarget\n sdpOffer: string\n sessionId?: string\n /**\n * Relay-only flag. A cloud peer (Alexa's RTCSessionController) sets\n * this `true` — it's only reachable via TURN, never our host/srflx.\n * A browser doing client-offer passthrough leaves it absent/false so\n * a direct LAN/Tailscale host pair carries full native quality. See\n * the cap definition (`webrtc-session.cap.ts`) for the rationale.\n */\n relayOnly?: boolean\n /**\n * Non-trickle flag. A peer that doesn't support trickle ICE (Alexa's\n * RTCSessionController) sets this `true` so the answer SDP carries the\n * server's gathered candidates instead of trickling them separately. A\n * browser leaves it absent/false and polls `getIceCandidates`. See the\n * cap definition (`webrtc-session.cap.ts`) for the rationale.\n */\n nonTrickle?: boolean\n /**\n * Disable-TURN flag. Strips TURN servers from the per-session iceConfig\n * so the answer carries ONLY host + srflx candidates. See the cap\n * definition (`webrtc-session.cap.ts`) for the rationale.\n */\n disableTurn?: boolean\n /**\n * Disable-IPv6 flag. Suppresses IPv6 host candidates from werift\n * gathering and from `additionalHostAddresses`. Set `true` by peers\n * that document IPv4-only ICE (Alexa). See the cap definition.\n */\n disableIpv6?: boolean\n /** Subscriber attribution. See `createSession` for the contract. */\n consumerAttribution?: BrokerConsumerAttribution\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 = this.resolveSessionDebug(input.deviceId)\n const camStreamId = this.resolveCamStreamId(input.deviceId, target)\n const transcodeProfile = this.resolveTranscodeProfile(input.deviceId, camStreamId)\n return this.webrtcServer.handleOffer(streamId, input.sdpOffer, undefined, {\n streamingDebug,\n sessionId: input.sessionId,\n relayOnly: input.relayOnly === true,\n nonTrickle: input.nonTrickle === true,\n disableTurn: input.disableTurn === true,\n disableIpv6: input.disableIpv6 === true,\n transcodeProfile,\n ...(input.consumerAttribution !== undefined ? { consumerAttribution: input.consumerAttribution } : {}),\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 addIceCandidate(input: {\n deviceId: number\n sessionId: string\n candidate: string\n sdpMid?: string | null\n sdpMLineIndex?: number | null\n }): Promise<void> {\n await this.webrtcServer.addIceCandidate(input.sessionId, {\n candidate: input.candidate,\n sdpMid: input.sdpMid ?? null,\n sdpMLineIndex: input.sdpMLineIndex ?? null,\n })\n }\n\n async getIceCandidates(input: { deviceId: number; sessionId: string }): Promise<{\n candidates: { candidate: string; sdpMid: string | null; sdpMLineIndex: number | null }[]\n done: boolean\n }> {\n return this.webrtcServer.getIceCandidates(input.sessionId)\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 async getSessionState(input: { deviceId: number; sessionId: string }): Promise<{ pendingRenegotiation: { target: WebrtcStreamTarget; epoch: number } | null }> {\n return this.webrtcServer.getSessionRenegotiationState(input.sessionId)\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 {\n StreamBrokerManager, type ProfileMapPersister,\n type DeviceOverride, DeviceOverridesMapSchema, type DeviceOverridesMap,\n RtspTokensSchema, RtspEnabledSchema, ProfileMapSchema,\n type RtspTokensBlob, type RtspEnabledBlob, type ProfileMapBlob,\n} from './stream-broker/stream-broker-manager'\nimport { CameraStreamsProvider, WebrtcSessionProvider } from './device-scoped-providers'\nimport { BrokerWebrtcServer } from './webrtc/broker-webrtc-server'\nimport type { FfmpegHwAccelSetting } from './stream-broker/ffmpeg-invocation'\nimport { hwAccelSettingToPrefer, pickDecodeHwAccel } from './stream-broker/ffmpeg-invocation'\nimport type { CamProfile } from '@camstack/types'\n\nconst FFMPEG_HW_ACCEL_VALUES: readonly FfmpegHwAccelSetting[] = [\n 'auto', 'none', 'videotoolbox', 'vaapi', 'qsv', 'cuda',\n]\nfunction isFfmpegHwAccel(v: unknown): v is FfmpegHwAccelSetting {\n return typeof v === 'string' && (FFMPEG_HW_ACCEL_VALUES as readonly string[]).includes(v)\n}\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 * How often (seconds) the broker re-pulls every camera's `stream-catalog`\n * to reconcile its cam-stream registry. The backstop for any missed\n * device / stream-params event; primary signal is those events, so a\n * generous cadence keeps the cost negligible.\n */\n readonly catalogReconcileIntervalSec: 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 private catalogReconcileTimer: ReturnType<typeof setInterval> | null = null\n /** Kept so onShutdown can close every live PeerConnection — releasing\n * the ICE UDP sockets. Without this, a graceful runner respawn (deploy)\n * leaks those sockets and wedges the next runner's werift ICE agent\n * (DTLS never connects until a full hub restart). */\n private webrtcServer: BrokerWebrtcServer | 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 catalogReconcileIntervalSec: 30,\n })\n }\n\n /**\n * Read the cluster `ffmpeg` config section (binaryPath / hwAccel /\n * threadCount) and push it into the broker. Defensive: any missing or\n * malformed field falls back to the safe default (PATH `ffmpeg`, `auto`\n * hw-accel, `0` threads), and a read failure leaves the defaults in place.\n */\n private async loadFfmpegConfig(): Promise<void> {\n if (!this.brokerManager) return\n try {\n const section = (await this.ctx.settings?.getSection('ffmpeg')) ?? {}\n const binaryPathRaw = section['binaryPath']\n const binaryPath =\n typeof binaryPathRaw === 'string' && binaryPathRaw.trim().length > 0\n ? binaryPathRaw\n : 'ffmpeg'\n const hwAccel = isFfmpegHwAccel(section['hwAccel']) ? section['hwAccel'] : 'auto'\n const threadCountRaw = section['threadCount']\n const threadCount =\n typeof threadCountRaw === 'number' && Number.isFinite(threadCountRaw) && threadCountRaw >= 0\n ? Math.floor(threadCountRaw)\n : 0\n this.brokerManager.setFfmpegConfig({ binaryPath, hwAccel, threadCount })\n this.ctx.logger.info('Loaded ffmpeg config', { meta: { binaryPath, hwAccel, threadCount } })\n } catch (err) {\n this.ctx.logger.warn('Failed to read ffmpeg config section — using defaults', {\n meta: { error: errMsg(err) },\n })\n }\n }\n\n /**\n * Resolve the decode `-hwaccel` backend for the WebRTC transcode feed: the\n * configured `ffmpeg.hwAccel` preference through `platformProbe.resolveHwAccel`,\n * intersected with what the configured ffmpeg can actually decode\n * (`getHardwareDecodeAccels`). `null` ⇒ software decode. Mirrors the transcode\n * egress's gate; the feed itself falls back to software on a per-stream miss.\n */\n private async resolveTranscodeDecodeHwAccel(): Promise<string | null> {\n try {\n const section = (await this.ctx.settings?.getSection('ffmpeg')) ?? {}\n const hwAccel = isFfmpegHwAccel(section['hwAccel']) ? section['hwAccel'] : 'auto'\n const prefer = hwAccelSettingToPrefer(hwAccel)\n const res = await this.ctx.api.platformProbe.resolveHwAccel.query({ prefer })\n const supported = await this.ctx.api.platformProbe.getHardwareDecodeAccels.query()\n return pickDecodeHwAccel(res.preferred, supported.methods)\n } catch (err) {\n this.ctx.logger.warn('resolveTranscodeDecodeHwAccel failed — software decode', {\n meta: { error: errMsg(err) },\n })\n return null\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 // Load the system `ffmpeg` config (binaryPath / hwAccel / threadCount) so\n // the transcode egress spawns the configured binary with the operator's\n // hw-accel preference. Defaults (PATH ffmpeg / auto / 0) on any miss.\n await this.loadFfmpegConfig()\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 const rtspTokensState = this.state<RtspTokensBlob>('rtspTokens', RtspTokensSchema, {})\n rtspProvider.setTokenPersister(async (tokens) => {\n // MERGE, never replace. `tokens` holds ONLY the brokers currently\n // registered with the listen server; a broker that was later\n // `unregisterRestreamer`-ed disappears from this map. A naive replace\n // would erase its persisted token, turning an external NVR/recording\n // URL into a dead 404 on the next idle/reboot. The contract is\n // \"tokens are stable forever per (deviceId, camStreamId) unless\n // explicitly regenerated\" — `update` reads the durable set and merges.\n try {\n await rtspTokensState.update(prev => {\n const merged: Record<string, string> = { ...prev }\n for (const [k, v] of tokens) merged[k] = v\n return merged\n })\n this.ctx.logger.info('Persisted RTSP tokens', { meta: { current: tokens.size } })\n } catch (err: unknown) {\n this.ctx.logger.warn('Failed to persist RTSP tokens', { meta: { error: errMsg(err) } })\n }\n })\n\n const rtspEnabledState = this.state<RtspEnabledBlob>('rtspEnabled', RtspEnabledSchema, {})\n rtspProvider.setEnabledPersister((states) => {\n const obj: Record<string, boolean> = {}\n for (const [k, v] of states) obj[k] = v\n rtspEnabledState.set(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, debug flags, derived\n // streams) — whole-object durable handle so NO field is ever dropped.\n // The previous hand-rolled persister silently omitted `derivedStreams`,\n // which made derived streams vanish on every restart.\n const deviceOverridesState = this.state<DeviceOverridesMap>('deviceOverrides', DeviceOverridesMapSchema, {})\n try {\n const obj = await deviceOverridesState.get()\n const overrides = new Map<number, DeviceOverride>()\n for (const [rawKey, ov] of Object.entries(obj)) {\n const id = Number(rawKey)\n if (Number.isInteger(id)) overrides.set(id, ov)\n }\n this.brokerManager.loadPersistedDeviceOverrides(overrides)\n this.ctx.logger.info('Loaded persisted device overrides', { meta: { overrideCount: overrides.size } })\n } catch (err) {\n this.ctx.logger.warn('Failed to load device overrides', { meta: { error: errMsg(err) } })\n }\n\n this.brokerManager.setDeviceOverridePersister((overrides) => {\n const obj: DeviceOverridesMap = {}\n for (const [deviceId, ov] of overrides) obj[`${deviceId}`] = ov\n deviceOverridesState.set(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 // through a durable handle (whole-object Zod round-trip).\n const profileMapState = this.state<ProfileMapBlob>('profileMap', ProfileMapSchema, {})\n try {\n const obj = await profileMapState.get()\n const loaded = new Map<number, PersistedAssignment>()\n for (const [rawKey, val] of Object.entries(obj)) {\n const deviceId = Number(rawKey)\n if (!Number.isInteger(deviceId)) continue\n const map: Partial<Record<CamProfile, string>> = {}\n for (const profile of PROFILE_KEYS) {\n const v = val.map[profile]\n if (typeof v === 'string' && v.length > 0) map[profile] = v\n }\n loaded.set(deviceId, { map, auto: val.auto })\n }\n this.brokerManager.loadPersistedProfileMap(loaded)\n this.ctx.logger.info('Loaded persisted profile map', { meta: { deviceCount: loaded.size } })\n } catch (err) {\n this.ctx.logger.warn('Failed to load profile map', { meta: { error: errMsg(err) } })\n }\n\n const profileMapPersister: ProfileMapPersister = (assignments) => {\n const obj: ProfileMapBlob = {}\n for (const [deviceId, assignment] of assignments) {\n obj[`${deviceId}`] = { map: { ...assignment.map }, auto: assignment.auto }\n }\n profileMapState.set(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 // ── Catalog reconcile (PULL) ──────────────────────────────────────\n // The broker is the authority for its cam-stream registry: it PULLS each\n // camera's `stream-catalog` and reconciles, rather than relying on a push.\n // Trigger set: a start-up fetch, a configurable poll (the loss-is-a-bug\n // backstop for any missed event), and low-latency device / stream-params\n // events. The poll cadence is generous so its cost stays negligible; the\n // events make it feel instant.\n const reconcileAll = (reason: string): void => {\n void this.brokerManager?.reconcileAllCatalogs().catch((err: unknown) => {\n this.ctx.logger.warn('catalog reconcile failed', { meta: { reason, error: errMsg(err) } })\n })\n }\n const reconcileDevice = (deviceId: number, reason: string): void => {\n void this.brokerManager?.reconcileDeviceCatalog(deviceId).catch((err: unknown) => {\n this.ctx.logger.warn('device catalog reconcile failed', {\n tags: { deviceId }, meta: { reason, error: errMsg(err) },\n })\n })\n }\n\n await (this.brokerManager?.reconcileAllCatalogsAtStartup().catch((err: unknown) => {\n this.ctx.logger.warn('catalog startup reconcile failed', { meta: { error: errMsg(err) } })\n }) ?? Promise.resolve())\n const reconcileMs = Math.max(5, this.config.catalogReconcileIntervalSec) * 1000\n this.catalogReconcileTimer = setInterval(() => reconcileAll('poll'), reconcileMs)\n\n this.subscribe({ category: EventCategory.DeviceRegistered }, (event) => {\n const deviceId = event.data.deviceId\n if (typeof deviceId === 'number') reconcileDevice(deviceId, 'device-registered')\n })\n this.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {\n const deviceId = event.data.deviceId\n if (typeof deviceId === 'number') {\n void this.brokerManager?.retractDevice(deviceId).catch((err: unknown) => {\n this.ctx.logger.warn('device retract failed', { tags: { deviceId }, meta: { error: errMsg(err) } })\n })\n }\n })\n this.subscribe({ category: EventCategory.StreamParamsChanged }, (event) => {\n const deviceId = event.data.deviceId\n if (typeof deviceId === 'number') reconcileDevice(deviceId, 'stream-params-changed')\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-provider` collection cap. `getTurnServers` is an array-output\n // method on a collection cap, so calling it without an `addonId`\n // hits the auto-mount's generic aggregate — the union of ICE servers\n // from every ENABLED `turn-provider` (cloudflare-turn, future coturn\n // / Twilio, …). There's NO per-device round-trip; the providers\n // cache at their own layer (cloudflare-turn refreshes ICE\n // credentials in the background ~12 h before the 24 h TTL), so the\n // per-session call costs a single cap dispatch. Without TURN\n // configured the call returns `[]` and WebRTC falls back to\n // host/srflx candidates.\n const turnApi = this.ctx.api as unknown as {\n turnProvider?: {\n getTurnServers: { 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.turnProvider) return []\n try {\n const servers = await turnApi.turnProvider.getTurnServers.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 this.ctx.logger.info('broker getIceServers resolved', {\n meta: { count: out.length, urls: out.map((s) => (Array.isArray(s.urls) ? s.urls[0] : s.urls)) },\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('turnProvider.getTurnServers failed — session will start without TURN', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return []\n }\n },\n // Feed the AdaptiveController the device's latest client-reported\n // packet-loss % over async tRPC. The broker runs in its own\n // addon-runner process, so it reads the hub-resident\n // NetworkQualityTracker via `ctx.api.networkQuality.getDeviceStats`\n // (a safe, idempotent query) and synthesizes the monotonic loss sample\n // locally (see BrokerWebrtcServer.startAdaptive). This lets the\n // benchmark \"Network Simulation\" presets drive the downgrade ladder\n // without inducing real RTCP loss. Only polled inside `startAdaptive`,\n // i.e. for `/adaptive` sessions; the RTCP Receiver-Report path\n // (`onReceiverReport`) drives the ladder independently.\n clientLossPct: async (deviceId) => {\n try {\n const stats = await this.ctx.api.networkQuality.getDeviceStats.query({ deviceId })\n return stats?.client?.packetLossPercent ?? null\n } catch {\n return null\n }\n },\n // Decode hw-accel for the in-process transcode feed. Resolved once\n // (cached by the server): the configured `ffmpeg.hwAccel` preference\n // through the platform probe, gated by what the configured ffmpeg can\n // actually decode (`getHardwareDecodeAccels`). The feed falls back to\n // software decode if hw decode produces no output (per-stream fragility).\n resolveTranscodeDecodeHwAccel: () => this.resolveTranscodeDecodeHwAccel(),\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 // Retain for onShutdown teardown (closes PeerConnections → frees ICE sockets).\n this.webrtcServer = 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 tab: 'device-tab',\n label: 'Stream Brokers',\n kind: 'remote' as const,\n remote: {\n remoteName: 'addon_stream_broker_widgets',\n exposedModule: './widgets',\n componentKey: 'stream-broker-panel',\n },\n stableId: 'stream-broker-panel',\n description: 'Per-camera stream broker panel with adaptive controls.',\n icon: 'radio',\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 tab: 'device-tab',\n label: 'FFmpeg Parameters',\n kind: 'remote' as const,\n remote: {\n remoteName: 'addon_stream_broker_widgets',\n exposedModule: './widgets',\n componentKey: 'ffmpeg-params',\n },\n stableId: 'ffmpeg-params',\n description: 'Per-camera FFmpeg encoding parameter editor.',\n icon: 'settings',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'md' as const,\n allowedSizes: ['md', 'lg'] as const,\n defaultColumns: 6,\n defaultRows: 2,\n },\n ],\n }\n\n this.ctx.logger.info('Stream broker manager initialized')\n // `webrtc-session` is a singleton cap. The broker registers its builtin\n // (werift) provider unconditionally; `addon-webrtc-native` (libwebrtc) can\n // register a second provider for the same cap, and the operator selects the\n // active one via the framework's singleton provider preference\n // (capabilities.setPreference → `capabilities.singleton.webrtc-session`),\n // i.e. the system Capabilities UI — no broker-specific setting.\n const registrations: ProviderRegistration[] = [\n { capability: streamBrokerCapability, provider: this.brokerManager },\n {\n capability: cameraStreamsCapability,\n provider: cameraStreamsProvider,\n },\n {\n capability: webrtcSessionCapability,\n provider: webrtcSessionProvider,\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 if (this.catalogReconcileTimer) {\n clearInterval(this.catalogReconcileTimer)\n this.catalogReconcileTimer = null\n }\n // Close the WebRTC server FIRST: it iterates live sessions and closes\n // each PeerConnection, which is the only thing that releases the werift\n // ICE UDP sockets. destroyAll() handles brokers + RTSP but NOT these —\n // leaving them open leaks the sockets past process exit and wedges the\n // next respawned runner's ICE agent (DTLS never connects until a clean\n // hub restart). Wrapped so a teardown error can't block broker cleanup.\n try {\n await this.webrtcServer?.stop()\n } catch (err) {\n this.ctx?.logger?.warn?.('WebRTC server stop failed during shutdown', { meta: { error: errMsg(err) } })\n }\n this.webrtcServer = null\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 type: 'number',\n key: 'catalogReconcileIntervalSec',\n label: 'Catalog Reconcile Interval',\n description: 'How often the broker re-pulls every camera’s stream catalog (reconcile backstop)',\n min: 5,\n max: 300,\n step: 5,\n default: 30,\n unit: 's',\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","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 { spawn, type ChildProcess } from 'node:child_process'\nimport type { IDecoderSession, DecoderSessionConfig, DecoderStats, EncodedPacket, DecodedFrame, Unsubscribe, IScopedLogger } from '@camstack/types'\nimport { FrameDropper } from './frame-dropper'\nimport { logBannerArgs } from './ffmpeg-args-common.js'\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 ...logBannerArgs('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","net","type","parameterSets","AudioSoundFormat","record","messageTypeId","killed","startCode","buildFfmpegArgs","key","z.object","z.boolean","z.number","z.string","z.record","i","tryConvertWithLengthReader","tryConvertWithLengthReader16","tryConvertWithLengthReader24","dnsLookup","console","NAL_TYPE_SPS","NAL_TYPE_PPS","NAL_HEADER_SIZE","LENGTH_FIELD_SIZE","layerId","tid","nalType","fnri","t","names","spawn","path","senderCodec","crypto","entry","c","URL_SCHEME"],"mappings":"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"}