@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.
- package/dist/audio-analyzer/index.js +4 -4
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +9 -9
- package/dist/detection-pipeline/index.mjs +3 -3
- package/dist/{dist-Bn6Uq4vq.js → dist-B1u-QN05.js} +176 -12
- package/dist/{dist-CnwwDzX3.mjs → dist-DKeNH_5z.mjs} +176 -12
- package/dist/{model-download-service-C-IHWnXx-3Mmeob3l.mjs → model-download-service-Cp9f4dk6-0wh7OK3s.mjs} +2 -2
- package/dist/{model-download-service-C-IHWnXx-D326YNnt.js → model-download-service-Cp9f4dk6-Cp_GTKTw.js} +2 -2
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +123 -4
- package/dist/pipeline-runner/index.mjs +122 -3
- package/dist/recorder/index.js +993 -7
- package/dist/recorder/index.mjs +990 -5
- package/dist/session-decode/decode-worker-child.js +14 -1
- package/dist/session-decode/decode-worker-child.mjs +14 -1
- package/dist/{hub-hostname-cCknRYKj.mjs → sheet-geometry-BN80zpCa.js} +56 -1
- package/dist/{hub-hostname-DAJXlOgV.js → sheet-geometry-Bk3fR4s7.mjs} +39 -6
- package/dist/{step-definitions-D7NOrnzz.js → step-definitions-CPch37vh.js} +1 -1
- package/dist/{step-definitions-DhEe57Wn.mjs → step-definitions-DUB27V8Y.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- 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
- 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
- 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
- package/dist/stream-broker/{hostInit-D1Vdyu-4.mjs → hostInit-BLn2Pv8Q.mjs} +3 -3
- package/dist/stream-broker/index.js +622 -149
- package/dist/stream-broker/index.mjs +622 -149
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CG5EE0W4.js → MaskShapeCanvas-DI4BY7W2-B0_nx15B.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BFeLUNOl.js → MotionZonesSettings-NcxxQN8r-B8uM-VeO.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-pQQunq1F.js → PrivacyMaskSettings-APgPLF7p-DMOs4Hj9.js} +1 -1
- package/embed-dist/assets/index-W9-H5LTj.js +81 -0
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- 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
- 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
- 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-
|
|
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 {
|
|
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-
|
|
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,
|