@camstack/addon-pipeline-orchestrator 1.2.27 → 1.2.28
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/_stub.js +2 -2
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DZurdE2f.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-Myx4bNgp.mjs} +2 -2
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-LWwgi5Gh.mjs +26 -0
- package/dist/{_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CkysRyul.mjs → _virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DuE5H5lK.mjs} +1 -1
- package/dist/{hostInit-Bz4OrWey.mjs → hostInit-BVCY0SPK.mjs} +2 -2
- package/dist/index.js +423 -17
- package/dist/index.mjs +423 -17
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CA_bQsJB.mjs +0 -26
package/dist/index.js
CHANGED
|
@@ -6518,7 +6518,20 @@ var BrokerStatsSchema = object({
|
|
|
6518
6518
|
sampleRate: number(),
|
|
6519
6519
|
channels: number(),
|
|
6520
6520
|
supported: boolean()
|
|
6521
|
-
}).nullable().optional()
|
|
6521
|
+
}).nullable().optional(),
|
|
6522
|
+
/**
|
|
6523
|
+
* BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
|
|
6524
|
+
* distributing none of the device's audio, on live or recording.
|
|
6525
|
+
*
|
|
6526
|
+
* Present so a silent camera can be told apart from a broken one on the
|
|
6527
|
+
* stream panel itself, without cross-referencing the switch group: a
|
|
6528
|
+
* broker holding an `audio` track descriptor while `audioMuted` is true is
|
|
6529
|
+
* working exactly as asked. `audioMutedDropped` counts the audio units
|
|
6530
|
+
* thrown away since the current dial — it is how you confirm from stats
|
|
6531
|
+
* alone that the mute is on the packet path and not merely persisted.
|
|
6532
|
+
*/
|
|
6533
|
+
audioMuted: boolean().optional(),
|
|
6534
|
+
audioMutedDropped: number().optional()
|
|
6522
6535
|
});
|
|
6523
6536
|
/**
|
|
6524
6537
|
* Exporter-facing "profile restream" entry. Returned by
|
|
@@ -7685,6 +7698,19 @@ object({
|
|
|
7685
7698
|
* | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
|
|
7686
7699
|
* | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
|
|
7687
7700
|
* | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
|
|
7701
|
+
* | `broker-audio` | `streamBroker.setDeviceAudioMute` → `DeviceOverride.audioMuted` | `StreamBroker.setAudioMuted` drops the audio plane at the source: no `type:'audio'` packet leaves `fanOutEncoded`, no RTP reaches the restreamer, and the restreamer serves the video-only SDP |
|
|
7702
|
+
*
|
|
7703
|
+
* ## `device-audio` and `broker-audio` are two functions, not two knobs
|
|
7704
|
+
*
|
|
7705
|
+
* They look adjacent and they are not the same control ([D83](../../../../docs/decisions/adr-0083.md)):
|
|
7706
|
+
* `device-audio` writes the CAMERA, so it is hardware privacy — the microphone
|
|
7707
|
+
* genuinely stops, it survives CamStack entirely, and it costs a multi-second
|
|
7708
|
+
* encoder restart on every flip. `broker-audio` writes THIS server, so it is
|
|
7709
|
+
* instant, vendor-independent and reversible without touching the camera, and
|
|
7710
|
+
* a camera that ignores or lacks the ISAPI/Reolink control is still silenced.
|
|
7711
|
+
* D62 forbids a second switch that *disagrees* with the first; these two
|
|
7712
|
+
* cannot disagree, because neither reads the other's store — the camera holds
|
|
7713
|
+
* one, the broker holds the other, and each reports its own fact.
|
|
7688
7714
|
*
|
|
7689
7715
|
* ## The two switches whose authority is not on this server
|
|
7690
7716
|
*
|
|
@@ -7746,6 +7772,7 @@ var CameraSwitchIdSchema = _enum([
|
|
|
7746
7772
|
"object-detection",
|
|
7747
7773
|
"privacy-mask",
|
|
7748
7774
|
"device-audio",
|
|
7775
|
+
"broker-audio",
|
|
7749
7776
|
"audio-analysis",
|
|
7750
7777
|
"recording",
|
|
7751
7778
|
"notifications"
|
|
@@ -7761,12 +7788,17 @@ var CameraSwitchIdSchema = _enum([
|
|
|
7761
7788
|
* `object-detection` despite feeding it: a mask blanks REGIONS, so its blast
|
|
7762
7789
|
* radius is partial, and the "broadest first" rule does not rank a partial
|
|
7763
7790
|
* control above a whole-function one.
|
|
7791
|
+
*
|
|
7792
|
+
* `broker-audio` sits directly BELOW `device-audio` by the same source-first
|
|
7793
|
+
* rule: the camera's microphone feeds the broker, so silencing the camera
|
|
7794
|
+
* leaves the broker's mute with nothing to suppress; the reverse is not true.
|
|
7764
7795
|
*/
|
|
7765
7796
|
var CAMERA_SWITCH_ORDER = [
|
|
7766
7797
|
"stream-broker",
|
|
7767
7798
|
"object-detection",
|
|
7768
7799
|
"privacy-mask",
|
|
7769
7800
|
"device-audio",
|
|
7801
|
+
"broker-audio",
|
|
7770
7802
|
"audio-analysis",
|
|
7771
7803
|
"recording",
|
|
7772
7804
|
"notifications"
|
|
@@ -7792,7 +7824,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7792
7824
|
object({
|
|
7793
7825
|
kind: literal("camera-mask"),
|
|
7794
7826
|
capName: string()
|
|
7795
|
-
})
|
|
7827
|
+
}),
|
|
7828
|
+
object({ kind: literal("broker-audio-mute") })
|
|
7796
7829
|
]);
|
|
7797
7830
|
/**
|
|
7798
7831
|
* Why a switch is not offered for this camera. Rendered instead of the
|
|
@@ -7891,6 +7924,13 @@ var CAMERA_SWITCH_CATALOG = {
|
|
|
7891
7924
|
},
|
|
7892
7925
|
countsAsSwitchedOff: true
|
|
7893
7926
|
},
|
|
7927
|
+
"broker-audio": {
|
|
7928
|
+
id: "broker-audio",
|
|
7929
|
+
label: "Audio distribution",
|
|
7930
|
+
costWhenOff: "Off: this server distributes no sound for this camera — live view is silent and everything recorded while it is off is silent FOREVER, even after you turn it back on. Audio detection and classification also have nothing to analyse. The camera keeps capturing sound, so nothing about the camera changes and turning this back on is instant, with no interruption to the picture.",
|
|
7931
|
+
authority: { kind: "broker-audio-mute" },
|
|
7932
|
+
countsAsSwitchedOff: true
|
|
7933
|
+
},
|
|
7894
7934
|
"audio-analysis": {
|
|
7895
7935
|
id: "audio-analysis",
|
|
7896
7936
|
label: "Audio detection & classification",
|
|
@@ -8009,6 +8049,18 @@ function resolveState(descriptor, input) {
|
|
|
8009
8049
|
enabled: mask.enabled
|
|
8010
8050
|
};
|
|
8011
8051
|
}
|
|
8052
|
+
case "broker-audio-mute": {
|
|
8053
|
+
const broker = input.brokerAudio;
|
|
8054
|
+
if (broker === null) return {
|
|
8055
|
+
available: false,
|
|
8056
|
+
enabled: true,
|
|
8057
|
+
unavailableReason: "source-unreachable"
|
|
8058
|
+
};
|
|
8059
|
+
return {
|
|
8060
|
+
available: true,
|
|
8061
|
+
enabled: !broker.muted
|
|
8062
|
+
};
|
|
8063
|
+
}
|
|
8012
8064
|
}
|
|
8013
8065
|
}
|
|
8014
8066
|
/**
|
|
@@ -10251,7 +10303,25 @@ method(object({
|
|
|
10251
10303
|
}), _void(), {
|
|
10252
10304
|
kind: "mutation",
|
|
10253
10305
|
auth: "admin"
|
|
10254
|
-
}), method(object({ brokerId: string() }), boolean()), object({
|
|
10306
|
+
}), method(object({ brokerId: string() }), boolean()), method(object({ deviceId: number().int() }), object({
|
|
10307
|
+
muted: boolean(),
|
|
10308
|
+
/**
|
|
10309
|
+
* How many live non-derived brokers currently hold the mute. Purely
|
|
10310
|
+
* diagnostic: `muted` is the policy and is authoritative on its own
|
|
10311
|
+
* (it applies to brokers that do not exist yet), while this says
|
|
10312
|
+
* whether anything is presently being silenced.
|
|
10313
|
+
*/
|
|
10314
|
+
appliedBrokers: number().int().nonnegative()
|
|
10315
|
+
})), method(object({
|
|
10316
|
+
deviceId: number().int(),
|
|
10317
|
+
muted: boolean()
|
|
10318
|
+
}), object({
|
|
10319
|
+
muted: boolean(),
|
|
10320
|
+
appliedBrokers: number().int().nonnegative()
|
|
10321
|
+
}), {
|
|
10322
|
+
kind: "mutation",
|
|
10323
|
+
auth: "admin"
|
|
10324
|
+
}), object({
|
|
10255
10325
|
deviceId: number().int().nonnegative(),
|
|
10256
10326
|
camStreamId: string(),
|
|
10257
10327
|
profile: CamProfileSchema
|
|
@@ -15515,6 +15585,30 @@ var TrackSourceSchema = _enum([
|
|
|
15515
15585
|
"audio"
|
|
15516
15586
|
]);
|
|
15517
15587
|
/**
|
|
15588
|
+
* Where a track sits in the RETRAIN lifecycle (D81).
|
|
15589
|
+
*
|
|
15590
|
+
* - `none` — never marked, or un-marked. Evictable.
|
|
15591
|
+
* - `staging` — the operator wants this track as training material and has not
|
|
15592
|
+
* finished with it. **This is the only state retention holds**: the track and
|
|
15593
|
+
* everything it owns (object events, crops, keyframes, CLIP vector) survive
|
|
15594
|
+
* the device's age window.
|
|
15595
|
+
* - `trained` — the retrain page has taken what it needed. The frames it chose
|
|
15596
|
+
* were COPIED into the retrain dataset at selection time, so the dataset no
|
|
15597
|
+
* longer depends on the track's media and the track becomes EVICTABLE again.
|
|
15598
|
+
* Terminal for the plain `markForTrain` toggle: returning it to `staging` is
|
|
15599
|
+
* a deliberate action of the retrain page, not a side effect of a checkbox.
|
|
15600
|
+
*
|
|
15601
|
+
* There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
|
|
15602
|
+
* the store's filter language has only positive equality and `whereIn` — no
|
|
15603
|
+
* negation, no IS NULL — so a NULL would be unselectable by ANY predicate and
|
|
15604
|
+
* would make the entire pre-column history immortal in one deploy.
|
|
15605
|
+
*/
|
|
15606
|
+
var RetrainStatusSchema = _enum([
|
|
15607
|
+
"none",
|
|
15608
|
+
"staging",
|
|
15609
|
+
"trained"
|
|
15610
|
+
]);
|
|
15611
|
+
/**
|
|
15518
15612
|
* Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
|
|
15519
15613
|
* by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
|
|
15520
15614
|
* so the two surfaces cannot drift.
|
|
@@ -15524,18 +15618,31 @@ var TrackSourceSchema = _enum([
|
|
|
15524
15618
|
* columns existed read as absent, and a consumer that needs a boolean should say
|
|
15525
15619
|
* `flag === true`, not `flag !== false`.
|
|
15526
15620
|
*
|
|
15527
|
-
*
|
|
15528
|
-
*
|
|
15529
|
-
*
|
|
15530
|
-
* `
|
|
15621
|
+
* `markForTrain` is the WIRE FACE of {@link RetrainStatusSchema}, not a column:
|
|
15622
|
+
* it is exactly `retrainStatus === 'staging'`, in both directions. Writing
|
|
15623
|
+
* `true` moves `none → staging`, writing `false` moves `staging → none`, and a
|
|
15624
|
+
* `trained` track reports `false` while refusing both writes. The boolean is
|
|
15625
|
+
* kept because three surfaces drive a toggle off it; anything that needs to tell
|
|
15626
|
+
* "never marked" from "already trained" must read `retrainStatus`.
|
|
15627
|
+
*
|
|
15628
|
+
* `debug` does NOT pin; it is attention, not durability.
|
|
15531
15629
|
*/
|
|
15532
15630
|
var TrackFlagFields = {
|
|
15533
|
-
/** Operator marked this track as training material.
|
|
15631
|
+
/** Operator marked this track as training material — i.e. `retrainStatus` is
|
|
15632
|
+
* `'staging'`. */
|
|
15534
15633
|
markForTrain: boolean().optional(),
|
|
15535
15634
|
/** Operator marked this track for diagnostic attention. */
|
|
15536
15635
|
debug: boolean().optional()
|
|
15537
15636
|
};
|
|
15538
15637
|
/**
|
|
15638
|
+
* The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
|
|
15639
|
+
* Deliberately NOT part of {@link TrackFlagFields}: that group also builds the
|
|
15640
|
+
* write patch, and the status is not something the toggle sets — it is what the
|
|
15641
|
+
* toggle's boolean is derived from. Absent on an in-RAM track never touched;
|
|
15642
|
+
* always present on a persisted row (the column default materialises `'none'`).
|
|
15643
|
+
*/
|
|
15644
|
+
var TrackRetrainFields = { retrainStatus: RetrainStatusSchema.optional() };
|
|
15645
|
+
/**
|
|
15539
15646
|
* The write half: a PARTIAL patch. An omitted key is left untouched, so setting
|
|
15540
15647
|
* one flag can never clear the other — the toggles are independent and are
|
|
15541
15648
|
* driven from three surfaces that do not know about each other.
|
|
@@ -15549,7 +15656,32 @@ var TrackFlagsPatchSchema = object(TrackFlagFields);
|
|
|
15549
15656
|
var TrackFlagsSchema = object({
|
|
15550
15657
|
trackId: string(),
|
|
15551
15658
|
markForTrain: boolean(),
|
|
15552
|
-
debug: boolean()
|
|
15659
|
+
debug: boolean(),
|
|
15660
|
+
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
15661
|
+
* a track row) because this shape is only ever produced by the write body,
|
|
15662
|
+
* which always knows it — and a surface that has just written needs to render
|
|
15663
|
+
* `trained` without a re-fetch. */
|
|
15664
|
+
retrainStatus: RetrainStatusSchema
|
|
15665
|
+
});
|
|
15666
|
+
/** Per-camera slice of a training-export estimate. */
|
|
15667
|
+
var TrainingExportDeviceTotalsSchema = object({
|
|
15668
|
+
deviceId: number(),
|
|
15669
|
+
tracks: number().int(),
|
|
15670
|
+
files: number().int(),
|
|
15671
|
+
bytes: number().int()
|
|
15672
|
+
});
|
|
15673
|
+
/**
|
|
15674
|
+
* What a training export WOULD contain. Computed from media index rows only —
|
|
15675
|
+
* no blob is read to produce this.
|
|
15676
|
+
*/
|
|
15677
|
+
var TrainingExportSummarySchema = object({
|
|
15678
|
+
generatedAt: number(),
|
|
15679
|
+
trackCount: number().int(),
|
|
15680
|
+
fileCount: number().int(),
|
|
15681
|
+
byteCount: number().int(),
|
|
15682
|
+
/** More marked tracks exist than a single pass carries. */
|
|
15683
|
+
truncated: boolean(),
|
|
15684
|
+
devices: array(TrainingExportDeviceTotalsSchema).readonly()
|
|
15553
15685
|
});
|
|
15554
15686
|
var TrackSchema = object({
|
|
15555
15687
|
trackId: string(),
|
|
@@ -15594,7 +15726,8 @@ var TrackSchema = object({
|
|
|
15594
15726
|
* Populated from the persisted envelope columns on historical reads;
|
|
15595
15727
|
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
15596
15728
|
envelope: TrackEnvelopeSchema.optional(),
|
|
15597
|
-
...TrackFlagFields
|
|
15729
|
+
...TrackFlagFields,
|
|
15730
|
+
...TrackRetrainFields
|
|
15598
15731
|
});
|
|
15599
15732
|
var BaseEventFields = {
|
|
15600
15733
|
id: string(),
|
|
@@ -15808,7 +15941,8 @@ var KeyEventSchema = object({
|
|
|
15808
15941
|
bestEventId: string(),
|
|
15809
15942
|
/** Track lifetime in ms (lastSeen - firstSeen). */
|
|
15810
15943
|
windowMs: number().optional(),
|
|
15811
|
-
...TrackFlagFields
|
|
15944
|
+
...TrackFlagFields,
|
|
15945
|
+
...TrackRetrainFields
|
|
15812
15946
|
});
|
|
15813
15947
|
object({
|
|
15814
15948
|
trackId: string(),
|
|
@@ -16069,6 +16203,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
16069
16203
|
}), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
|
|
16070
16204
|
kind: "query",
|
|
16071
16205
|
auth: "admin"
|
|
16206
|
+
}), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
|
|
16207
|
+
kind: "query",
|
|
16208
|
+
auth: "admin"
|
|
16209
|
+
}), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
16210
|
+
kind: "query",
|
|
16211
|
+
auth: "admin"
|
|
16072
16212
|
}), method(object({
|
|
16073
16213
|
eventId: string(),
|
|
16074
16214
|
kind: MediaFileKindEnum.optional()
|
|
@@ -22345,6 +22485,173 @@ DeviceType.Camera, method(object({
|
|
|
22345
22485
|
status: OsdStatusSchema
|
|
22346
22486
|
});
|
|
22347
22487
|
/**
|
|
22488
|
+
* `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
|
|
22489
|
+
*
|
|
22490
|
+
* The `osd` cap is the firmware contract: it probes a camera's overlay
|
|
22491
|
+
* SLOTS and writes literal text into one. It has no idea WHERE that text
|
|
22492
|
+
* comes from, and it must not — a driver that grew a "show the temperature
|
|
22493
|
+
* here" feature would grow it once per vendor.
|
|
22494
|
+
*
|
|
22495
|
+
* This cap owns the other half: a per-(camera, slot) BINDING that says
|
|
22496
|
+
* which value feeds the slot, how it is formatted, and under which
|
|
22497
|
+
* conditions it is shown at all. One addon renders every binding on every
|
|
22498
|
+
* camera, so a new source costs zero driver code.
|
|
22499
|
+
*
|
|
22500
|
+
* Three deliberate choices, each with a rejected alternative:
|
|
22501
|
+
*
|
|
22502
|
+
* 1. A source is `(capName, valuePath)` over the kernel's device
|
|
22503
|
+
* runtime-state mirror — NOT a closed enum of source kinds. Every
|
|
22504
|
+
* cap-keyed slice a device publishes is bindable the day the cap
|
|
22505
|
+
* ships. The rejected alternative (one enum member per source, with
|
|
22506
|
+
* a resolver branch each) is what makes "add the humidity too" a
|
|
22507
|
+
* code change.
|
|
22508
|
+
* 2. The display gate reuses `NcConditionsSchema` verbatim — the
|
|
22509
|
+
* notification centre's condition vocabulary — rather than a parallel
|
|
22510
|
+
* model. An operator who has learned one condition editor has learned
|
|
22511
|
+
* both.
|
|
22512
|
+
* 3. Because the renderer's facts are device STATE and not a detection
|
|
22513
|
+
* record, only a SUBSET of that vocabulary can be answered here.
|
|
22514
|
+
* `setSlotBinding` REJECTS the rest at write time (see
|
|
22515
|
+
* `getConditionSupport`). It does not accept-then-fail-closed: a
|
|
22516
|
+
* condition that can never be true renders a permanently blank
|
|
22517
|
+
* overlay, and a blank overlay looks exactly like a broken camera.
|
|
22518
|
+
*/
|
|
22519
|
+
/** Where a slot's value comes from. */
|
|
22520
|
+
var OsdSourceSchema = discriminatedUnion("kind", [
|
|
22521
|
+
object({
|
|
22522
|
+
kind: literal("static"),
|
|
22523
|
+
text: string().max(64)
|
|
22524
|
+
}),
|
|
22525
|
+
object({
|
|
22526
|
+
kind: literal("clock"),
|
|
22527
|
+
/** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
|
|
22528
|
+
pattern: string().min(1).max(32).default("HH:mm"),
|
|
22529
|
+
/** IANA zone. Omitted = the server's zone. */
|
|
22530
|
+
timezone: string().min(1).max(64).optional()
|
|
22531
|
+
}),
|
|
22532
|
+
object({
|
|
22533
|
+
kind: literal("device-state"),
|
|
22534
|
+
deviceId: number().int().optional(),
|
|
22535
|
+
capName: string().min(1).max(64),
|
|
22536
|
+
/** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
|
|
22537
|
+
valuePath: string().min(1).max(64)
|
|
22538
|
+
})
|
|
22539
|
+
]);
|
|
22540
|
+
var OsdSlotBindingSchema = object({
|
|
22541
|
+
/** Off = the manager stops driving this slot. It does NOT clear it. */
|
|
22542
|
+
enabled: boolean().default(true),
|
|
22543
|
+
source: OsdSourceSchema,
|
|
22544
|
+
/** `${value}` and `${unit}` are substituted; every occurrence. */
|
|
22545
|
+
template: string().max(96).default("${value}"),
|
|
22546
|
+
/** Truncate with an ellipsis past this length. Absent = no limit. */
|
|
22547
|
+
maxCharacters: number().int().min(4).max(64).optional(),
|
|
22548
|
+
/**
|
|
22549
|
+
* Decimal places for a numeric value. `0` yields an integer — the
|
|
22550
|
+
* documented workaround for firmwares that reject `.` in overlay text.
|
|
22551
|
+
*/
|
|
22552
|
+
maxDecimals: number().int().min(0).max(4).default(1),
|
|
22553
|
+
/** Appended via `${unit}`. The state mirror does not carry units. */
|
|
22554
|
+
unitLabel: string().max(8).optional(),
|
|
22555
|
+
/** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
|
|
22556
|
+
valueMap: record(string(), string()).optional(),
|
|
22557
|
+
/** Time windows in which the slot is shown. Absent = always. */
|
|
22558
|
+
schedule: NcScheduleSchema.optional(),
|
|
22559
|
+
/**
|
|
22560
|
+
* Display gate, in the notification centre's condition vocabulary.
|
|
22561
|
+
* Only the keys reported by `getConditionSupport` are accepted.
|
|
22562
|
+
*/
|
|
22563
|
+
conditions: NcConditionsSchema.optional(),
|
|
22564
|
+
/** Rendered when the gate is closed or the value unreadable. Empty = hide. */
|
|
22565
|
+
fallbackText: string().max(64).default("")
|
|
22566
|
+
});
|
|
22567
|
+
/** One camera slot, as the operator sees it: firmware truth + our binding. */
|
|
22568
|
+
var OsdSlotViewSchema = object({
|
|
22569
|
+
slotId: string(),
|
|
22570
|
+
kind: OsdOverlayKindEnum,
|
|
22571
|
+
/** Firmware refuses text edits (a timestamp, the channel name). */
|
|
22572
|
+
readOnly: boolean(),
|
|
22573
|
+
cameraEnabled: boolean(),
|
|
22574
|
+
cameraText: string().optional(),
|
|
22575
|
+
binding: OsdSlotBindingSchema.nullable()
|
|
22576
|
+
});
|
|
22577
|
+
/**
|
|
22578
|
+
* What happened to one slot on one render pass. `unchanged` exists so the
|
|
22579
|
+
* operator can tell "we are driving this and the value is steady" from
|
|
22580
|
+
* "we never got there" — and so the loop can prove it is not rewriting
|
|
22581
|
+
* identical text to the camera every tick.
|
|
22582
|
+
*/
|
|
22583
|
+
var OsdRenderOutcomeEnum = _enum([
|
|
22584
|
+
"written",
|
|
22585
|
+
"unchanged",
|
|
22586
|
+
"gated",
|
|
22587
|
+
"unreadable",
|
|
22588
|
+
"disabled",
|
|
22589
|
+
"unbound",
|
|
22590
|
+
"failed"
|
|
22591
|
+
]);
|
|
22592
|
+
var OsdRenderResultSchema = object({
|
|
22593
|
+
slotId: string(),
|
|
22594
|
+
outcome: OsdRenderOutcomeEnum,
|
|
22595
|
+
/** The text the slot should carry. Empty = the slot is switched off. */
|
|
22596
|
+
text: string(),
|
|
22597
|
+
/** Why, whenever the outcome is not a plain write. Never silent. */
|
|
22598
|
+
reason: string().optional()
|
|
22599
|
+
});
|
|
22600
|
+
var OsdSourceValueTypeEnum = _enum([
|
|
22601
|
+
"number",
|
|
22602
|
+
"boolean",
|
|
22603
|
+
"string",
|
|
22604
|
+
"enum"
|
|
22605
|
+
]);
|
|
22606
|
+
/**
|
|
22607
|
+
* One bindable value, derived from a cap's `runtimeState` schema — never
|
|
22608
|
+
* hand-listed. The editor renders from this, so a cap that ships a new
|
|
22609
|
+
* state field becomes bindable with no UI change.
|
|
22610
|
+
*/
|
|
22611
|
+
var OsdSourceOptionSchema = object({
|
|
22612
|
+
deviceId: number().int(),
|
|
22613
|
+
deviceName: string(),
|
|
22614
|
+
capName: string(),
|
|
22615
|
+
valuePath: string(),
|
|
22616
|
+
label: string(),
|
|
22617
|
+
valueType: OsdSourceValueTypeEnum,
|
|
22618
|
+
/** Present for `enum`; the editor offers these as `valueMap` keys. */
|
|
22619
|
+
enumValues: array(string()).readonly().optional()
|
|
22620
|
+
});
|
|
22621
|
+
method(object({ deviceId: number().int() }), object({
|
|
22622
|
+
supported: boolean(),
|
|
22623
|
+
slots: array(OsdSlotViewSchema)
|
|
22624
|
+
}), { auth: "admin" }), method(object({ deviceId: number().int() }), object({ sources: array(OsdSourceOptionSchema) }), { auth: "admin" }), method(object({}), object({
|
|
22625
|
+
supported: array(string()),
|
|
22626
|
+
catalog: array(NcConditionDescriptorSchema)
|
|
22627
|
+
}), { auth: "admin" }), method(object({
|
|
22628
|
+
deviceId: number().int(),
|
|
22629
|
+
slotId: string().min(1),
|
|
22630
|
+
binding: OsdSlotBindingSchema
|
|
22631
|
+
}), object({
|
|
22632
|
+
slot: OsdSlotViewSchema,
|
|
22633
|
+
render: OsdRenderResultSchema
|
|
22634
|
+
}), {
|
|
22635
|
+
kind: "mutation",
|
|
22636
|
+
auth: "admin"
|
|
22637
|
+
}), method(object({
|
|
22638
|
+
deviceId: number().int(),
|
|
22639
|
+
slotId: string().min(1)
|
|
22640
|
+
}), object({ success: literal(true) }), {
|
|
22641
|
+
kind: "mutation",
|
|
22642
|
+
auth: "admin"
|
|
22643
|
+
}), method(object({
|
|
22644
|
+
deviceId: number().int(),
|
|
22645
|
+
slotId: string().min(1),
|
|
22646
|
+
binding: OsdSlotBindingSchema.optional()
|
|
22647
|
+
}), OsdRenderResultSchema, {
|
|
22648
|
+
kind: "mutation",
|
|
22649
|
+
auth: "admin"
|
|
22650
|
+
}), method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
|
|
22651
|
+
kind: "mutation",
|
|
22652
|
+
auth: "admin"
|
|
22653
|
+
});
|
|
22654
|
+
/**
|
|
22348
22655
|
* Feeder connectivity / power status — mirrors the HA petkit device-status
|
|
22349
22656
|
* enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
|
|
22350
22657
|
* `on_batteries` (running on battery backup). `null` until first reported.
|
|
@@ -27377,6 +27684,48 @@ Object.freeze({
|
|
|
27377
27684
|
addonId: null,
|
|
27378
27685
|
access: "create"
|
|
27379
27686
|
},
|
|
27687
|
+
"osdManager.clearSlotBinding": {
|
|
27688
|
+
capName: "osd-manager",
|
|
27689
|
+
capScope: "system",
|
|
27690
|
+
addonId: null,
|
|
27691
|
+
access: "delete"
|
|
27692
|
+
},
|
|
27693
|
+
"osdManager.getConditionSupport": {
|
|
27694
|
+
capName: "osd-manager",
|
|
27695
|
+
capScope: "system",
|
|
27696
|
+
addonId: null,
|
|
27697
|
+
access: "view"
|
|
27698
|
+
},
|
|
27699
|
+
"osdManager.getDeviceOsd": {
|
|
27700
|
+
capName: "osd-manager",
|
|
27701
|
+
capScope: "system",
|
|
27702
|
+
addonId: null,
|
|
27703
|
+
access: "view"
|
|
27704
|
+
},
|
|
27705
|
+
"osdManager.getSourceCatalog": {
|
|
27706
|
+
capName: "osd-manager",
|
|
27707
|
+
capScope: "system",
|
|
27708
|
+
addonId: null,
|
|
27709
|
+
access: "view"
|
|
27710
|
+
},
|
|
27711
|
+
"osdManager.previewSlot": {
|
|
27712
|
+
capName: "osd-manager",
|
|
27713
|
+
capScope: "system",
|
|
27714
|
+
addonId: null,
|
|
27715
|
+
access: "create"
|
|
27716
|
+
},
|
|
27717
|
+
"osdManager.renderDevice": {
|
|
27718
|
+
capName: "osd-manager",
|
|
27719
|
+
capScope: "system",
|
|
27720
|
+
addonId: null,
|
|
27721
|
+
access: "create"
|
|
27722
|
+
},
|
|
27723
|
+
"osdManager.setSlotBinding": {
|
|
27724
|
+
capName: "osd-manager",
|
|
27725
|
+
capScope: "system",
|
|
27726
|
+
addonId: null,
|
|
27727
|
+
access: "create"
|
|
27728
|
+
},
|
|
27380
27729
|
"petFeeder.callPet": {
|
|
27381
27730
|
capName: "pet-feeder",
|
|
27382
27731
|
capScope: "device",
|
|
@@ -27539,6 +27888,18 @@ Object.freeze({
|
|
|
27539
27888
|
addonId: null,
|
|
27540
27889
|
access: "view"
|
|
27541
27890
|
},
|
|
27891
|
+
"pipelineAnalytics.getTrainingExportSummary": {
|
|
27892
|
+
capName: "pipeline-analytics",
|
|
27893
|
+
capScope: "device",
|
|
27894
|
+
addonId: null,
|
|
27895
|
+
access: "view"
|
|
27896
|
+
},
|
|
27897
|
+
"pipelineAnalytics.getTrainingExportUrl": {
|
|
27898
|
+
capName: "pipeline-analytics",
|
|
27899
|
+
capScope: "device",
|
|
27900
|
+
addonId: null,
|
|
27901
|
+
access: "view"
|
|
27902
|
+
},
|
|
27542
27903
|
"pipelineAnalytics.listEventKinds": {
|
|
27543
27904
|
capName: "pipeline-analytics",
|
|
27544
27905
|
capScope: "device",
|
|
@@ -29015,6 +29376,12 @@ Object.freeze({
|
|
|
29015
29376
|
addonId: null,
|
|
29016
29377
|
access: "view"
|
|
29017
29378
|
},
|
|
29379
|
+
"streamBroker.getDeviceAudioMute": {
|
|
29380
|
+
capName: "stream-broker",
|
|
29381
|
+
capScope: "system",
|
|
29382
|
+
addonId: null,
|
|
29383
|
+
access: "view"
|
|
29384
|
+
},
|
|
29018
29385
|
"streamBroker.getPreBufferInfo": {
|
|
29019
29386
|
capName: "stream-broker",
|
|
29020
29387
|
capScope: "system",
|
|
@@ -29135,6 +29502,12 @@ Object.freeze({
|
|
|
29135
29502
|
addonId: null,
|
|
29136
29503
|
access: "create"
|
|
29137
29504
|
},
|
|
29505
|
+
"streamBroker.setDeviceAudioMute": {
|
|
29506
|
+
capName: "stream-broker",
|
|
29507
|
+
capScope: "system",
|
|
29508
|
+
addonId: null,
|
|
29509
|
+
access: "create"
|
|
29510
|
+
},
|
|
29138
29511
|
"streamBroker.setPreBufferDuration": {
|
|
29139
29512
|
capName: "stream-broker",
|
|
29140
29513
|
capScope: "system",
|
|
@@ -32107,6 +32480,18 @@ function parseDeviceKeyEngine(deviceKey) {
|
|
|
32107
32480
|
}
|
|
32108
32481
|
/** Per-stage timeout applied by `boundedStage` to every remote-fetch stage. */
|
|
32109
32482
|
var STAGE_TIMEOUT_MS = 3e3;
|
|
32483
|
+
/**
|
|
32484
|
+
* Every switch whose OFF position leaves audio analysis with nothing to
|
|
32485
|
+
* analyse — the camera not capturing, this server not distributing, or the
|
|
32486
|
+
* analyzer not running. Named once because `CameraStatus.audio.enabled` must
|
|
32487
|
+
* agree with all three, and a list rebuilt at the call site is a list that
|
|
32488
|
+
* will miss the next one.
|
|
32489
|
+
*/
|
|
32490
|
+
var AUDIO_SILENCING_SWITCH_IDS = [
|
|
32491
|
+
"device-audio",
|
|
32492
|
+
"broker-audio",
|
|
32493
|
+
"audio-analysis"
|
|
32494
|
+
];
|
|
32110
32495
|
var CameraStatusService = class {
|
|
32111
32496
|
deps;
|
|
32112
32497
|
constructor(deps) {
|
|
@@ -32301,12 +32686,19 @@ var CameraStatusService = class {
|
|
|
32301
32686
|
* is precisely the failure the switch group exists to remove, and leaving
|
|
32302
32687
|
* the lie in place would have made the group's own status block disagree
|
|
32303
32688
|
* with it.
|
|
32689
|
+
*
|
|
32690
|
+
* THREE switches can silence this stage, not one (D83). `audio-analysis`
|
|
32691
|
+
* stops the analyzer; `device-audio` stops the camera capturing; and
|
|
32692
|
+
* `broker-audio` stops this server distributing. Any of them leaves the
|
|
32693
|
+
* analyzer with nothing, and reporting `enabled: true` for the other two
|
|
32694
|
+
* would reintroduce the exact hardcoded lie the previous paragraph is about
|
|
32695
|
+
* — one row further down the group.
|
|
32304
32696
|
*/
|
|
32305
32697
|
buildAudioStage(audioNodeId, switchedOff) {
|
|
32306
32698
|
if (audioNodeId === null) return null;
|
|
32307
32699
|
return {
|
|
32308
32700
|
nodeId: audioNodeId,
|
|
32309
|
-
enabled: !switchedOff.includes(
|
|
32701
|
+
enabled: !AUDIO_SILENCING_SWITCH_IDS.some((id) => switchedOff.includes(id))
|
|
32310
32702
|
};
|
|
32311
32703
|
}
|
|
32312
32704
|
/**
|
|
@@ -32553,6 +32945,12 @@ var CameraSwitchService = class {
|
|
|
32553
32945
|
patch: { enabled }
|
|
32554
32946
|
});
|
|
32555
32947
|
return;
|
|
32948
|
+
case "broker-audio-mute":
|
|
32949
|
+
await api.streamBroker.setDeviceAudioMute.mutate({
|
|
32950
|
+
deviceId,
|
|
32951
|
+
muted: !enabled
|
|
32952
|
+
}, nodePin(this.deps.assignSource(deviceId)));
|
|
32953
|
+
return;
|
|
32556
32954
|
}
|
|
32557
32955
|
}
|
|
32558
32956
|
/**
|
|
@@ -32571,7 +32969,8 @@ var CameraSwitchService = class {
|
|
|
32571
32969
|
recordingEnabled: null,
|
|
32572
32970
|
notificationsMuted: null,
|
|
32573
32971
|
deviceAudio: null,
|
|
32574
|
-
privacyMask: null
|
|
32972
|
+
privacyMask: null,
|
|
32973
|
+
brokerAudio: null
|
|
32575
32974
|
},
|
|
32576
32975
|
wrapperAddonIdByCap: /* @__PURE__ */ new Map(),
|
|
32577
32976
|
recordingConfig: null
|
|
@@ -32611,11 +33010,16 @@ var CameraSwitchService = class {
|
|
|
32611
33010
|
this.warn(deviceId, "notificationRules.listDeviceMutes", err);
|
|
32612
33011
|
return null;
|
|
32613
33012
|
});
|
|
32614
|
-
const
|
|
33013
|
+
const brokerAudioPromise = api.streamBroker.getDeviceAudioMute.query({ deviceId }, nodePin(this.deps.assignSource(deviceId))).then((r) => ({ muted: r.muted })).catch((err) => {
|
|
33014
|
+
this.warn(deviceId, "streamBroker.getDeviceAudioMute", err);
|
|
33015
|
+
return null;
|
|
33016
|
+
});
|
|
33017
|
+
const [device, bindings, recordingConfig, mutedDeviceIds, brokerAudio] = await Promise.all([
|
|
32615
33018
|
devicePromise,
|
|
32616
33019
|
bindingsPromise,
|
|
32617
33020
|
recordingPromise,
|
|
32618
|
-
mutesPromise
|
|
33021
|
+
mutesPromise,
|
|
33022
|
+
brokerAudioPromise
|
|
32619
33023
|
]);
|
|
32620
33024
|
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : api.deviceManager.listBindableCapsForDeviceType.query({ deviceType: device.type }).catch((err) => {
|
|
32621
33025
|
this.warn(deviceId, "listBindableCapsForDeviceType", err);
|
|
@@ -32636,7 +33040,8 @@ var CameraSwitchService = class {
|
|
|
32636
33040
|
recordingEnabled: recordingConfig === null ? null : recordingConfig.enabled,
|
|
32637
33041
|
notificationsMuted: mutedDeviceIds === null ? null : mutedDeviceIds.includes(deviceId),
|
|
32638
33042
|
deviceAudio: privacy.deviceAudio,
|
|
32639
|
-
privacyMask: privacy.privacyMask
|
|
33043
|
+
privacyMask: privacy.privacyMask,
|
|
33044
|
+
brokerAudio
|
|
32640
33045
|
},
|
|
32641
33046
|
wrapperAddonIdByCap,
|
|
32642
33047
|
recordingConfig
|
|
@@ -38416,7 +38821,8 @@ async function buildOrchestratorControllers(deps) {
|
|
|
38416
38821
|
});
|
|
38417
38822
|
const cameraSwitchService = new CameraSwitchService({
|
|
38418
38823
|
api: () => deps.ctx().api ?? null,
|
|
38419
|
-
logger: deps.ctx().logger
|
|
38824
|
+
logger: deps.ctx().logger,
|
|
38825
|
+
assignSource: (deviceId) => topology.assignSource(deviceId)
|
|
38420
38826
|
});
|
|
38421
38827
|
const cameraStatusService = new CameraStatusService({
|
|
38422
38828
|
api: () => deps.ctx().api,
|