@camstack/addon-pipeline 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/audio-analyzer/index.js +2 -4
  2. package/dist/audio-analyzer/index.js.map +1 -1
  3. package/dist/audio-analyzer/index.mjs +2 -4
  4. package/dist/audio-analyzer/index.mjs.map +1 -1
  5. package/dist/audio-codec-nodeav/index.js +1 -1
  6. package/dist/audio-codec-nodeav/index.mjs +1 -1
  7. package/dist/decoder-nodeav/index.js +552 -18
  8. package/dist/decoder-nodeav/index.js.map +1 -1
  9. package/dist/decoder-nodeav/index.mjs +553 -19
  10. package/dist/decoder-nodeav/index.mjs.map +1 -1
  11. package/dist/detection-pipeline/index.js +2 -4
  12. package/dist/detection-pipeline/index.js.map +1 -1
  13. package/dist/detection-pipeline/index.mjs +2 -4
  14. package/dist/detection-pipeline/index.mjs.map +1 -1
  15. package/dist/{index-DKh0uEve.mjs → index-CVzLrojg.mjs} +539 -97
  16. package/dist/index-CVzLrojg.mjs.map +1 -0
  17. package/dist/{index-CFPKrb2Y.js → index-p-6GfKOg.js} +539 -97
  18. package/dist/index-p-6GfKOg.js.map +1 -0
  19. package/dist/motion-wasm/index.js +2 -4
  20. package/dist/motion-wasm/index.js.map +1 -1
  21. package/dist/motion-wasm/index.mjs +2 -4
  22. package/dist/motion-wasm/index.mjs.map +1 -1
  23. package/dist/pipeline-runner/index.js +133 -54
  24. package/dist/pipeline-runner/index.js.map +1 -1
  25. package/dist/pipeline-runner/index.mjs +133 -54
  26. package/dist/pipeline-runner/index.mjs.map +1 -1
  27. package/dist/stream-broker/@mf-types.zip +0 -0
  28. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +19 -0
  29. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-B4l8Nb2y.mjs +20 -0
  30. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DePVYdid.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DAssX3h0.mjs} +4 -2
  31. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CBlCGyx5.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-DFoJJhpt.mjs} +1 -1
  32. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-DZchZKbW.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-x7XMEeuJ.mjs} +1 -1
  33. package/dist/stream-broker/_stub.js +2 -2
  34. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CqeKw-Ig.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-iA_8b8fz.mjs} +6 -6
  35. package/dist/stream-broker/{client-BK73l2KT.mjs → client-CZXrddDR.mjs} +2990 -3217
  36. package/dist/stream-broker/{hostInit-DkjoXTMb.mjs → hostInit-1sVsS6_a.mjs} +12 -12
  37. package/dist/stream-broker/{index-BP0-1QYT.mjs → index-BCEx31Mh.mjs} +3808 -3100
  38. package/dist/stream-broker/{index-lmXLeXy8.mjs → index-BvV3RVTZ.mjs} +1 -1
  39. package/dist/stream-broker/{index-IUYKHbxX.mjs → index-C0BzaWmB.mjs} +1 -1
  40. package/dist/stream-broker/index-CWkKuNLr.mjs +232 -0
  41. package/dist/stream-broker/{index-ns1fRD30.mjs → index-CZNxa0ad.mjs} +1 -1
  42. package/dist/stream-broker/index-Kb4xa8FX.mjs +36403 -0
  43. package/dist/stream-broker/{index-BxHaCH3N.mjs → index-KtR7Pp0O.mjs} +1 -1
  44. package/dist/stream-broker/{index-Ss9m7Jum.mjs → index-cYW01SNH.mjs} +1 -1
  45. package/dist/stream-broker/index.js +802 -541
  46. package/dist/stream-broker/index.js.map +1 -1
  47. package/dist/stream-broker/index.mjs +802 -519
  48. package/dist/stream-broker/index.mjs.map +1 -1
  49. package/dist/stream-broker/{jsx-runtime-ZdY5pIZz.mjs → jsx-runtime-B_evVsXl.mjs} +1 -1
  50. package/dist/stream-broker/remoteEntry.js +1 -1
  51. package/package.json +23 -31
  52. package/dist/index-CFPKrb2Y.js.map +0 -1
  53. package/dist/index-DKh0uEve.mjs.map +0 -1
  54. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-CpCK52pE.mjs +0 -19
  55. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BN3K4dM8.mjs +0 -20
  56. package/dist/stream-broker/index-DKercbDS.mjs +0 -20855
  57. package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
  58. package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
  59. package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
  60. package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
  61. package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
  62. package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
  63. package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
  64. package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
  65. package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
  66. package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
  67. package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
  68. package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
  69. package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
  70. package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
  71. package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
  72. package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
  73. package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
  74. package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
  75. package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
  76. package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/decoder-nodeav/nodeav-decoder-session.ts","../../src/decoder-nodeav/addon/index.ts"],"sourcesContent":["/**\n * NodeAvDecoderSession — IDecoderSession using node-av **push mode**.\n *\n * Receives raw H.264/H.265 Annex-B packets from the StreamBroker and decodes\n * them in-process using the node-av low-level API:\n *\n * CodecParser → splits raw Annex-B bytes into proper AVPackets\n * CodecContext → software decode (sendPacket / receiveFrame)\n * SoftwareScaleContext → scale + convert to target format\n *\n * Output format depends on config.outputFormat:\n * 'jpeg' → scale to RGB24, encode JPEG via sharp (for detection pipeline)\n * 'gray' → scale to GRAY8 raw buffer (for motion detection only)\n *\n * No child process, no duplicate RTSP connection.\n */\nimport type {\n IDecoderSession, DecoderSessionConfig, DecoderStats,\n EncodedPacket, DecodedFrame, Unsubscribe, IScopedLogger,\n HwAccelBackend, IKernelHwAccel,\n} from '@camstack/types'\nimport type { AVPixelFormat, SwsFlags, AVError, AVHWDeviceType } from 'node-av/constants'\nimport { errMsg } from '@camstack/types'\n\n// Lazy-load node-av to avoid hard dependency at import time\ntype NodeAvModule = typeof import('node-av')\ntype NodeAvConstants = typeof import('node-av/constants')\n\nexport type HwAccelPref = 'auto' | 'none' | HwAccelBackend\n\nexport interface NodeAvDecoderSessionOptions {\n /** Addon-level hwaccel preference — per-agent. Default `'auto'`. */\n readonly hwaccel?: HwAccelPref\n /**\n * Kernel hwaccel resolver — typically `ctx.kernel.hwaccel` passed\n * from the addon. When present, `hwaccel: 'auto'` calls\n * `resolver.resolve(null)` to get the platform-ordered backend\n * list; when absent, `'auto'` degrades to software (the session\n * cannot probe on its own because it lives in a package that must\n * not depend on `@camstack/kernel`).\n */\n readonly hwaccelResolver?: IKernelHwAccel\n}\n\n/** Map our canonical backend name to the node-av `AV_HWDEVICE_TYPE_*` constant. */\nfunction backendToHwDeviceConst(backend: HwAccelBackend, consts: NodeAvConstants): AVHWDeviceType | null {\n switch (backend) {\n case 'videotoolbox': return consts.AV_HWDEVICE_TYPE_VIDEOTOOLBOX\n case 'cuda': return consts.AV_HWDEVICE_TYPE_CUDA\n case 'nvdec': return consts.AV_HWDEVICE_TYPE_CUDA // node-av exposes only CUDA; nvdec aliases to it\n case 'vaapi': return consts.AV_HWDEVICE_TYPE_VAAPI\n case 'qsv': return consts.AV_HWDEVICE_TYPE_QSV\n case 'd3d11va': return consts.AV_HWDEVICE_TYPE_D3D11VA\n case 'dxva2': return consts.AV_HWDEVICE_TYPE_DXVA2\n case 'amf': return consts.AV_HWDEVICE_TYPE_AMF\n case 'vdpau': return consts.AV_HWDEVICE_TYPE_VDPAU\n case 'drm': return consts.AV_HWDEVICE_TYPE_DRM\n default: return null\n }\n}\n\n/** Pick the hw pixel format for a given backend — used in get_format callback. */\nfunction backendToHwPixFmt(backend: HwAccelBackend, consts: NodeAvConstants): AVPixelFormat | null {\n switch (backend) {\n case 'videotoolbox': return consts.AV_PIX_FMT_VIDEOTOOLBOX\n case 'cuda': return consts.AV_PIX_FMT_CUDA\n case 'nvdec': return consts.AV_PIX_FMT_CUDA\n case 'vaapi': return consts.AV_PIX_FMT_VAAPI\n case 'qsv': return consts.AV_PIX_FMT_QSV\n case 'd3d11va': return consts.AV_PIX_FMT_D3D11\n case 'dxva2': return consts.AV_PIX_FMT_DXVA2_VLD\n case 'amf': return consts.AV_PIX_FMT_D3D11\n case 'vdpau': return consts.AV_PIX_FMT_VDPAU\n case 'drm': return consts.AV_PIX_FMT_DRM_PRIME\n default: return null\n }\n}\n\nlet _nav: NodeAvModule | null = null\nlet _consts: NodeAvConstants | null = null\ntype SharpFn = (input: Buffer | Uint8Array, options?: Record<string, unknown>) => import('sharp').Sharp\n\nlet _sharp: SharpFn | null = null\n\nasync function getNodeAv(): Promise<NodeAvModule> {\n if (!_nav) _nav = await import('node-av')\n return _nav\n}\nasync function getConstants(): Promise<NodeAvConstants> {\n if (!_consts) _consts = await import('node-av/constants')\n return _consts\n}\nasync function getSharp(): Promise<SharpFn> {\n if (!_sharp) {\n const mod = await import('sharp')\n _sharp = mod.default as SharpFn\n }\n return _sharp\n}\n\nconst noopLogger: IScopedLogger = {\n debug() {}, info() {}, warn() {}, error() {},\n child() { return noopLogger },\n withTags() { return noopLogger },\n}\n\nexport class NodeAvDecoderSession implements IDecoderSession {\n private config: DecoderSessionConfig\n private readonly logger: IScopedLogger\n private frameCallbacks = new Set<(frame: DecodedFrame) => void>()\n private destroyed = false\n\n // Low-level node-av objects (initialized on first keyframe)\n private parser: InstanceType<NodeAvModule['CodecParser']> | null = null\n private codecCtx: InstanceType<NodeAvModule['CodecContext']> | null = null\n private scaler: InstanceType<NodeAvModule['SoftwareScaleContext']> | null = null\n private avPacket: InstanceType<NodeAvModule['Packet']> | null = null\n private avFrame: InstanceType<NodeAvModule['Frame']> | null = null\n private dstFrame: InstanceType<NodeAvModule['Frame']> | null = null\n\n // Cached constants (loaded during init, used in hot path)\n // Hard-coded numeric defaults are replaced in initDecoder() with the real\n // branded constants from `node-av/constants`. The `as` cast at the literal\n // is a one-time branding bridge — once initDecoder runs we hold the exact\n // AVPixelFormat/SwsFlags/AVError values.\n private EAGAIN: AVError = -11 as AVError\n private PIX_FMT_GRAY8: AVPixelFormat = 8 as AVPixelFormat\n private PIX_FMT_RGB24: AVPixelFormat = 2 as AVPixelFormat\n private SWS_FAST_BILINEAR: SwsFlags = 1 as SwsFlags\n\n /**\n * Decoder output mode. Drives both the scaler's destination pixel\n * format and whether sharp runs the JPEG encode at the end:\n *\n * - `'jpeg'` — scaler→RGB24 → sharp encode → emit JPEG bytes\n * - `'rgb'` — scaler→RGB24 → emit raw RGB24 (no sharp)\n * - `'gray'` — scaler→GRAY8 → emit raw GRAY8 (no sharp)\n *\n * The broker holds the policy decision on which mode to request based\n * on its active subscribers; on-the-fly conversion (e.g. RGB→JPEG for\n * a WebRTC consumer that joined while detection holds the decoder in\n * RGB mode) happens broker-side via the per-frame conversion cache.\n */\n private outputMode: 'jpeg' | 'rgb' | 'gray'\n private sharpFn: SharpFn | null = null\n\n /**\n * Backpressure for the sharp JPEG encode pipeline. The broker\n * currently creates sessions with `maxFps: 0` (unlimited) and relies\n * on per-subscriber throttling, so without a bound the\n * fire-and-forget `sharp(...).toBuffer()` chain would accumulate\n * unboundedly whenever sharp falls behind the decoder. Cap at\n * `MAX_JPEG_INFLIGHT` pending encodes per session — any frame that\n * arrives while the cap is saturated is dropped and counted.\n */\n private static readonly MAX_JPEG_INFLIGHT = 2\n private jpegEncodeInFlight = 0\n\n /**\n * Map a `DecoderSessionConfig.outputFormat` value to one of the three\n * native scaler/encoder modes the session understands. The cap-level\n * format vocabulary is broader (it accepts `bgr`, `yuv420`) than what\n * libav's scaler is wired for here — anything else degrades to RGB\n * (the canonical raw mode) and the broker is expected to convert\n * downstream if a subscriber needs a different shape.\n */\n private static resolveOutputMode(format: string | undefined): 'jpeg' | 'rgb' | 'gray' {\n if (format === 'jpeg' || format === undefined) return 'jpeg'\n if (format === 'gray') return 'gray'\n return 'rgb'\n }\n\n private initialized = false\n private initializing = false\n private scalerInitializing = false\n /**\n * Monotonic counter incremented by `updateConfig` whenever the\n * scaler + dstFrame get invalidated (e.g. output format toggle).\n * `initScaler` captures the current value at entry and aborts — or\n * disposes the locally-built scaler — if the epoch moved while\n * its async init was in flight. Without this, a toggle racing an\n * in-flight init could leave two scalers allocated natively while\n * `this.scaler` only holds a reference to one → libav leak.\n */\n private scalerEpoch = 0\n /**\n * One-shot guard for the \"first frame\" diagnostic log + raw frame\n * dump. Setting this synchronously inside `emitDecodedFrame`\n * prevents re-entry — without it we were using `outputFrames === 0`\n * which stays true until the async sharp encode callback runs, so\n * several decoded frames could trigger the dump before the first\n * JPEG landed.\n */\n private firstFrameLogged = false\n\n // Output dimensions\n private outWidth = 0\n private outHeight = 0\n\n // FPS limiter\n private lastEmitTime = 0\n private minIntervalMs: number\n\n // Stats\n private inputPackets = 0\n private outputFrames = 0\n private droppedFrames = 0\n private totalDecodeTimeMs = 0\n private startTime = Date.now()\n\n private readonly hwaccelPref: HwAccelPref\n private readonly hwaccelResolver: IKernelHwAccel | null\n /** The backend that actually initialised successfully — `'none'` = software fallback. */\n private activeHwAccel: 'none' | HwAccelBackend = 'none'\n private hwDevice: InstanceType<NodeAvModule['HardwareDeviceContext']> | null = null\n private swTransferFrame: InstanceType<NodeAvModule['Frame']> | null = null\n\n constructor(\n config: DecoderSessionConfig,\n logger: IScopedLogger = noopLogger,\n options?: NodeAvDecoderSessionOptions,\n ) {\n this.config = { ...config }\n // Bind per-session tags so every warn/error from this decoder lands\n // with `deviceId` + `tag` populated — required for filtering log\n // bursts to a single misbehaving camera (e.g. node-av sendPacket\n // errors on one Reolink in a fleet of 8).\n const sessionTags: Record<string, string | number> = {}\n if (typeof config.deviceId === 'number') sessionTags['deviceId'] = config.deviceId\n if (typeof config.tag === 'string' && config.tag.length > 0) sessionTags['tag'] = config.tag\n this.logger = Object.keys(sessionTags).length > 0 ? logger.withTags(sessionTags) : logger\n this.minIntervalMs = config.maxFps > 0 ? 1000 / config.maxFps : 0\n this.outputMode = NodeAvDecoderSession.resolveOutputMode(config.outputFormat)\n this.hwaccelPref = options?.hwaccel ?? 'auto'\n this.hwaccelResolver = options?.hwaccelResolver ?? null\n }\n\n /**\n * Resolve the backend preference list and try each one against\n * node-av's HW context APIs. The first backend whose\n * `HardwareDeviceContext.create()` succeeds gets attached to\n * `codecCtx.hwDeviceCtx` + its hw pixel format registered via\n * `setHardwarePixelFormat`. On any failure, falls through to the\n * next backend; if all fail, returns with `activeHwAccel='none'`\n * and the decoder runs in software on the same context.\n */\n private async tryAttachHwAccel(nav: NodeAvModule, C: NodeAvConstants): Promise<void> {\n if (!this.codecCtx) return\n if (this.hwaccelPref === 'none') {\n this.activeHwAccel = 'none'\n return\n }\n\n // Resolve the ordered backend list. Explicit override → single-entry\n // list. `'auto'` → ask the kernel resolver running on this same\n // process (same hardware as the decoder). No resolver wired →\n // software fallback (no probe available in the session package).\n const explicit: HwAccelBackend | null =\n this.hwaccelPref === 'auto' ? null : this.hwaccelPref\n const resolution = this.hwaccelResolver\n ? await this.hwaccelResolver.resolve(explicit)\n : explicit\n ? { preferred: [explicit] as readonly HwAccelBackend[], rationale: 'explicit (no resolver)' }\n : { preferred: [] as readonly HwAccelBackend[], rationale: 'auto + no resolver → software' }\n\n if (resolution.preferred.length === 0) {\n this.activeHwAccel = 'none'\n return\n }\n\n for (const backend of resolution.preferred) {\n const deviceType = backendToHwDeviceConst(backend, C)\n const hwPixFmt = backendToHwPixFmt(backend, C)\n if (!deviceType || !hwPixFmt) continue\n\n const device = new nav.HardwareDeviceContext()\n const rc = device.create(deviceType)\n if (rc < 0) {\n this.logger.warn('node-av: hwaccel device create failed — trying next', {\n meta: { backend, rc },\n })\n device.free()\n continue\n }\n try {\n this.codecCtx.hwDeviceCtx = device\n this.codecCtx.setHardwarePixelFormat(hwPixFmt)\n } catch (err) {\n this.logger.warn('node-av: hwaccel context attach failed — trying next', {\n meta: { backend, error: errMsg(err) },\n })\n device.free()\n continue\n }\n this.hwDevice = device\n this.activeHwAccel = backend\n return\n }\n // Every preferred backend failed — log once and fall through to sw.\n this.logger.warn('node-av: no hwaccel backend initialised — using software', {\n meta: { attempted: resolution.preferred.join(','), rationale: resolution.rationale },\n })\n this.activeHwAccel = 'none'\n }\n\n /**\n * Download a HW frame (format == hw pix fmt) into a SW frame so the\n * rest of the pipeline (scaler, JPEG encoder, grayscale passthrough)\n * handles it identically to the pure-software path. Uses the sync\n * variant so the synchronous receive loop below doesn't need to be\n * async-ified. Returns `null` on transfer failure, meaning the\n * caller should drop the frame.\n */\n private transferHwFrame(hwFrame: InstanceType<NodeAvModule['Frame']>): InstanceType<NodeAvModule['Frame']> | null {\n if (this.activeHwAccel === 'none' || !this.swTransferFrame) return hwFrame\n const rc = hwFrame.hwframeTransferDataSync(this.swTransferFrame, 0)\n if (rc < 0) {\n this.logger.warn('node-av: hwframeTransferData failed', { meta: { rc } })\n return null\n }\n return this.swTransferFrame\n }\n\n /**\n * Initialize the decoder pipeline on the first keyframe.\n * After this returns, all hot-path methods are fully synchronous (except JPEG encode).\n */\n private async initDecoder(): Promise<void> {\n if (this.initialized || this.initializing || this.destroyed) return\n this.initializing = true\n\n try {\n const nav = await getNodeAv()\n const C = await getConstants()\n\n // Cache constants for hot path (branded AVPixelFormat/SwsFlags/AVError)\n this.EAGAIN = C.AVERROR_EAGAIN\n this.PIX_FMT_GRAY8 = C.AV_PIX_FMT_GRAY8\n this.PIX_FMT_RGB24 = C.AV_PIX_FMT_RGB24\n this.SWS_FAST_BILINEAR = C.SWS_FAST_BILINEAR\n\n // Pre-load sharp for JPEG encoding if needed\n if (this.outputMode === 'jpeg') {\n this.sharpFn = await getSharp()\n }\n\n // Suppress noisy FFmpeg logs — HEVC decoder emits ERROR-level\n // \"Could not find ref with POC\" on every missed reference frame.\n // AV_LOG_FATAL silences these while keeping genuine fatal errors.\n nav.Log.setLevel(C.AV_LOG_FATAL)\n\n // Determine codec\n const isHevc = this.config.codec === 'h265' || this.config.codec === 'hevc'\n const codecId = isHevc ? C.AV_CODEC_ID_HEVC : C.AV_CODEC_ID_H264\n\n // 1. Parser — splits raw Annex-B bytes into proper AVPackets\n this.parser = new nav.CodecParser()\n this.parser.init(codecId)\n\n // 2. Codec context — software decode\n const codec = nav.Codec.findDecoder(codecId)\n if (!codec) {\n this.logger.error('node-av: no decoder found', { meta: { codec: this.config.codec } })\n return\n }\n\n this.codecCtx = new nav.CodecContext()\n this.codecCtx.allocContext3(codec)\n\n if (this.config.width && this.config.height) {\n this.codecCtx.width = this.config.width\n this.codecCtx.height = this.config.height\n }\n\n this.codecCtx.threadCount = 1\n\n // Try HW accel backends in preference order. Any failure (device\n // create, hw_pix_fmt missing, open2 non-zero) falls through to\n // software decode on the same CodecContext — swFrame transfer is\n // a no-op when activeHwAccel stays 'none'.\n await this.tryAttachHwAccel(nav, C)\n\n const ret = await this.codecCtx.open2(codec)\n if (ret < 0) {\n // If HW attach tainted the context, reset and retry in software.\n if (this.activeHwAccel !== 'none') {\n this.logger.warn('node-av: open2 failed with hwaccel — retrying in software', {\n meta: { ret, hwAccel: this.activeHwAccel },\n })\n this.hwDevice?.free()\n this.hwDevice = null\n this.activeHwAccel = 'none'\n this.codecCtx = new nav.CodecContext()\n this.codecCtx.allocContext3(codec)\n if (this.config.width && this.config.height) {\n this.codecCtx.width = this.config.width\n this.codecCtx.height = this.config.height\n }\n this.codecCtx.threadCount = 1\n const retry = await this.codecCtx.open2(codec)\n if (retry < 0) {\n this.logger.error('node-av: failed to open decoder (sw fallback)', { meta: { ret: retry } })\n return\n }\n } else {\n this.logger.error('node-av: failed to open decoder', { meta: { ret } })\n return\n }\n }\n\n // Pre-allocate the sw transfer frame once when running HW — every\n // decoded frame lands on `avFrame` in hw memory, gets copied into\n // `swTransferFrame` via `hwframeTransferData`, and then feeds the\n // scaler just like the pure-software path.\n if (this.activeHwAccel !== 'none') {\n this.swTransferFrame = new nav.Frame()\n this.swTransferFrame.alloc()\n }\n\n // 3. Reusable packet and frame objects\n this.avPacket = new nav.Packet()\n this.avPacket.alloc()\n\n this.avFrame = new nav.Frame()\n this.avFrame.alloc()\n\n this.initialized = true\n this.logger.info('node-av push decoder initialized', {\n meta: {\n codec: this.config.codec,\n output: this.outputMode,\n // Reports the backend that actually succeeded at\n // `open2(codec)` with `hwDeviceCtx` attached, or `'none'` if\n // we fell back to software (explicit `hwaccel: 'none'`\n // override, empty resolver output, or every attempted\n // backend failed init).\n hwAccel: this.activeHwAccel,\n },\n })\n } catch (err) {\n this.logger.error('node-av init error', { meta: { error: errMsg(err) } })\n } finally {\n this.initializing = false\n }\n }\n\n /**\n * Initialize the scaler after the first frame tells us the actual\n * dimensions. Output pixel format: RGB24 for JPEG encoding, GRAY8\n * for raw motion.\n *\n * Builds `scaler` + `dstFrame` on local variables and publishes\n * them onto `this` in a single atomic step at the end. Captures\n * `scalerEpoch` at entry; if `updateConfig` invalidated the scaler\n * while this init was in flight (epoch mismatch), the locally\n * built pair is disposed and discarded so the later init wins.\n * Without the local-first approach, partial state (scaler set,\n * dstFrame still null) could be observed by a concurrent\n * `emitDecodedFrame` call.\n */\n private async initScaler(srcW: number, srcH: number, srcFmt: number): Promise<void> {\n if (this.scalerInitializing) return\n this.scalerInitializing = true\n const myEpoch = this.scalerEpoch\n\n let localScaler: InstanceType<NodeAvModule['SoftwareScaleContext']> | null = null\n let localDstFrame: InstanceType<NodeAvModule['Frame']> | null = null\n\n try {\n const nav = await getNodeAv()\n\n if (this.destroyed || myEpoch !== this.scalerEpoch) return\n\n const scale = this.config.scale > 1 ? this.config.scale : 1\n const maxW = Math.floor(640 / scale)\n const outWidth = Math.min(srcW, maxW)\n const outHeight = Math.round(outWidth * srcH / srcW)\n\n // RGB / JPEG modes: scaler outputs RGB24 (sharp may consume that\n // for the JPEG encode). Gray mode: scaler outputs GRAY8 directly\n // for the WASM motion path.\n const dstFmt = this.outputMode === 'gray' ? this.PIX_FMT_GRAY8 : this.PIX_FMT_RGB24\n const fmtName = this.outputMode === 'gray' ? 'gray8' : 'rgb24'\n\n localScaler = new nav.SoftwareScaleContext()\n localScaler.getContext(\n srcW, srcH, srcFmt as AVPixelFormat,\n outWidth, outHeight, dstFmt,\n this.SWS_FAST_BILINEAR,\n )\n const ret = localScaler.initContext()\n if (ret < 0) {\n this.logger.error('node-av: sws_init_context failed', { meta: { ret } })\n return\n }\n\n localDstFrame = new nav.Frame()\n localDstFrame.alloc()\n localDstFrame.width = outWidth\n localDstFrame.height = outHeight\n localDstFrame.format = dstFmt\n const allocRet = localDstFrame.allocBuffer()\n if (allocRet < 0) {\n this.logger.error('node-av: dst frame allocBuffer failed', { meta: { ret: allocRet } })\n return\n }\n\n // Epoch check right before publishing — if updateConfig bumped\n // the epoch while we were doing async work, discard the local\n // pair and let the subsequent initScaler (or none) produce the\n // winning scaler.\n if (this.destroyed || myEpoch !== this.scalerEpoch) return\n\n // Paranoia: dispose any prior scaler/dstFrame that might have\n // slipped in despite the epoch guard. Normal case: both null.\n this.scaler?.[Symbol.dispose]?.()\n this.dstFrame?.[Symbol.dispose]?.()\n\n this.scaler = localScaler\n this.dstFrame = localDstFrame\n this.outWidth = outWidth\n this.outHeight = outHeight\n localScaler = null\n localDstFrame = null\n\n this.logger.info('node-av scaler initialized', {\n meta: { srcWidth: srcW, srcHeight: srcH, outWidth, outHeight, format: fmtName },\n })\n } catch (err) {\n this.logger.error('Scaler init failed', { meta: { error: errMsg(err) } })\n } finally {\n // Dispose whatever wasn't successfully published.\n localScaler?.[Symbol.dispose]?.()\n localDstFrame?.[Symbol.dispose]?.()\n this.scalerInitializing = false\n }\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (this.destroyed) return\n this.inputPackets++\n\n if (!this.initialized && !this.initializing && packet.keyframe) {\n this.initDecoder().then(() => {\n if (this.initialized) this.decodeRawData(packet.data, packet.pts ?? Date.now())\n }).catch((err) => {\n this.logger.error('node-av init failed', { meta: { error: errMsg(err) } })\n })\n return\n }\n\n if (!this.initialized) return\n this.decodeRawData(packet.data, packet.pts ?? Date.now())\n }\n\n private decodeRawData(data: Buffer | Uint8Array, pts: number): void {\n if (!this.parser || !this.codecCtx || !this.avPacket || !this.avFrame) return\n\n const buf = Buffer.isBuffer(data) ? data : Buffer.from(data)\n const bigPts = BigInt(pts)\n let offset = 0\n\n while (offset < buf.length) {\n const remaining = buf.subarray(offset)\n const consumed = this.parser.parse2(\n this.codecCtx,\n this.avPacket,\n remaining,\n bigPts, bigPts,\n offset,\n )\n\n if (consumed < 0) {\n this.logger.warn('node-av parser error', { meta: { ret: consumed } })\n break\n }\n\n offset += consumed\n\n if (this.avPacket.size > 0) {\n this.decodePacket()\n this.avPacket.unref()\n }\n }\n }\n\n private decodePacket(): void {\n if (!this.codecCtx || !this.avFrame) return\n\n const sendRet = this.codecCtx.sendPacketSync(this.avPacket!)\n if (sendRet < 0 && sendRet !== this.EAGAIN) {\n this.logger.warn('node-av sendPacket error', { meta: { ret: sendRet } })\n return\n }\n\n while (true) {\n const recvRet = this.codecCtx.receiveFrameSync(this.avFrame)\n if (recvRet < 0) break\n\n // Pull HW frame into system memory when hwaccel is active. For\n // software decode the helper is a no-op and returns the input.\n const frame = this.transferHwFrame(this.avFrame)\n if (!frame) continue\n\n this.emitDecodedFrame(frame)\n }\n }\n\n private emitDecodedFrame(frame: InstanceType<NodeAvModule['Frame']>): void {\n const now = performance.now()\n\n if (this.minIntervalMs > 0 && now - this.lastEmitTime < this.minIntervalMs) {\n this.droppedFrames++\n return\n }\n\n // Lazy-init scaler on first decoded frame\n if (!this.scaler && !this.scalerInitializing) {\n this.initScaler(frame.width, frame.height, frame.format as number)\n return\n }\n\n if (!this.dstFrame || !this.scaler) return\n\n const decodeStart = performance.now()\n\n try {\n this.dstFrame.makeWritable()\n this.scaler.scaleFrameSync(this.dstFrame, frame)\n } catch (err) {\n this.logger.warn('node-av scale error', { meta: { error: errMsg(err) } })\n return\n }\n\n // Log frame dimensions once + dump first frame for diagnosis.\n // Uses a sync-settable flag so the async JPEG encode doesn't let\n // multiple frames all pass the \"first frame\" guard.\n if (!this.firstFrameLogged) {\n this.firstFrameLogged = true\n const channels = this.outputMode === 'gray' ? 1 : 3\n const ls = this.dstFrame.linesize\n const buf = this.dstFrame.toBuffer()\n this.logger.info('dstFrame after scale', {\n meta: {\n phase: 'frame-debug',\n width: this.dstFrame.width,\n height: this.dstFrame.height,\n linesize: [ls[0], ls[1], ls[2]],\n expectedStride: this.dstFrame.width * channels,\n bufLen: buf.length,\n expectedPacked: this.dstFrame.width * channels * this.dstFrame.height,\n srcFormat: frame.format ?? '?',\n },\n })\n\n // Dump first raw frame for manual inspection (only when scaler\n // is producing RGB — gray-only mode skips the dump because the\n // existing debug fixture expects RGB24).\n if (this.outputMode !== 'gray') {\n import('node:fs').then(fsModule => {\n import('node:path').then(pathModule => {\n const dumpPath = pathModule.join(process.cwd(), 'camstack-data', 'debug-frame-rgb24.raw')\n fsModule.writeFileSync(dumpPath, buf)\n this.logger.info('Dumped first RGB24 frame', { meta: { phase: 'frame-debug', path: dumpPath, bytes: buf.length } })\n }).catch(() => { /* ignore */ })\n }).catch(() => { /* ignore */ })\n }\n }\n\n // Extract packed pixel data — dstFrame.toBuffer() may include linesize padding\n // that sharp/WASM consumers don't expect. Strip padding if linesize > expected stride.\n const rawBuf = this.extractPackedBuffer(this.dstFrame)\n\n if (this.outputMode === 'jpeg') {\n // Async JPEG encode via sharp — fire and forget per frame\n this.encodeAndEmitJpeg(rawBuf, decodeStart)\n } else if (this.outputMode === 'rgb') {\n // Raw RGB24 fast path — no sharp encode, no async hop. Detection\n // (and any other raw consumer) gets the buffer straight.\n this.emitRawFrame(rawBuf, 'rgb', decodeStart)\n } else {\n this.emitRawFrame(rawBuf, 'gray', decodeStart)\n }\n }\n\n /**\n * Extract packed pixel buffer from a decoded frame.\n * FFmpeg's av_frame_get_buffer() may pad each row to alignment (32/64 bytes).\n * Sharp and WASM consumers expect tightly-packed rows (stride = width * channels).\n * If linesize matches expected stride, return the buffer directly (zero-copy).\n */\n private extractPackedBuffer(frame: { toBuffer: () => Buffer; data: ReadonlyArray<Buffer> | null; linesize: ReadonlyArray<number>; width: number; height: number }): Buffer {\n const channels = this.outputMode === 'gray' ? 1 : 3 // RGB24=3, GRAY8=1\n const expectedStride = frame.width * channels\n const actualStride = frame.linesize[0] ?? expectedStride\n\n // Use frame.data[0] (the actual plane buffer) instead of toBuffer()\n // because toBuffer() may return stale data after scaleFrameSync()\n const src = frame.data?.[0]\n if (!src) {\n return frame.toBuffer()\n }\n\n if (actualStride === expectedStride) {\n // No padding — but data[0] may be larger than needed (allocated with padding)\n // Copy only the packed data\n return Buffer.from(src.buffer, src.byteOffset, expectedStride * frame.height)\n }\n\n // Linesize has padding — copy row by row to strip it\n const dst = Buffer.allocUnsafe(expectedStride * frame.height)\n for (let y = 0; y < frame.height; y++) {\n src.copy(dst, y * expectedStride, y * actualStride, y * actualStride + expectedStride)\n }\n return dst\n }\n\n /**\n * Encode RGB24 raw buffer as JPEG and emit.\n *\n * Drops the frame (and counts it) when `MAX_JPEG_INFLIGHT` encodes\n * are already pending — prevents unbounded growth of the\n * fire-and-forget promise chain when sharp cannot keep up with the\n * decode rate.\n */\n private encodeAndEmitJpeg(rgb: Buffer, decodeStart: number): void {\n if (!this.sharpFn) return\n if (this.jpegEncodeInFlight >= NodeAvDecoderSession.MAX_JPEG_INFLIGHT) {\n this.droppedFrames++\n return\n }\n\n this.jpegEncodeInFlight++\n this.sharpFn(rgb, {\n raw: { width: this.outWidth, height: this.outHeight, channels: 3 },\n })\n .jpeg({ quality: 80, mozjpeg: false })\n .toBuffer()\n .then((jpegBuf) => {\n if (this.destroyed) return\n this.emitRawFrame(jpegBuf, 'jpeg', decodeStart)\n })\n .catch((err: unknown) => {\n this.logger.warn('sharp jpeg encode error', { meta: { error: errMsg(err) } })\n })\n .finally(() => {\n this.jpegEncodeInFlight--\n })\n }\n\n private emitRawFrame(data: Buffer, format: DecodedFrame['format'], decodeStart: number): void {\n const decodeMs = performance.now() - decodeStart\n this.totalDecodeTimeMs += decodeMs\n\n this.outputFrames++\n this.lastEmitTime = performance.now()\n\n if (this.outputFrames === 1 || this.outputFrames % 500 === 0) {\n this.logger.info('node-av frame emitted', {\n meta: {\n frameNumber: this.outputFrames,\n width: this.outWidth,\n height: this.outHeight,\n format,\n decodeMs,\n subs: this.frameCallbacks.size,\n },\n })\n }\n\n const decodedFrame: DecodedFrame = {\n data,\n width: this.outWidth,\n height: this.outHeight,\n format,\n timestamp: Date.now(),\n }\n\n for (const cb of this.frameCallbacks) {\n cb(decodedFrame)\n }\n }\n\n onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe {\n this.frameCallbacks.add(callback)\n return () => { this.frameCallbacks.delete(callback) }\n }\n\n updateConfig(update: Partial<DecoderSessionConfig>): void {\n const prevFormat = this.config.outputFormat\n this.config = { ...this.config, ...update }\n if (update.maxFps !== undefined) {\n this.minIntervalMs = update.maxFps > 0 ? 1000 / update.maxFps : 0\n }\n // Reinitialize scaler when output format changes (e.g. gray → jpeg upgrade).\n // Bumping `scalerEpoch` causes any initScaler in flight to\n // dispose its locally-built scaler + dstFrame instead of\n // publishing them — prevents a double-init leak when toggles\n // race the async init path.\n if (update.outputFormat !== undefined && update.outputFormat !== prevFormat) {\n const prevMode = this.outputMode\n this.outputMode = NodeAvDecoderSession.resolveOutputMode(update.outputFormat)\n // Skip the scaler reinit when only the cap-level format label\n // changed but the underlying scaler mode (jpeg/rgb/gray) is the\n // same — saves one libav teardown + reallocation per swap.\n if (this.outputMode === prevMode) return\n this.scalerEpoch++\n if (this.scaler) {\n this.scaler[Symbol.dispose]?.()\n this.scaler = null\n }\n if (this.dstFrame) {\n this.dstFrame[Symbol.dispose]?.()\n this.dstFrame = null\n }\n // Re-load sharp if switching to jpeg\n if (this.outputMode === 'jpeg' && !this.sharpFn) {\n getSharp().then(fn => { this.sharpFn = fn }).catch(() => {})\n }\n this.logger.info('node-av: output format changed — scaler will reinit', {\n meta: { from: prevFormat, to: update.outputFormat, mode: this.outputMode },\n })\n }\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n this.frameCallbacks.clear()\n\n this.dstFrame?.[Symbol.dispose]?.()\n this.avFrame?.[Symbol.dispose]?.()\n this.avPacket?.[Symbol.dispose]?.()\n this.scaler?.[Symbol.dispose]?.()\n this.parser?.[Symbol.dispose]?.()\n this.swTransferFrame?.[Symbol.dispose]?.()\n this.codecCtx?.[Symbol.dispose]?.()\n this.hwDevice?.free()\n\n this.dstFrame = null\n this.avFrame = null\n this.avPacket = null\n this.scaler = null\n this.parser = null\n this.codecCtx = null\n this.swTransferFrame = null\n this.hwDevice = null\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.outputFrames > 0 ? this.totalDecodeTimeMs / this.outputFrames : 0,\n droppedFrames: this.droppedFrames,\n }\n }\n\n get isPullMode(): boolean { return false }\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n DecoderHwAccelConfig,\n HwAccelChoice,\n ProviderRegistration,\n IDecoderSession,\n DecoderStats,\n IDecoderCapProvider,\n FrameFormat,\n} from '@camstack/types'\nimport {\n BaseAddon,\n DEFAULT_DECODER_HWACCEL_CONFIG,\n HWACCEL_OPTIONS,\n decoderCapability,\n RingBuffer,\n} from '@camstack/types'\nimport { NodeAvDecoderSession } from '../nodeav-decoder-session.js'\n\n/**\n * Phase 2d of the pipeline-settings migration — decoder addon now\n * owns its own `hwaccel` choice + `probedBestHwaccel` hint. Sessions\n * resolve the effective backend from this addon's global settings\n * instead of the orchestrator's legacy `AgentPipelineSettings.hwaccel`.\n * Shared config shape + UI options live in `@camstack/types`\n * (`DecoderHwAccelConfig` / `HWACCEL_OPTIONS`) so every decoder addon\n * speaks the same vocabulary.\n */\n\n/** Cap-compatible frame shape — format must match DecodedFrameSchema enum values. */\ntype CapDecodedFrame = {\n data: Uint8Array<ArrayBuffer>\n width: number\n height: number\n format: FrameFormat\n timestamp: number\n}\n\n/** Cap-compatible session config — matches DecoderSessionConfigSchema output type. */\ntype CapDecoderSessionConfig = {\n codec: string\n maxFps: number\n outputFormat: FrameFormat\n scale: number\n width?: number\n height?: number\n}\n\n/** Cap-compatible encoded packet — data is Uint8Array matching EncodedPacketSchema. */\ntype CapEncodedPacket = {\n type: 'video' | 'audio'\n data: Uint8Array<ArrayBuffer>\n pts: number\n dts: number\n keyframe: boolean\n codec: string\n}\n\nconst FRAME_BUFFER_CAPACITY = 32\n\n/**\n * Decoder addon using node-av (FFmpeg native bindings).\n *\n * Hardware-accelerated decode via VideoToolbox (macOS), VAAPI (Linux), CUDA (NVIDIA).\n * Outputs grayscale frames for motion detection — zero JPEG overhead.\n *\n * Implements the sessionId-based IDecoderCapProvider cap interface.\n * Sessions are managed internally via a Map; frames are polled via RingBuffer.\n */\n/** Per-session metadata recorded at creation time, surfaced via `listActiveSessions`. */\ninterface SessionMeta {\n readonly codec: string\n readonly outputFormat: string\n readonly createdAtMs: number\n}\n\nexport default class DecoderNodeAvAddon extends BaseAddon<DecoderHwAccelConfig> implements IDecoderCapProvider {\n private readonly sessions = new Map<string, IDecoderSession>()\n private readonly frameBuffers = new Map<string, RingBuffer<CapDecodedFrame>>()\n private readonly unsubscribers = new Map<string, () => void>()\n private readonly sessionMeta = new Map<string, SessionMeta>()\n\n constructor() { super(DEFAULT_DECODER_HWACCEL_CONFIG) }\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [{\n id: 'hwaccel',\n title: 'Hardware acceleration',\n tab: 'decoder',\n description: 'Backend used by node-av decoder sessions. \"Auto\" defers to the probed best; concrete backends force it. Changes apply to NEW sessions — existing sessions keep the backend they were created with.',\n fields: [\n this.field({\n type: 'select',\n key: 'hwaccel',\n label: 'Preferred backend',\n options: [...HWACCEL_OPTIONS],\n default: 'auto',\n immediate: true,\n }),\n this.field({\n type: 'text',\n key: 'probedBestHwaccel',\n label: 'Probed best',\n description: 'Auto-detected best decoder backend on this host. Click the refresh icon to re-run the probe.',\n readonlyField: true,\n default: '',\n actions: [\n { action: 'reprobe-hwaccel', icon: 'refresh-cw', tooltip: 'Re-probe hwaccel' },\n ],\n }),\n ],\n }],\n })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.ctx.logger.info('node-av decoder addon initialized')\n // Auto-seed probedBestHwaccel on first boot so the UI can show\n // the node's best backend without waiting for an operator click.\n if (!this.config.probedBestHwaccel) {\n this.reprobeHwaccel().catch((err: unknown) => {\n this.ctx.logger.warn('nodeav: auto-reprobe hwaccel failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n return [{ capability: decoderCapability, provider: this }]\n }\n\n /**\n * Resolve the effective hwaccel backend for a new session. Reads\n * this addon's own `hwaccel` setting first. `'auto'` defers to the\n * session's local resolver (`ctx.kernel.hwaccel`) which probes the\n * host and picks. No more orchestrator round-trip — decoder addon\n * is self-sufficient for this setting as of phase 2d.\n */\n private resolveHwAccelPref(): HwAccelChoice {\n return this.config.hwaccel\n }\n\n /**\n * Re-run the platform probe on this host and persist the detected\n * backend as `probedBestHwaccel`. The operator's `hwaccel` setting\n * is intentionally left alone — the probe only updates the hint.\n */\n async reprobeHwaccel(): Promise<{ backend: string }> {\n const resolver = this.ctx.kernel.hwaccel\n if (!resolver) {\n this.ctx.logger.warn('reprobeHwaccel: no kernel hwaccel resolver — returning none')\n await this.ctx.settings?.writeAddonStore({ probedBestHwaccel: 'none' })\n return { backend: 'none' }\n }\n try {\n const res = await resolver.resolve()\n const backend = (res.preferred[0] ?? 'none') as string\n await this.ctx.settings?.writeAddonStore({ probedBestHwaccel: backend })\n this.ctx.logger.info('reprobeHwaccel: wrote probedBestHwaccel', {\n meta: { backend, rationale: res.rationale, preferred: res.preferred },\n })\n return { backend }\n } catch (err) {\n this.ctx.logger.warn('reprobeHwaccel failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n await this.ctx.settings?.writeAddonStore({ probedBestHwaccel: 'none' })\n return { backend: 'none' }\n }\n }\n\n async supportsCodec(input: { codec: string }): Promise<boolean> {\n return ['h264', 'h265', 'hevc'].includes(input.codec.toLowerCase())\n }\n\n async getInfo(): Promise<{ id: string; name: string; isPullMode?: boolean; priority?: number }> {\n return {\n id: 'decoder-nodeav',\n name: 'Decoder (node-av)',\n isPullMode: false,\n priority: 10,\n }\n }\n\n async createSession(config: CapDecoderSessionConfig): Promise<{ sessionId: string; nodeId: string }> {\n const sessionId = randomUUID()\n const hwaccel = this.resolveHwAccelPref()\n const session = new NodeAvDecoderSession(config, this.ctx.logger, {\n hwaccel,\n hwaccelResolver: this.ctx.kernel.hwaccel,\n })\n const ringBuffer = new RingBuffer<CapDecodedFrame>(FRAME_BUFFER_CAPACITY)\n\n const unsub = session.onFrame((frame) => {\n // Map internal DecodedFrame to cap-compatible shape.\n const { format } = frame\n if (format !== 'jpeg' && format !== 'rgb' && format !== 'bgr' && format !== 'yuv420' && format !== 'gray') return\n // Copy frame data into a fresh ArrayBuffer so the cap-facing Uint8Array\n // has a concrete ArrayBuffer (not a SharedArrayBuffer / Buffer backing).\n const arrayBuf = new ArrayBuffer(frame.data.byteLength)\n new Uint8Array(arrayBuf).set(frame.data)\n const capData = new Uint8Array(arrayBuf)\n const capFrame: CapDecodedFrame = {\n data: capData,\n width: frame.width,\n height: frame.height,\n format,\n timestamp: frame.timestamp,\n }\n ringBuffer.push(capFrame)\n })\n\n this.sessions.set(sessionId, session)\n this.frameBuffers.set(sessionId, ringBuffer)\n this.unsubscribers.set(sessionId, unsub)\n this.sessionMeta.set(sessionId, {\n codec: config.codec,\n outputFormat: config.outputFormat,\n createdAtMs: Date.now(),\n })\n\n this.ctx.logger.info('node-av: created session', { meta: { sessionId, codec: config.codec, hwaccelPref: hwaccel } })\n return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? 'local' }\n }\n\n async destroySession(input: { sessionId: string }): Promise<void> {\n const { sessionId } = input\n const session = this.sessions.get(sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${sessionId}`)\n }\n\n const unsub = this.unsubscribers.get(sessionId)\n if (unsub) unsub()\n\n await session.destroy()\n\n this.sessions.delete(sessionId)\n this.frameBuffers.delete(sessionId)\n this.unsubscribers.delete(sessionId)\n this.sessionMeta.delete(sessionId)\n\n this.ctx.logger.info('node-av: destroyed session', { meta: { sessionId } })\n }\n\n async listActiveSessions(): Promise<readonly { sessionId: string; codec: string; outputFormat: string; createdAtMs: number }[]> {\n const out: Array<{ sessionId: string; codec: string; outputFormat: string; createdAtMs: number }> = []\n for (const [sessionId, meta] of this.sessionMeta) {\n out.push({ sessionId, codec: meta.codec, outputFormat: meta.outputFormat, createdAtMs: meta.createdAtMs })\n }\n return out\n }\n\n async pushPacket(input: { sessionId: string; packet: CapEncodedPacket }): Promise<void> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n // Convert Uint8Array/Buffer at the cap boundary before passing to the internal session.\n // MsgPack may deliver binary as Buffer, Uint8Array, or (in edge cases) plain Object\n // with numeric keys when the binary exceeds msgpack5's bin threshold.\n const rawData = input.packet.data\n const data = Buffer.isBuffer(rawData)\n ? rawData\n : rawData instanceof Uint8Array\n ? Buffer.from(rawData.buffer, rawData.byteOffset, rawData.byteLength)\n : Buffer.from(rawData as unknown as number[])\n session.pushPacket({ ...input.packet, data })\n }\n\n async openStream(input: { sessionId: string; url: string }): Promise<void> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n if (session.openStream) {\n await session.openStream(input.url)\n }\n }\n\n async pullFrames(input: { sessionId: string; maxCount: number }): Promise<CapDecodedFrame[]> {\n const ringBuffer = this.frameBuffers.get(input.sessionId)\n if (!ringBuffer) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n return ringBuffer.drain(input.maxCount)\n }\n\n async updateConfig(input: { sessionId: string; config: Partial<CapDecoderSessionConfig> }): Promise<void> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n session.updateConfig(input.config)\n }\n\n async getStats(input: { sessionId: string }): Promise<DecoderStats> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n return session.getStats()\n }\n\n protected async onShutdown(): Promise<void> {\n this.ctx.logger.info('node-av decoder addon shutdown — destroying all sessions')\n const destroyPromises: Promise<void>[] = []\n for (const [sessionId, session] of this.sessions) {\n const unsub = this.unsubscribers.get(sessionId)\n if (unsub) unsub()\n destroyPromises.push(session.destroy())\n }\n await Promise.all(destroyPromises)\n this.sessions.clear()\n this.frameBuffers.clear()\n this.unsubscribers.clear()\n this.sessionMeta.clear()\n }\n}\n"],"names":["errMsg","BaseAddon","DEFAULT_DECODER_HWACCEL_CONFIG","HWACCEL_OPTIONS","decoderCapability","randomUUID","RingBuffer"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAS,uBAAuB,SAAyB,QAAgD;AACvG,UAAQ,SAAA;AAAA,IACN,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC;AAAqB,aAAO;AAAA,EAAA;AAEhC;AAGA,SAAS,kBAAkB,SAAyB,QAA+C;AACjG,UAAQ,SAAA;AAAA,IACN,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC,KAAK;AAAgB,aAAO,OAAO;AAAA,IACnC;AAAqB,aAAO;AAAA,EAAA;AAEhC;AAEA,IAAI,OAA4B;AAChC,IAAI,UAAkC;AAGtC,IAAI,SAAyB;AAE7B,eAAe,YAAmC;AAChD,MAAI,CAAC,KAAM,QAAO,MAAM,OAAO,SAAS;AACxC,SAAO;AACT;AACA,eAAe,eAAyC;AACtD,MAAI,CAAC,QAAS,WAAU,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,QAAO,sBAAmB,CAAA;AACxD,SAAO;AACT;AACA,eAAe,WAA6B;AAC1C,MAAI,CAAC,QAAQ;AACX,UAAM,MAAM,MAAM,OAAO,OAAO;AAChC,aAAS,IAAI;AAAA,EACf;AACA,SAAO;AACT;AAEA,MAAM,aAA4B;AAAA,EAChC,QAAQ;AAAA,EAAC;AAAA,EAAG,OAAO;AAAA,EAAC;AAAA,EAAG,OAAO;AAAA,EAAC;AAAA,EAAG,QAAQ;AAAA,EAAC;AAAA,EAC3C,QAAQ;AAAE,WAAO;AAAA,EAAW;AAAA,EAC5B,WAAW;AAAE,WAAO;AAAA,EAAW;AACjC;AAEO,MAAM,qBAAgD;AAAA,EACnD;AAAA,EACS;AAAA,EACT,qCAAqB,IAAA;AAAA,EACrB,YAAY;AAAA;AAAA,EAGZ,SAA2D;AAAA,EAC3D,WAA8D;AAAA,EAC9D,SAAoE;AAAA,EACpE,WAAwD;AAAA,EACxD,UAAsD;AAAA,EACtD,WAAuD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvD,SAAkB;AAAA,EAClB,gBAA+B;AAAA,EAC/B,gBAA+B;AAAA,EAC/B,oBAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAe9B;AAAA,EACA,UAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWlC,OAAwB,oBAAoB;AAAA,EACpC,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU7B,OAAe,kBAAkB,QAAqD;AACpF,QAAI,WAAW,UAAU,WAAW,OAAW,QAAO;AACtD,QAAI,WAAW,OAAQ,QAAO;AAC9B,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUrB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASd,mBAAmB;AAAA;AAAA,EAGnB,WAAW;AAAA,EACX,YAAY;AAAA;AAAA,EAGZ,eAAe;AAAA,EACf;AAAA;AAAA,EAGA,eAAe;AAAA,EACf,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,YAAY,KAAK,IAAA;AAAA,EAER;AAAA,EACA;AAAA;AAAA,EAET,gBAAyC;AAAA,EACzC,WAAuE;AAAA,EACvE,kBAA8D;AAAA,EAEtE,YACE,QACA,SAAwB,YACxB,SACA;AACA,SAAK,SAAS,EAAE,GAAG,OAAA;AAKnB,UAAM,cAA+C,CAAA;AACrD,QAAI,OAAO,OAAO,aAAa,SAAU,aAAY,UAAU,IAAI,OAAO;AAC1E,QAAI,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,SAAS,EAAG,aAAY,KAAK,IAAI,OAAO;AACzF,SAAK,SAAS,OAAO,KAAK,WAAW,EAAE,SAAS,IAAI,OAAO,SAAS,WAAW,IAAI;AACnF,SAAK,gBAAgB,OAAO,SAAS,IAAI,MAAO,OAAO,SAAS;AAChE,SAAK,aAAa,qBAAqB,kBAAkB,OAAO,YAAY;AAC5E,SAAK,cAAc,SAAS,WAAW;AACvC,SAAK,kBAAkB,SAAS,mBAAmB;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,iBAAiB,KAAmB,GAAmC;AACnF,QAAI,CAAC,KAAK,SAAU;AACpB,QAAI,KAAK,gBAAgB,QAAQ;AAC/B,WAAK,gBAAgB;AACrB;AAAA,IACF;AAMA,UAAM,WACJ,KAAK,gBAAgB,SAAS,OAAO,KAAK;AAC5C,UAAM,aAAa,KAAK,kBACpB,MAAM,KAAK,gBAAgB,QAAQ,QAAQ,IAC3C,WACE,EAAE,WAAW,CAAC,QAAQ,GAAgC,WAAW,yBAAA,IACjE,EAAE,WAAW,CAAA,GAAiC,WAAW,gCAAA;AAE/D,QAAI,WAAW,UAAU,WAAW,GAAG;AACrC,WAAK,gBAAgB;AACrB;AAAA,IACF;AAEA,eAAW,WAAW,WAAW,WAAW;AAC1C,YAAM,aAAa,uBAAuB,SAAS,CAAC;AACpD,YAAM,WAAW,kBAAkB,SAAS,CAAC;AAC7C,UAAI,CAAC,cAAc,CAAC,SAAU;AAE9B,YAAM,SAAS,IAAI,IAAI,sBAAA;AACvB,YAAM,KAAK,OAAO,OAAO,UAAU;AACnC,UAAI,KAAK,GAAG;AACV,aAAK,OAAO,KAAK,uDAAuD;AAAA,UACtE,MAAM,EAAE,SAAS,GAAA;AAAA,QAAG,CACrB;AACD,eAAO,KAAA;AACP;AAAA,MACF;AACA,UAAI;AACF,aAAK,SAAS,cAAc;AAC5B,aAAK,SAAS,uBAAuB,QAAQ;AAAA,MAC/C,SAAS,KAAK;AACZ,aAAK,OAAO,KAAK,wDAAwD;AAAA,UACvE,MAAM,EAAE,SAAS,OAAOA,MAAAA,OAAO,GAAG,EAAA;AAAA,QAAE,CACrC;AACD,eAAO,KAAA;AACP;AAAA,MACF;AACA,WAAK,WAAW;AAChB,WAAK,gBAAgB;AACrB;AAAA,IACF;AAEA,SAAK,OAAO,KAAK,4DAA4D;AAAA,MAC3E,MAAM,EAAE,WAAW,WAAW,UAAU,KAAK,GAAG,GAAG,WAAW,WAAW,UAAA;AAAA,IAAU,CACpF;AACD,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,gBAAgB,SAA0F;AAChH,QAAI,KAAK,kBAAkB,UAAU,CAAC,KAAK,gBAAiB,QAAO;AACnE,UAAM,KAAK,QAAQ,wBAAwB,KAAK,iBAAiB,CAAC;AAClE,QAAI,KAAK,GAAG;AACV,WAAK,OAAO,KAAK,uCAAuC,EAAE,MAAM,EAAE,GAAA,GAAM;AACxE,aAAO;AAAA,IACT;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cAA6B;AACzC,QAAI,KAAK,eAAe,KAAK,gBAAgB,KAAK,UAAW;AAC7D,SAAK,eAAe;AAEpB,QAAI;AACF,YAAM,MAAM,MAAM,UAAA;AAClB,YAAM,IAAI,MAAM,aAAA;AAGhB,WAAK,SAAS,EAAE;AAChB,WAAK,gBAAgB,EAAE;AACvB,WAAK,gBAAgB,EAAE;AACvB,WAAK,oBAAoB,EAAE;AAG3B,UAAI,KAAK,eAAe,QAAQ;AAC9B,aAAK,UAAU,MAAM,SAAA;AAAA,MACvB;AAKA,UAAI,IAAI,SAAS,EAAE,YAAY;AAG/B,YAAM,SAAS,KAAK,OAAO,UAAU,UAAU,KAAK,OAAO,UAAU;AACrE,YAAM,UAAU,SAAS,EAAE,mBAAmB,EAAE;AAGhD,WAAK,SAAS,IAAI,IAAI,YAAA;AACtB,WAAK,OAAO,KAAK,OAAO;AAGxB,YAAM,QAAQ,IAAI,MAAM,YAAY,OAAO;AAC3C,UAAI,CAAC,OAAO;AACV,aAAK,OAAO,MAAM,6BAA6B,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,MAAA,EAAM,CAAG;AACrF;AAAA,MACF;AAEA,WAAK,WAAW,IAAI,IAAI,aAAA;AACxB,WAAK,SAAS,cAAc,KAAK;AAEjC,UAAI,KAAK,OAAO,SAAS,KAAK,OAAO,QAAQ;AAC3C,aAAK,SAAS,QAAQ,KAAK,OAAO;AAClC,aAAK,SAAS,SAAS,KAAK,OAAO;AAAA,MACrC;AAEA,WAAK,SAAS,cAAc;AAM5B,YAAM,KAAK,iBAAiB,KAAK,CAAC;AAElC,YAAM,MAAM,MAAM,KAAK,SAAS,MAAM,KAAK;AAC3C,UAAI,MAAM,GAAG;AAEX,YAAI,KAAK,kBAAkB,QAAQ;AACjC,eAAK,OAAO,KAAK,6DAA6D;AAAA,YAC5E,MAAM,EAAE,KAAK,SAAS,KAAK,cAAA;AAAA,UAAc,CAC1C;AACD,eAAK,UAAU,KAAA;AACf,eAAK,WAAW;AAChB,eAAK,gBAAgB;AACrB,eAAK,WAAW,IAAI,IAAI,aAAA;AACxB,eAAK,SAAS,cAAc,KAAK;AACjC,cAAI,KAAK,OAAO,SAAS,KAAK,OAAO,QAAQ;AAC3C,iBAAK,SAAS,QAAQ,KAAK,OAAO;AAClC,iBAAK,SAAS,SAAS,KAAK,OAAO;AAAA,UACrC;AACA,eAAK,SAAS,cAAc;AAC5B,gBAAM,QAAQ,MAAM,KAAK,SAAS,MAAM,KAAK;AAC7C,cAAI,QAAQ,GAAG;AACb,iBAAK,OAAO,MAAM,iDAAiD,EAAE,MAAM,EAAE,KAAK,MAAA,GAAS;AAC3F;AAAA,UACF;AAAA,QACF,OAAO;AACL,eAAK,OAAO,MAAM,mCAAmC,EAAE,MAAM,EAAE,IAAA,GAAO;AACtE;AAAA,QACF;AAAA,MACF;AAMA,UAAI,KAAK,kBAAkB,QAAQ;AACjC,aAAK,kBAAkB,IAAI,IAAI,MAAA;AAC/B,aAAK,gBAAgB,MAAA;AAAA,MACvB;AAGA,WAAK,WAAW,IAAI,IAAI,OAAA;AACxB,WAAK,SAAS,MAAA;AAEd,WAAK,UAAU,IAAI,IAAI,MAAA;AACvB,WAAK,QAAQ,MAAA;AAEb,WAAK,cAAc;AACnB,WAAK,OAAO,KAAK,oCAAoC;AAAA,QACnD,MAAM;AAAA,UACJ,OAAO,KAAK,OAAO;AAAA,UACnB,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMb,SAAS,KAAK;AAAA,QAAA;AAAA,MAChB,CACD;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,OAAO,MAAM,sBAAsB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,IAC1E,UAAA;AACE,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAc,WAAW,MAAc,MAAc,QAA+B;AAClF,QAAI,KAAK,mBAAoB;AAC7B,SAAK,qBAAqB;AAC1B,UAAM,UAAU,KAAK;AAErB,QAAI,cAAyE;AAC7E,QAAI,gBAA4D;AAEhE,QAAI;AACF,YAAM,MAAM,MAAM,UAAA;AAElB,UAAI,KAAK,aAAa,YAAY,KAAK,YAAa;AAEpD,YAAM,QAAQ,KAAK,OAAO,QAAQ,IAAI,KAAK,OAAO,QAAQ;AAC1D,YAAM,OAAO,KAAK,MAAM,MAAM,KAAK;AACnC,YAAM,WAAW,KAAK,IAAI,MAAM,IAAI;AACpC,YAAM,YAAY,KAAK,MAAM,WAAW,OAAO,IAAI;AAKnD,YAAM,SAAS,KAAK,eAAe,SAAS,KAAK,gBAAgB,KAAK;AACtE,YAAM,UAAU,KAAK,eAAe,SAAS,UAAU;AAEvD,oBAAc,IAAI,IAAI,qBAAA;AACtB,kBAAY;AAAA,QACV;AAAA,QAAM;AAAA,QAAM;AAAA,QACZ;AAAA,QAAU;AAAA,QAAW;AAAA,QACrB,KAAK;AAAA,MAAA;AAEP,YAAM,MAAM,YAAY,YAAA;AACxB,UAAI,MAAM,GAAG;AACX,aAAK,OAAO,MAAM,oCAAoC,EAAE,MAAM,EAAE,IAAA,GAAO;AACvE;AAAA,MACF;AAEA,sBAAgB,IAAI,IAAI,MAAA;AACxB,oBAAc,MAAA;AACd,oBAAc,QAAQ;AACtB,oBAAc,SAAS;AACvB,oBAAc,SAAS;AACvB,YAAM,WAAW,cAAc,YAAA;AAC/B,UAAI,WAAW,GAAG;AAChB,aAAK,OAAO,MAAM,yCAAyC,EAAE,MAAM,EAAE,KAAK,SAAA,GAAY;AACtF;AAAA,MACF;AAMA,UAAI,KAAK,aAAa,YAAY,KAAK,YAAa;AAIpD,WAAK,SAAS,OAAO,OAAO,IAAA;AAC5B,WAAK,WAAW,OAAO,OAAO,IAAA;AAE9B,WAAK,SAAS;AACd,WAAK,WAAW;AAChB,WAAK,WAAW;AAChB,WAAK,YAAY;AACjB,oBAAc;AACd,sBAAgB;AAEhB,WAAK,OAAO,KAAK,8BAA8B;AAAA,QAC7C,MAAM,EAAE,UAAU,MAAM,WAAW,MAAM,UAAU,WAAW,QAAQ,QAAA;AAAA,MAAQ,CAC/E;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,OAAO,MAAM,sBAAsB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,IAC1E,UAAA;AAEE,oBAAc,OAAO,OAAO,IAAA;AAC5B,sBAAgB,OAAO,OAAO,IAAA;AAC9B,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,WAAW,QAA6B;AACtC,QAAI,KAAK,UAAW;AACpB,SAAK;AAEL,QAAI,CAAC,KAAK,eAAe,CAAC,KAAK,gBAAgB,OAAO,UAAU;AAC9D,WAAK,cAAc,KAAK,MAAM;AAC5B,YAAI,KAAK,YAAa,MAAK,cAAc,OAAO,MAAM,OAAO,OAAO,KAAK,IAAA,CAAK;AAAA,MAChF,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,aAAK,OAAO,MAAM,uBAAuB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,MAC3E,CAAC;AACD;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,YAAa;AACvB,SAAK,cAAc,OAAO,MAAM,OAAO,OAAO,KAAK,KAAK;AAAA,EAC1D;AAAA,EAEQ,cAAc,MAA2B,KAAmB;AAClE,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,YAAY,CAAC,KAAK,YAAY,CAAC,KAAK,QAAS;AAEvE,UAAM,MAAM,OAAO,SAAS,IAAI,IAAI,OAAO,OAAO,KAAK,IAAI;AAC3D,UAAM,SAAS,OAAO,GAAG;AACzB,QAAI,SAAS;AAEb,WAAO,SAAS,IAAI,QAAQ;AAC1B,YAAM,YAAY,IAAI,SAAS,MAAM;AACrC,YAAM,WAAW,KAAK,OAAO;AAAA,QAC3B,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QAAQ;AAAA,QACR;AAAA,MAAA;AAGF,UAAI,WAAW,GAAG;AAChB,aAAK,OAAO,KAAK,wBAAwB,EAAE,MAAM,EAAE,KAAK,SAAA,GAAY;AACpE;AAAA,MACF;AAEA,gBAAU;AAEV,UAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,aAAK,aAAA;AACL,aAAK,SAAS,MAAA;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI,CAAC,KAAK,YAAY,CAAC,KAAK,QAAS;AAErC,UAAM,UAAU,KAAK,SAAS,eAAe,KAAK,QAAS;AAC3D,QAAI,UAAU,KAAK,YAAY,KAAK,QAAQ;AAC1C,WAAK,OAAO,KAAK,4BAA4B,EAAE,MAAM,EAAE,KAAK,QAAA,GAAW;AACvE;AAAA,IACF;AAEA,WAAO,MAAM;AACX,YAAM,UAAU,KAAK,SAAS,iBAAiB,KAAK,OAAO;AAC3D,UAAI,UAAU,EAAG;AAIjB,YAAM,QAAQ,KAAK,gBAAgB,KAAK,OAAO;AAC/C,UAAI,CAAC,MAAO;AAEZ,WAAK,iBAAiB,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAkD;AACzE,UAAM,MAAM,YAAY,IAAA;AAExB,QAAI,KAAK,gBAAgB,KAAK,MAAM,KAAK,eAAe,KAAK,eAAe;AAC1E,WAAK;AACL;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,oBAAoB;AAC5C,WAAK,WAAW,MAAM,OAAO,MAAM,QAAQ,MAAM,MAAgB;AACjE;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,YAAY,CAAC,KAAK,OAAQ;AAEpC,UAAM,cAAc,YAAY,IAAA;AAEhC,QAAI;AACF,WAAK,SAAS,aAAA;AACd,WAAK,OAAO,eAAe,KAAK,UAAU,KAAK;AAAA,IACjD,SAAS,KAAK;AACZ,WAAK,OAAO,KAAK,uBAAuB,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AACxE;AAAA,IACF;AAKA,QAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAK,mBAAmB;AACxB,YAAM,WAAW,KAAK,eAAe,SAAS,IAAI;AAClD,YAAM,KAAK,KAAK,SAAS;AACzB,YAAM,MAAM,KAAK,SAAS,SAAA;AAC1B,WAAK,OAAO,KAAK,wBAAwB;AAAA,QACvC,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,OAAO,KAAK,SAAS;AAAA,UACrB,QAAQ,KAAK,SAAS;AAAA,UACtB,UAAU,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;AAAA,UAC9B,gBAAgB,KAAK,SAAS,QAAQ;AAAA,UACtC,QAAQ,IAAI;AAAA,UACZ,gBAAgB,KAAK,SAAS,QAAQ,WAAW,KAAK,SAAS;AAAA,UAC/D,WAAW,MAAM,UAAU;AAAA,QAAA;AAAA,MAC7B,CACD;AAKD,UAAI,KAAK,eAAe,QAAQ;AAC9B,eAAO,SAAS,EAAE,KAAK,CAAA,aAAY;AACjC,iBAAO,WAAW,EAAE,KAAK,CAAA,eAAc;AACrC,kBAAM,WAAW,WAAW,KAAK,QAAQ,IAAA,GAAO,iBAAiB,uBAAuB;AACxF,qBAAS,cAAc,UAAU,GAAG;AACpC,iBAAK,OAAO,KAAK,4BAA4B,EAAE,MAAM,EAAE,OAAO,eAAe,MAAM,UAAU,OAAO,IAAI,OAAA,GAAU;AAAA,UACpH,CAAC,EAAE,MAAM,MAAM;AAAA,UAAe,CAAC;AAAA,QACjC,CAAC,EAAE,MAAM,MAAM;AAAA,QAAe,CAAC;AAAA,MACjC;AAAA,IACF;AAIA,UAAM,SAAS,KAAK,oBAAoB,KAAK,QAAQ;AAErD,QAAI,KAAK,eAAe,QAAQ;AAE9B,WAAK,kBAAkB,QAAQ,WAAW;AAAA,IAC5C,WAAW,KAAK,eAAe,OAAO;AAGpC,WAAK,aAAa,QAAQ,OAAO,WAAW;AAAA,IAC9C,OAAO;AACL,WAAK,aAAa,QAAQ,QAAQ,WAAW;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBAAoB,OAA+I;AACzK,UAAM,WAAW,KAAK,eAAe,SAAS,IAAI;AAClD,UAAM,iBAAiB,MAAM,QAAQ;AACrC,UAAM,eAAe,MAAM,SAAS,CAAC,KAAK;AAI1C,UAAM,MAAM,MAAM,OAAO,CAAC;AAC1B,QAAI,CAAC,KAAK;AACR,aAAO,MAAM,SAAA;AAAA,IACf;AAEA,QAAI,iBAAiB,gBAAgB;AAGnC,aAAO,OAAO,KAAK,IAAI,QAAQ,IAAI,YAAY,iBAAiB,MAAM,MAAM;AAAA,IAC9E;AAGA,UAAM,MAAM,OAAO,YAAY,iBAAiB,MAAM,MAAM;AAC5D,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAI,KAAK,KAAK,IAAI,gBAAgB,IAAI,cAAc,IAAI,eAAe,cAAc;AAAA,IACvF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,kBAAkB,KAAa,aAA2B;AAChE,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI,KAAK,sBAAsB,qBAAqB,mBAAmB;AACrE,WAAK;AACL;AAAA,IACF;AAEA,SAAK;AACL,SAAK,QAAQ,KAAK;AAAA,MAChB,KAAK,EAAE,OAAO,KAAK,UAAU,QAAQ,KAAK,WAAW,UAAU,EAAA;AAAA,IAAE,CAClE,EACE,KAAK,EAAE,SAAS,IAAI,SAAS,MAAA,CAAO,EACpC,SAAA,EACA,KAAK,CAAC,YAAY;AACjB,UAAI,KAAK,UAAW;AACpB,WAAK,aAAa,SAAS,QAAQ,WAAW;AAAA,IAChD,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,WAAK,OAAO,KAAK,2BAA2B,EAAE,MAAM,EAAE,OAAOA,MAAAA,OAAO,GAAG,EAAA,GAAK;AAAA,IAC9E,CAAC,EACA,QAAQ,MAAM;AACb,WAAK;AAAA,IACP,CAAC;AAAA,EACL;AAAA,EAEQ,aAAa,MAAc,QAAgC,aAA2B;AAC5F,UAAM,WAAW,YAAY,IAAA,IAAQ;AACrC,SAAK,qBAAqB;AAE1B,SAAK;AACL,SAAK,eAAe,YAAY,IAAA;AAEhC,QAAI,KAAK,iBAAiB,KAAK,KAAK,eAAe,QAAQ,GAAG;AAC5D,WAAK,OAAO,KAAK,yBAAyB;AAAA,QACxC,MAAM;AAAA,UACJ,aAAa,KAAK;AAAA,UAClB,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA,MAAM,KAAK,eAAe;AAAA,QAAA;AAAA,MAC5B,CACD;AAAA,IACH;AAEA,UAAM,eAA6B;AAAA,MACjC;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,IAAI;AAGtB,eAAW,MAAM,KAAK,gBAAgB;AACpC,SAAG,YAAY;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,QAAQ,UAAsD;AAC5D,SAAK,eAAe,IAAI,QAAQ;AAChC,WAAO,MAAM;AAAE,WAAK,eAAe,OAAO,QAAQ;AAAA,IAAE;AAAA,EACtD;AAAA,EAEA,aAAa,QAA6C;AACxD,UAAM,aAAa,KAAK,OAAO;AAC/B,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAA;AACnC,QAAI,OAAO,WAAW,QAAW;AAC/B,WAAK,gBAAgB,OAAO,SAAS,IAAI,MAAO,OAAO,SAAS;AAAA,IAClE;AAMA,QAAI,OAAO,iBAAiB,UAAa,OAAO,iBAAiB,YAAY;AAC3E,YAAM,WAAW,KAAK;AACtB,WAAK,aAAa,qBAAqB,kBAAkB,OAAO,YAAY;AAI5E,UAAI,KAAK,eAAe,SAAU;AAClC,WAAK;AACL,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,OAAO,OAAO,IAAA;AAC1B,aAAK,SAAS;AAAA,MAChB;AACA,UAAI,KAAK,UAAU;AACjB,aAAK,SAAS,OAAO,OAAO,IAAA;AAC5B,aAAK,WAAW;AAAA,MAClB;AAEA,UAAI,KAAK,eAAe,UAAU,CAAC,KAAK,SAAS;AAC/C,iBAAA,EAAW,KAAK,CAAA,OAAM;AAAE,eAAK,UAAU;AAAA,QAAG,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC7D;AACA,WAAK,OAAO,KAAK,uDAAuD;AAAA,QACtE,MAAM,EAAE,MAAM,YAAY,IAAI,OAAO,cAAc,MAAM,KAAK,WAAA;AAAA,MAAW,CAC1E;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,SAAK,eAAe,MAAA;AAEpB,SAAK,WAAW,OAAO,OAAO,IAAA;AAC9B,SAAK,UAAU,OAAO,OAAO,IAAA;AAC7B,SAAK,WAAW,OAAO,OAAO,IAAA;AAC9B,SAAK,SAAS,OAAO,OAAO,IAAA;AAC5B,SAAK,SAAS,OAAO,OAAO,IAAA;AAC5B,SAAK,kBAAkB,OAAO,OAAO,IAAA;AACrC,SAAK,WAAW,OAAO,OAAO,IAAA;AAC9B,SAAK,UAAU,KAAA;AAEf,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,kBAAkB;AACvB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,WAAyB;AACvB,UAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,KAAK,aAAa,KAAM,CAAC;AAClE,WAAO;AAAA,MACL,UAAU,KAAK,eAAe;AAAA,MAC9B,WAAW,KAAK,eAAe;AAAA,MAC/B,iBAAiB,KAAK,eAAe,IAAI,KAAK,oBAAoB,KAAK,eAAe;AAAA,MACtF,eAAe,KAAK;AAAA,IAAA;AAAA,EAExB;AAAA,EAEA,IAAI,aAAsB;AAAE,WAAO;AAAA,EAAM;AAC3C;AClyBA,MAAM,wBAAwB;AAkB9B,MAAqB,2BAA2BC,MAAAA,UAA+D;AAAA,EAC5F,+BAAe,IAAA;AAAA,EACf,mCAAmB,IAAA;AAAA,EACnB,oCAAoB,IAAA;AAAA,EACpB,kCAAkB,IAAA;AAAA,EAEnC,cAAc;AAAE,UAAMC,MAAAA,8BAA8B;AAAA,EAAE;AAAA,EAE5C,uBAAuB;AAC/B,WAAO,KAAK,OAAO;AAAA,MACjB,UAAU,CAAC;AAAA,QACT,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,KAAK;AAAA,QACL,aAAa;AAAA,QACb,QAAQ;AAAA,UACN,KAAK,MAAM;AAAA,YACT,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,SAAS,CAAC,GAAGC,qBAAe;AAAA,YAC5B,SAAS;AAAA,YACT,WAAW;AAAA,UAAA,CACZ;AAAA,UACD,KAAK,MAAM;AAAA,YACT,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,eAAe;AAAA,YACf,SAAS;AAAA,YACT,SAAS;AAAA,cACP,EAAE,QAAQ,mBAAmB,MAAM,cAAc,SAAS,mBAAA;AAAA,YAAmB;AAAA,UAC/E,CACD;AAAA,QAAA;AAAA,MACH,CACD;AAAA,IAAA,CACF;AAAA,EACH;AAAA,EAEA,MAAgB,eAAgD;AAC9D,SAAK,IAAI,OAAO,KAAK,mCAAmC;AAGxD,QAAI,CAAC,KAAK,OAAO,mBAAmB;AAClC,WAAK,eAAA,EAAiB,MAAM,CAAC,QAAiB;AAC5C,aAAK,IAAI,OAAO,KAAK,uCAAuC;AAAA,UAC1D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAA;AAAA,QAAE,CACjE;AAAA,MACH,CAAC;AAAA,IACH;AACA,WAAO,CAAC,EAAE,YAAYC,MAAAA,mBAAmB,UAAU,MAAM;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,qBAAoC;AAC1C,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,iBAA+C;AACnD,UAAM,WAAW,KAAK,IAAI,OAAO;AACjC,QAAI,CAAC,UAAU;AACb,WAAK,IAAI,OAAO,KAAK,6DAA6D;AAClF,YAAM,KAAK,IAAI,UAAU,gBAAgB,EAAE,mBAAmB,QAAQ;AACtE,aAAO,EAAE,SAAS,OAAA;AAAA,IACpB;AACA,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,QAAA;AAC3B,YAAM,UAAW,IAAI,UAAU,CAAC,KAAK;AACrC,YAAM,KAAK,IAAI,UAAU,gBAAgB,EAAE,mBAAmB,SAAS;AACvE,WAAK,IAAI,OAAO,KAAK,2CAA2C;AAAA,QAC9D,MAAM,EAAE,SAAS,WAAW,IAAI,WAAW,WAAW,IAAI,UAAA;AAAA,MAAU,CACrE;AACD,aAAO,EAAE,QAAA;AAAA,IACX,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,yBAAyB;AAAA,QAC5C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAA;AAAA,MAAE,CACjE;AACD,YAAM,KAAK,IAAI,UAAU,gBAAgB,EAAE,mBAAmB,QAAQ;AACtE,aAAO,EAAE,SAAS,OAAA;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,OAA4C;AAC9D,WAAO,CAAC,QAAQ,QAAQ,MAAM,EAAE,SAAS,MAAM,MAAM,aAAa;AAAA,EACpE;AAAA,EAEA,MAAM,UAA0F;AAC9F,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,UAAU;AAAA,IAAA;AAAA,EAEd;AAAA,EAEA,MAAM,cAAc,QAAiF;AACnG,UAAM,YAAYC,OAAAA,WAAA;AAClB,UAAM,UAAU,KAAK,mBAAA;AACrB,UAAM,UAAU,IAAI,qBAAqB,QAAQ,KAAK,IAAI,QAAQ;AAAA,MAChE;AAAA,MACA,iBAAiB,KAAK,IAAI,OAAO;AAAA,IAAA,CAClC;AACD,UAAM,aAAa,IAAIC,MAAAA,WAA4B,qBAAqB;AAExE,UAAM,QAAQ,QAAQ,QAAQ,CAAC,UAAU;AAEvC,YAAM,EAAE,WAAW;AACnB,UAAI,WAAW,UAAU,WAAW,SAAS,WAAW,SAAS,WAAW,YAAY,WAAW,OAAQ;AAG3G,YAAM,WAAW,IAAI,YAAY,MAAM,KAAK,UAAU;AACtD,UAAI,WAAW,QAAQ,EAAE,IAAI,MAAM,IAAI;AACvC,YAAM,UAAU,IAAI,WAAW,QAAQ;AACvC,YAAM,WAA4B;AAAA,QAChC,MAAM;AAAA,QACN,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM;AAAA,QACd;AAAA,QACA,WAAW,MAAM;AAAA,MAAA;AAEnB,iBAAW,KAAK,QAAQ;AAAA,IAC1B,CAAC;AAED,SAAK,SAAS,IAAI,WAAW,OAAO;AACpC,SAAK,aAAa,IAAI,WAAW,UAAU;AAC3C,SAAK,cAAc,IAAI,WAAW,KAAK;AACvC,SAAK,YAAY,IAAI,WAAW;AAAA,MAC9B,OAAO,OAAO;AAAA,MACd,cAAc,OAAO;AAAA,MACrB,aAAa,KAAK,IAAA;AAAA,IAAI,CACvB;AAED,SAAK,IAAI,OAAO,KAAK,4BAA4B,EAAE,MAAM,EAAE,WAAW,OAAO,OAAO,OAAO,aAAa,QAAA,GAAW;AACnH,WAAO,EAAE,WAAW,QAAQ,KAAK,IAAI,OAAO,eAAe,QAAA;AAAA,EAC7D;AAAA,EAEA,MAAM,eAAe,OAA6C;AAChE,UAAM,EAAE,cAAc;AACtB,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,qCAAqC,SAAS,EAAE;AAAA,IAClE;AAEA,UAAM,QAAQ,KAAK,cAAc,IAAI,SAAS;AAC9C,QAAI,MAAO,OAAA;AAEX,UAAM,QAAQ,QAAA;AAEd,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,aAAa,OAAO,SAAS;AAClC,SAAK,cAAc,OAAO,SAAS;AACnC,SAAK,YAAY,OAAO,SAAS;AAEjC,SAAK,IAAI,OAAO,KAAK,8BAA8B,EAAE,MAAM,EAAE,UAAA,GAAa;AAAA,EAC5E;AAAA,EAEA,MAAM,qBAA0H;AAC9H,UAAM,MAA8F,CAAA;AACpG,eAAW,CAAC,WAAW,IAAI,KAAK,KAAK,aAAa;AAChD,UAAI,KAAK,EAAE,WAAW,OAAO,KAAK,OAAO,cAAc,KAAK,cAAc,aAAa,KAAK,YAAA,CAAa;AAAA,IAC3G;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,OAAuE;AACtF,UAAM,UAAU,KAAK,SAAS,IAAI,MAAM,SAAS;AACjD,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,qCAAqC,MAAM,SAAS,EAAE;AAAA,IACxE;AAIA,UAAM,UAAU,MAAM,OAAO;AAC7B,UAAM,OAAO,OAAO,SAAS,OAAO,IAChC,UACA,mBAAmB,aACjB,OAAO,KAAK,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU,IAClE,OAAO,KAAK,OAA8B;AAChD,YAAQ,WAAW,EAAE,GAAG,MAAM,QAAQ,MAAM;AAAA,EAC9C;AAAA,EAEA,MAAM,WAAW,OAA0D;AACzE,UAAM,UAAU,KAAK,SAAS,IAAI,MAAM,SAAS;AACjD,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,qCAAqC,MAAM,SAAS,EAAE;AAAA,IACxE;AACA,QAAI,QAAQ,YAAY;AACtB,YAAM,QAAQ,WAAW,MAAM,GAAG;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,OAA4E;AAC3F,UAAM,aAAa,KAAK,aAAa,IAAI,MAAM,SAAS;AACxD,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,qCAAqC,MAAM,SAAS,EAAE;AAAA,IACxE;AACA,WAAO,WAAW,MAAM,MAAM,QAAQ;AAAA,EACxC;AAAA,EAEA,MAAM,aAAa,OAAuF;AACxG,UAAM,UAAU,KAAK,SAAS,IAAI,MAAM,SAAS;AACjD,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,qCAAqC,MAAM,SAAS,EAAE;AAAA,IACxE;AACA,YAAQ,aAAa,MAAM,MAAM;AAAA,EACnC;AAAA,EAEA,MAAM,SAAS,OAAqD;AAClE,UAAM,UAAU,KAAK,SAAS,IAAI,MAAM,SAAS;AACjD,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,qCAAqC,MAAM,SAAS,EAAE;AAAA,IACxE;AACA,WAAO,QAAQ,SAAA;AAAA,EACjB;AAAA,EAEA,MAAgB,aAA4B;AAC1C,SAAK,IAAI,OAAO,KAAK,0DAA0D;AAC/E,UAAM,kBAAmC,CAAA;AACzC,eAAW,CAAC,WAAW,OAAO,KAAK,KAAK,UAAU;AAChD,YAAM,QAAQ,KAAK,cAAc,IAAI,SAAS;AAC9C,UAAI,MAAO,OAAA;AACX,sBAAgB,KAAK,QAAQ,SAAS;AAAA,IACxC;AACA,UAAM,QAAQ,IAAI,eAAe;AACjC,SAAK,SAAS,MAAA;AACd,SAAK,aAAa,MAAA;AAClB,SAAK,cAAc,MAAA;AACnB,SAAK,YAAY,MAAA;AAAA,EACnB;AACF;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/decoder-nodeav/frame-ring-sink.ts","../../src/decoder-nodeav/nodeav-decoder-session.ts","../../src/decoder-nodeav/addon/index.ts"],"sourcesContent":["/**\n * `DecoderFrameRingSink` — the decoder's shared-memory write side (Phase 5 / D9).\n *\n * When a decoder session is configured with `frameSink: 'shm'`, the decoder\n * **owns** the shared-memory ring segment for that stream: it creates the\n * segment on the first decoded frame (when the output geometry is known),\n * writes every subsequent decoded frame into the ring via a `FrameRingWriter`,\n * and closes + unlinks the segment when the session is destroyed.\n *\n * What leaves the decoder is no longer the pixel `Buffer` — it is a tiny,\n * serialisable `FrameHandle` (`FrameRingWriter.writeFrame`'s return value).\n * Same-host consumers (motion, detection, the WebRTC encoder) open the same\n * segment with a `FrameRingReader` and read the pixels zero-copy.\n *\n * ## Lazy segment creation\n *\n * The segment cannot be sized until the first frame: `slotByteLength` is\n * `width × height × bytesPerPixel`, and the output dimensions are only known\n * once the scaler has produced its first `dstFrame`. So `writeFrame` is a\n * no-op-until-armed: the first call sizes + creates the segment, every later\n * call writes into it.\n *\n * ## Resolution-change decision\n *\n * A live camera stream can change resolution mid-stream (the decoder's scaler\n * is rebuilt on a config toggle, or the source renegotiates). The slot is\n * sized for the **first** frame's geometry. A later frame that no longer fits\n * the slot triggers a **segment re-create**: the old segment is closed +\n * unlinked and a fresh, larger segment is created under a new generation-tagged\n * name. This is simpler and leak-free versus over-allocating slots for a\n * worst-case 4K frame on every stream; resolution changes on a live camera are\n * rare, and a brief gap while consumers re-open the segment is acceptable\n * (latest-wins — a missed frame is correct behaviour).\n */\nimport {\n createSegment,\n FrameRingWriter,\n computeSegmentSize,\n computeSlotByteLength,\n deriveSlotCount,\n MIN_RING_SLOTS,\n} from '@camstack/shm-ring'\nimport type { ShmSegment, FrameMeta, FrameSlotWrite } from '@camstack/shm-ring'\nimport type { IScopedLogger, FrameHandle } from '@camstack/types'\n\n/**\n * A reserved ring slot handed back by {@link DecoderFrameRingSink.beginFrame}.\n *\n * The slot's seqlock is open (odd) — a reader skips it. The caller fills\n * `buffer` (a writable view directly over the mapped segment — the node-av\n * scaler scatters its packed output straight into it, zero write-side copy)\n * then publishes the frame via {@link DecoderFrameRingSink.commitFrame}.\n */\nexport interface DecoderFrameSlot {\n /** The ring slot index — pass it back to `commitFrame`. */\n readonly slot: number\n /** A writable view directly over the slot's pixel region in shared memory. */\n readonly buffer: Buffer\n}\n\n/**\n * Per-ring shared-memory budget (MB) — the slot count is derived per-resolution\n * from this budget via {@link deriveSlotCount}, so a 360p stream gets many\n * slots and a 4K stream a few, both inside the same memory footprint.\n *\n * Read ONCE at module load from `CAMSTACK_SHM_RING_BUDGET_MB`; a non-finite or\n * non-positive value falls back to the 128 MB default.\n */\nexport const RING_BUDGET_MB = (() => {\n const raw = Number(process.env['CAMSTACK_SHM_RING_BUDGET_MB'])\n return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 128\n})()\n\n/** {@link RING_BUDGET_MB} in bytes — the budget passed to `deriveSlotCount`. */\nexport const RING_BUDGET_BYTES = RING_BUDGET_MB * 1024 * 1024\n\n/** A unique, stable shared-memory segment name for a decoder stream.\n *\n * macOS POSIX shm names are capped at ~31 characters (`PSHMNAMLEN`). A\n * `camstack.frames.<deviceId>.<streamId>` scheme overflows that for realistic\n * ids, so the sink uses a short, collision-resistant scheme instead:\n * `csf.<base36 hash>.<gen>`. The hash folds the device id, the session tag\n * and a per-process random salt; the generation suffix makes a re-created\n * segment (resolution change) a distinct name so a stale consumer mapping is\n * never silently reused.\n */\nexport function makeSegmentName(seed: string, generation: number): string {\n // djb2 — a small, fast, well-distributed string hash. The salt in `seed`\n // (a random suffix added by the caller) keeps two sessions for the same\n // device from colliding within one process.\n let hash = 5381\n for (let i = 0; i < seed.length; i += 1) {\n hash = ((hash << 5) + hash + seed.charCodeAt(i)) | 0\n }\n const tag = (hash >>> 0).toString(36)\n return `csf.${tag}.${generation}`\n}\n\n/** Options for a `DecoderFrameRingSink`. */\nexport interface DecoderFrameRingSinkOptions {\n /**\n * Stable seed identifying this stream — folded into the segment name. The\n * decoder passes `deviceId:tag` (or a random fallback); the sink adds a\n * per-instance random salt so concurrent sessions never collide.\n */\n readonly seed: string\n readonly logger: IScopedLogger\n /**\n * Cluster node id of the decoder that owns this ring. Stamped into every\n * `FrameHandle` the writer produces so a downstream consumer knows which\n * node's `decoder.getFrame` to route a read through.\n */\n readonly nodeId: string\n}\n\n/** shm ring usage snapshot — see {@link DecoderFrameRingSink.getShmStats}. */\nexport interface DecoderShmStats {\n readonly slotCount: number\n readonly slotByteLength: number\n readonly segmentBytes: number\n readonly framesWritten: number\n}\n\n/**\n * The decoder-side owner of one stream's shared-memory frame ring.\n *\n * Not constructed until a session actually uses the shm sink; the segment\n * itself is created lazily on the first `writeFrame`.\n */\nexport class DecoderFrameRingSink {\n private readonly seed: string\n private readonly logger: IScopedLogger\n private readonly nodeId: string\n\n private segment: ShmSegment | null = null\n private writer: FrameRingWriter | null = null\n private segmentName: string | null = null\n private slotByteLength = 0\n private generation = 0\n private destroyed = false\n /** Frames committed into the ring across this sink's lifetime (all generations). */\n private framesWritten = 0\n\n constructor(options: DecoderFrameRingSinkOptions) {\n // A per-instance random salt keeps the segment name unique even when two\n // sessions share the same `seed` (same device, two profiles).\n const salt = Math.random().toString(36).slice(2, 8)\n this.seed = `${options.seed}.${salt}`\n this.logger = options.logger\n this.nodeId = options.nodeId\n }\n\n /** Whether a segment has been created (i.e. at least one frame written). */\n get isArmed(): boolean {\n return this.writer !== null\n }\n\n /** The current segment name, or `null` before the first frame. */\n get currentSegmentName(): string | null {\n return this.segmentName\n }\n\n /**\n * Write one decoded frame into the ring and return its `FrameHandle`.\n *\n * On the first call (or after a geometry change that overflows the current\n * slot) the segment is created / re-created sized for this frame. Returns\n * `null` only when the sink has been destroyed.\n *\n * This is the copy-in convenience form (it copies `pixels` into the slot).\n * The decoder's hot path uses the zero-copy {@link beginFrame} /\n * {@link commitFrame} scatter-write pair instead — the scaler produces its\n * packed output directly into the slot, eliminating the write-side memcpy.\n */\n writeFrame(pixels: Buffer, meta: FrameMeta): FrameHandle | null {\n if (this.destroyed) return null\n\n if (\n this.writer === null ||\n computeSlotByteLength(meta.width, meta.height, meta.format) >\n this.slotByteLength\n ) {\n this.recreateSegment(\n computeSlotByteLength(meta.width, meta.height, meta.format),\n )\n }\n\n const writer = this.writer\n if (writer === null) {\n // recreateSegment logs the failure; drop the frame.\n return null\n }\n const handle = writer.writeFrame(pixels, meta)\n this.framesWritten += 1\n return handle\n }\n\n /**\n * Reserve a ring slot for a frame of the given geometry — the **zero-copy**\n * scatter-write entry point (Phase 5 / D9 Task 7c).\n *\n * The segment is created / re-created here if this is the first frame or the\n * geometry overflows the current slot capacity, so the slot is correctly\n * sized before the caller fills it. The returned `buffer` is a writable view\n * **directly over the mapped segment** — the node-av scaler scatters its\n * packed output straight into it, with no intermediate copy. The caller MUST\n * call {@link commitFrame} with the returned `slot` once the slot is filled.\n *\n * Returns `null` when the sink is destroyed or the segment cannot be created.\n */\n beginFrame(\n width: number,\n height: number,\n format: FrameMeta['format'],\n ): DecoderFrameSlot | null {\n if (this.destroyed) return null\n\n const requiredSlotBytes = computeSlotByteLength(width, height, format)\n if (this.writer === null || requiredSlotBytes > this.slotByteLength) {\n this.recreateSegment(requiredSlotBytes)\n }\n\n const writer = this.writer\n if (writer === null) {\n // recreateSegment logs the failure; drop the frame.\n return null\n }\n const { slot, buffer }: FrameSlotWrite = writer.beginFrame()\n return { slot, buffer }\n }\n\n /**\n * Publish the frame whose slot was reserved by {@link beginFrame} and filled\n * in place by the caller. `slot` MUST be the value from the matching\n * `beginFrame`. Returns the published `FrameHandle`, or `null` if the sink\n * was destroyed (or the segment lost) between begin and commit.\n */\n commitFrame(slot: number, meta: FrameMeta): FrameHandle | null {\n if (this.destroyed) return null\n const writer = this.writer\n if (writer === null) return null\n const handle = writer.commitFrame(slot, meta)\n this.framesWritten += 1\n return handle\n }\n\n /**\n * Current shm ring usage — `null` until the first frame arms the segment.\n * Surfaced through `decoder.getShmStats` so a downstream consumer can\n * observe ring pressure (slot depth, byte budget, frames written).\n */\n getShmStats(): DecoderShmStats | null {\n if (this.writer === null) return null\n return {\n slotCount: this.writer.slotCount,\n slotByteLength: this.slotByteLength,\n segmentBytes: computeSegmentSize(this.writer.slotCount, this.slotByteLength),\n framesWritten: this.framesWritten,\n }\n }\n\n /**\n * Abandon a slot reserved by {@link beginFrame} **without publishing it** —\n * the degenerate-path counterpart of {@link commitFrame}.\n *\n * A caller that reserved a slot but then could not produce valid pixels (no\n * decoded source planes, or the scaler threw) MUST call this instead of\n * `commitFrame`: it closes the open seqlock without advancing `writeIndex`,\n * so no reader ever sees the slot's uninitialised bytes as a real frame, and\n * no `FrameHandle` is handed downstream. `slot` MUST be the value from the\n * matching `beginFrame`. A no-op if the sink was destroyed (or the segment\n * lost) between begin and abort.\n */\n abortFrame(slot: number): void {\n if (this.destroyed) return\n const writer = this.writer\n if (writer === null) return\n writer.abortFrame(slot)\n }\n\n /** Close + unlink the segment. Idempotent. */\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.releaseSegment()\n }\n\n /**\n * Create a fresh segment sized for at least `slotByteLength` bytes per slot,\n * replacing any prior one. A re-create bumps the generation so the new\n * segment has a distinct name — a consumer holding the old mapping is never\n * silently handed a resized segment.\n */\n private recreateSegment(slotByteLength: number): void {\n this.releaseSegment()\n\n this.generation += 1\n const name = makeSegmentName(this.seed, this.generation)\n // The slot count is derived per-resolution from the shared-memory budget:\n // a small frame gets many slots, a large one few, within one footprint.\n const slotCount = deriveSlotCount(RING_BUDGET_BYTES, slotByteLength)\n if (\n slotCount === MIN_RING_SLOTS &&\n MIN_RING_SLOTS * slotByteLength > RING_BUDGET_BYTES\n ) {\n this.logger.warn(\n 'decoder shm ring: budget too small for resolution — using MIN slots',\n { meta: { slotByteLength, budgetMb: RING_BUDGET_MB } },\n )\n }\n const totalBytes = computeSegmentSize(slotCount, slotByteLength)\n\n try {\n const segment = createSegment(name, totalBytes)\n this.segment = segment\n this.segmentName = name\n this.slotByteLength = slotByteLength\n this.writer = new FrameRingWriter(\n segment.buffer,\n name,\n slotCount,\n slotByteLength,\n this.nodeId,\n )\n this.logger.info('decoder shm ring: segment created', {\n meta: {\n segment: name,\n slotCount,\n slotByteLength,\n totalBytes,\n generation: this.generation,\n },\n })\n } catch (err) {\n this.segment = null\n this.writer = null\n this.segmentName = null\n this.slotByteLength = 0\n this.logger.error('decoder shm ring: segment create failed', {\n meta: {\n segment: name,\n slotByteLength,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n\n /** Unmap + unlink the current segment, if any. */\n private releaseSegment(): void {\n const segment = this.segment\n if (segment === null) return\n this.segment = null\n this.writer = null\n const name = this.segmentName\n this.segmentName = null\n try {\n segment.close()\n segment.unlink()\n this.logger.info('decoder shm ring: segment released', {\n meta: { segment: name },\n })\n } catch (err) {\n this.logger.warn('decoder shm ring: segment release failed', {\n meta: {\n segment: name,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n}\n","/**\n * NodeAvDecoderSession — IDecoderSession using node-av **push mode**.\n *\n * Receives raw H.264/H.265 Annex-B packets from the StreamBroker and decodes\n * them in-process using the node-av low-level API:\n *\n * CodecParser → splits raw Annex-B bytes into proper AVPackets\n * CodecContext → software decode (sendPacket / receiveFrame)\n * SoftwareScaleContext → scale + convert to target format\n *\n * Output format depends on config.outputFormat:\n * 'jpeg' → scale to RGB24, encode JPEG via sharp (for detection pipeline)\n * 'gray' → scale to GRAY8 raw buffer (for motion detection only)\n *\n * No child process, no duplicate RTSP connection.\n */\nimport type {\n IDecoderSession, DecoderSessionConfig, DecoderStats,\n EncodedPacket, DecodedFrame, Unsubscribe, IScopedLogger,\n HwAccelBackend, IKernelHwAccel, FrameHandle,\n} from '@camstack/types'\nimport type { AVPixelFormat, SwsFlags, AVError, AVHWDeviceType } from 'node-av/constants'\nimport { errMsg } from '@camstack/types'\nimport { DecoderFrameRingSink } from './frame-ring-sink.js'\n\n// Lazy-load node-av to avoid hard dependency at import time\ntype NodeAvModule = typeof import('node-av')\ntype NodeAvConstants = typeof import('node-av/constants')\n\nexport type HwAccelPref = 'auto' | 'none' | HwAccelBackend\n\n/**\n * Where a decoded frame's pixels go.\n *\n * - `'callback'` — the legacy in-process path: each decoded frame is delivered\n * to `onFrame` subscribers as a `DecodedFrame` carrying the pixel `Buffer`.\n * Required for the cross-host case (a remote consumer needs a detached copy)\n * and as the Phase 5 (D9) perf baseline (shm-handle vs in-process reference).\n * - `'shm'` — the Phase 5 shared-memory frame plane: each decoded frame is\n * written into an OS shared-memory ring and delivered to `onFrameHandle`\n * subscribers as a tiny serialisable `FrameHandle`. `onFrame` does NOT fire\n * in this mode — no pixel `Buffer` is handed to callbacks.\n */\nexport type DecoderFrameSink = 'callback' | 'shm'\n\n/** A decoded frame delivered as a shared-memory ring reference. */\nexport interface DecodedFrameHandle {\n readonly handle: FrameHandle\n /** Wall-clock time the frame was written into the ring. */\n readonly timestamp: number\n}\n\nexport interface NodeAvDecoderSessionOptions {\n /** Addon-level hwaccel preference — per-agent. Default `'auto'`. */\n readonly hwaccel?: HwAccelPref\n /**\n * Kernel hwaccel resolver — typically `ctx.kernel.hwaccel` passed\n * from the addon. When present, `hwaccel: 'auto'` calls\n * `resolver.resolve(null)` to get the platform-ordered backend\n * list; when absent, `'auto'` degrades to software (the session\n * cannot probe on its own because it lives in a package that must\n * not depend on `@camstack/kernel`).\n */\n readonly hwaccelResolver?: IKernelHwAccel\n /**\n * Frame delivery mode. Default `'callback'` (the legacy in-process pixel\n * path). `'shm'` writes frames into a shared-memory ring and delivers\n * `FrameHandle`s — see {@link DecoderFrameSink}.\n */\n readonly frameSink?: DecoderFrameSink\n /**\n * Cluster node id of the decoder addon that owns this session. Stamped\n * into every `FrameHandle` the `'shm'` sink produces so a downstream\n * consumer knows which node's `decoder.getFrame` to route a read through.\n * Default `'local'` when the addon has no resolved Moleculer node id.\n */\n readonly nodeId?: string\n}\n\n/** Map our canonical backend name to the node-av `AV_HWDEVICE_TYPE_*` constant. */\nfunction backendToHwDeviceConst(backend: HwAccelBackend, consts: NodeAvConstants): AVHWDeviceType | null {\n switch (backend) {\n case 'videotoolbox': return consts.AV_HWDEVICE_TYPE_VIDEOTOOLBOX\n case 'cuda': return consts.AV_HWDEVICE_TYPE_CUDA\n case 'nvdec': return consts.AV_HWDEVICE_TYPE_CUDA // node-av exposes only CUDA; nvdec aliases to it\n case 'vaapi': return consts.AV_HWDEVICE_TYPE_VAAPI\n case 'qsv': return consts.AV_HWDEVICE_TYPE_QSV\n case 'd3d11va': return consts.AV_HWDEVICE_TYPE_D3D11VA\n case 'dxva2': return consts.AV_HWDEVICE_TYPE_DXVA2\n case 'amf': return consts.AV_HWDEVICE_TYPE_AMF\n case 'vdpau': return consts.AV_HWDEVICE_TYPE_VDPAU\n case 'drm': return consts.AV_HWDEVICE_TYPE_DRM\n default: return null\n }\n}\n\n/** Pick the hw pixel format for a given backend — used in get_format callback. */\nfunction backendToHwPixFmt(backend: HwAccelBackend, consts: NodeAvConstants): AVPixelFormat | null {\n switch (backend) {\n case 'videotoolbox': return consts.AV_PIX_FMT_VIDEOTOOLBOX\n case 'cuda': return consts.AV_PIX_FMT_CUDA\n case 'nvdec': return consts.AV_PIX_FMT_CUDA\n case 'vaapi': return consts.AV_PIX_FMT_VAAPI\n case 'qsv': return consts.AV_PIX_FMT_QSV\n case 'd3d11va': return consts.AV_PIX_FMT_D3D11\n case 'dxva2': return consts.AV_PIX_FMT_DXVA2_VLD\n case 'amf': return consts.AV_PIX_FMT_D3D11\n case 'vdpau': return consts.AV_PIX_FMT_VDPAU\n case 'drm': return consts.AV_PIX_FMT_DRM_PRIME\n default: return null\n }\n}\n\nlet _nav: NodeAvModule | null = null\nlet _consts: NodeAvConstants | null = null\ntype SharpFn = (input: Buffer | Uint8Array, options?: Record<string, unknown>) => import('sharp').Sharp\n\nlet _sharp: SharpFn | null = null\n\nasync function getNodeAv(): Promise<NodeAvModule> {\n if (!_nav) _nav = await import('node-av')\n return _nav\n}\nasync function getConstants(): Promise<NodeAvConstants> {\n if (!_consts) _consts = await import('node-av/constants')\n return _consts\n}\nasync function getSharp(): Promise<SharpFn> {\n if (!_sharp) {\n const mod = await import('sharp')\n _sharp = mod.default as SharpFn\n }\n return _sharp\n}\n\nconst noopLogger: IScopedLogger = {\n debug() {}, info() {}, warn() {}, error() {},\n child() { return noopLogger },\n withTags() { return noopLogger },\n}\n\nexport class NodeAvDecoderSession implements IDecoderSession {\n private config: DecoderSessionConfig\n private readonly logger: IScopedLogger\n private frameCallbacks = new Set<(frame: DecodedFrame) => void>()\n private handleCallbacks = new Set<(frame: DecodedFrameHandle) => void>()\n private destroyed = false\n\n /**\n * Frame delivery mode (see {@link DecoderFrameSink}). `'shm'` lazily\n * constructs `frameRingSink` and routes every decoded frame through it.\n */\n private readonly frameSink: DecoderFrameSink\n /**\n * The shared-memory ring writer for this stream. Created lazily on the\n * first decoded frame in `'shm'` mode (the segment cannot be sized until\n * the output geometry is known) and torn down in `destroy`.\n */\n private frameRingSink: DecoderFrameRingSink | null = null\n\n // Low-level node-av objects (initialized on first keyframe)\n private parser: InstanceType<NodeAvModule['CodecParser']> | null = null\n private codecCtx: InstanceType<NodeAvModule['CodecContext']> | null = null\n private scaler: InstanceType<NodeAvModule['SoftwareScaleContext']> | null = null\n private avPacket: InstanceType<NodeAvModule['Packet']> | null = null\n private avFrame: InstanceType<NodeAvModule['Frame']> | null = null\n private dstFrame: InstanceType<NodeAvModule['Frame']> | null = null\n\n // Cached constants (loaded during init, used in hot path)\n // Hard-coded numeric defaults are replaced in initDecoder() with the real\n // branded constants from `node-av/constants`. The `as` cast at the literal\n // is a one-time branding bridge — once initDecoder runs we hold the exact\n // AVPixelFormat/SwsFlags/AVError values.\n private EAGAIN: AVError = -11 as AVError\n private PIX_FMT_GRAY8: AVPixelFormat = 8 as AVPixelFormat\n private PIX_FMT_RGB24: AVPixelFormat = 2 as AVPixelFormat\n private SWS_FAST_BILINEAR: SwsFlags = 1 as SwsFlags\n\n /**\n * Decoder output mode. Drives both the scaler's destination pixel\n * format and whether sharp runs the JPEG encode at the end:\n *\n * - `'jpeg'` — scaler→RGB24 → sharp encode → emit JPEG bytes\n * - `'rgb'` — scaler→RGB24 → emit raw RGB24 (no sharp)\n * - `'gray'` — scaler→GRAY8 → emit raw GRAY8 (no sharp)\n *\n * The broker holds the policy decision on which mode to request based\n * on its active subscribers; on-the-fly conversion (e.g. RGB→JPEG for\n * a WebRTC consumer that joined while detection holds the decoder in\n * RGB mode) happens broker-side via the per-frame conversion cache.\n */\n private outputMode: 'jpeg' | 'rgb' | 'gray'\n private sharpFn: SharpFn | null = null\n\n /**\n * Backpressure for the sharp JPEG encode pipeline. The broker\n * currently creates sessions with `maxFps: 0` (unlimited) and relies\n * on per-subscriber throttling, so without a bound the\n * fire-and-forget `sharp(...).toBuffer()` chain would accumulate\n * unboundedly whenever sharp falls behind the decoder. Cap at\n * `MAX_JPEG_INFLIGHT` pending encodes per session — any frame that\n * arrives while the cap is saturated is dropped and counted.\n */\n private static readonly MAX_JPEG_INFLIGHT = 2\n private jpegEncodeInFlight = 0\n\n /**\n * Map a `DecoderSessionConfig.outputFormat` value to one of the three\n * native scaler/encoder modes the session understands. The cap-level\n * format vocabulary is broader (it accepts `bgr`, `yuv420`) than what\n * libav's scaler is wired for here — anything else degrades to RGB\n * (the canonical raw mode) and the broker is expected to convert\n * downstream if a subscriber needs a different shape.\n */\n private static resolveOutputMode(format: string | undefined): 'jpeg' | 'rgb' | 'gray' {\n if (format === 'jpeg' || format === undefined) return 'jpeg'\n if (format === 'gray') return 'gray'\n return 'rgb'\n }\n\n private initialized = false\n private initializing = false\n private scalerInitializing = false\n /**\n * Monotonic counter incremented by `updateConfig` whenever the\n * scaler + dstFrame get invalidated (e.g. output format toggle).\n * `initScaler` captures the current value at entry and aborts — or\n * disposes the locally-built scaler — if the epoch moved while\n * its async init was in flight. Without this, a toggle racing an\n * in-flight init could leave two scalers allocated natively while\n * `this.scaler` only holds a reference to one → libav leak.\n */\n private scalerEpoch = 0\n /**\n * One-shot guard for the \"first frame\" diagnostic log + raw frame\n * dump. Setting this synchronously inside `emitDecodedFrame`\n * prevents re-entry — without it we were using `outputFrames === 0`\n * which stays true until the async sharp encode callback runs, so\n * several decoded frames could trigger the dump before the first\n * JPEG landed.\n */\n private firstFrameLogged = false\n\n // Output dimensions\n private outWidth = 0\n private outHeight = 0\n\n // FPS limiter\n private lastEmitTime = 0\n private minIntervalMs: number\n\n // Stats\n private inputPackets = 0\n private outputFrames = 0\n private droppedFrames = 0\n private totalDecodeTimeMs = 0\n private startTime = Date.now()\n\n private readonly hwaccelPref: HwAccelPref\n private readonly hwaccelResolver: IKernelHwAccel | null\n /** Cluster node id stamped into every `FrameHandle` the `'shm'` sink emits. */\n private readonly nodeId: string\n /** The backend that actually initialised successfully — `'none'` = software fallback. */\n private activeHwAccel: 'none' | HwAccelBackend = 'none'\n private hwDevice: InstanceType<NodeAvModule['HardwareDeviceContext']> | null = null\n private swTransferFrame: InstanceType<NodeAvModule['Frame']> | null = null\n\n constructor(\n config: DecoderSessionConfig,\n logger: IScopedLogger = noopLogger,\n options?: NodeAvDecoderSessionOptions,\n ) {\n this.config = { ...config }\n // Bind per-session tags so every warn/error from this decoder lands\n // with `deviceId` + `tag` populated — required for filtering log\n // bursts to a single misbehaving camera (e.g. node-av sendPacket\n // errors on one Reolink in a fleet of 8).\n const sessionTags: Record<string, string | number> = {}\n if (typeof config.deviceId === 'number') sessionTags['deviceId'] = config.deviceId\n if (typeof config.tag === 'string' && config.tag.length > 0) sessionTags['tag'] = config.tag\n this.logger = Object.keys(sessionTags).length > 0 ? logger.withTags(sessionTags) : logger\n this.minIntervalMs = config.maxFps > 0 ? 1000 / config.maxFps : 0\n this.outputMode = NodeAvDecoderSession.resolveOutputMode(config.outputFormat)\n this.hwaccelPref = options?.hwaccel ?? 'auto'\n this.hwaccelResolver = options?.hwaccelResolver ?? null\n this.frameSink = options?.frameSink ?? 'callback'\n this.nodeId = options?.nodeId ?? 'local'\n }\n\n /**\n * The shared-memory ring sink for this stream, or `null` before the first\n * `'shm'`-mode frame lazily creates it. Exposed so the owning addon can\n * surface ring stats via `decoder.getShmStats`.\n */\n get frameRingSinkOrNull(): DecoderFrameRingSink | null {\n return this.frameRingSink\n }\n\n /**\n * Lazily build the shared-memory ring sink for this stream. The segment\n * itself is still created lazily by the sink on its first `writeFrame`,\n * once the decoded geometry is known.\n */\n private ensureFrameRingSink(): DecoderFrameRingSink {\n if (this.frameRingSink === null) {\n // Seed the segment name with whatever stable identifiers the session\n // holds — `deviceId:tag` when present, else the session is anonymous\n // and the sink falls back to its own per-instance random salt.\n const seedParts: string[] = []\n if (typeof this.config.deviceId === 'number') {\n seedParts.push(String(this.config.deviceId))\n }\n if (typeof this.config.tag === 'string' && this.config.tag.length > 0) {\n seedParts.push(this.config.tag)\n }\n const seed = seedParts.length > 0 ? seedParts.join(':') : 'anon'\n this.frameRingSink = new DecoderFrameRingSink({\n seed, logger: this.logger, nodeId: this.nodeId,\n })\n }\n return this.frameRingSink\n }\n\n /**\n * Resolve the backend preference list and try each one against\n * node-av's HW context APIs. The first backend whose\n * `HardwareDeviceContext.create()` succeeds gets attached to\n * `codecCtx.hwDeviceCtx` + its hw pixel format registered via\n * `setHardwarePixelFormat`. On any failure, falls through to the\n * next backend; if all fail, returns with `activeHwAccel='none'`\n * and the decoder runs in software on the same context.\n */\n private async tryAttachHwAccel(nav: NodeAvModule, C: NodeAvConstants): Promise<void> {\n if (!this.codecCtx) return\n if (this.hwaccelPref === 'none') {\n this.activeHwAccel = 'none'\n return\n }\n\n // Resolve the ordered backend list. Explicit override → single-entry\n // list. `'auto'` → ask the kernel resolver running on this same\n // process (same hardware as the decoder). No resolver wired →\n // software fallback (no probe available in the session package).\n const explicit: HwAccelBackend | null =\n this.hwaccelPref === 'auto' ? null : this.hwaccelPref\n const resolution = this.hwaccelResolver\n ? await this.hwaccelResolver.resolve(explicit)\n : explicit\n ? { preferred: [explicit] as readonly HwAccelBackend[], rationale: 'explicit (no resolver)' }\n : { preferred: [] as readonly HwAccelBackend[], rationale: 'auto + no resolver → software' }\n\n if (resolution.preferred.length === 0) {\n this.activeHwAccel = 'none'\n return\n }\n\n for (const backend of resolution.preferred) {\n const deviceType = backendToHwDeviceConst(backend, C)\n const hwPixFmt = backendToHwPixFmt(backend, C)\n if (!deviceType || !hwPixFmt) continue\n\n const device = new nav.HardwareDeviceContext()\n const rc = device.create(deviceType)\n if (rc < 0) {\n this.logger.warn('node-av: hwaccel device create failed — trying next', {\n meta: { backend, rc },\n })\n device.free()\n continue\n }\n try {\n this.codecCtx.hwDeviceCtx = device\n this.codecCtx.setHardwarePixelFormat(hwPixFmt)\n } catch (err) {\n this.logger.warn('node-av: hwaccel context attach failed — trying next', {\n meta: { backend, error: errMsg(err) },\n })\n device.free()\n continue\n }\n this.hwDevice = device\n this.activeHwAccel = backend\n return\n }\n // Every preferred backend failed — log once and fall through to sw.\n this.logger.warn('node-av: no hwaccel backend initialised — using software', {\n meta: { attempted: resolution.preferred.join(','), rationale: resolution.rationale },\n })\n this.activeHwAccel = 'none'\n }\n\n /**\n * Download a HW frame (format == hw pix fmt) into a SW frame so the\n * rest of the pipeline (scaler, JPEG encoder, grayscale passthrough)\n * handles it identically to the pure-software path. Uses the sync\n * variant so the synchronous receive loop below doesn't need to be\n * async-ified. Returns `null` on transfer failure, meaning the\n * caller should drop the frame.\n */\n private transferHwFrame(hwFrame: InstanceType<NodeAvModule['Frame']>): InstanceType<NodeAvModule['Frame']> | null {\n if (this.activeHwAccel === 'none' || !this.swTransferFrame) return hwFrame\n const rc = hwFrame.hwframeTransferDataSync(this.swTransferFrame, 0)\n if (rc < 0) {\n this.logger.warn('node-av: hwframeTransferData failed', { meta: { rc } })\n return null\n }\n return this.swTransferFrame\n }\n\n /**\n * Initialize the decoder pipeline on the first keyframe.\n * After this returns, all hot-path methods are fully synchronous (except JPEG encode).\n */\n private async initDecoder(): Promise<void> {\n if (this.initialized || this.initializing || this.destroyed) return\n this.initializing = true\n\n try {\n const nav = await getNodeAv()\n const C = await getConstants()\n\n // Cache constants for hot path (branded AVPixelFormat/SwsFlags/AVError)\n this.EAGAIN = C.AVERROR_EAGAIN\n this.PIX_FMT_GRAY8 = C.AV_PIX_FMT_GRAY8\n this.PIX_FMT_RGB24 = C.AV_PIX_FMT_RGB24\n this.SWS_FAST_BILINEAR = C.SWS_FAST_BILINEAR\n\n // Pre-load sharp for JPEG encoding if needed\n if (this.outputMode === 'jpeg') {\n this.sharpFn = await getSharp()\n }\n\n // Suppress noisy FFmpeg logs — HEVC decoder emits ERROR-level\n // \"Could not find ref with POC\" on every missed reference frame.\n // AV_LOG_FATAL silences these while keeping genuine fatal errors.\n nav.Log.setLevel(C.AV_LOG_FATAL)\n\n // Determine codec\n const isHevc = this.config.codec === 'h265' || this.config.codec === 'hevc'\n const codecId = isHevc ? C.AV_CODEC_ID_HEVC : C.AV_CODEC_ID_H264\n\n // 1. Parser — splits raw Annex-B bytes into proper AVPackets\n this.parser = new nav.CodecParser()\n this.parser.init(codecId)\n\n // 2. Codec context — software decode\n const codec = nav.Codec.findDecoder(codecId)\n if (!codec) {\n this.logger.error('node-av: no decoder found', { meta: { codec: this.config.codec } })\n return\n }\n\n this.codecCtx = new nav.CodecContext()\n this.codecCtx.allocContext3(codec)\n\n if (this.config.width && this.config.height) {\n this.codecCtx.width = this.config.width\n this.codecCtx.height = this.config.height\n }\n\n this.codecCtx.threadCount = 1\n\n // Try HW accel backends in preference order. Any failure (device\n // create, hw_pix_fmt missing, open2 non-zero) falls through to\n // software decode on the same CodecContext — swFrame transfer is\n // a no-op when activeHwAccel stays 'none'.\n await this.tryAttachHwAccel(nav, C)\n\n const ret = await this.codecCtx.open2(codec)\n if (ret < 0) {\n // If HW attach tainted the context, reset and retry in software.\n if (this.activeHwAccel !== 'none') {\n this.logger.warn('node-av: open2 failed with hwaccel — retrying in software', {\n meta: { ret, hwAccel: this.activeHwAccel },\n })\n this.hwDevice?.free()\n this.hwDevice = null\n this.activeHwAccel = 'none'\n this.codecCtx = new nav.CodecContext()\n this.codecCtx.allocContext3(codec)\n if (this.config.width && this.config.height) {\n this.codecCtx.width = this.config.width\n this.codecCtx.height = this.config.height\n }\n this.codecCtx.threadCount = 1\n const retry = await this.codecCtx.open2(codec)\n if (retry < 0) {\n this.logger.error('node-av: failed to open decoder (sw fallback)', { meta: { ret: retry } })\n return\n }\n } else {\n this.logger.error('node-av: failed to open decoder', { meta: { ret } })\n return\n }\n }\n\n // Pre-allocate the sw transfer frame once when running HW — every\n // decoded frame lands on `avFrame` in hw memory, gets copied into\n // `swTransferFrame` via `hwframeTransferData`, and then feeds the\n // scaler just like the pure-software path.\n if (this.activeHwAccel !== 'none') {\n this.swTransferFrame = new nav.Frame()\n this.swTransferFrame.alloc()\n }\n\n // 3. Reusable packet and frame objects\n this.avPacket = new nav.Packet()\n this.avPacket.alloc()\n\n this.avFrame = new nav.Frame()\n this.avFrame.alloc()\n\n this.initialized = true\n this.logger.info('node-av push decoder initialized', {\n meta: {\n codec: this.config.codec,\n output: this.outputMode,\n // Reports the backend that actually succeeded at\n // `open2(codec)` with `hwDeviceCtx` attached, or `'none'` if\n // we fell back to software (explicit `hwaccel: 'none'`\n // override, empty resolver output, or every attempted\n // backend failed init).\n hwAccel: this.activeHwAccel,\n },\n })\n } catch (err) {\n this.logger.error('node-av init error', { meta: { error: errMsg(err) } })\n } finally {\n this.initializing = false\n }\n }\n\n /**\n * Initialize the scaler after the first frame tells us the actual\n * dimensions. Output pixel format: RGB24 for JPEG encoding, GRAY8\n * for raw motion.\n *\n * Builds `scaler` + `dstFrame` on local variables and publishes\n * them onto `this` in a single atomic step at the end. Captures\n * `scalerEpoch` at entry; if `updateConfig` invalidated the scaler\n * while this init was in flight (epoch mismatch), the locally\n * built pair is disposed and discarded so the later init wins.\n * Without the local-first approach, partial state (scaler set,\n * dstFrame still null) could be observed by a concurrent\n * `emitDecodedFrame` call.\n */\n private async initScaler(srcW: number, srcH: number, srcFmt: number): Promise<void> {\n if (this.scalerInitializing) return\n this.scalerInitializing = true\n const myEpoch = this.scalerEpoch\n\n let localScaler: InstanceType<NodeAvModule['SoftwareScaleContext']> | null = null\n let localDstFrame: InstanceType<NodeAvModule['Frame']> | null = null\n\n try {\n const nav = await getNodeAv()\n\n if (this.destroyed || myEpoch !== this.scalerEpoch) return\n\n const scale = this.config.scale > 1 ? this.config.scale : 1\n const maxW = Math.floor(640 / scale)\n const outWidth = Math.min(srcW, maxW)\n const outHeight = Math.round(outWidth * srcH / srcW)\n\n // RGB / JPEG modes: scaler outputs RGB24 (sharp may consume that\n // for the JPEG encode). Gray mode: scaler outputs GRAY8 directly\n // for the WASM motion path.\n const dstFmt = this.outputMode === 'gray' ? this.PIX_FMT_GRAY8 : this.PIX_FMT_RGB24\n const fmtName = this.outputMode === 'gray' ? 'gray8' : 'rgb24'\n\n localScaler = new nav.SoftwareScaleContext()\n localScaler.getContext(\n srcW, srcH, srcFmt as AVPixelFormat,\n outWidth, outHeight, dstFmt,\n this.SWS_FAST_BILINEAR,\n )\n const ret = localScaler.initContext()\n if (ret < 0) {\n this.logger.error('node-av: sws_init_context failed', { meta: { ret } })\n return\n }\n\n localDstFrame = new nav.Frame()\n localDstFrame.alloc()\n localDstFrame.width = outWidth\n localDstFrame.height = outHeight\n localDstFrame.format = dstFmt\n const allocRet = localDstFrame.allocBuffer()\n if (allocRet < 0) {\n this.logger.error('node-av: dst frame allocBuffer failed', { meta: { ret: allocRet } })\n return\n }\n\n // Epoch check right before publishing — if updateConfig bumped\n // the epoch while we were doing async work, discard the local\n // pair and let the subsequent initScaler (or none) produce the\n // winning scaler.\n if (this.destroyed || myEpoch !== this.scalerEpoch) return\n\n // Paranoia: dispose any prior scaler/dstFrame that might have\n // slipped in despite the epoch guard. Normal case: both null.\n this.scaler?.[Symbol.dispose]?.()\n this.dstFrame?.[Symbol.dispose]?.()\n\n this.scaler = localScaler\n this.dstFrame = localDstFrame\n this.outWidth = outWidth\n this.outHeight = outHeight\n localScaler = null\n localDstFrame = null\n\n this.logger.info('node-av scaler initialized', {\n meta: { srcWidth: srcW, srcHeight: srcH, outWidth, outHeight, format: fmtName },\n })\n } catch (err) {\n this.logger.error('Scaler init failed', { meta: { error: errMsg(err) } })\n } finally {\n // Dispose whatever wasn't successfully published.\n localScaler?.[Symbol.dispose]?.()\n localDstFrame?.[Symbol.dispose]?.()\n this.scalerInitializing = false\n }\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (this.destroyed) return\n this.inputPackets++\n\n if (!this.initialized && !this.initializing && packet.keyframe) {\n this.initDecoder().then(() => {\n if (this.initialized) this.decodeRawData(packet.data, packet.pts ?? Date.now())\n }).catch((err) => {\n this.logger.error('node-av init failed', { meta: { error: errMsg(err) } })\n })\n return\n }\n\n if (!this.initialized) return\n this.decodeRawData(packet.data, packet.pts ?? Date.now())\n }\n\n private decodeRawData(data: Buffer | Uint8Array, pts: number): void {\n if (!this.parser || !this.codecCtx || !this.avPacket || !this.avFrame) return\n\n const buf = Buffer.isBuffer(data) ? data : Buffer.from(data)\n const bigPts = BigInt(pts)\n let offset = 0\n\n while (offset < buf.length) {\n const remaining = buf.subarray(offset)\n const consumed = this.parser.parse2(\n this.codecCtx,\n this.avPacket,\n remaining,\n bigPts, bigPts,\n offset,\n )\n\n if (consumed < 0) {\n this.logger.warn('node-av parser error', { meta: { ret: consumed } })\n break\n }\n\n offset += consumed\n\n if (this.avPacket.size > 0) {\n this.decodePacket()\n this.avPacket.unref()\n }\n }\n }\n\n private decodePacket(): void {\n if (!this.codecCtx || !this.avFrame) return\n\n const sendRet = this.codecCtx.sendPacketSync(this.avPacket!)\n if (sendRet < 0 && sendRet !== this.EAGAIN) {\n this.logger.warn('node-av sendPacket error', { meta: { ret: sendRet } })\n return\n }\n\n while (true) {\n const recvRet = this.codecCtx.receiveFrameSync(this.avFrame)\n if (recvRet < 0) break\n\n // Pull HW frame into system memory when hwaccel is active. For\n // software decode the helper is a no-op and returns the input.\n const frame = this.transferHwFrame(this.avFrame)\n if (!frame) continue\n\n this.emitDecodedFrame(frame)\n }\n }\n\n private emitDecodedFrame(frame: InstanceType<NodeAvModule['Frame']>): void {\n const now = performance.now()\n\n if (this.minIntervalMs > 0 && now - this.lastEmitTime < this.minIntervalMs) {\n this.droppedFrames++\n return\n }\n\n // Lazy-init scaler on first decoded frame\n if (!this.scaler && !this.scalerInitializing) {\n this.initScaler(frame.width, frame.height, frame.format as number)\n return\n }\n\n if (!this.dstFrame || !this.scaler) return\n\n const decodeStart = performance.now()\n\n // Zero write-side copy fast path (Phase 5 / D9 Task 7c): in `'shm'` mode\n // with a raw output format (rgb/bgr/gray — not jpeg, which needs a sharp\n // encode), scale the decoded frame DIRECTLY into the shared-memory ring\n // slot. `sws_scale` writes packed pixels into the caller-supplied\n // destination buffer at `linesize = width × bpp` — no padding, so no\n // `extractPacked` row-copy and no `writeFrame` memcpy.\n if (this.frameSink === 'shm' && this.outputMode !== 'jpeg') {\n this.scaleIntoRingSlot(frame, decodeStart)\n return\n }\n\n try {\n this.dstFrame.makeWritable()\n this.scaler.scaleFrameSync(this.dstFrame, frame)\n } catch (err) {\n this.logger.warn('node-av scale error', { meta: { error: errMsg(err) } })\n return\n }\n\n // Log frame dimensions once + dump first frame for diagnosis.\n // Uses a sync-settable flag so the async JPEG encode doesn't let\n // multiple frames all pass the \"first frame\" guard.\n if (!this.firstFrameLogged) {\n this.firstFrameLogged = true\n const channels = this.outputMode === 'gray' ? 1 : 3\n const ls = this.dstFrame.linesize\n const buf = this.dstFrame.toBuffer()\n this.logger.info('dstFrame after scale', {\n meta: {\n phase: 'frame-debug',\n width: this.dstFrame.width,\n height: this.dstFrame.height,\n linesize: [ls[0], ls[1], ls[2]],\n expectedStride: this.dstFrame.width * channels,\n bufLen: buf.length,\n expectedPacked: this.dstFrame.width * channels * this.dstFrame.height,\n srcFormat: frame.format ?? '?',\n },\n })\n\n // Dump first raw frame for manual inspection (only when scaler\n // is producing RGB — gray-only mode skips the dump because the\n // existing debug fixture expects RGB24).\n if (this.outputMode !== 'gray') {\n import('node:fs').then(fsModule => {\n import('node:path').then(pathModule => {\n const dumpPath = pathModule.join(process.cwd(), 'camstack-data', 'debug-frame-rgb24.raw')\n fsModule.writeFileSync(dumpPath, buf)\n this.logger.info('Dumped first RGB24 frame', { meta: { phase: 'frame-debug', path: dumpPath, bytes: buf.length } })\n }).catch(() => { /* ignore */ })\n }).catch(() => { /* ignore */ })\n }\n }\n\n // Extract packed pixel data — dstFrame.toBuffer() may include linesize padding\n // that sharp/WASM consumers don't expect. Strip padding if linesize > expected stride.\n const rawBuf = this.extractPackedBuffer(this.dstFrame)\n\n if (this.outputMode === 'jpeg') {\n // Async JPEG encode via sharp — fire and forget per frame\n this.encodeAndEmitJpeg(rawBuf, decodeStart)\n } else if (this.outputMode === 'rgb') {\n // Raw RGB24 fast path — no sharp encode, no async hop. Detection\n // (and any other raw consumer) gets the buffer straight.\n this.emitRawFrame(rawBuf, 'rgb', decodeStart)\n } else {\n this.emitRawFrame(rawBuf, 'gray', decodeStart)\n }\n }\n\n /**\n * Scale a decoded frame **directly into a shared-memory ring slot** — the\n * zero write-side copy path (Phase 5 / D9 Task 7c).\n *\n * `beginFrame` reserves the slot (its seqlock open / odd — readers skip it);\n * `SoftwareScaleContext.scaleSync` (the low-level `sws_scale` mapping) writes\n * the packed pixels straight into the slot buffer with `linesize = width ×\n * bpp` — no linesize padding, so there is nothing to strip and nothing to\n * memcpy; `commitFrame` writes the metadata, closes the seqlock and publishes\n * the slot. The decoded source planes feed `scaleSync` as `srcSlice` +\n * `srcStride` (`frame.data` / `frame.linesize`).\n *\n * Used only for raw output formats (rgb/bgr/gray). The jpeg path keeps the\n * `dstFrame` + sharp encode route because sharp must consume an RGB buffer\n * before the final (variable-length) jpeg bytes exist.\n */\n private scaleIntoRingSlot(\n frame: InstanceType<NodeAvModule['Frame']>,\n decodeStart: number,\n ): void {\n if (!this.scaler) return\n\n const format: DecodedFrame['format'] = this.outputMode === 'gray' ? 'gray' : 'rgb'\n const channels = this.outputMode === 'gray' ? 1 : 3\n const sink = this.ensureFrameRingSink()\n\n // Reserve a slot sized for this geometry (the sink creates / re-creates the\n // segment here if needed — handles the first frame and resolution changes).\n const reserved = sink.beginFrame(this.outWidth, this.outHeight, format)\n if (reserved === null) {\n this.droppedFrames++\n return\n }\n\n // Source planes + strides of the decoded frame. `frame.data` is one buffer\n // per plane (Y/U/V for planar YUV); `frame.linesize` the matching strides.\n const srcPlanes = frame.data\n if (!srcPlanes || srcPlanes.length === 0) {\n // No decoded planes — abort the reserved slot. `abortFrame` closes the\n // open seqlock without advancing writeIndex and without returning a\n // handle, so the slot's uninitialised bytes are never published as a\n // frame. The begin/abort pairing keeps the writer balanced.\n sink.abortFrame(reserved.slot)\n this.droppedFrames++\n return\n }\n\n const dstStride = this.outWidth * channels\n try {\n // `sws_scale` reads one (plane, stride) pair per source plane — pass the\n // strides truncated to the count of populated planes `frame.data`\n // reports (`frame.linesize` is a fixed-length 8-entry array). The\n // destination is the single packed slot buffer at the tight `dstStride`:\n // `sws_scale` writes the packed pixels straight into the mapped ring\n // slot, no padding.\n const srcStrides = Array.from(frame.linesize).slice(0, srcPlanes.length)\n this.scaler.scaleSync(\n Array.from(srcPlanes),\n srcStrides,\n 0,\n frame.height,\n [reserved.buffer],\n [dstStride],\n )\n } catch (err) {\n this.logger.warn('node-av scale-into-slot error', {\n meta: { error: errMsg(err) },\n })\n // The scale failed — the slot holds garbage / partial bytes. Abort it:\n // `abortFrame` closes the open seqlock without advancing writeIndex and\n // without returning a handle, so the corrupt slot is never published as\n // a frame. The begin/abort pairing keeps the writer balanced.\n sink.abortFrame(reserved.slot)\n this.droppedFrames++\n return\n }\n\n const decodeMs = performance.now() - decodeStart\n this.totalDecodeTimeMs += decodeMs\n this.outputFrames++\n this.lastEmitTime = performance.now()\n\n if (!this.firstFrameLogged) {\n this.firstFrameLogged = true\n this.logger.info('node-av: scaled directly into shm ring slot', {\n meta: {\n phase: 'frame-debug',\n width: this.outWidth,\n height: this.outHeight,\n dstStride,\n format,\n },\n })\n }\n if (this.outputFrames === 1 || this.outputFrames % 500 === 0) {\n this.logger.info('node-av frame emitted', {\n meta: {\n frameNumber: this.outputFrames,\n width: this.outWidth,\n height: this.outHeight,\n format,\n decodeMs,\n sink: 'shm',\n subs: this.handleCallbacks.size,\n },\n })\n }\n\n const handle = sink.commitFrame(reserved.slot, {\n width: this.outWidth,\n height: this.outHeight,\n format,\n // pts uses performance.now() (monotonic, process-relative) for\n // ring-ordering; DecodedFrameHandle.timestamp carries the wall clock.\n pts: performance.now(),\n byteLength: dstStride * this.outHeight,\n })\n if (handle === null) {\n this.droppedFrames++\n return\n }\n const delivered: DecodedFrameHandle = { handle, timestamp: Date.now() }\n for (const cb of this.handleCallbacks) {\n cb(delivered)\n }\n }\n\n /**\n * Extract packed pixel buffer from a decoded frame.\n * FFmpeg's av_frame_get_buffer() may pad each row to alignment (32/64 bytes).\n * Sharp and WASM consumers expect tightly-packed rows (stride = width * channels).\n * If linesize matches expected stride, return the buffer directly (zero-copy).\n */\n private extractPackedBuffer(frame: { toBuffer: () => Buffer; data: ReadonlyArray<Buffer> | null; linesize: ReadonlyArray<number>; width: number; height: number }): Buffer {\n const channels = this.outputMode === 'gray' ? 1 : 3 // RGB24=3, GRAY8=1\n const expectedStride = frame.width * channels\n const actualStride = frame.linesize[0] ?? expectedStride\n\n // Use frame.data[0] (the actual plane buffer) instead of toBuffer()\n // because toBuffer() may return stale data after scaleFrameSync()\n const src = frame.data?.[0]\n if (!src) {\n return frame.toBuffer()\n }\n\n if (actualStride === expectedStride) {\n // No padding — but data[0] may be larger than needed (allocated with padding)\n // Copy only the packed data\n return Buffer.from(src.buffer, src.byteOffset, expectedStride * frame.height)\n }\n\n // Linesize has padding — copy row by row to strip it\n const dst = Buffer.allocUnsafe(expectedStride * frame.height)\n for (let y = 0; y < frame.height; y++) {\n src.copy(dst, y * expectedStride, y * actualStride, y * actualStride + expectedStride)\n }\n return dst\n }\n\n /**\n * Encode RGB24 raw buffer as JPEG and emit.\n *\n * Drops the frame (and counts it) when `MAX_JPEG_INFLIGHT` encodes\n * are already pending — prevents unbounded growth of the\n * fire-and-forget promise chain when sharp cannot keep up with the\n * decode rate.\n */\n private encodeAndEmitJpeg(rgb: Buffer, decodeStart: number): void {\n if (!this.sharpFn) return\n if (this.jpegEncodeInFlight >= NodeAvDecoderSession.MAX_JPEG_INFLIGHT) {\n this.droppedFrames++\n return\n }\n\n this.jpegEncodeInFlight++\n this.sharpFn(rgb, {\n raw: { width: this.outWidth, height: this.outHeight, channels: 3 },\n })\n .jpeg({ quality: 80, mozjpeg: false })\n .toBuffer()\n .then((jpegBuf) => {\n if (this.destroyed) return\n this.emitRawFrame(jpegBuf, 'jpeg', decodeStart)\n })\n .catch((err: unknown) => {\n this.logger.warn('sharp jpeg encode error', { meta: { error: errMsg(err) } })\n })\n .finally(() => {\n this.jpegEncodeInFlight--\n })\n }\n\n private emitRawFrame(data: Buffer, format: DecodedFrame['format'], decodeStart: number): void {\n const decodeMs = performance.now() - decodeStart\n this.totalDecodeTimeMs += decodeMs\n\n this.outputFrames++\n this.lastEmitTime = performance.now()\n\n if (this.outputFrames === 1 || this.outputFrames % 500 === 0) {\n this.logger.info('node-av frame emitted', {\n meta: {\n frameNumber: this.outputFrames,\n width: this.outWidth,\n height: this.outHeight,\n format,\n decodeMs,\n sink: this.frameSink,\n subs: this.frameSink === 'shm'\n ? this.handleCallbacks.size\n : this.frameCallbacks.size,\n },\n })\n }\n\n if (this.frameSink === 'shm') {\n this.emitShmFrame(data, format)\n return\n }\n\n const decodedFrame: DecodedFrame = {\n data,\n width: this.outWidth,\n height: this.outHeight,\n format,\n timestamp: Date.now(),\n }\n\n for (const cb of this.frameCallbacks) {\n cb(decodedFrame)\n }\n }\n\n /**\n * Write a decoded frame into this stream's shared-memory ring and deliver\n * the resulting `FrameHandle` to `onFrameHandle` subscribers. No pixel\n * `Buffer` is handed to callbacks — consumers open the segment and read the\n * pixels zero-copy via a `FrameRingReader`.\n */\n private emitShmFrame(data: Buffer, format: DecodedFrame['format']): void {\n const sink = this.ensureFrameRingSink()\n const handle = sink.writeFrame(data, {\n width: this.outWidth,\n height: this.outHeight,\n format,\n // The decoder does not carry a source PTS down to this point; use a\n // monotone-ish wall clock so a consumer can still order frames.\n // NOTE: pts uses performance.now() (monotonic, process-relative) while\n // DecodedFrameHandle.timestamp uses Date.now() (wall-clock epoch).\n // The two clocks are intentionally distinct — pts is for ring-ordering,\n // timestamp is for wall-clock correlation — and must not be compared.\n pts: performance.now(),\n byteLength: data.byteLength,\n })\n if (handle === null) {\n // The sink failed to create / write the segment — it has already logged\n // the cause. Count it as a drop so the stats reflect the loss.\n this.droppedFrames++\n return\n }\n const frame: DecodedFrameHandle = { handle, timestamp: Date.now() }\n for (const cb of this.handleCallbacks) {\n cb(frame)\n }\n }\n\n onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe {\n this.frameCallbacks.add(callback)\n return () => { this.frameCallbacks.delete(callback) }\n }\n\n /**\n * Subscribe to shared-memory frame handles. Fires only when the session was\n * created with `frameSink: 'shm'`; in the legacy `'callback'` mode no\n * handles are produced and this subscription never fires.\n */\n onFrameHandle(callback: (frame: DecodedFrameHandle) => void): Unsubscribe {\n this.handleCallbacks.add(callback)\n return () => { this.handleCallbacks.delete(callback) }\n }\n\n updateConfig(update: Partial<DecoderSessionConfig>): void {\n const prevFormat = this.config.outputFormat\n this.config = { ...this.config, ...update }\n if (update.maxFps !== undefined) {\n this.minIntervalMs = update.maxFps > 0 ? 1000 / update.maxFps : 0\n }\n // Reinitialize scaler when output format changes (e.g. gray → jpeg upgrade).\n // Bumping `scalerEpoch` causes any initScaler in flight to\n // dispose its locally-built scaler + dstFrame instead of\n // publishing them — prevents a double-init leak when toggles\n // race the async init path.\n if (update.outputFormat !== undefined && update.outputFormat !== prevFormat) {\n const prevMode = this.outputMode\n this.outputMode = NodeAvDecoderSession.resolveOutputMode(update.outputFormat)\n // Skip the scaler reinit when only the cap-level format label\n // changed but the underlying scaler mode (jpeg/rgb/gray) is the\n // same — saves one libav teardown + reallocation per swap.\n if (this.outputMode === prevMode) return\n this.scalerEpoch++\n if (this.scaler) {\n this.scaler[Symbol.dispose]?.()\n this.scaler = null\n }\n if (this.dstFrame) {\n this.dstFrame[Symbol.dispose]?.()\n this.dstFrame = null\n }\n // Re-load sharp if switching to jpeg\n if (this.outputMode === 'jpeg' && !this.sharpFn) {\n getSharp().then(fn => { this.sharpFn = fn }).catch(() => {})\n }\n this.logger.info('node-av: output format changed — scaler will reinit', {\n meta: { from: prevFormat, to: update.outputFormat, mode: this.outputMode },\n })\n }\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n this.frameCallbacks.clear()\n this.handleCallbacks.clear()\n\n // Close + unlink the shared-memory segment this stream owned. The seqlock\n // makes this safe even with consumers still mapped — a dead writer leaves\n // them reading stale-not-torn frames until they themselves close.\n this.frameRingSink?.destroy()\n this.frameRingSink = null\n\n this.dstFrame?.[Symbol.dispose]?.()\n this.avFrame?.[Symbol.dispose]?.()\n this.avPacket?.[Symbol.dispose]?.()\n this.scaler?.[Symbol.dispose]?.()\n this.parser?.[Symbol.dispose]?.()\n this.swTransferFrame?.[Symbol.dispose]?.()\n this.codecCtx?.[Symbol.dispose]?.()\n this.hwDevice?.free()\n\n this.dstFrame = null\n this.avFrame = null\n this.avPacket = null\n this.scaler = null\n this.parser = null\n this.codecCtx = null\n this.swTransferFrame = null\n this.hwDevice = null\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.outputFrames > 0 ? this.totalDecodeTimeMs / this.outputFrames : 0,\n droppedFrames: this.droppedFrames,\n }\n }\n\n get isPullMode(): boolean { return false }\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n DecoderHwAccelConfig,\n HwAccelChoice,\n ProviderRegistration,\n DecoderStats,\n IDecoderCapProvider,\n FrameFormat,\n FrameHandle,\n ShmRingStats,\n} from '@camstack/types'\nimport {\n BaseAddon,\n DEFAULT_DECODER_HWACCEL_CONFIG,\n HWACCEL_OPTIONS,\n decoderCapability,\n RingBuffer,\n} from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\nimport { NodeAvDecoderSession } from '../nodeav-decoder-session.js'\nimport { RING_BUDGET_MB } from '../frame-ring-sink.js'\n\n/**\n * Phase 2d of the pipeline-settings migration — decoder addon now\n * owns its own `hwaccel` choice + `probedBestHwaccel` hint. Sessions\n * resolve the effective backend from this addon's global settings\n * instead of the orchestrator's legacy `AgentPipelineSettings.hwaccel`.\n * Shared config shape + UI options live in `@camstack/types`\n * (`DecoderHwAccelConfig` / `HWACCEL_OPTIONS`) so every decoder addon\n * speaks the same vocabulary.\n */\n\n/** Cap-compatible frame shape — format must match DecodedFrameSchema enum values. */\ntype CapDecodedFrame = {\n data: Uint8Array<ArrayBuffer>\n width: number\n height: number\n format: FrameFormat\n timestamp: number\n}\n\n/** Frame delivery mode of a decoder session — matches `DecoderSessionConfigSchema.frameSink`. */\ntype CapFrameSink = 'callback' | 'shm'\n\n/** Cap-compatible session config — matches DecoderSessionConfigSchema output type. */\ntype CapDecoderSessionConfig = {\n codec: string\n maxFps: number\n outputFormat: FrameFormat\n scale: number\n width?: number\n height?: number\n deviceId?: number\n tag?: string\n frameSink: CapFrameSink\n}\n\n/** Cap-compatible encoded packet — data is Uint8Array matching EncodedPacketSchema. */\ntype CapEncodedPacket = {\n type: 'video' | 'audio'\n data: Uint8Array<ArrayBuffer>\n pts: number\n dts: number\n keyframe: boolean\n codec: string\n}\n\n// Bounds both the pixel-frame (callback mode) and FrameHandle (shm mode) ring buffers.\n// A FrameHandle is tiny (~tens of bytes), so 32 is generous for the shm path;\n// the limit exists to cap memory if the broker stalls draining.\nconst FRAME_BUFFER_CAPACITY = 32\n\n/**\n * Decoder addon using node-av (FFmpeg native bindings).\n *\n * Hardware-accelerated decode via VideoToolbox (macOS), VAAPI (Linux), CUDA (NVIDIA).\n * Outputs grayscale frames for motion detection — zero JPEG overhead.\n *\n * Implements the sessionId-based IDecoderCapProvider cap interface.\n * Sessions are managed internally via a Map; frames are polled via RingBuffer.\n */\n/** Per-session metadata recorded at creation time, surfaced via `listActiveSessions`. */\ninterface SessionMeta {\n readonly codec: string\n readonly outputFormat: string\n readonly createdAtMs: number\n}\n\nexport default class DecoderNodeAvAddon extends BaseAddon<DecoderHwAccelConfig> implements IDecoderCapProvider {\n /**\n * Sessions are stored as the concrete `NodeAvDecoderSession` (the only type\n * this addon ever creates) so `getShmStats` can reach the per-session shm\n * ring sink — `IDecoderSession` does not expose it.\n */\n private readonly sessions = new Map<string, NodeAvDecoderSession>()\n /** Pixel-frame buffers — populated only for `frameSink: 'callback'` sessions. */\n private readonly frameBuffers = new Map<string, RingBuffer<CapDecodedFrame>>()\n /** `FrameHandle` buffers — populated only for `frameSink: 'shm'` sessions. */\n private readonly handleBuffers = new Map<string, RingBuffer<FrameHandle>>()\n private readonly unsubscribers = new Map<string, () => void>()\n private readonly sessionMeta = new Map<string, SessionMeta>()\n /**\n * Per-shm-segment `FrameRingReader` cache backing `getFrame`. Opens each\n * named segment once and reuses the reader for every later handle on it;\n * built in `onInitialize` (it needs the scoped logger) and closed in\n * `onShutdown`.\n */\n private frameReaders: FrameRingReaderCache | null = null\n /** Running `getFrame` hit/miss counters surfaced via `getShmStats`. */\n private getFrameHits = 0\n private getFrameMisses = 0\n\n constructor() { super(DEFAULT_DECODER_HWACCEL_CONFIG) }\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [{\n id: 'hwaccel',\n title: 'Hardware acceleration',\n tab: 'decoder',\n description: 'Backend used by node-av decoder sessions. \"Auto\" defers to the probed best; concrete backends force it. Changes apply to NEW sessions — existing sessions keep the backend they were created with.',\n fields: [\n this.field({\n type: 'select',\n key: 'hwaccel',\n label: 'Preferred backend',\n options: [...HWACCEL_OPTIONS],\n default: 'auto',\n immediate: true,\n }),\n this.field({\n type: 'text',\n key: 'probedBestHwaccel',\n label: 'Probed best',\n description: 'Auto-detected best decoder backend on this host. Click the refresh icon to re-run the probe.',\n readonlyField: true,\n default: '',\n actions: [\n { action: 'reprobe-hwaccel', icon: 'refresh-cw', tooltip: 'Re-probe hwaccel' },\n ],\n }),\n ],\n }],\n })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.ctx.logger.info('node-av decoder addon initialized')\n // The reader cache needs the scoped logger — `ctx` is only set now.\n this.frameReaders = new FrameRingReaderCache(this.ctx.logger)\n // Auto-seed probedBestHwaccel on first boot so the UI can show\n // the node's best backend without waiting for an operator click.\n if (!this.config.probedBestHwaccel) {\n this.reprobeHwaccel().catch((err: unknown) => {\n this.ctx.logger.warn('nodeav: auto-reprobe hwaccel failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n return [{ capability: decoderCapability, provider: this }]\n }\n\n /**\n * Resolve the effective hwaccel backend for a new session. Reads\n * this addon's own `hwaccel` setting first. `'auto'` defers to the\n * session's local resolver (`ctx.kernel.hwaccel`) which probes the\n * host and picks. No more orchestrator round-trip — decoder addon\n * is self-sufficient for this setting as of phase 2d.\n */\n private resolveHwAccelPref(): HwAccelChoice {\n return this.config.hwaccel\n }\n\n /**\n * Re-run the platform probe on this host and persist the detected\n * backend as `probedBestHwaccel`. The operator's `hwaccel` setting\n * is intentionally left alone — the probe only updates the hint.\n */\n async reprobeHwaccel(): Promise<{ backend: string }> {\n const resolver = this.ctx.kernel.hwaccel\n if (!resolver) {\n this.ctx.logger.warn('reprobeHwaccel: no kernel hwaccel resolver — returning none')\n await this.ctx.settings?.writeAddonStore({ probedBestHwaccel: 'none' })\n return { backend: 'none' }\n }\n try {\n const res = await resolver.resolve()\n const backend = (res.preferred[0] ?? 'none') as string\n await this.ctx.settings?.writeAddonStore({ probedBestHwaccel: backend })\n this.ctx.logger.info('reprobeHwaccel: wrote probedBestHwaccel', {\n meta: { backend, rationale: res.rationale, preferred: res.preferred },\n })\n return { backend }\n } catch (err) {\n this.ctx.logger.warn('reprobeHwaccel failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n await this.ctx.settings?.writeAddonStore({ probedBestHwaccel: 'none' })\n return { backend: 'none' }\n }\n }\n\n async supportsCodec(input: { codec: string }): Promise<boolean> {\n return ['h264', 'h265', 'hevc'].includes(input.codec.toLowerCase())\n }\n\n async getInfo(): Promise<{ id: string; name: string; isPullMode?: boolean; priority?: number }> {\n return {\n id: 'decoder-nodeav',\n name: 'Decoder (node-av)',\n isPullMode: false,\n priority: 10,\n }\n }\n\n /**\n * The cluster node id of this decoder — the Moleculer nodeID (`hub`,\n * `dev-agent-0`, …), not the addon id. Stamped into session-owned\n * `FrameHandle`s so a downstream consumer routes `getFrame` to the node\n * that holds the shm ring. A hierarchical broker nodeID (`hub/...`) is\n * collapsed to the cluster-visible parent; in-process boot is left as-is.\n * Mirrors the resolution `PipelineRunnerAddon.onInitialize` performs.\n */\n private resolveLocalNodeId(): string {\n const raw = this.ctx.kernel.localNodeId ?? this.ctx.id\n return raw.includes('/') ? raw.split('/')[0]! : raw\n }\n\n async createSession(config: CapDecoderSessionConfig): Promise<{ sessionId: string; nodeId: string }> {\n const sessionId = randomUUID()\n const hwaccel = this.resolveHwAccelPref()\n const { frameSink } = config\n const nodeId = this.resolveLocalNodeId()\n const session = new NodeAvDecoderSession(config, this.ctx.logger, {\n hwaccel,\n hwaccelResolver: this.ctx.kernel.hwaccel,\n frameSink,\n nodeId,\n })\n\n // A session is exactly one mode: `'shm'` buffers `FrameHandle`s drained\n // by `pullHandles`; `'callback'` buffers pixel frames drained by\n // `pullFrames`. The unused buffer map is simply never populated.\n const unsub = frameSink === 'shm'\n ? this.wireShmSink(sessionId, session)\n : this.wireCallbackSink(sessionId, session)\n\n this.sessions.set(sessionId, session)\n this.unsubscribers.set(sessionId, unsub)\n this.sessionMeta.set(sessionId, {\n codec: config.codec,\n outputFormat: config.outputFormat,\n createdAtMs: Date.now(),\n })\n\n this.ctx.logger.info('node-av: created session', { meta: { sessionId, codec: config.codec, hwaccelPref: hwaccel, frameSink, nodeId } })\n return { sessionId, nodeId }\n }\n\n /**\n * Subscribe a `'callback'` session's pixel frames into a per-session\n * ring buffer drained by `pullFrames`.\n */\n private wireCallbackSink(sessionId: string, session: NodeAvDecoderSession): () => void {\n const ringBuffer = new RingBuffer<CapDecodedFrame>(FRAME_BUFFER_CAPACITY)\n this.frameBuffers.set(sessionId, ringBuffer)\n return session.onFrame((frame) => {\n // Map internal DecodedFrame to cap-compatible shape.\n const { format } = frame\n if (format !== 'jpeg' && format !== 'rgb' && format !== 'bgr' && format !== 'yuv420' && format !== 'gray') return\n // Copy frame data into a fresh ArrayBuffer so the cap-facing Uint8Array\n // has a concrete ArrayBuffer (not a SharedArrayBuffer / Buffer backing).\n const arrayBuf = new ArrayBuffer(frame.data.byteLength)\n new Uint8Array(arrayBuf).set(frame.data)\n const capData = new Uint8Array(arrayBuf)\n const capFrame: CapDecodedFrame = {\n data: capData,\n width: frame.width,\n height: frame.height,\n format,\n timestamp: frame.timestamp,\n }\n ringBuffer.push(capFrame)\n })\n }\n\n /**\n * Subscribe a `'shm'` session's `FrameHandle`s into a per-session ring\n * buffer drained by `pullHandles`. No pixel bytes cross the cap boundary\n * — the broker opens the named segment and reads the pixels zero-copy.\n */\n private wireShmSink(sessionId: string, session: NodeAvDecoderSession): () => void {\n const handleRing = new RingBuffer<FrameHandle>(FRAME_BUFFER_CAPACITY)\n this.handleBuffers.set(sessionId, handleRing)\n return session.onFrameHandle((frame) => {\n // `frame.timestamp` is intentionally not propagated: it is a decoder-process-local\n // wall-clock epoch and is outside the serializable cap contract. The `FrameHandle`\n // already carries `pts` (the codec/presentation timestamp), which IS part of the\n // contract and crosses the cap boundary correctly.\n handleRing.push(frame.handle)\n })\n }\n\n async destroySession(input: { sessionId: string }): Promise<void> {\n const { sessionId } = input\n const session = this.sessions.get(sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${sessionId}`)\n }\n\n const unsub = this.unsubscribers.get(sessionId)\n if (unsub) unsub()\n\n await session.destroy()\n\n this.sessions.delete(sessionId)\n this.frameBuffers.delete(sessionId)\n this.handleBuffers.delete(sessionId)\n this.unsubscribers.delete(sessionId)\n this.sessionMeta.delete(sessionId)\n\n this.ctx.logger.info('node-av: destroyed session', { meta: { sessionId } })\n }\n\n async listActiveSessions(): Promise<readonly { sessionId: string; codec: string; outputFormat: string; createdAtMs: number }[]> {\n const out: Array<{ sessionId: string; codec: string; outputFormat: string; createdAtMs: number }> = []\n for (const [sessionId, meta] of this.sessionMeta) {\n out.push({ sessionId, codec: meta.codec, outputFormat: meta.outputFormat, createdAtMs: meta.createdAtMs })\n }\n return out\n }\n\n async pushPacket(input: { sessionId: string; packet: CapEncodedPacket }): Promise<void> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n // Convert Uint8Array/Buffer at the cap boundary before passing to the internal session.\n // MsgPack may deliver binary as Buffer, Uint8Array, or (in edge cases) plain Object\n // with numeric keys when the binary exceeds msgpack5's bin threshold.\n const rawData = input.packet.data\n const data = Buffer.isBuffer(rawData)\n ? rawData\n : rawData instanceof Uint8Array\n ? Buffer.from(rawData.buffer, rawData.byteOffset, rawData.byteLength)\n : Buffer.from(rawData as unknown as number[])\n session.pushPacket({ ...input.packet, data })\n }\n\n async openStream(input: { sessionId: string; url: string }): Promise<void> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n // `NodeAvDecoderSession` is push-mode only — it consumes packets via\n // `pushPacket`. `openStream` is part of the cap surface for future\n // pull-mode decoders (e.g. ffmpeg dialing RTSP directly); on this\n // addon it is a no-op.\n void input.url\n }\n\n async pullFrames(input: { sessionId: string; maxCount: number }): Promise<CapDecodedFrame[]> {\n if (!this.sessions.has(input.sessionId)) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n // A `frameSink: 'shm'` session has no pixel buffer — `pullFrames`\n // returns nothing for it (the caller drains `pullHandles` instead).\n const ringBuffer = this.frameBuffers.get(input.sessionId)\n if (!ringBuffer) return []\n return ringBuffer.drain(input.maxCount)\n }\n\n async pullHandles(input: { sessionId: string; maxCount: number }): Promise<FrameHandle[]> {\n if (!this.sessions.has(input.sessionId)) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n // A `frameSink: 'callback'` session has no handle buffer — `pullHandles`\n // returns nothing for it (the caller drains `pullFrames` instead).\n const handleRing = this.handleBuffers.get(input.sessionId)\n if (!handleRing) return []\n return handleRing.drain(input.maxCount)\n }\n\n async updateConfig(input: { sessionId: string; config: Partial<CapDecoderSessionConfig> }): Promise<void> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n session.updateConfig(input.config)\n }\n\n async getStats(input: { sessionId: string }): Promise<DecoderStats> {\n const session = this.sessions.get(input.sessionId)\n if (!session) {\n throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`)\n }\n return session.getStats()\n }\n\n /**\n * Read back the pixels a `FrameHandle` refers to from this node's shm ring\n * (Phase 5 / D9 downstream access). Returns `null` when the slot was\n * already recycled (latest-wins drop) or the segment could not be opened.\n * The reader cache opens each named segment once and reuses the reader.\n *\n * The reader hands back a `Buffer` view over its **reusable scratch\n * buffer**, only valid until the next `read` on the same reader; the\n * cap caller may keep the frame across reads, so the pixels are copied\n * into a fresh detached `ArrayBuffer` here. This matches the existing\n * callback-path copy in `wireCallbackSink`.\n */\n async getFrame(input: { handle: FrameHandle }): Promise<CapDecodedFrame | null> {\n const frame = this.frameReaders?.read(input.handle) ?? null\n if (!frame) {\n this.getFrameMisses += 1\n return null\n }\n this.getFrameHits += 1\n const arrayBuf = new ArrayBuffer(frame.data.byteLength)\n new Uint8Array(arrayBuf).set(frame.data)\n return {\n data: new Uint8Array(arrayBuf),\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n }\n\n /**\n * shm ring usage stats for a `frameSink: 'shm'` session — slot geometry,\n * frames written, byte budget, plus this addon's running `getFrame`\n * hit/miss counters. Returns `null` for an unknown session or one whose\n * shm ring has not yet been armed by a first decoded frame.\n */\n async getShmStats(input: { sessionId: string }): Promise<ShmRingStats | null> {\n const session = this.sessions.get(input.sessionId)\n const stats = session?.frameRingSinkOrNull?.getShmStats() ?? null\n if (!stats) return null\n return {\n sessionId: input.sessionId,\n ...stats,\n budgetMb: RING_BUDGET_MB,\n getFrameHits: this.getFrameHits,\n getFrameMisses: this.getFrameMisses,\n }\n }\n\n protected async onShutdown(): Promise<void> {\n this.ctx.logger.info('node-av decoder addon shutdown — destroying all sessions')\n const destroyPromises: Promise<void>[] = []\n for (const [sessionId, session] of this.sessions) {\n const unsub = this.unsubscribers.get(sessionId)\n if (unsub) unsub()\n destroyPromises.push(session.destroy())\n }\n await Promise.all(destroyPromises)\n this.sessions.clear()\n this.frameBuffers.clear()\n this.handleBuffers.clear()\n this.unsubscribers.clear()\n this.sessionMeta.clear()\n // Release every cached shm `FrameRingReader` mapping opened by `getFrame`.\n this.frameReaders?.close()\n this.frameReaders = null\n }\n}\n"],"names":["computeSlotByteLength","computeSegmentSize","deriveSlotCount","MIN_RING_SLOTS","createSegment","FrameRingWriter","errMsg","BaseAddon","DEFAULT_DECODER_HWACCEL_CONFIG","HWACCEL_OPTIONS","FrameRingReaderCache","decoderCapability","randomUUID","RingBuffer"],"mappings":"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"}