@camstack/addon-pipeline 1.2.0 → 1.2.2
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 +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +2 -2
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-DWy-H6bo.js → dist-CVjqvU8G.js} +95 -13
- package/dist/{dist-DLWXeXC1.mjs → dist-ClyAFBZk.mjs} +95 -13
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +228 -24
- package/dist/pipeline-runner/index.mjs +228 -24
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +55 -7
- package/dist/session-decode/decode-worker-child.mjs +55 -7
- package/dist/{step-definitions-BQG_6dmZ.js → step-definitions-D76MMwbo.js} +1 -1
- package/dist/{step-definitions-CVk19Y0u.mjs → step-definitions-DMOw4fzn.mjs} +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CpB1jG0N.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-7jRy3Uzr.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQC_f4Yv.mjs +26 -0
- package/dist/stream-broker/{hostInit--Zair8Av.mjs → hostInit-4pxims1d.mjs} +3 -3
- package/dist/stream-broker/index.js +1 -1
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-CyVJTZEO.mjs → worker-protocol-D7RzZIla.mjs} +16 -2
- package/dist/{worker-protocol-PP4jKHHJ.js → worker-protocol-DextwlTX.js} +21 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-sOOxiGvP.js → MaskShapeCanvas-DI4BY7W2-BPRZW9TX.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DdTQuT23.js → MotionZonesSettings-NcxxQN8r-Bw6Nlg-Q.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BrR0L3ql.js → PrivacyMaskSettings-APgPLF7p-BLF9QKG8.js} +1 -1
- package/embed-dist/assets/{index-Dk21l1_3.js → index-DiUhKfSk.js} +13 -13
- package/embed-dist/assets/index-Dv5H-32C.css +2 -0
- package/embed-dist/index.html +2 -2
- 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-BflyNuZv.mjs +0 -26
- package/embed-dist/assets/index-D4MGuECB.css +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { H as errMsg, N as pipelineRunnerCapability, S as defineCustomActions, U as BaseAddon, b as customAction, ct as number, dt as string, it as boolean, j as nodePin, lt as object, nt as _enum, ot as lazy, pt as EventCategory, q as createEvent, rt as array } from "../dist-
|
|
1
|
+
import { H as errMsg, N as pipelineRunnerCapability, S as defineCustomActions, U as BaseAddon, b as customAction, ct as number, dt as string, it as boolean, j as nodePin, lt as object, nt as _enum, ot as lazy, pt as EventCategory, q as createEvent, rt as array, s as DEVICE_BACKEND_TO_FORMAT } from "../dist-ClyAFBZk.mjs";
|
|
2
2
|
import { n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-BeHi78PZ.mjs";
|
|
3
3
|
import { r as resolveHubHostname } from "../sheet-geometry-DcpuGTCm.mjs";
|
|
4
|
-
import { t as isWorkerReply } from "../worker-protocol-
|
|
5
|
-
import { a as getStepDefinition } from "../step-definitions-
|
|
4
|
+
import { t as isWorkerReply } from "../worker-protocol-D7RzZIla.mjs";
|
|
5
|
+
import { a as getStepDefinition } from "../step-definitions-DMOw4fzn.mjs";
|
|
6
6
|
import { fork } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import sharp from "sharp";
|
|
@@ -1146,6 +1146,17 @@ function summarizeDeviceLoad(attachedDeviceKeys) {
|
|
|
1146
1146
|
})).toSorted((a, b) => a.deviceKey.localeCompare(b.deviceKey));
|
|
1147
1147
|
}
|
|
1148
1148
|
//#endregion
|
|
1149
|
+
//#region src/pipeline-runner/native-crop-tier.ts
|
|
1150
|
+
/**
|
|
1151
|
+
* Pure tier decision for `getNativeCrop`. A native HIT always resolves to the
|
|
1152
|
+
* native surface. On a native MISS, only a full-frame request falls back to the
|
|
1153
|
+
* ≤640 RAM store; an ROI miss is reported as `miss` (no quality masquerade).
|
|
1154
|
+
*/
|
|
1155
|
+
function resolveNativeCropSource(isFullFrame, nativeHit) {
|
|
1156
|
+
if (nativeHit) return "native";
|
|
1157
|
+
return isFullFrame ? "ram-fullframe" : "miss";
|
|
1158
|
+
}
|
|
1159
|
+
//#endregion
|
|
1149
1160
|
//#region src/session-decode/fork-decode-worker.ts
|
|
1150
1161
|
/**
|
|
1151
1162
|
* Real `fork` implementation for the session-decode coordinator — Epic C P1,
|
|
@@ -1663,11 +1674,18 @@ var SessionDecodeSession = class {
|
|
|
1663
1674
|
});
|
|
1664
1675
|
return;
|
|
1665
1676
|
case "nativeCropMiss":
|
|
1677
|
+
if (raw.reason !== void 0) this.logger.debug("session-decode: native crop miss", { meta: {
|
|
1678
|
+
requestId: raw.requestId,
|
|
1679
|
+
reason: raw.reason
|
|
1680
|
+
} });
|
|
1666
1681
|
this.settleNativeCrop(raw.requestId, null);
|
|
1667
1682
|
return;
|
|
1668
1683
|
case "error":
|
|
1669
1684
|
this.settleError(raw.message, raw.frameId);
|
|
1670
1685
|
return;
|
|
1686
|
+
case "log":
|
|
1687
|
+
this.logger[raw.level](raw.message);
|
|
1688
|
+
return;
|
|
1671
1689
|
}
|
|
1672
1690
|
}
|
|
1673
1691
|
handleExit(info) {
|
|
@@ -2236,13 +2254,93 @@ async function runDetailSubtree(deps, input) {
|
|
|
2236
2254
|
if (!crop) return null;
|
|
2237
2255
|
const details = [];
|
|
2238
2256
|
for (const step of steps) {
|
|
2239
|
-
const
|
|
2257
|
+
const deviceKey = deps.resolveStepDevice?.(input.deviceId, step);
|
|
2258
|
+
const result = await deps.runPipeline([step], crop.jpeg, input.deviceId, deviceKey);
|
|
2240
2259
|
if (!result) continue;
|
|
2241
2260
|
for (const detection of result.detections) details.push(toDetailResult(detection, step.addonId, crop));
|
|
2242
2261
|
}
|
|
2243
2262
|
return { details };
|
|
2244
2263
|
}
|
|
2245
2264
|
//#endregion
|
|
2265
|
+
//#region src/pipeline-runner/device-jump.ts
|
|
2266
|
+
/**
|
|
2267
|
+
* Step-tree device jump (same-node, phase 1) — the PURE resolution core.
|
|
2268
|
+
*
|
|
2269
|
+
* Given the camera's elected ("effective") inference device and the node's
|
|
2270
|
+
* enabled-device roster, decide which device ONE enrichment step should run on:
|
|
2271
|
+
*
|
|
2272
|
+
* 1. A manual `jumpDeviceKey` override wins when it names a candidate that can
|
|
2273
|
+
* run the step (bypasses the auto choice; operator intent).
|
|
2274
|
+
* 2. Otherwise, if the effective device's format can run the step's model →
|
|
2275
|
+
* NO jump (the step inherits the effective device — the common case).
|
|
2276
|
+
* 3. Otherwise AUTO-jump: among the node's OTHER enabled devices whose format
|
|
2277
|
+
* can run the step (and that are under their `maxSessions` cap), pick the
|
|
2278
|
+
* weighted-least-loaded one — mirroring the balancer's per-device core.
|
|
2279
|
+
* 4. If nothing on the node can run it → `no-candidate` (the caller emits the
|
|
2280
|
+
* loud once-per-(camera,step) warn that replaces today's silent degrade).
|
|
2281
|
+
*
|
|
2282
|
+
* Framework-free + side-effect-free so the whole decision table is unit-testable
|
|
2283
|
+
* without a live runner/catalog. The `canRun(format)` predicate is injected by
|
|
2284
|
+
* the runner (which owns the model catalog); this module never imports it.
|
|
2285
|
+
*/
|
|
2286
|
+
/** The `ModelFormat` of a `<backend>:<device>` key, or `undefined` for an unknown backend. */
|
|
2287
|
+
function formatOf(deviceKey) {
|
|
2288
|
+
const colon = deviceKey.indexOf(":");
|
|
2289
|
+
return DEVICE_BACKEND_TO_FORMAT[colon >= 0 ? deviceKey.slice(0, colon) : deviceKey];
|
|
2290
|
+
}
|
|
2291
|
+
/** True when the step's model has a build for `deviceKey`'s format. */
|
|
2292
|
+
function canRunOn(deviceKey, canRun) {
|
|
2293
|
+
const format = formatOf(deviceKey);
|
|
2294
|
+
return format !== void 0 && canRun(format);
|
|
2295
|
+
}
|
|
2296
|
+
/** Effective weighted load of a device (session count ÷ weight); lower = preferred. */
|
|
2297
|
+
function weightedLoad(d) {
|
|
2298
|
+
return d.attachedCameras / (d.weight > 0 ? d.weight : 1);
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* Weighted-least-loaded pick with a deterministic `deviceKey` tie-break —
|
|
2302
|
+
* mirrors the node/device balancer's decision core (least `load/weight`, ties
|
|
2303
|
+
* broken stably). `devices` is assumed non-empty.
|
|
2304
|
+
*/
|
|
2305
|
+
function pickWeightedLeastLoaded(devices) {
|
|
2306
|
+
return devices.reduce((best, d) => {
|
|
2307
|
+
const dScore = weightedLoad(d);
|
|
2308
|
+
const bScore = weightedLoad(best);
|
|
2309
|
+
if (dScore < bScore) return d;
|
|
2310
|
+
if (dScore > bScore) return best;
|
|
2311
|
+
return d.deviceKey < best.deviceKey ? d : best;
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Resolve the device one enrichment step runs on. See the module header for the
|
|
2316
|
+
* decision table. Pure — never throws, no I/O.
|
|
2317
|
+
*/
|
|
2318
|
+
function resolveStepDevice(input) {
|
|
2319
|
+
const { effectiveDeviceKey, manualJumpDeviceKey, canRun } = input;
|
|
2320
|
+
if (manualJumpDeviceKey !== void 0 && manualJumpDeviceKey !== effectiveDeviceKey) {
|
|
2321
|
+
const target = input.devices.find((d) => d.deviceKey === manualJumpDeviceKey);
|
|
2322
|
+
if (target && canRunOn(target.deviceKey, canRun)) return {
|
|
2323
|
+
kind: "jumped",
|
|
2324
|
+
deviceKey: target.deviceKey,
|
|
2325
|
+
reason: "manual"
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
if (canRunOn(effectiveDeviceKey, canRun)) return {
|
|
2329
|
+
kind: "no-jump",
|
|
2330
|
+
deviceKey: effectiveDeviceKey
|
|
2331
|
+
};
|
|
2332
|
+
const runnable = input.devices.filter((d) => d.deviceKey !== effectiveDeviceKey && (d.maxSessions === null || d.attachedCameras < d.maxSessions) && canRunOn(d.deviceKey, canRun));
|
|
2333
|
+
if (runnable.length === 0) return {
|
|
2334
|
+
kind: "no-candidate",
|
|
2335
|
+
deviceKey: effectiveDeviceKey
|
|
2336
|
+
};
|
|
2337
|
+
return {
|
|
2338
|
+
kind: "jumped",
|
|
2339
|
+
deviceKey: pickWeightedLeastLoaded(runnable).deviceKey,
|
|
2340
|
+
reason: "auto"
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
//#endregion
|
|
2246
2344
|
//#region src/pipeline-runner/index.ts
|
|
2247
2345
|
var DEFAULT_CONFIG = {
|
|
2248
2346
|
maxQueueDepth: 30,
|
|
@@ -3178,31 +3276,77 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3178
3276
|
return { success: true };
|
|
3179
3277
|
}
|
|
3180
3278
|
/**
|
|
3181
|
-
*
|
|
3182
|
-
*
|
|
3183
|
-
*
|
|
3184
|
-
*
|
|
3185
|
-
*
|
|
3279
|
+
* Native-resolution crop for a retention-ring frame (native-crop feature).
|
|
3280
|
+
* Looks the handle up in {@link nativeCropRegistry}; the worker downloads ONLY
|
|
3281
|
+
* the ROI — a full native frame never travels the wire.
|
|
3282
|
+
*
|
|
3283
|
+
* Tier decision ({@link resolveNativeCropSource}): a native-lease HIT always
|
|
3284
|
+
* wins. On a MISS (never registered, or the worker's tiny native ring already
|
|
3285
|
+
* evicted the frame), an ROI (subject-crop) request returns `null` so the
|
|
3286
|
+
* caller reports the miss and RETRIES — it is NEVER handed the ≤640 detection
|
|
3287
|
+
* raster masquerading as a native crop (Task S1). A full-frame request keeps
|
|
3288
|
+
* the ≤640 RAM `RetainedFrameStore` fallback (blank-frame guard / 640-snapshot
|
|
3289
|
+
* resolve legitimately want the lower-RES honest frame).
|
|
3186
3290
|
*/
|
|
3187
3291
|
async getNativeCrop(input) {
|
|
3188
3292
|
const b = input.bbox;
|
|
3189
|
-
|
|
3293
|
+
const isFullFrame = b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999;
|
|
3294
|
+
const isUnboundedFullFrame = isFullFrame && input.maxWidth === void 0;
|
|
3295
|
+
let nativeResult = null;
|
|
3296
|
+
if (!isUnboundedFullFrame) {
|
|
3190
3297
|
const requester = this.nativeCropRegistry.get(input.handle);
|
|
3191
3298
|
if (requester) {
|
|
3192
3299
|
const result = await requester(input.bbox, input.maxWidth);
|
|
3193
|
-
if (result)
|
|
3300
|
+
if (result) nativeResult = {
|
|
3194
3301
|
bytes: result.bytes,
|
|
3195
3302
|
width: result.width,
|
|
3196
3303
|
height: result.height
|
|
3197
3304
|
};
|
|
3198
3305
|
}
|
|
3199
3306
|
}
|
|
3307
|
+
const tier = resolveNativeCropSource(isFullFrame, nativeResult !== null);
|
|
3308
|
+
if (tier === "native" && nativeResult) return this.encodeNativeCropResult(nativeResult, tier, input.encodeJpeg);
|
|
3309
|
+
if (tier !== "ram-fullframe") return null;
|
|
3200
3310
|
const crop = this.retainedFrames?.crop(input.handle, input.bbox, input.maxWidth);
|
|
3201
|
-
|
|
3311
|
+
if (!crop) return null;
|
|
3312
|
+
return this.encodeNativeCropResult({
|
|
3202
3313
|
bytes: crop.bytes,
|
|
3203
3314
|
width: crop.width,
|
|
3204
3315
|
height: crop.height
|
|
3205
|
-
}
|
|
3316
|
+
}, tier, input.encodeJpeg);
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Shape a resolved RGB crop into a `getNativeCrop` result. Raw path (default):
|
|
3320
|
+
* return the zero-copy `bytes` — same-node callers encode locally. Compressed
|
|
3321
|
+
* path (`encodeJpeg`, the CROSS-NODE case): encode to JPEG in-process, on the
|
|
3322
|
+
* OWNING node, and return `jpeg` (base64) with NO `bytes` — the payload then
|
|
3323
|
+
* fits Moleculer's `maxPacketSize` and no raw pixels cross the process
|
|
3324
|
+
* boundary (CLAUDE.md invariant #21). On an encode failure the crop is a miss
|
|
3325
|
+
* (`null`) — the caller retries — never a silent raw-RGB cross-node fallback.
|
|
3326
|
+
*/
|
|
3327
|
+
async encodeNativeCropResult(crop, tier, encodeJpeg) {
|
|
3328
|
+
if (encodeJpeg !== true) return {
|
|
3329
|
+
bytes: crop.bytes,
|
|
3330
|
+
width: crop.width,
|
|
3331
|
+
height: crop.height,
|
|
3332
|
+
tier
|
|
3333
|
+
};
|
|
3334
|
+
try {
|
|
3335
|
+
const sharp = (await import("sharp")).default;
|
|
3336
|
+
return {
|
|
3337
|
+
jpeg: (await sharp(Buffer.from(crop.bytes), { raw: {
|
|
3338
|
+
width: crop.width,
|
|
3339
|
+
height: crop.height,
|
|
3340
|
+
channels: 3
|
|
3341
|
+
} }).jpeg({ quality: 90 }).toBuffer()).toString("base64"),
|
|
3342
|
+
width: crop.width,
|
|
3343
|
+
height: crop.height,
|
|
3344
|
+
tier
|
|
3345
|
+
};
|
|
3346
|
+
} catch (err) {
|
|
3347
|
+
this.ctx.logger.warn("getNativeCrop: cross-node JPEG encode failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
3348
|
+
return null;
|
|
3349
|
+
}
|
|
3206
3350
|
}
|
|
3207
3351
|
/**
|
|
3208
3352
|
* Two-plane design: run the DETAIL subtree (crop children) for a single
|
|
@@ -3217,10 +3361,72 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3217
3361
|
return runDetailSubtree({
|
|
3218
3362
|
resolveChildSteps: (deviceId, parentClassName, filter) => this.resolveDetailChildSteps(deviceId, parentClassName, filter),
|
|
3219
3363
|
getNativeCrop: (deviceId, handle, paddedBbox) => this.getNativeCropForDetail(deviceId, handle, paddedBbox),
|
|
3220
|
-
runPipeline: (steps, imageJpeg, deviceId) => this.runDetailPipeline(steps, imageJpeg, deviceId)
|
|
3364
|
+
runPipeline: (steps, imageJpeg, deviceId, deviceKey) => this.runDetailPipeline(steps, imageJpeg, deviceId, deviceKey),
|
|
3365
|
+
resolveStepDevice: (deviceId, step) => this.resolveDetailStepDevice(deviceId, step)
|
|
3221
3366
|
}, input);
|
|
3222
3367
|
}
|
|
3223
3368
|
/**
|
|
3369
|
+
* Step-tree device jump (phase 1) — pick which same-node inference device an
|
|
3370
|
+
* enrichment `step` runs on. AUTO-jumps only when the camera's elected device
|
|
3371
|
+
* cannot run the step's model (its format has no build); an operator
|
|
3372
|
+
* `step.jumpDeviceKey` overrides. Returns the resolved `<backend>:<device>`
|
|
3373
|
+
* key, or `undefined` (⇒ the runner's per-camera default pool) when there is
|
|
3374
|
+
* nothing to route: no elected device, or no jump roster on the attach.
|
|
3375
|
+
*
|
|
3376
|
+
* When NO enabled device on the node can run the step, logs a LOUD warn once
|
|
3377
|
+
* per (camera, step) — killing the historical SILENT per-child degrade (an
|
|
3378
|
+
* `edgetpu` root would otherwise drop faces/plates/clip with no signal).
|
|
3379
|
+
*/
|
|
3380
|
+
resolveDetailStepDevice(deviceId, step) {
|
|
3381
|
+
const config = this.attached.get(deviceId)?.config;
|
|
3382
|
+
const effectiveDeviceKey = config?.deviceKey;
|
|
3383
|
+
if (!effectiveDeviceKey) return void 0;
|
|
3384
|
+
const roster = config?.inferenceDevices;
|
|
3385
|
+
if (!roster || roster.length < 2) return effectiveDeviceKey;
|
|
3386
|
+
const attachedByKey = this.perDeviceAttachedCounts();
|
|
3387
|
+
const devices = roster.map((d) => ({
|
|
3388
|
+
deviceKey: d.deviceKey,
|
|
3389
|
+
weight: d.weight,
|
|
3390
|
+
maxSessions: d.maxSessions,
|
|
3391
|
+
attachedCameras: attachedByKey.get(d.deviceKey) ?? 0
|
|
3392
|
+
}));
|
|
3393
|
+
const def = tryGetStepDefinition(step.addonId);
|
|
3394
|
+
const canRun = (format) => def === null || def.models.some((m) => m.formats[format]);
|
|
3395
|
+
const result = resolveStepDevice({
|
|
3396
|
+
effectiveDeviceKey,
|
|
3397
|
+
devices,
|
|
3398
|
+
...step.jumpDeviceKey !== void 0 ? { manualJumpDeviceKey: step.jumpDeviceKey } : {},
|
|
3399
|
+
canRun
|
|
3400
|
+
});
|
|
3401
|
+
if (result.kind === "no-candidate") this.warnStepHasNoRunnableDevice(deviceId, step.addonId, effectiveDeviceKey);
|
|
3402
|
+
return result.deviceKey;
|
|
3403
|
+
}
|
|
3404
|
+
/** Live attached-camera count per inference `deviceKey` on this runner. */
|
|
3405
|
+
perDeviceAttachedCounts() {
|
|
3406
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3407
|
+
for (const cam of this.attached.values()) {
|
|
3408
|
+
const key = cam.config.deviceKey;
|
|
3409
|
+
if (key === void 0) continue;
|
|
3410
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
3411
|
+
}
|
|
3412
|
+
return counts;
|
|
3413
|
+
}
|
|
3414
|
+
/** `${deviceId}:${addonId}` pairs already warned about a no-runnable-device step. */
|
|
3415
|
+
warnedNoJumpDevice = /* @__PURE__ */ new Set();
|
|
3416
|
+
warnStepHasNoRunnableDevice(deviceId, addonId, effectiveDeviceKey) {
|
|
3417
|
+
const key = `${deviceId}:${addonId}`;
|
|
3418
|
+
if (this.warnedNoJumpDevice.has(key)) return;
|
|
3419
|
+
this.warnedNoJumpDevice.add(key);
|
|
3420
|
+
this.ctx?.logger.warn("enrichment step has no runnable inference device on this node — producing NO results for it", {
|
|
3421
|
+
tags: { deviceId },
|
|
3422
|
+
meta: {
|
|
3423
|
+
step: addonId,
|
|
3424
|
+
electedDeviceKey: effectiveDeviceKey,
|
|
3425
|
+
hint: "the elected device format has no build of this step, and no enabled same-node device can run it — enable a compatible accelerator or add a jumpDeviceKey"
|
|
3426
|
+
}
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3224
3430
|
* Real `resolveChildSteps` dep: `null` when the camera isn't attached
|
|
3225
3431
|
* (matches the cap doc — "camera not attached" is a distinct case from
|
|
3226
3432
|
* "attached but no matching child steps", which returns an empty array
|
|
@@ -3242,16 +3448,12 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3242
3448
|
async getNativeCropForDetail(_deviceId, handle, paddedBbox) {
|
|
3243
3449
|
const native = await this.getNativeCrop({
|
|
3244
3450
|
handle,
|
|
3245
|
-
bbox: paddedBbox
|
|
3451
|
+
bbox: paddedBbox,
|
|
3452
|
+
encodeJpeg: true
|
|
3246
3453
|
});
|
|
3247
|
-
if (!native) return null;
|
|
3248
|
-
const sharp = (await import("sharp")).default;
|
|
3454
|
+
if (!native?.jpeg) return null;
|
|
3249
3455
|
return {
|
|
3250
|
-
jpeg:
|
|
3251
|
-
width: native.width,
|
|
3252
|
-
height: native.height,
|
|
3253
|
-
channels: 3
|
|
3254
|
-
} }).jpeg({ quality: 90 }).toBuffer(),
|
|
3456
|
+
jpeg: Buffer.from(native.jpeg, "base64"),
|
|
3255
3457
|
width: native.width,
|
|
3256
3458
|
height: native.height,
|
|
3257
3459
|
frameSpace: denormalizeRect(paddedBbox, handle.width, handle.height)
|
|
@@ -3311,10 +3513,10 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3311
3513
|
})();
|
|
3312
3514
|
}
|
|
3313
3515
|
/** Real `runPipeline` dep: node-local `pipelineExecutor.runPipeline` on the `'full'` plane. */
|
|
3314
|
-
async runDetailPipeline(steps, imageJpeg, deviceId) {
|
|
3516
|
+
async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey) {
|
|
3315
3517
|
const api = this.ctxIfReady?.api;
|
|
3316
3518
|
if (!api) return null;
|
|
3317
|
-
const detailDeviceKey = this.attached.get(deviceId)?.config?.deviceKey;
|
|
3519
|
+
const detailDeviceKey = stepDeviceKey ?? this.attached.get(deviceId)?.config?.deviceKey;
|
|
3318
3520
|
try {
|
|
3319
3521
|
return await api.pipelineExecutor.runPipeline.mutate({
|
|
3320
3522
|
steps: [...steps],
|
|
@@ -3391,6 +3593,8 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3391
3593
|
this.lastDecodeUnexpectedEndAt.delete(deviceId);
|
|
3392
3594
|
this.thumbSampler?.forget(deviceId);
|
|
3393
3595
|
this.packageGovernor.forget(deviceId);
|
|
3596
|
+
const warnPrefix = `${deviceId}:`;
|
|
3597
|
+
for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
|
|
3394
3598
|
this.attached.delete(deviceId);
|
|
3395
3599
|
this.lastMotionAt.delete(deviceId);
|
|
3396
3600
|
this.lastEmittedCameraMetrics.delete(deviceId);
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
2
|
-
const require_dist = require("../dist-
|
|
2
|
+
const require_dist = require("../dist-CVjqvU8G.js");
|
|
3
3
|
const require_sheet_geometry = require("../sheet-geometry-BR-ip0CP.js");
|
|
4
4
|
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-Dv-oFwjN.js");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as recordingExportCapability, H as errMsg, I as resolveScrubThumbnailGeometry, J as hydrateSchema, K as DeviceType, P as recordingCapability, R as storageEvictableCapability, U as BaseAddon, Y as isEvent, c as EVENT_PAD_MS, dt as string, et as selectAssignedProfileSlots, f as OpsLogEntrySchema, j as nodePin, k as migrateConfigToBands, p as RecordingConfigSchema, pt as EventCategory, rt as array, u as ExportRecordSchema, ut as record } from "../dist-
|
|
1
|
+
import { F as recordingExportCapability, H as errMsg, I as resolveScrubThumbnailGeometry, J as hydrateSchema, K as DeviceType, P as recordingCapability, R as storageEvictableCapability, U as BaseAddon, Y as isEvent, c as EVENT_PAD_MS, dt as string, et as selectAssignedProfileSlots, f as OpsLogEntrySchema, j as nodePin, k as migrateConfigToBands, p as RecordingConfigSchema, pt as EventCategory, rt as array, u as ExportRecordSchema, ut as record } from "../dist-ClyAFBZk.mjs";
|
|
2
2
|
import { n as computeSheetGeometry, r as resolveHubHostname, t as allTilePlacements } from "../sheet-geometry-DcpuGTCm.mjs";
|
|
3
3
|
import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
3
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
3
|
+
const require_worker_protocol = require("../worker-protocol-DextwlTX.js");
|
|
4
4
|
let node_url = require("node:url");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -597,6 +597,44 @@ function retainedFrameBytes(width, height, rgb24) {
|
|
|
597
597
|
function rgbCropSourceOffset(top, left, stride) {
|
|
598
598
|
return top * stride + left * 3;
|
|
599
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Resolve the SOURCE the RGB native-crop scaler reads.
|
|
602
|
+
*
|
|
603
|
+
* The fast path is a zero-copy view: offset the packed RGB24 plane to the crop
|
|
604
|
+
* origin and let the scaler stride over it ({@link rgbCropSourceOffset}). That
|
|
605
|
+
* view is safe ONLY while it still holds `stride * height + slack` bytes — the
|
|
606
|
+
* conservative span libswscale reads (full rows + a SIMD overshoot on the last
|
|
607
|
+
* row). For a ROI whose bottom edge sits at (or near) the frame bottom AND whose
|
|
608
|
+
* left offset is > 0, the tail of the plane is `left*3` bytes short of that span,
|
|
609
|
+
* so the scaler reads past the allocation and THROWS — the crop is reported as a
|
|
610
|
+
* native miss. A FULL-frame crop (left=0, full height) is always exactly sized,
|
|
611
|
+
* which is why full-frame keyFrames landed while small offset ROIs missed
|
|
612
|
+
* (the subject-ROI-vs-full-frame asymmetry).
|
|
613
|
+
*
|
|
614
|
+
* When the zero-copy view is too short, repack the ROI into a tightly-packed
|
|
615
|
+
* buffer (stride = `width*3`) with the SIMD tail slack — identical pixels, but
|
|
616
|
+
* guaranteed in-bounds for every ROI position. Pure + exported so the bounds
|
|
617
|
+
* math is unit-testable without node-av.
|
|
618
|
+
*/
|
|
619
|
+
function resolveRgbCropSource(plane, planeStride, region) {
|
|
620
|
+
const offset = rgbCropSourceOffset(region.top, region.left, planeStride);
|
|
621
|
+
const rowBytes = region.width * 3;
|
|
622
|
+
const requiredSpan = planeStride * region.height + 64;
|
|
623
|
+
if (plane.length - offset >= requiredSpan) return {
|
|
624
|
+
src: plane.subarray(offset),
|
|
625
|
+
stride: planeStride
|
|
626
|
+
};
|
|
627
|
+
const tightStride = rowBytes;
|
|
628
|
+
const packed = new Uint8Array(tightStride * region.height + 64);
|
|
629
|
+
for (let row = 0; row < region.height; row++) {
|
|
630
|
+
const srcRowStart = offset + row * planeStride;
|
|
631
|
+
packed.set(plane.subarray(srcRowStart, srcRowStart + rowBytes), row * tightStride);
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
src: packed,
|
|
635
|
+
stride: tightStride
|
|
636
|
+
};
|
|
637
|
+
}
|
|
600
638
|
//#endregion
|
|
601
639
|
//#region src/session-decode/decode-worker-child.ts
|
|
602
640
|
/**
|
|
@@ -1201,7 +1239,8 @@ var DecodeWorkerChild = class {
|
|
|
1201
1239
|
this.nativeCropMisses++;
|
|
1202
1240
|
this.send({
|
|
1203
1241
|
kind: "nativeCropMiss",
|
|
1204
|
-
requestId
|
|
1242
|
+
requestId,
|
|
1243
|
+
reason: `frame ${frameId} not retained (lease/ring evicted before crop)`
|
|
1205
1244
|
});
|
|
1206
1245
|
return;
|
|
1207
1246
|
}
|
|
@@ -1218,10 +1257,12 @@ var DecodeWorkerChild = class {
|
|
|
1218
1257
|
});
|
|
1219
1258
|
} catch (err) {
|
|
1220
1259
|
this.nativeCropMisses++;
|
|
1221
|
-
|
|
1260
|
+
const reason = errMessage(err);
|
|
1261
|
+
this.emitStderr(`decode-worker-child: native crop failed (requestId ${requestId}) — ${reason}\n`);
|
|
1222
1262
|
this.send({
|
|
1223
1263
|
kind: "nativeCropMiss",
|
|
1224
|
-
requestId
|
|
1264
|
+
requestId,
|
|
1265
|
+
reason: `crop failed — ${reason}`
|
|
1225
1266
|
});
|
|
1226
1267
|
}
|
|
1227
1268
|
}
|
|
@@ -1389,13 +1430,13 @@ var DecodeWorkerChild = class {
|
|
|
1389
1430
|
const { region, target } = resolved;
|
|
1390
1431
|
const plane = frame.data?.[0];
|
|
1391
1432
|
if (!plane) throw new Error("decode-worker-child: retained RGB frame missing packed plane data");
|
|
1392
|
-
const
|
|
1393
|
-
const srcSlice =
|
|
1433
|
+
const { src: srcRgb, stride: srcSliceStride } = resolveRgbCropSource(plane, frame.linesize[0] ?? 0, region);
|
|
1434
|
+
const srcSlice = Buffer.from(srcRgb.buffer, srcRgb.byteOffset, srcRgb.byteLength);
|
|
1394
1435
|
const scaler = this.ensureNativeCropScaler(nav, C, region.width, region.height, target.width, target.height, C.AV_PIX_FMT_RGB24);
|
|
1395
1436
|
const tightStride = target.width * 3;
|
|
1396
1437
|
const dstStride = tightStride + 63 & -64;
|
|
1397
1438
|
const dstBuffer = Buffer.allocUnsafe(dstStride * target.height + 64);
|
|
1398
|
-
const scaledHeight = scaler.scaleSync([srcSlice], [
|
|
1439
|
+
const scaledHeight = scaler.scaleSync([srcSlice], [srcSliceStride], 0, region.height, [dstBuffer], [dstStride]);
|
|
1399
1440
|
if (scaledHeight < 0) throw new Error(`native rgb crop sws_scale failed: ${scaledHeight}`);
|
|
1400
1441
|
if (scaledHeight !== target.height) throw new Error(`native rgb crop scaler produced ${scaledHeight} of ${target.height} rows`);
|
|
1401
1442
|
if (dstStride === tightStride) return dstBuffer.subarray(0, tightStride * target.height);
|
|
@@ -1857,6 +1898,13 @@ var DecodeWorkerChild = class {
|
|
|
1857
1898
|
emitStderr(line) {
|
|
1858
1899
|
const tag = this.deviceId === null ? "[dev:?]" : `[dev:${this.deviceId}]`;
|
|
1859
1900
|
process.stderr.write(`${tag} ${line}`);
|
|
1901
|
+
try {
|
|
1902
|
+
this.send({
|
|
1903
|
+
kind: "log",
|
|
1904
|
+
level: require_worker_protocol.logLevelForLine(line),
|
|
1905
|
+
message: `${tag} ${line.trimEnd()}`
|
|
1906
|
+
});
|
|
1907
|
+
} catch {}
|
|
1860
1908
|
}
|
|
1861
1909
|
sendError(message, frameId) {
|
|
1862
1910
|
this.emitStderr(`${message}\n`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as isWorkerRequest } from "../worker-protocol-
|
|
1
|
+
import { n as isWorkerRequest, r as logLevelForLine } from "../worker-protocol-D7RzZIla.mjs";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -593,6 +593,44 @@ function retainedFrameBytes(width, height, rgb24) {
|
|
|
593
593
|
function rgbCropSourceOffset(top, left, stride) {
|
|
594
594
|
return top * stride + left * 3;
|
|
595
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Resolve the SOURCE the RGB native-crop scaler reads.
|
|
598
|
+
*
|
|
599
|
+
* The fast path is a zero-copy view: offset the packed RGB24 plane to the crop
|
|
600
|
+
* origin and let the scaler stride over it ({@link rgbCropSourceOffset}). That
|
|
601
|
+
* view is safe ONLY while it still holds `stride * height + slack` bytes — the
|
|
602
|
+
* conservative span libswscale reads (full rows + a SIMD overshoot on the last
|
|
603
|
+
* row). For a ROI whose bottom edge sits at (or near) the frame bottom AND whose
|
|
604
|
+
* left offset is > 0, the tail of the plane is `left*3` bytes short of that span,
|
|
605
|
+
* so the scaler reads past the allocation and THROWS — the crop is reported as a
|
|
606
|
+
* native miss. A FULL-frame crop (left=0, full height) is always exactly sized,
|
|
607
|
+
* which is why full-frame keyFrames landed while small offset ROIs missed
|
|
608
|
+
* (the subject-ROI-vs-full-frame asymmetry).
|
|
609
|
+
*
|
|
610
|
+
* When the zero-copy view is too short, repack the ROI into a tightly-packed
|
|
611
|
+
* buffer (stride = `width*3`) with the SIMD tail slack — identical pixels, but
|
|
612
|
+
* guaranteed in-bounds for every ROI position. Pure + exported so the bounds
|
|
613
|
+
* math is unit-testable without node-av.
|
|
614
|
+
*/
|
|
615
|
+
function resolveRgbCropSource(plane, planeStride, region) {
|
|
616
|
+
const offset = rgbCropSourceOffset(region.top, region.left, planeStride);
|
|
617
|
+
const rowBytes = region.width * 3;
|
|
618
|
+
const requiredSpan = planeStride * region.height + 64;
|
|
619
|
+
if (plane.length - offset >= requiredSpan) return {
|
|
620
|
+
src: plane.subarray(offset),
|
|
621
|
+
stride: planeStride
|
|
622
|
+
};
|
|
623
|
+
const tightStride = rowBytes;
|
|
624
|
+
const packed = new Uint8Array(tightStride * region.height + 64);
|
|
625
|
+
for (let row = 0; row < region.height; row++) {
|
|
626
|
+
const srcRowStart = offset + row * planeStride;
|
|
627
|
+
packed.set(plane.subarray(srcRowStart, srcRowStart + rowBytes), row * tightStride);
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
src: packed,
|
|
631
|
+
stride: tightStride
|
|
632
|
+
};
|
|
633
|
+
}
|
|
596
634
|
//#endregion
|
|
597
635
|
//#region src/session-decode/decode-worker-child.ts
|
|
598
636
|
/**
|
|
@@ -1197,7 +1235,8 @@ var DecodeWorkerChild = class {
|
|
|
1197
1235
|
this.nativeCropMisses++;
|
|
1198
1236
|
this.send({
|
|
1199
1237
|
kind: "nativeCropMiss",
|
|
1200
|
-
requestId
|
|
1238
|
+
requestId,
|
|
1239
|
+
reason: `frame ${frameId} not retained (lease/ring evicted before crop)`
|
|
1201
1240
|
});
|
|
1202
1241
|
return;
|
|
1203
1242
|
}
|
|
@@ -1214,10 +1253,12 @@ var DecodeWorkerChild = class {
|
|
|
1214
1253
|
});
|
|
1215
1254
|
} catch (err) {
|
|
1216
1255
|
this.nativeCropMisses++;
|
|
1217
|
-
|
|
1256
|
+
const reason = errMessage(err);
|
|
1257
|
+
this.emitStderr(`decode-worker-child: native crop failed (requestId ${requestId}) — ${reason}\n`);
|
|
1218
1258
|
this.send({
|
|
1219
1259
|
kind: "nativeCropMiss",
|
|
1220
|
-
requestId
|
|
1260
|
+
requestId,
|
|
1261
|
+
reason: `crop failed — ${reason}`
|
|
1221
1262
|
});
|
|
1222
1263
|
}
|
|
1223
1264
|
}
|
|
@@ -1385,13 +1426,13 @@ var DecodeWorkerChild = class {
|
|
|
1385
1426
|
const { region, target } = resolved;
|
|
1386
1427
|
const plane = frame.data?.[0];
|
|
1387
1428
|
if (!plane) throw new Error("decode-worker-child: retained RGB frame missing packed plane data");
|
|
1388
|
-
const
|
|
1389
|
-
const srcSlice =
|
|
1429
|
+
const { src: srcRgb, stride: srcSliceStride } = resolveRgbCropSource(plane, frame.linesize[0] ?? 0, region);
|
|
1430
|
+
const srcSlice = Buffer.from(srcRgb.buffer, srcRgb.byteOffset, srcRgb.byteLength);
|
|
1390
1431
|
const scaler = this.ensureNativeCropScaler(nav, C, region.width, region.height, target.width, target.height, C.AV_PIX_FMT_RGB24);
|
|
1391
1432
|
const tightStride = target.width * 3;
|
|
1392
1433
|
const dstStride = tightStride + 63 & -64;
|
|
1393
1434
|
const dstBuffer = Buffer.allocUnsafe(dstStride * target.height + 64);
|
|
1394
|
-
const scaledHeight = scaler.scaleSync([srcSlice], [
|
|
1435
|
+
const scaledHeight = scaler.scaleSync([srcSlice], [srcSliceStride], 0, region.height, [dstBuffer], [dstStride]);
|
|
1395
1436
|
if (scaledHeight < 0) throw new Error(`native rgb crop sws_scale failed: ${scaledHeight}`);
|
|
1396
1437
|
if (scaledHeight !== target.height) throw new Error(`native rgb crop scaler produced ${scaledHeight} of ${target.height} rows`);
|
|
1397
1438
|
if (dstStride === tightStride) return dstBuffer.subarray(0, tightStride * target.height);
|
|
@@ -1853,6 +1894,13 @@ var DecodeWorkerChild = class {
|
|
|
1853
1894
|
emitStderr(line) {
|
|
1854
1895
|
const tag = this.deviceId === null ? "[dev:?]" : `[dev:${this.deviceId}]`;
|
|
1855
1896
|
process.stderr.write(`${tag} ${line}`);
|
|
1897
|
+
try {
|
|
1898
|
+
this.send({
|
|
1899
|
+
kind: "log",
|
|
1900
|
+
level: logLevelForLine(line),
|
|
1901
|
+
message: `${tag} ${line.trimEnd()}`
|
|
1902
|
+
});
|
|
1903
|
+
} catch {}
|
|
1856
1904
|
}
|
|
1857
1905
|
sendError(message, frameId) {
|
|
1858
1906
|
this.emitStderr(`${message}\n`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-
|
|
1
|
+
import { E as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-ClyAFBZk.mjs";
|
|
2
2
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
3
3
|
var HF_REPO = "camstack/camstack-models";
|
|
4
4
|
var HF_SCRYPTED = "scrypted/plugin-models";
|
|
@@ -2,7 +2,7 @@ import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as
|
|
|
2
2
|
import { a as d, i as f, n as p, o as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
|
|
3
3
|
import { n as _, r as v, t as y } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
|
|
4
4
|
import { n as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
|
|
5
|
-
import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-
|
|
5
|
+
import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQC_f4Yv.mjs";
|
|
6
6
|
//#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
|
|
7
7
|
var C = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), w = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), T = (e) => {
|
|
8
8
|
let t = w(e);
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.2",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_stream_broker_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.2",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_stream_broker_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.2",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_stream_broker_widgets",
|