@camstack/addon-post-analysis 1.2.48 → 1.2.49
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/{dist-DKhwOB5L.js → dist-BaqsyWbd.js} +328 -23
- package/dist/{dist-4NhN1aPx.mjs → dist-DYOxxL_e.mjs} +328 -23
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-XPzbKGXi.js → node-CdY4IvEy.js} +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CD55gKkr.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B-HELo_d.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BfiNBOyJ.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CiIJt_s8.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-DnhLbxcA.mjs → hostInit-wlxZFMRT.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +408 -74
- package/dist/pipeline-analytics/index.mjs +407 -73
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -6531,9 +6531,38 @@ var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
|
6531
6531
|
/**
|
|
6532
6532
|
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
6533
6533
|
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
6534
|
+
*
|
|
6535
|
+
* ## The id is normalised here, and it has to be
|
|
6536
|
+
*
|
|
6537
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
6538
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
6539
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
6540
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
6541
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
6542
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
6543
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
6544
|
+
* addressed a node that does not exist.
|
|
6545
|
+
*
|
|
6546
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
6547
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
6548
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
6549
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
6550
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
6551
|
+
* hardware", which was false.
|
|
6552
|
+
*
|
|
6553
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
6554
|
+
* it is here and not at the call sites.
|
|
6534
6555
|
*/
|
|
6535
6556
|
function nodePin(nodeId) {
|
|
6536
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: nodeId } };
|
|
6557
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
6558
|
+
}
|
|
6559
|
+
/**
|
|
6560
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
6561
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
6562
|
+
*/
|
|
6563
|
+
function toNodeId(idOrRunnerId) {
|
|
6564
|
+
const head = idOrRunnerId.split("/")[0];
|
|
6565
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
6537
6566
|
}
|
|
6538
6567
|
/**
|
|
6539
6568
|
* Output schema shared by the contribution + live methods.
|
|
@@ -7271,7 +7300,7 @@ object({
|
|
|
7271
7300
|
* ## This file adds no state
|
|
7272
7301
|
*
|
|
7273
7302
|
* Every switch here is a VIEW onto an authority that already existed
|
|
7274
|
-
* ([
|
|
7303
|
+
* ([D62](../../../../docs/decisions/adr-0062.md)). The whole point of the
|
|
7275
7304
|
* group is that there is exactly one place each function is turned off, and
|
|
7276
7305
|
* the group routes to it:
|
|
7277
7306
|
*
|
|
@@ -7282,6 +7311,40 @@ object({
|
|
|
7282
7311
|
* | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
|
|
7283
7312
|
* | `recording` | `recording.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
|
|
7284
7313
|
* | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
|
|
7314
|
+
* | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
|
|
7315
|
+
* | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
|
|
7316
|
+
*
|
|
7317
|
+
* ## The two switches whose authority is not on this server
|
|
7318
|
+
*
|
|
7319
|
+
* `privacy-mask` and `device-audio` write the CAMERA. That is not a loophole
|
|
7320
|
+
* in "the group stores nothing" — it is the purest form of it: the camera
|
|
7321
|
+
* holds the fact, every read is a read-through, and there is no server-side
|
|
7322
|
+
* copy that could drift. Their availability therefore cannot come from
|
|
7323
|
+
* `listBindableCapsForDeviceType` (a device-NATIVE cap carries no wrappers and
|
|
7324
|
+
* is filtered out there); it comes from the cap's own camera-probed
|
|
7325
|
+
* `privacyMask.getOptions()`, which is strictly more honest — it answers for
|
|
7326
|
+
* THIS camera rather than for the device type
|
|
7327
|
+
* ([D74](../../../../docs/decisions/adr-0074.md)).
|
|
7328
|
+
*
|
|
7329
|
+
* ## `privacy-mask` is the one row whose ON is not "the function is working"
|
|
7330
|
+
*
|
|
7331
|
+
* Every other switch means *this camera's function is doing its job*, so
|
|
7332
|
+
* `enabled: false` is a thing an operator took away. `privacy-mask` means **the
|
|
7333
|
+
* MASK is active** — `enabled: true` is video deliberately obscured. The
|
|
7334
|
+
* polarity is not a choice made here: `addon-export-hap`'s privacy `Switch`
|
|
7335
|
+
* (`builders/privacy-switch.ts`) already mirrors `patch.enabled` verbatim, and
|
|
7336
|
+
* a HomeKit toggle that disagreed with the app's toggle for the same camera is
|
|
7337
|
+
* worse than either surface not having one.
|
|
7338
|
+
*
|
|
7339
|
+
* Two consequences follow and both are load-bearing:
|
|
7340
|
+
*
|
|
7341
|
+
* - **It never counts as `switchedOff`.** `countsAsSwitchedOff` is `false` for
|
|
7342
|
+
* exactly this row. With the polarity above, every camera that has NOT drawn
|
|
7343
|
+
* a privacy mask would otherwise report `switchedOff: ['privacy-mask']` — the
|
|
7344
|
+
* normal, healthy state of most cameras rendered as an operator disablement.
|
|
7345
|
+
* - **Its cost line names BOTH directions.** `costWhenOff` is rendered
|
|
7346
|
+
* unconditionally by both clients, so for this row it has to read correctly
|
|
7347
|
+
* whichever way the switch is sitting.
|
|
7285
7348
|
*
|
|
7286
7349
|
* The wrapper-binding pair is not a new idea: `legacy-migrations.ts` already
|
|
7287
7350
|
* migrated the legacy `audioEnabled` / `pipelineEnabled` /
|
|
@@ -7300,14 +7363,17 @@ object({
|
|
|
7300
7363
|
* `CameraStatus.switchedOff`.
|
|
7301
7364
|
*/
|
|
7302
7365
|
/**
|
|
7303
|
-
* The
|
|
7304
|
-
*
|
|
7366
|
+
* The functions the operator named — five on 2026-08-05, plus the camera's own
|
|
7367
|
+
* microphone on 2026-08-07. Deliberately NOT one id per pipeline step: face
|
|
7368
|
+
* recognition and plate/LPR are per-step toggles on
|
|
7305
7369
|
* `pipelineOrchestrator.setCameraStepToggle` and belong in the pipeline
|
|
7306
|
-
* editor, not in a
|
|
7370
|
+
* editor, not in a safety group.
|
|
7307
7371
|
*/
|
|
7308
7372
|
var CameraSwitchIdSchema = _enum([
|
|
7309
7373
|
"stream-broker",
|
|
7310
7374
|
"object-detection",
|
|
7375
|
+
"privacy-mask",
|
|
7376
|
+
"device-audio",
|
|
7311
7377
|
"audio-analysis",
|
|
7312
7378
|
"recording",
|
|
7313
7379
|
"notifications"
|
|
@@ -7325,14 +7391,26 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7325
7391
|
capName: string()
|
|
7326
7392
|
}),
|
|
7327
7393
|
object({ kind: literal("recording-config") }),
|
|
7328
|
-
object({ kind: literal("notification-mute") })
|
|
7394
|
+
object({ kind: literal("notification-mute") }),
|
|
7395
|
+
object({
|
|
7396
|
+
kind: literal("camera-audio"),
|
|
7397
|
+
capName: string()
|
|
7398
|
+
}),
|
|
7399
|
+
object({
|
|
7400
|
+
kind: literal("camera-mask"),
|
|
7401
|
+
capName: string()
|
|
7402
|
+
})
|
|
7329
7403
|
]);
|
|
7330
7404
|
/**
|
|
7331
7405
|
* Why a switch is not offered for this camera. Rendered instead of the
|
|
7332
7406
|
* control, never as a dead control — an absent function and a broken one must
|
|
7333
7407
|
* not look the same.
|
|
7334
7408
|
*/
|
|
7335
|
-
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7409
|
+
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7410
|
+
"no-provider",
|
|
7411
|
+
"source-unreachable",
|
|
7412
|
+
"not-configured"
|
|
7413
|
+
]);
|
|
7336
7414
|
/**
|
|
7337
7415
|
* One switch, resolved for one camera.
|
|
7338
7416
|
*
|
|
@@ -9325,6 +9403,26 @@ var EgressTranscodeRequestSchema = object({
|
|
|
9325
9403
|
"h264_mp4toannexb",
|
|
9326
9404
|
"hevc_mp4toannexb"
|
|
9327
9405
|
]).optional(),
|
|
9406
|
+
/**
|
|
9407
|
+
* Publish the transcode as a LOCAL push cam stream, instead of leaving the
|
|
9408
|
+
* consumer to dial the returned url. The broker picks the id and returns it
|
|
9409
|
+
* as `camStreamId` — a caller-supplied one would be circular, since the
|
|
9410
|
+
* sharing key is computed FROM this request.
|
|
9411
|
+
*
|
|
9412
|
+
* The url is still returned and still the contract for a transcode pinned to
|
|
9413
|
+
* another node. But dialling it locally costs an RTSP round trip that changes
|
|
9414
|
+
* the transport underneath the consumer: a dialled stream is an RTP source,
|
|
9415
|
+
* so `isRtpSource()` is true and the session takes the RTP-passthrough +
|
|
9416
|
+
* repacketizer branch. The push branch — the one the derived mechanism has
|
|
9417
|
+
* live hours on — is never reached. Measured on Alexa: broker registered, RTP
|
|
9418
|
+
* arriving, key frame arriving, black screen, on a chain healthy at every
|
|
9419
|
+
* other point.
|
|
9420
|
+
*
|
|
9421
|
+
* Same idea the transport already applies to CALLS, where `classifyCapRoute`
|
|
9422
|
+
* gives priority to `hub-in-process` so a local call never leaves the node.
|
|
9423
|
+
* This is that rule for media.
|
|
9424
|
+
*/
|
|
9425
|
+
publishLocally: boolean().optional(),
|
|
9328
9426
|
pixelFormat: _enum(["yuv420p", "nv12"]).optional(),
|
|
9329
9427
|
/**
|
|
9330
9428
|
* Operator/consumer override for decode hardware. ABSENT is the normal case
|
|
@@ -9369,7 +9467,13 @@ var EgressTranscodeSchema = object({
|
|
|
9369
9467
|
* Returned rather than assumed: a consumer that asked for hardware and got
|
|
9370
9468
|
* software needs to be able to see that without reading the broker's logs.
|
|
9371
9469
|
*/
|
|
9372
|
-
decodeHwAccel: string().nullable()
|
|
9470
|
+
decodeHwAccel: string().nullable(),
|
|
9471
|
+
/**
|
|
9472
|
+
* Set when `publishLocally` was honoured: attach to THIS instead of dialling
|
|
9473
|
+
* `url`, and the session takes the push/deframe transport rather than the
|
|
9474
|
+
* RTP-passthrough one. `null` means the consumer must dial.
|
|
9475
|
+
*/
|
|
9476
|
+
camStreamId: string().nullable()
|
|
9373
9477
|
});
|
|
9374
9478
|
method(object({
|
|
9375
9479
|
deviceId: number().int().nonnegative(),
|
|
@@ -18128,9 +18232,15 @@ DeviceType.Camera, method(object({
|
|
|
18128
18232
|
* Bypass the cache freshness check and fetch directly from the
|
|
18129
18233
|
* native (or stream-broker fallback). Triggered by the UI's
|
|
18130
18234
|
* "refresh" button so an operator can force a fresh frame
|
|
18131
|
-
* even when the cache is well within
|
|
18132
|
-
*
|
|
18133
|
-
*
|
|
18235
|
+
* even when the cache is well within the device's
|
|
18236
|
+
* `snapshotMaxAgeS` window.
|
|
18237
|
+
*
|
|
18238
|
+
* **`force` is an OPERATOR signal, not a freshness preference.** On a
|
|
18239
|
+
* battery camera it is the one thing that walks past the wrapper's
|
|
18240
|
+
* sleep gate and wakes the camera, so a background caller — a poller,
|
|
18241
|
+
* an event handler, a thumbnail — must NEVER set it. Every such caller
|
|
18242
|
+
* gets the cached frame, which on a sleeping battery camera is the
|
|
18243
|
+
* correct answer: stale but honest beats woken.
|
|
18134
18244
|
*/
|
|
18135
18245
|
force: boolean().optional()
|
|
18136
18246
|
}), SnapshotImageSchema.nullable()), method(object({ deviceId: number() }), _void(), {
|
|
@@ -23612,12 +23722,30 @@ var pressureSensorCapability = {
|
|
|
23612
23722
|
runtimeState: PressureSensorStatusSchema
|
|
23613
23723
|
};
|
|
23614
23724
|
/**
|
|
23615
|
-
*
|
|
23616
|
-
*
|
|
23617
|
-
*
|
|
23618
|
-
*
|
|
23619
|
-
*
|
|
23620
|
-
*
|
|
23725
|
+
* PRIVACY — what the camera deliberately does not capture. Two planes:
|
|
23726
|
+
*
|
|
23727
|
+
* - **video**: up to `maxRegions` SHAPES the camera blanks out (NOT a cell
|
|
23728
|
+
* grid). Reolink `<shelterList>` zones are rectangles; Hikvision ISAPI
|
|
23729
|
+
* `<RegionCoordinatesList>` zones are free polygons (this camera: exactly
|
|
23730
|
+
* 4 vertices, not necessarily axis-aligned). The cap composes the shared
|
|
23731
|
+
* rect|polygon subset of the MaskShape vocabulary. All coords are
|
|
23732
|
+
* normalized 0..1 (top-left origin).
|
|
23733
|
+
* - **audio**: the camera's microphone. `setAudioEnabled(false)` stops the
|
|
23734
|
+
* camera encoding an audio track at all, so EVERY consumer — live view,
|
|
23735
|
+
* recording, the audio analyzer, an export — sees silent video. There is
|
|
23736
|
+
* no server-side copy of this fact; the camera is the store and every read
|
|
23737
|
+
* is a read-through, which is why a switch over it cannot drift
|
|
23738
|
+
* ([D62](../../../../docs/decisions/adr-0062.md)).
|
|
23739
|
+
*
|
|
23740
|
+
* Both belong here for one reason: they are the two things an operator turns
|
|
23741
|
+
* off when the answer to "what is this camera allowed to record" changes, and
|
|
23742
|
+
* both are applied ON the device, before anything leaves it.
|
|
23743
|
+
*
|
|
23744
|
+
* **The audio flag has exactly one writer.** `stream-params` used to carry a
|
|
23745
|
+
* per-profile `audio` in its patch schema — reachable from no UI and honoured
|
|
23746
|
+
* by one provider — and it was removed when this landed. A second writer onto
|
|
23747
|
+
* one device register is the shape of every knob this repo has shipped that
|
|
23748
|
+
* disagreed with the one the reader read.
|
|
23621
23749
|
*/
|
|
23622
23750
|
/** A privacy-mask region's geometry — rectangle or free polygon. */
|
|
23623
23751
|
var PrivacyMaskShapeSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
|
|
@@ -23629,21 +23757,45 @@ var PrivacyMaskRegionSchema = object({
|
|
|
23629
23757
|
enabled: boolean(),
|
|
23630
23758
|
shape: PrivacyMaskShapeSchema
|
|
23631
23759
|
});
|
|
23632
|
-
/** Current on-camera privacy
|
|
23760
|
+
/** Current on-camera privacy state — mask master enable + zones + microphone. */
|
|
23633
23761
|
var PrivacyMaskStatusSchema = object({
|
|
23634
23762
|
enabled: boolean(),
|
|
23635
23763
|
/** Active zones (normalized 0..1). Length ≤ maxRegions. */
|
|
23636
23764
|
regions: array(PrivacyMaskRegionSchema),
|
|
23765
|
+
/**
|
|
23766
|
+
* Is the camera capturing sound right now? Read from the camera, never from
|
|
23767
|
+
* a server-side mirror.
|
|
23768
|
+
*
|
|
23769
|
+
* `null` means "no answer" — either this camera exposes no controllable
|
|
23770
|
+
* microphone (`getOptions().supportsAudioMute === false`) or the read
|
|
23771
|
+
* failed. A consumer must render `null` as UNKNOWN and never as `false`:
|
|
23772
|
+
* "the microphone is off" and "we could not ask" look identical to an
|
|
23773
|
+
* operator only until one of them is wrong.
|
|
23774
|
+
*
|
|
23775
|
+
* On a camera whose profiles carry the flag independently (Reolink writes
|
|
23776
|
+
* it per stream), `true` means AT LEAST ONE profile still carries audio —
|
|
23777
|
+
* privacy is only satisfied when every one of them is silent.
|
|
23778
|
+
*/
|
|
23779
|
+
audioEnabled: boolean().nullable(),
|
|
23637
23780
|
lastFetchedAt: number()
|
|
23638
23781
|
});
|
|
23639
|
-
/** Per-camera availability. */
|
|
23782
|
+
/** Per-camera availability. Probed, never assumed from the model name. */
|
|
23640
23783
|
var PrivacyMaskOptionsSchema = object({
|
|
23641
23784
|
/** Maximum number of supported zones. */
|
|
23642
23785
|
maxRegions: number(),
|
|
23643
23786
|
/** Shape kinds this camera accepts — Reolink: ['rect']; Hikvision: ['rect','polygon']. */
|
|
23644
23787
|
supportedShapes: array(MaskShapeKindSchema),
|
|
23645
23788
|
/** Polygon vertex bounds when 'polygon' is supported (Hikvision: {min:4,max:4}). */
|
|
23646
|
-
polygonVertices: MaskPolygonVerticesSchema.optional()
|
|
23789
|
+
polygonVertices: MaskPolygonVerticesSchema.optional(),
|
|
23790
|
+
/**
|
|
23791
|
+
* Does this camera expose a microphone switch we can actually write?
|
|
23792
|
+
*
|
|
23793
|
+
* Camera-probed: `true` only when the firmware answered with an audio flag
|
|
23794
|
+
* we know how to patch. A camera that never answered is `false` — a control
|
|
23795
|
+
* the operator can press that changes nothing is worse than no control, and
|
|
23796
|
+
* the switch group renders "not available" instead.
|
|
23797
|
+
*/
|
|
23798
|
+
supportsAudioMute: boolean()
|
|
23647
23799
|
});
|
|
23648
23800
|
/** Partial change — every field optional. */
|
|
23649
23801
|
var PrivacyMaskPatchSchema = object({
|
|
@@ -23670,6 +23822,27 @@ var privacyMaskCapability = {
|
|
|
23670
23822
|
}), _void(), {
|
|
23671
23823
|
kind: "mutation",
|
|
23672
23824
|
auth: "admin"
|
|
23825
|
+
}),
|
|
23826
|
+
/**
|
|
23827
|
+
* Turn the camera's microphone on or off, at the camera.
|
|
23828
|
+
*
|
|
23829
|
+
* Deliberately its OWN mutation rather than a field on
|
|
23830
|
+
* {@link PrivacyMaskPatchSchema}: `patch.enabled` already means "the video
|
|
23831
|
+
* mask master switch", and overloading it would make one boolean mean two
|
|
23832
|
+
* unrelated things on the same call. It is also the only method here whose
|
|
23833
|
+
* write leaves the device in a state a later `getStatus` reads back
|
|
23834
|
+
* verbatim, which is what makes it safe as a switch authority.
|
|
23835
|
+
*
|
|
23836
|
+
* A camera whose `getOptions().supportsAudioMute` is false must REJECT
|
|
23837
|
+
* this rather than silently accept it — a write nothing applies is exactly
|
|
23838
|
+
* what the switch group exists to remove.
|
|
23839
|
+
*/
|
|
23840
|
+
setAudioEnabled: method(object({
|
|
23841
|
+
deviceId: number(),
|
|
23842
|
+
enabled: boolean()
|
|
23843
|
+
}), _void(), {
|
|
23844
|
+
kind: "mutation",
|
|
23845
|
+
auth: "admin"
|
|
23673
23846
|
})
|
|
23674
23847
|
},
|
|
23675
23848
|
status: {
|
|
@@ -23952,6 +24125,21 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
|
23952
24125
|
})]);
|
|
23953
24126
|
/** Raw bytes of one finalized footage segment (read off disk on the recording node). */
|
|
23954
24127
|
var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
|
|
24128
|
+
/**
|
|
24129
|
+
* One GOP of a finalized segment, cut by byte range through the segment's own
|
|
24130
|
+
* `mfra` (D31 on the D42 feeder path). `data` is the `ftyp`+`moov` head plus
|
|
24131
|
+
* the single `moof`+`mdat` covering the requested instant — standalone-
|
|
24132
|
+
* demuxable, never the whole file. When the segment's index cannot be parsed
|
|
24133
|
+
* the provider degrades INSIDE the mechanism to the whole segment (still one
|
|
24134
|
+
* `data`, `gopStartMs` = the segment start) — a worse read, not another path.
|
|
24135
|
+
*/
|
|
24136
|
+
var ReadGopBytesResultSchema = object({
|
|
24137
|
+
data: _instanceof(Uint8Array),
|
|
24138
|
+
/** Absolute epoch ms of the returned fragment's first sample. */
|
|
24139
|
+
gopStartMs: number(),
|
|
24140
|
+
/** Media ms the returned fragment covers. */
|
|
24141
|
+
gopDurMs: number()
|
|
24142
|
+
});
|
|
23955
24143
|
method(object({
|
|
23956
24144
|
deviceId: number(),
|
|
23957
24145
|
fromMs: number(),
|
|
@@ -23994,6 +24182,14 @@ method(object({
|
|
|
23994
24182
|
}), ReadSegmentBytesResultSchema, {
|
|
23995
24183
|
kind: "query",
|
|
23996
24184
|
auth: "admin"
|
|
24185
|
+
}), method(object({
|
|
24186
|
+
deviceId: number(),
|
|
24187
|
+
profile: string(),
|
|
24188
|
+
startMs: number(),
|
|
24189
|
+
epochMs: number()
|
|
24190
|
+
}), ReadGopBytesResultSchema, {
|
|
24191
|
+
kind: "query",
|
|
24192
|
+
auth: "admin"
|
|
23997
24193
|
}), method(object({
|
|
23998
24194
|
deviceId: number(),
|
|
23999
24195
|
config: RecordingConfigSchema
|
|
@@ -24553,6 +24749,16 @@ var StreamProfileConfigSchema = object({
|
|
|
24553
24749
|
"baseline"
|
|
24554
24750
|
]).optional(),
|
|
24555
24751
|
gop: number().optional(),
|
|
24752
|
+
/**
|
|
24753
|
+
* Whether THIS profile currently carries an audio track. READ-ONLY here.
|
|
24754
|
+
*
|
|
24755
|
+
* There is no matching field on {@link StreamProfilePatchSchema}: the
|
|
24756
|
+
* camera's microphone is owned by `privacy-mask` (`setAudioEnabled`), which
|
|
24757
|
+
* writes every profile at once so "audio off" means silent everywhere. A
|
|
24758
|
+
* per-profile writer beside it would let a camera be half-muted and would be
|
|
24759
|
+
* a second knob onto one device register — the failure D62 exists to
|
|
24760
|
+
* prevent. Absent when the firmware does not report the flag.
|
|
24761
|
+
*/
|
|
24556
24762
|
audio: boolean().optional()
|
|
24557
24763
|
});
|
|
24558
24764
|
var StreamParamsStatusSchema = object({
|
|
@@ -24593,7 +24799,13 @@ var StreamParamsOptionsSchema = object({
|
|
|
24593
24799
|
ext: StreamProfileOptionsSchema.optional()
|
|
24594
24800
|
});
|
|
24595
24801
|
/** A partial change to one profile — every field optional; a provider
|
|
24596
|
-
* ignores fields it doesn't support.
|
|
24802
|
+
* ignores fields it doesn't support.
|
|
24803
|
+
*
|
|
24804
|
+
* There is deliberately NO `audio` here. It existed until 2026-08-07,
|
|
24805
|
+
* reachable from no form and honoured by exactly one provider, while the
|
|
24806
|
+
* camera's microphone is a whole-device fact. It now has one writer,
|
|
24807
|
+
* `privacyMask.setAudioEnabled`, which writes every profile — see
|
|
24808
|
+
* `privacy-mask.cap.ts`. */
|
|
24597
24809
|
var StreamProfilePatchSchema = object({
|
|
24598
24810
|
width: number().optional(),
|
|
24599
24811
|
height: number().optional(),
|
|
@@ -24606,8 +24818,7 @@ var StreamProfilePatchSchema = object({
|
|
|
24606
24818
|
"main",
|
|
24607
24819
|
"baseline"
|
|
24608
24820
|
]).optional(),
|
|
24609
|
-
gop: number().optional()
|
|
24610
|
-
audio: boolean().optional()
|
|
24821
|
+
gop: number().optional()
|
|
24611
24822
|
});
|
|
24612
24823
|
var streamParamsCapability = {
|
|
24613
24824
|
name: "stream-params",
|
|
@@ -30339,6 +30550,12 @@ Object.freeze({
|
|
|
30339
30550
|
addonId: null,
|
|
30340
30551
|
access: "view"
|
|
30341
30552
|
},
|
|
30553
|
+
"privacyMask.setAudioEnabled": {
|
|
30554
|
+
capName: "privacy-mask",
|
|
30555
|
+
capScope: "device",
|
|
30556
|
+
addonId: null,
|
|
30557
|
+
access: "create"
|
|
30558
|
+
},
|
|
30342
30559
|
"privacyMask.setMask": {
|
|
30343
30560
|
capName: "privacy-mask",
|
|
30344
30561
|
capScope: "device",
|
|
@@ -30507,6 +30724,12 @@ Object.freeze({
|
|
|
30507
30724
|
addonId: null,
|
|
30508
30725
|
access: "create"
|
|
30509
30726
|
},
|
|
30727
|
+
"recording.readGopBytes": {
|
|
30728
|
+
capName: "recording",
|
|
30729
|
+
capScope: "system",
|
|
30730
|
+
addonId: null,
|
|
30731
|
+
access: "view"
|
|
30732
|
+
},
|
|
30510
30733
|
"recording.readSegmentBytes": {
|
|
30511
30734
|
capName: "recording",
|
|
30512
30735
|
capScope: "system",
|
|
@@ -31973,6 +32196,88 @@ object({
|
|
|
31973
32196
|
paddingRatio: .15,
|
|
31974
32197
|
square: false
|
|
31975
32198
|
}).paddingRatio;
|
|
32199
|
+
/**
|
|
32200
|
+
* WHICH delivered frames the decode worker retains a native copy of.
|
|
32201
|
+
*
|
|
32202
|
+
* - `all` — every frame the worker delivered to the runner. The shipped
|
|
32203
|
+
* behaviour, and the only correct one if something can ask for a crop of a
|
|
32204
|
+
* frame the runner never sent to inference.
|
|
32205
|
+
* - `inferred` — only the frames the runner ADMITTED to its detection queue.
|
|
32206
|
+
* A native-crop request always names a `frameId` that rode an inference
|
|
32207
|
+
* result, so that is the only set a request can name. How much it drops is
|
|
32208
|
+
* the two-plane governor's admit ratio and nothing else: measured at ~50% on
|
|
32209
|
+
* this cluster, not the ~80% the design sketch assumed, because the governor
|
|
32210
|
+
* was not throttling as hard as the sketch supposed. Read
|
|
32211
|
+
* `leaseAdmitted`/`leaseOffered` off the metrics line for the camera in front
|
|
32212
|
+
* of you rather than quoting a number from here. The newest delivered frame is
|
|
32213
|
+
* croppable regardless — it is still the worker's reserved slot, not a lease —
|
|
32214
|
+
* which covers the one-frame race between a mark and the supersede that
|
|
32215
|
+
* consumes it.
|
|
32216
|
+
*/
|
|
32217
|
+
var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
|
|
32218
|
+
object({
|
|
32219
|
+
/**
|
|
32220
|
+
* How long a retained native frame is served before it counts as a miss.
|
|
32221
|
+
*
|
|
32222
|
+
* Must cover the FULL late-crop horizon: detection inference + the
|
|
32223
|
+
* cross-process inference-result hop to hub post-analysis + tracking + the
|
|
32224
|
+
* tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
|
|
32225
|
+
* outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
|
|
32226
|
+
* RAM per busy camera grows linearly with no measured hit-rate gain.
|
|
32227
|
+
*/
|
|
32228
|
+
ttlMs: number().int().min(250).max(1e4),
|
|
32229
|
+
/**
|
|
32230
|
+
* Hard per-decode-worker RAM ceiling for retained native frames, in MB.
|
|
32231
|
+
*
|
|
32232
|
+
* Intended as a SAFETY ceiling with the TTL as the effective cap — but check
|
|
32233
|
+
* which one is actually binding before reasoning from that. At the shipped
|
|
32234
|
+
* 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
|
|
32235
|
+
* at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
|
|
32236
|
+
* `leaseFrames` on the metrics line say which. When the ceiling binds, a
|
|
32237
|
+
* change that admits fewer frames buys retention WINDOW at constant RAM
|
|
32238
|
+
* rather than giving RAM back — lower this knob if RAM is what you wanted.
|
|
32239
|
+
* `0` DISABLES the lease entirely and falls the worker back to the tiny
|
|
32240
|
+
* leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
|
|
32241
|
+
* to replace).
|
|
32242
|
+
*/
|
|
32243
|
+
budgetMb: number().int().min(0).max(4096),
|
|
32244
|
+
/**
|
|
32245
|
+
* Demand window: eager per-frame native retention runs only within this many
|
|
32246
|
+
* ms of the last native-crop request (or of the dial starting).
|
|
32247
|
+
*
|
|
32248
|
+
* `0` means ALWAYS ON — it disables the gate, it does not disable retention.
|
|
32249
|
+
* That is the legacy behaviour that saturated an N100 (24 native-4K downloads
|
|
32250
|
+
* per second on a camera with zero crop demand), so leave it non-zero unless
|
|
32251
|
+
* you are reproducing that.
|
|
32252
|
+
*/
|
|
32253
|
+
activityMs: number().int().min(0).max(12e4),
|
|
32254
|
+
/**
|
|
32255
|
+
* Which delivered frames are retained at all — see
|
|
32256
|
+
* {@link NativeLeaseAdmissionSchema}. This is the only knob of the four that
|
|
32257
|
+
* changes WHAT is kept rather than for how long, so it is also the only one
|
|
32258
|
+
* that can turn a crop that used to hit into a miss. The worker counts every
|
|
32259
|
+
* crop request naming a frame it did NOT see marked
|
|
32260
|
+
* (`leaseUnmarkedCrops` on the session-decode metrics line): a non-zero value
|
|
32261
|
+
* there is the signal that some caller names frames outside the inference set
|
|
32262
|
+
* and that this must go back to `all`.
|
|
32263
|
+
*/
|
|
32264
|
+
admission: NativeLeaseAdmissionSchema
|
|
32265
|
+
});
|
|
32266
|
+
/**
|
|
32267
|
+
* The values in force when the operator has set nothing — byte-for-byte the
|
|
32268
|
+
* constants the decode worker shipped with as env-var defaults, so making these
|
|
32269
|
+
* settings changed no behaviour on the day it landed.
|
|
32270
|
+
*/
|
|
32271
|
+
var DEFAULT_NATIVE_LEASE_SETTINGS = {
|
|
32272
|
+
ttlMs: 1200,
|
|
32273
|
+
budgetMb: 1024,
|
|
32274
|
+
activityMs: 15e3,
|
|
32275
|
+
admission: "inferred"
|
|
32276
|
+
};
|
|
32277
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
|
|
32278
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
|
|
32279
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
32280
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
31976
32281
|
//#endregion
|
|
31977
32282
|
Object.defineProperty(exports, "BaseAddon", {
|
|
31978
32283
|
enumerable: true,
|