@camstack/addon-pipeline 1.1.58 → 1.1.59

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 (37) hide show
  1. package/dist/audio-analyzer/index.js +4 -4
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/detection-pipeline/index.js +9 -9
  4. package/dist/detection-pipeline/index.mjs +3 -3
  5. package/dist/{dist-Bn6Uq4vq.js → dist-B1u-QN05.js} +176 -12
  6. package/dist/{dist-CnwwDzX3.mjs → dist-DKeNH_5z.mjs} +176 -12
  7. package/dist/{model-download-service-C-IHWnXx-3Mmeob3l.mjs → model-download-service-Cp9f4dk6-0wh7OK3s.mjs} +2 -2
  8. package/dist/{model-download-service-C-IHWnXx-D326YNnt.js → model-download-service-Cp9f4dk6-Cp_GTKTw.js} +2 -2
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +123 -4
  12. package/dist/pipeline-runner/index.mjs +122 -3
  13. package/dist/recorder/index.js +993 -7
  14. package/dist/recorder/index.mjs +990 -5
  15. package/dist/session-decode/decode-worker-child.js +14 -1
  16. package/dist/session-decode/decode-worker-child.mjs +14 -1
  17. package/dist/{hub-hostname-cCknRYKj.mjs → sheet-geometry-BN80zpCa.js} +56 -1
  18. package/dist/{hub-hostname-DAJXlOgV.js → sheet-geometry-Bk3fR4s7.mjs} +39 -6
  19. package/dist/{step-definitions-D7NOrnzz.js → step-definitions-CPch37vh.js} +1 -1
  20. package/dist/{step-definitions-DhEe57Wn.mjs → step-definitions-DUB27V8Y.mjs} +1 -1
  21. package/dist/stream-broker/_stub.js +2 -2
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B8FQ1e0a.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DdkfKDgw.mjs} +3 -3
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D3h_W9uu.mjs +26 -0
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DSXctoaw.mjs +26 -0
  25. package/dist/stream-broker/{hostInit-D1Vdyu-4.mjs → hostInit-BLn2Pv8Q.mjs} +3 -3
  26. package/dist/stream-broker/index.js +622 -149
  27. package/dist/stream-broker/index.mjs +622 -149
  28. package/dist/stream-broker/remoteEntry.js +1 -1
  29. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CG5EE0W4.js → MaskShapeCanvas-DI4BY7W2-B0_nx15B.js} +1 -1
  30. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BFeLUNOl.js → MotionZonesSettings-NcxxQN8r-B8uM-VeO.js} +1 -1
  31. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-pQQunq1F.js → PrivacyMaskSettings-APgPLF7p-DMOs4Hj9.js} +1 -1
  32. package/embed-dist/assets/index-W9-H5LTj.js +81 -0
  33. package/embed-dist/index.html +1 -1
  34. package/package.json +1 -1
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Ba7Eyd47.mjs +0 -26
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-TWCUWhCm.mjs +0 -26
  37. package/embed-dist/assets/index-BxqQ931j.js +0 -81
@@ -1,8 +1,8 @@
1
- import { D as nodePin, I as errMsg, L as BaseAddon, Q as boolean, V as createEvent, X as _enum, Z as array, _ as customAction, at as string, et as lazy, k as pipelineRunnerCapability, nt as number, rt as object, st as EventCategory, y as defineCustomActions } from "../dist-CnwwDzX3.mjs";
1
+ import { D as nodePin, I as errMsg, L as BaseAddon, Q as boolean, V as createEvent, X as _enum, Z as array, _ as customAction, at as string, et as lazy, k as pipelineRunnerCapability, nt as number, rt as object, st as EventCategory, y as defineCustomActions } from "../dist-DKeNH_5z.mjs";
2
2
  import { n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-BeHi78PZ.mjs";
3
- import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
3
+ import { r as resolveHubHostname } from "../sheet-geometry-Bk3fR4s7.mjs";
4
4
  import { t as isWorkerReply } from "../worker-protocol-CyVJTZEO.mjs";
5
- import { a as getStepDefinition } from "../step-definitions-DhEe57Wn.mjs";
5
+ import { a as getStepDefinition } from "../step-definitions-DUB27V8Y.mjs";
6
6
  import { fork } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import sharp from "sharp";
@@ -1273,6 +1273,40 @@ function cropRgb(frame, bbox, maxWidth) {
1273
1273
  height: outH
1274
1274
  };
1275
1275
  }
1276
+ var ThumbSamplerPolicy = class {
1277
+ intervalMs;
1278
+ minGapMs;
1279
+ now;
1280
+ /** Last accepted-sample wall clock per device. */
1281
+ lastAcceptedAt = /* @__PURE__ */ new Map();
1282
+ constructor(options) {
1283
+ this.intervalMs = Math.max(0, options.intervalMs);
1284
+ this.minGapMs = Math.max(0, options.minGapMs ?? 1e3);
1285
+ this.now = options.now ?? (() => Date.now());
1286
+ }
1287
+ /**
1288
+ * Decide whether to sample `deviceId` now. Returns true (and records the
1289
+ * acceptance) when the larger of `intervalMs` / `minGapMs` has elapsed since
1290
+ * the last accepted sample for this device; false otherwise. The FIRST call
1291
+ * for a device always samples (cold start).
1292
+ */
1293
+ shouldSample(deviceId) {
1294
+ const now = this.now();
1295
+ const gate = Math.max(this.intervalMs, this.minGapMs);
1296
+ const last = this.lastAcceptedAt.get(deviceId);
1297
+ if (last !== void 0 && now - last < gate) return false;
1298
+ this.lastAcceptedAt.set(deviceId, now);
1299
+ return true;
1300
+ }
1301
+ /** Forget a device (detach / phase exit) so its next frame cold-starts. */
1302
+ forget(deviceId) {
1303
+ this.lastAcceptedAt.delete(deviceId);
1304
+ }
1305
+ /** Drop all per-device state (shutdown). */
1306
+ clear() {
1307
+ this.lastAcceptedAt.clear();
1308
+ }
1309
+ };
1276
1310
  //#endregion
1277
1311
  //#region src/session-decode/session-decode-coordinator.ts
1278
1312
  /** How long to wait after `kill()` before escalating to `kill('SIGKILL')`. */
@@ -2023,6 +2057,8 @@ async function runDetailSubtree(deps, input) {
2023
2057
  //#region src/pipeline-runner/index.ts
2024
2058
  var DEFAULT_CONFIG = {
2025
2059
  maxQueueDepth: 30,
2060
+ thumbnailsEnabled: true,
2061
+ thumbnailSampleMs: 5e3,
2026
2062
  maxConcurrentInferences: 16,
2027
2063
  targetLoadPercent: 80,
2028
2064
  minThrottledFps: 1,
@@ -2344,6 +2380,12 @@ var PipelineRunnerAddon = class extends BaseAddon {
2344
2380
  * Initialised in {@link onInitialize} once `nodeId` is known.
2345
2381
  */
2346
2382
  retainedFrames = null;
2383
+ /**
2384
+ * Scrub-thumbnail sampler policy (2026-07-16). Gates the per-camera JPEG
2385
+ * encode to ~1 / `thumbnailSampleMs` (with a hard 1 Hz ceiling) so the cost
2386
+ * stays a couple ms of sharp every few seconds. Null until `onInitialize`.
2387
+ */
2388
+ thumbSampler = null;
2347
2389
  attached = /* @__PURE__ */ new Map();
2348
2390
  nodeId = "unknown";
2349
2391
  metricsSnapshotTimer = null;
@@ -2464,6 +2506,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
2464
2506
  nodeId: this.nodeId,
2465
2507
  logger: this.ctx.logger.child("retained-frames")
2466
2508
  });
2509
+ this.thumbSampler = new ThumbSamplerPolicy({ intervalMs: this.config.thumbnailSampleMs });
2467
2510
  this.runner = new PipelineRunner({
2468
2511
  maxQueueDepth: this.config.maxQueueDepth,
2469
2512
  maxConcurrentInferences: this.config.maxConcurrentInferences,
@@ -2553,6 +2596,8 @@ var PipelineRunnerAddon = class extends BaseAddon {
2553
2596
  this.nativeCropRegistry.clear();
2554
2597
  this.retainedFrames?.clear();
2555
2598
  this.retainedFrames = null;
2599
+ this.thumbSampler?.clear();
2600
+ this.thumbSampler = null;
2556
2601
  }
2557
2602
  async cacheBenchFrame(input) {
2558
2603
  const sharp = (await import("sharp")).default;
@@ -3015,6 +3060,59 @@ var PipelineRunnerAddon = class extends BaseAddon {
3015
3060
  frameSpace: denormalizeRect(paddedBbox, handle.width, handle.height)
3016
3061
  };
3017
3062
  }
3063
+ /**
3064
+ * Scrub-thumbnail sampler (2026-07-16). Rides the already-decoded rgb
3065
+ * detection frame: at most once per `thumbnailSampleMs` per camera (policy
3066
+ * enforces a 1 Hz hard ceiling), downscale to a ~320px JPEG and emit it as
3067
+ * `recording.thumb-sampled` telemetry. The recorder (single designated node)
3068
+ * packs 5-minute sprite sheets from these. Fire-and-forget — a failed encode
3069
+ * is logged at debug and never disturbs the decode/detect path. Zero extra
3070
+ * decode (frame exists); one sharp encode per cadence per camera (D9: a
3071
+ * compressed payload at 1/5 Hz, never raw pixels at frame-rate).
3072
+ */
3073
+ maybeSampleThumbnail(deviceId, frame) {
3074
+ if (!this.config.thumbnailsEnabled) return;
3075
+ const sampler = this.thumbSampler;
3076
+ if (!sampler || frame.format !== "rgb") return;
3077
+ if (!sampler.shouldSample(deviceId)) return;
3078
+ const capturedAt = typeof frame.capturedAt === "number" && frame.capturedAt > 0 ? frame.capturedAt : Date.now();
3079
+ const width = frame.width;
3080
+ const height = frame.height;
3081
+ const data = frame.data;
3082
+ (async () => {
3083
+ try {
3084
+ const targetW = Math.min(320, width);
3085
+ const image = sharp(data, { raw: {
3086
+ width,
3087
+ height,
3088
+ channels: 3
3089
+ } });
3090
+ if (targetW < width) image.resize(targetW);
3091
+ const jpeg = await image.jpeg({ quality: 60 }).toBuffer();
3092
+ const meta = {
3093
+ width: targetW,
3094
+ height: Math.round(targetW * height / width)
3095
+ };
3096
+ this.ctxIfReady?.eventBus.emit(createEvent(EventCategory.RecordingThumbSampled, {
3097
+ type: "pipeline",
3098
+ id: deviceId,
3099
+ nodeId: this.nodeId,
3100
+ deviceId
3101
+ }, {
3102
+ deviceId,
3103
+ capturedAt,
3104
+ width: meta.width,
3105
+ height: meta.height,
3106
+ jpeg: new Uint8Array(jpeg)
3107
+ }));
3108
+ } catch (err) {
3109
+ this.ctx?.logger.debug("thumbnail sample encode failed", {
3110
+ tags: { deviceId },
3111
+ meta: { error: err instanceof Error ? err.message : String(err) }
3112
+ });
3113
+ }
3114
+ })();
3115
+ }
3018
3116
  /** Real `runPipeline` dep: node-local `pipelineExecutor.runPipeline` on the `'full'` plane. */
3019
3117
  async runDetailPipeline(steps, imageJpeg, deviceId) {
3020
3118
  const api = this.ctxIfReady?.api;
@@ -3092,6 +3190,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
3092
3190
  attachment.detectionUnsubscribe?.();
3093
3191
  this.lastMotionFrameAt.delete(deviceId);
3094
3192
  this.lastDecodeUnexpectedEndAt.delete(deviceId);
3193
+ this.thumbSampler?.forget(deviceId);
3095
3194
  this.attached.delete(deviceId);
3096
3195
  this.lastMotionAt.delete(deviceId);
3097
3196
  this.lastEmittedCameraMetrics.delete(deviceId);
@@ -3333,6 +3432,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
3333
3432
  const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
3334
3433
  if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
3335
3434
  this.enqueueSharedDetectionFrame(config.deviceId, frame, handle);
3435
+ this.maybeSampleThumbnail(config.deviceId, frame);
3336
3436
  }
3337
3437
  });
3338
3438
  }
@@ -3987,12 +4087,31 @@ var PipelineRunnerAddon = class extends BaseAddon {
3987
4087
  step: 1,
3988
4088
  default: DEFAULT_CONFIG.maxConcurrentDecodeSpawns,
3989
4089
  showValue: true
4090
+ },
4091
+ {
4092
+ type: "boolean",
4093
+ key: "thumbnailsEnabled",
4094
+ label: "Scrub thumbnails",
4095
+ description: "Sample the already-decoded detection frame into small JPEG scrub thumbnails (zero extra decode). The recorder packs them into 5-minute sprite sheets the viewer timeline scrubs. Disable to stop emitting thumbnail telemetry from this node.",
4096
+ default: DEFAULT_CONFIG.thumbnailsEnabled
4097
+ },
4098
+ {
4099
+ type: "slider",
4100
+ key: "thumbnailSampleMs",
4101
+ label: "Thumbnail cadence (ms)",
4102
+ description: "How often (ms) each camera contributes a scrub thumbnail. Lower = denser scrub preview + more storage; a hard 1 Hz ceiling always applies. Default 5000.",
4103
+ min: 1e3,
4104
+ max: 3e4,
4105
+ step: 1e3,
4106
+ default: DEFAULT_CONFIG.thumbnailSampleMs,
4107
+ showValue: true
3990
4108
  }
3991
4109
  ]
3992
4110
  }] });
3993
4111
  }
3994
4112
  async onConfigChanged() {
3995
4113
  this.runner?.updateLimits(this.config);
4114
+ this.thumbSampler = new ThumbSamplerPolicy({ intervalMs: this.config.thumbnailSampleMs });
3996
4115
  this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
3997
4116
  this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
3998
4117
  maxQueueDepth: this.config.maxQueueDepth,