@camstack/addon-pipeline-orchestrator 1.2.222 → 1.2.224
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/assets/viewer/.viewer-current-files.json +7 -7
- package/assets/viewer/.viewer-version +1 -1
- package/assets/viewer/_expo/static/js/web/{index-52215d1c19f7bf489007e4b4ecb9770f.js → index-44b4758d64ed6c629120bf8deef01485.js} +8 -8
- package/assets/viewer/embed/assets/{MaskShapeCanvas-DI4BY7W2-PKlfDq_e.js → MaskShapeCanvas-DI4BY7W2-DW9iUovV.js} +1 -1
- package/assets/viewer/embed/assets/MotionZonesSettings-7c3yiT5E-CzRwhaU6.js +1 -0
- package/assets/viewer/embed/assets/PrivacyMaskSettings-PkYs077O-D_lYJ0Lt.js +1 -0
- package/assets/viewer/embed/assets/SceneMonitorEditor-BvDy3x3k-zFGikTQ4.js +1 -0
- package/assets/viewer/embed/assets/index-BSfCOPKT.css +1 -0
- package/assets/viewer/embed/assets/index-G1DWVOwz.js +115 -0
- package/assets/viewer/embed/index.html +2 -2
- package/assets/viewer/index.html +1 -1
- package/dist/_stub.js +32 -32
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-Ba6QA7CL.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-D9hT9aw6.mjs} +2 -2
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CJKRvrm0.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-lkkSZHH0.mjs +26 -0
- package/dist/{hostInit-Be3_uqeJ.mjs → hostInit-B77DKArA.mjs} +2 -2
- package/dist/index.js +442 -33
- package/dist/index.mjs +442 -33
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/assets/viewer/embed/assets/MotionZonesSettings-DNdnFHso-BcC-Qswj.js +0 -1
- package/assets/viewer/embed/assets/PrivacyMaskSettings-DNSLc6ww-DTaiq5jX.js +0 -1
- package/assets/viewer/embed/assets/SceneMonitorEditor-d_A4kO-M-DeAdwgn5.js +0 -1
- package/assets/viewer/embed/assets/index-CXJTH6mq.css +0 -1
- package/assets/viewer/embed/assets/index-Qxe8ijDE.js +0 -115
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-SM4f3ulJ.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BI7e6C_J.mjs +0 -26
package/dist/index.mjs
CHANGED
|
@@ -5363,7 +5363,7 @@ var ZodIssueCode = {
|
|
|
5363
5363
|
var ZodFirstPartyTypeKind;
|
|
5364
5364
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5365
5365
|
//#endregion
|
|
5366
|
-
//#region ../types/dist/sleep-
|
|
5366
|
+
//#region ../types/dist/sleep-CiR4z7r9.mjs
|
|
5367
5367
|
/**
|
|
5368
5368
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5369
5369
|
* window to float samples (D455).
|
|
@@ -6521,6 +6521,23 @@ var CamProfileSchema = _enum([
|
|
|
6521
6521
|
"mid",
|
|
6522
6522
|
"low"
|
|
6523
6523
|
]);
|
|
6524
|
+
/** Canonical ordering. Hard-coded; never sorted. */
|
|
6525
|
+
var CAM_PROFILE_ORDER = [
|
|
6526
|
+
"high",
|
|
6527
|
+
"mid",
|
|
6528
|
+
"low"
|
|
6529
|
+
];
|
|
6530
|
+
/**
|
|
6531
|
+
* Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
|
|
6532
|
+
*
|
|
6533
|
+
* `auto` is a REAL value, not an absent one — it means "resolve it by the rule
|
|
6534
|
+
* that consumer owns". It lives beside `CamProfile` because a rung is the only
|
|
6535
|
+
* operator-facing way to name a stream: a cam-stream id is broker-internal and
|
|
6536
|
+
* moves when a slot is reassigned, so a stored id silently stops meaning what
|
|
6537
|
+
* the operator picked. Today's one user is the audio analyser, whose rule is
|
|
6538
|
+
* "the lowest AVAILABLE native rung" (D537).
|
|
6539
|
+
*/
|
|
6540
|
+
var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
|
|
6524
6541
|
var CamStreamKindSchema = _enum([
|
|
6525
6542
|
"pull-rtsp",
|
|
6526
6543
|
"pull-rtmp",
|
|
@@ -21004,6 +21021,7 @@ var MediaFileKindEnum = _enum([
|
|
|
21004
21021
|
"keyFrameSmall",
|
|
21005
21022
|
"thumbnailSmall",
|
|
21006
21023
|
"confirmationCrop",
|
|
21024
|
+
"debugCrop",
|
|
21007
21025
|
"sceneFrame"
|
|
21008
21026
|
]);
|
|
21009
21027
|
/**
|
|
@@ -22668,6 +22686,18 @@ var RunnerCameraConfigSchema = object({
|
|
|
22668
22686
|
motionStreamId: string(),
|
|
22669
22687
|
detectionStreamId: string(),
|
|
22670
22688
|
/**
|
|
22689
|
+
* Which rung this camera's audio analysis listens on — `auto` (the lowest
|
|
22690
|
+
* AVAILABLE native rung) or an explicit one.
|
|
22691
|
+
*
|
|
22692
|
+
* Not consumed by the runner: carried here so it travels with the camera's
|
|
22693
|
+
* config the way `audioMotionWindowMs` does, and so the orchestrator's
|
|
22694
|
+
* watchdog resolves the same stream the audio subscription did. OPTIONAL
|
|
22695
|
+
* with no default — absent means `auto`, which is what every camera written
|
|
22696
|
+
* before this field had, and an absent optional adds nothing to the
|
|
22697
|
+
* generated cap-input defaults (D537).
|
|
22698
|
+
*/
|
|
22699
|
+
audioStreamPick: AudioStreamPickSchema.optional(),
|
|
22700
|
+
/**
|
|
22671
22701
|
* Motion sources that can activate the pipeline. At least one must
|
|
22672
22702
|
* be present. Multiple sources OR-ed: any one firing arms the
|
|
22673
22703
|
* cooldown timer and transitions watching → active. The same
|
|
@@ -25690,6 +25720,104 @@ var WebrtcStreamChoiceSchema = object({
|
|
|
25690
25720
|
/** Bitrate in kbps (null for adaptive). */
|
|
25691
25721
|
bitrateKbps: number().nullable()
|
|
25692
25722
|
});
|
|
25723
|
+
/**
|
|
25724
|
+
* The last move the adaptive ladder made on this session, and the numbers it
|
|
25725
|
+
* rested on. Mirrors `LadderDecision` in the stream-broker's
|
|
25726
|
+
* `adaptive-controller.ts` — the log line's evidence, made readable.
|
|
25727
|
+
*
|
|
25728
|
+
* Nullability is load-bearing: `lossRatio: null` means the loss tracker had
|
|
25729
|
+
* no window to report, which is NOT a link with zero loss.
|
|
25730
|
+
*/
|
|
25731
|
+
var WebrtcLadderDecisionSchema = object({
|
|
25732
|
+
/** `loss` — RTCP / client-reported packet loss; `stall` — the picture
|
|
25733
|
+
* stopped moving; `healthy` — a promotion. */
|
|
25734
|
+
cause: _enum([
|
|
25735
|
+
"loss",
|
|
25736
|
+
"stall",
|
|
25737
|
+
"healthy"
|
|
25738
|
+
]),
|
|
25739
|
+
/** The rung the ladder moved TO. Null for a move that changed no rung
|
|
25740
|
+
* (an MTU reduction, a forced transcode-down at the bottom). */
|
|
25741
|
+
tier: CamProfileSchema.nullable(),
|
|
25742
|
+
/** Whether that move forced the egress transcode-down rung. */
|
|
25743
|
+
forcedTranscodeDown: boolean(),
|
|
25744
|
+
/** Loss ratio over the tracker's window at the moment of the decision. */
|
|
25745
|
+
lossRatio: number().nullable(),
|
|
25746
|
+
/** The client's reported downlink at that moment, in kbps. */
|
|
25747
|
+
clientKbps: number().nullable(),
|
|
25748
|
+
/** How long the stall detector had been seeing nothing arrive. */
|
|
25749
|
+
stalledForMs: number().nullable(),
|
|
25750
|
+
/** Whether the broker WAS sending while that was measured — the half that
|
|
25751
|
+
* separates "the camera stopped" from "the link stopped". */
|
|
25752
|
+
serverSending: boolean().nullable(),
|
|
25753
|
+
/** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
|
|
25754
|
+
* must not read like one from this second. */
|
|
25755
|
+
at: number()
|
|
25756
|
+
});
|
|
25757
|
+
/** Per-tier count of MEASURED downgrades this viewer session has suffered. */
|
|
25758
|
+
var WebrtcTierFailureSchema = object({
|
|
25759
|
+
tier: CamProfileSchema,
|
|
25760
|
+
failures: number().int().nonnegative()
|
|
25761
|
+
});
|
|
25762
|
+
/**
|
|
25763
|
+
* What the SERVER decided for one WebRTC session — the half the client cannot
|
|
25764
|
+
* measure. See `getSessionDebug`.
|
|
25765
|
+
*/
|
|
25766
|
+
var WebrtcSessionDebugSchema = object({
|
|
25767
|
+
sessionId: string(),
|
|
25768
|
+
/** Whether the session still exists here. Everything below is null when
|
|
25769
|
+
* it does not — a dead session has no measurements, not zero ones. */
|
|
25770
|
+
alive: boolean(),
|
|
25771
|
+
/** Epoch ms this snapshot was taken, so the overlay can show its age. */
|
|
25772
|
+
observedAt: number(),
|
|
25773
|
+
/** The broker actually feeding this session (`<deviceId>/<camStreamId>`). */
|
|
25774
|
+
brokerId: string().nullable(),
|
|
25775
|
+
camStreamId: string().nullable(),
|
|
25776
|
+
/** `derived` = this hub BUILT the rung being served (a transcode/scale of
|
|
25777
|
+
* another stream); `native` = the camera's own encode. */
|
|
25778
|
+
sourceKind: _enum([
|
|
25779
|
+
"native",
|
|
25780
|
+
"derived",
|
|
25781
|
+
"unknown"
|
|
25782
|
+
]),
|
|
25783
|
+
/** Whether the ladder is running at all (an `adaptive` target). */
|
|
25784
|
+
adaptive: boolean(),
|
|
25785
|
+
/** The rung `auto` ACTUALLY resolved to right now. Null when the broker
|
|
25786
|
+
* is bound to no profile slot (a manually-activated stream). */
|
|
25787
|
+
servedTier: CamProfileSchema.nullable(),
|
|
25788
|
+
/** Which node ingests the source. Null when unresolved. */
|
|
25789
|
+
ingestNodeId: string().nullable(),
|
|
25790
|
+
/** Whether that node is a remote one (the media crosses a node boundary
|
|
25791
|
+
* before it reaches this session). Null when unresolved. */
|
|
25792
|
+
ingestIsRemote: boolean().nullable(),
|
|
25793
|
+
/** Codec off the source, and the one negotiated with this client. */
|
|
25794
|
+
sourceCodec: string().nullable(),
|
|
25795
|
+
negotiatedCodec: string().nullable(),
|
|
25796
|
+
/** Whether the EGRESS leg re-encodes for this client or forwards the
|
|
25797
|
+
* source bytes untouched. */
|
|
25798
|
+
egressLeg: _enum([
|
|
25799
|
+
"copy",
|
|
25800
|
+
"transcode",
|
|
25801
|
+
"unknown"
|
|
25802
|
+
]),
|
|
25803
|
+
/** What the broker itself reports about the rung it is serving. */
|
|
25804
|
+
brokerStatus: string().nullable(),
|
|
25805
|
+
brokerCodec: string().nullable(),
|
|
25806
|
+
brokerBitrateKbps: number().nullable(),
|
|
25807
|
+
brokerInputFps: number().nullable(),
|
|
25808
|
+
brokerUptimeMs: number().nullable(),
|
|
25809
|
+
/** RTP packets this session has sent — the server's own "am I sending?". */
|
|
25810
|
+
rtpPacketsSent: number().nullable(),
|
|
25811
|
+
/** D42: the track may be carrying the recorded feeder, not live. The ladder
|
|
25812
|
+
* is suspended there, so a frozen picture in `recorded` mode means
|
|
25813
|
+
* something entirely different. */
|
|
25814
|
+
trackMode: _enum(["live", "recorded"]).nullable(),
|
|
25815
|
+
lastDecision: WebrtcLadderDecisionSchema.nullable(),
|
|
25816
|
+
tierFailures: array(WebrtcTierFailureSchema),
|
|
25817
|
+
/** Successful promotions in this viewer session (drives the progressive
|
|
25818
|
+
* healthy timer). Null when no ladder is running. */
|
|
25819
|
+
upgradeCount: number().nullable()
|
|
25820
|
+
});
|
|
25693
25821
|
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(WebrtcStreamChoiceSchema)), method(object({
|
|
25694
25822
|
deviceId: number().int().nonnegative(),
|
|
25695
25823
|
target: WebrtcStreamTargetSchema,
|
|
@@ -25854,7 +25982,10 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
|
|
|
25854
25982
|
* the same answer as "it ended": either way nothing is holding it up.
|
|
25855
25983
|
*/
|
|
25856
25984
|
alive: boolean()
|
|
25857
|
-
}))
|
|
25985
|
+
})), method(object({
|
|
25986
|
+
deviceId: number().int().nonnegative(),
|
|
25987
|
+
sessionId: string()
|
|
25988
|
+
}), WebrtcSessionDebugSchema, { kind: "query" });
|
|
25858
25989
|
object({
|
|
25859
25990
|
/** All accessory children of the parent. */
|
|
25860
25991
|
childDeviceIds: array(number()).readonly(),
|
|
@@ -28942,16 +29073,37 @@ var GridProfileOfferSchema = object({
|
|
|
28942
29073
|
]),
|
|
28943
29074
|
offered: boolean(),
|
|
28944
29075
|
/** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
|
|
28945
|
-
missingSources: array(number().int().positive())
|
|
29076
|
+
missingSources: array(number().int().positive()),
|
|
29077
|
+
/**
|
|
29078
|
+
* The canvas this profile composes onto, `WxH`, or empty when it is not
|
|
29079
|
+
* offered. DERIVED from the cells and the sources' own size at this profile —
|
|
29080
|
+
* it is reported because nothing else in the system would ever say what the
|
|
29081
|
+
* grid came out as, and because it is the number an operator would otherwise
|
|
29082
|
+
* expect to type.
|
|
29083
|
+
*/
|
|
29084
|
+
canvas: string(),
|
|
29085
|
+
/**
|
|
29086
|
+
* Whether this profile is PUBLISHED, of the ones the grid could serve.
|
|
29087
|
+
*
|
|
29088
|
+
* A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
|
|
29089
|
+
* a 4K canvas built from 4K decodes — something to opt into, not something a
|
|
29090
|
+
* viewer's adaptive should be handed by climbing to the top rung it can see.
|
|
29091
|
+
* Default is `mid` + `low`.
|
|
29092
|
+
*/
|
|
29093
|
+
published: boolean()
|
|
28946
29094
|
});
|
|
28947
29095
|
var GridLayoutViewSchema = object({
|
|
28948
29096
|
/** The persisted grid row this camera was declared from. */
|
|
28949
29097
|
instanceId: string(),
|
|
28950
29098
|
deviceId: number().int().nonnegative(),
|
|
28951
29099
|
name: string(),
|
|
28952
|
-
/**
|
|
28953
|
-
|
|
28954
|
-
|
|
29100
|
+
/**
|
|
29101
|
+
* NO canvas size. A grid's resolution is not authored: each profile derives
|
|
29102
|
+
* its own from the cells and its sources' dimensions. The two numbers that
|
|
29103
|
+
* used to be here were a text field that silently decided both how much the
|
|
29104
|
+
* composite cost and how sharp it was — see `profiles[].canvas` for what it
|
|
29105
|
+
* came out as.
|
|
29106
|
+
*/
|
|
28955
29107
|
fps: number().int(),
|
|
28956
29108
|
cells: array(GridLayoutCellSchema),
|
|
28957
29109
|
/** What the catalog will publish, and what it refuses to. Read-only. */
|
|
@@ -28960,9 +29112,13 @@ var GridLayoutViewSchema = object({
|
|
|
28960
29112
|
var GridLayoutPatchSchema = object({
|
|
28961
29113
|
deviceId: number().int().nonnegative(),
|
|
28962
29114
|
name: string().min(1).max(160).optional(),
|
|
28963
|
-
width: number().int().min(160).max(7680).optional(),
|
|
28964
|
-
height: number().int().min(120).max(4320).optional(),
|
|
28965
29115
|
fps: number().int().min(1).max(60).optional(),
|
|
29116
|
+
/** Which profiles to publish. See `GridProfileOffer.published`. */
|
|
29117
|
+
publishedProfiles: array(_enum([
|
|
29118
|
+
"high",
|
|
29119
|
+
"mid",
|
|
29120
|
+
"low"
|
|
29121
|
+
])).max(3).optional(),
|
|
28966
29122
|
/**
|
|
28967
29123
|
* The whole cell list at once. A per-cell patch would need an ordering the
|
|
28968
29124
|
* editor does not have, and a half-applied layout is a picture nobody asked
|
|
@@ -39631,6 +39787,12 @@ Object.freeze({
|
|
|
39631
39787
|
addonId: null,
|
|
39632
39788
|
access: "view"
|
|
39633
39789
|
},
|
|
39790
|
+
"webrtcSession.getSessionDebug": {
|
|
39791
|
+
capName: "webrtc-session",
|
|
39792
|
+
capScope: "device",
|
|
39793
|
+
addonId: null,
|
|
39794
|
+
access: "view"
|
|
39795
|
+
},
|
|
39634
39796
|
"webrtcSession.getSessionState": {
|
|
39635
39797
|
capName: "webrtc-session",
|
|
39636
39798
|
capScope: "device",
|
|
@@ -41614,6 +41776,11 @@ Object.freeze({
|
|
|
41614
41776
|
form: "single",
|
|
41615
41777
|
optional: false
|
|
41616
41778
|
}],
|
|
41779
|
+
"webrtcSession.getSessionDebug": [{
|
|
41780
|
+
name: "deviceId",
|
|
41781
|
+
form: "single",
|
|
41782
|
+
optional: false
|
|
41783
|
+
}],
|
|
41617
41784
|
"webrtcSession.getSessionState": [{
|
|
41618
41785
|
name: "deviceId",
|
|
41619
41786
|
form: "single",
|
|
@@ -42477,6 +42644,106 @@ function balanceAudio(input) {
|
|
|
42477
42644
|
};
|
|
42478
42645
|
}
|
|
42479
42646
|
//#endregion
|
|
42647
|
+
//#region src/audio-stream-selection.ts
|
|
42648
|
+
/**
|
|
42649
|
+
* Rung order for an audio consumer: cheapest first. The inverse of
|
|
42650
|
+
* `CAM_PROFILE_ORDER` (`high → mid → low`), derived from it rather than
|
|
42651
|
+
* re-spelled so the two can never disagree about which rungs exist.
|
|
42652
|
+
*/
|
|
42653
|
+
var AUDIO_RUNG_PREFERENCE = [...CAM_PROFILE_ORDER].reverse();
|
|
42654
|
+
/**
|
|
42655
|
+
* True when a cam-stream id names a broker-spawned transcode plane.
|
|
42656
|
+
*
|
|
42657
|
+
* The `derived:` prefix is the repo-wide convention (`stream-broker-manager`'s
|
|
42658
|
+
* `derivedDefinition`, `snapshot-grab-pick`, `prefer-live-adaptive-candidates`,
|
|
42659
|
+
* the device-scoped stream pickers). Judged from the ID because that is what
|
|
42660
|
+
* the caller holds: the profile-slot table carries `sourceCamStreamId`, not the
|
|
42661
|
+
* published `CameraStream.kind`, and asking the broker for the whole stream
|
|
42662
|
+
* catalogue to re-derive a fact the id already states would add an RPC to every
|
|
42663
|
+
* audio subscribe.
|
|
42664
|
+
*/
|
|
42665
|
+
function isDerivedCamStream(camStreamId) {
|
|
42666
|
+
return camStreamId.startsWith("derived:");
|
|
42667
|
+
}
|
|
42668
|
+
/** Every value the per-camera setting may hold, in the order the UI offers them. */
|
|
42669
|
+
var AUDIO_STREAM_PICKS = ["auto", ...CAM_PROFILE_ORDER];
|
|
42670
|
+
/**
|
|
42671
|
+
* Narrow a stored settings value to an {@link AudioStreamPick}.
|
|
42672
|
+
*
|
|
42673
|
+
* Anything that is not one of the four known values — absent, empty, a rung
|
|
42674
|
+
* from a schema this build does not have — reads as `auto`. That is the
|
|
42675
|
+
* behaviour every camera already has, so an unreadable value degrades to
|
|
42676
|
+
* today's rather than throwing the camera's whole detection-settings read.
|
|
42677
|
+
*/
|
|
42678
|
+
function toAudioStreamPick(value) {
|
|
42679
|
+
return AUDIO_STREAM_PICKS.find((pick) => pick === value) ?? "auto";
|
|
42680
|
+
}
|
|
42681
|
+
/**
|
|
42682
|
+
* Choose the cam-stream this camera's audio analysis attaches to.
|
|
42683
|
+
*
|
|
42684
|
+
* Pure. Every branch is either a selection carrying the reason, or a refusal
|
|
42685
|
+
* carrying the cause — there is no third answer and no silent default.
|
|
42686
|
+
*/
|
|
42687
|
+
function selectAudioCamStream(input) {
|
|
42688
|
+
const { pick, motionStreamId, assigned } = input;
|
|
42689
|
+
if (pick !== "auto") {
|
|
42690
|
+
if (assigned === null) return {
|
|
42691
|
+
kind: "refused",
|
|
42692
|
+
reason: "pick-unresolvable",
|
|
42693
|
+
pick,
|
|
42694
|
+
rejectedCamStreamId: null
|
|
42695
|
+
};
|
|
42696
|
+
const picked = assigned.get(pick);
|
|
42697
|
+
if (picked === void 0) return {
|
|
42698
|
+
kind: "refused",
|
|
42699
|
+
reason: "pick-unassigned",
|
|
42700
|
+
pick,
|
|
42701
|
+
rejectedCamStreamId: null
|
|
42702
|
+
};
|
|
42703
|
+
return {
|
|
42704
|
+
kind: "selected",
|
|
42705
|
+
camStreamId: picked,
|
|
42706
|
+
profile: pick,
|
|
42707
|
+
selectedBy: "operator-pick",
|
|
42708
|
+
derived: isDerivedCamStream(picked)
|
|
42709
|
+
};
|
|
42710
|
+
}
|
|
42711
|
+
if (assigned === null) {
|
|
42712
|
+
if (isDerivedCamStream(motionStreamId)) return {
|
|
42713
|
+
kind: "refused",
|
|
42714
|
+
reason: "slots-unknown-and-fallback-derived",
|
|
42715
|
+
pick,
|
|
42716
|
+
rejectedCamStreamId: motionStreamId
|
|
42717
|
+
};
|
|
42718
|
+
return {
|
|
42719
|
+
kind: "selected",
|
|
42720
|
+
camStreamId: motionStreamId,
|
|
42721
|
+
profile: null,
|
|
42722
|
+
selectedBy: "motion-stream-fallback",
|
|
42723
|
+
derived: false
|
|
42724
|
+
};
|
|
42725
|
+
}
|
|
42726
|
+
for (const profile of AUDIO_RUNG_PREFERENCE) {
|
|
42727
|
+
const camStreamId = assigned.get(profile);
|
|
42728
|
+
if (camStreamId === void 0) continue;
|
|
42729
|
+
if (isDerivedCamStream(camStreamId)) continue;
|
|
42730
|
+
return {
|
|
42731
|
+
kind: "selected",
|
|
42732
|
+
camStreamId,
|
|
42733
|
+
profile,
|
|
42734
|
+
selectedBy: "lowest-native-rung",
|
|
42735
|
+
derived: false
|
|
42736
|
+
};
|
|
42737
|
+
}
|
|
42738
|
+
const anyAssigned = AUDIO_RUNG_PREFERENCE.some((profile) => assigned.has(profile));
|
|
42739
|
+
return {
|
|
42740
|
+
kind: "refused",
|
|
42741
|
+
reason: anyAssigned ? "all-rungs-derived" : "no-assigned-rung",
|
|
42742
|
+
pick,
|
|
42743
|
+
rejectedCamStreamId: anyAssigned ? motionStreamId : null
|
|
42744
|
+
};
|
|
42745
|
+
}
|
|
42746
|
+
//#endregion
|
|
42480
42747
|
//#region src/keyed-async-lock.ts
|
|
42481
42748
|
/**
|
|
42482
42749
|
* Serializes critical sections per key. Different keys run fully
|
|
@@ -42976,15 +43243,46 @@ var AudioSubscriptionController = class {
|
|
|
42976
43243
|
this.deps.logger.info("audio subscribe deferred: audioMode=on-motion, no window open", { tags: { deviceId } });
|
|
42977
43244
|
return null;
|
|
42978
43245
|
}
|
|
42979
|
-
const
|
|
43246
|
+
const selection = selectAudioCamStream({
|
|
43247
|
+
pick: config.audioStreamPick ?? "auto",
|
|
43248
|
+
motionStreamId: config.motionStreamId,
|
|
43249
|
+
assigned: await this.deps.assignedProfiles(deviceId)
|
|
43250
|
+
});
|
|
43251
|
+
if (selection.kind === "refused") {
|
|
43252
|
+
this.deps.logger.warn("audio subscription REFUSED — no native rung to listen on (an audio-only consumer never causes a video transcode)", {
|
|
43253
|
+
tags: { deviceId },
|
|
43254
|
+
meta: {
|
|
43255
|
+
reason: selection.reason,
|
|
43256
|
+
pick: selection.pick,
|
|
43257
|
+
rejectedCamStreamId: selection.rejectedCamStreamId,
|
|
43258
|
+
motionStreamId: config.motionStreamId,
|
|
43259
|
+
hint: selection.reason === "pick-unassigned" ? "this camera no longer publishes the rung its audio is pinned to — repoint the Audio stream setting, or set it back to Auto" : "assign a native (non-derived) stream to one of this camera’s profile slots, or pick a rung explicitly"
|
|
43260
|
+
}
|
|
43261
|
+
});
|
|
43262
|
+
return null;
|
|
43263
|
+
}
|
|
43264
|
+
const audioStream = selection.camStreamId;
|
|
42980
43265
|
const audioBrokerId = makeSourceBrokerId(deviceId, audioStream);
|
|
43266
|
+
this.deps.logger.info("audio stream selected", {
|
|
43267
|
+
tags: { deviceId },
|
|
43268
|
+
meta: {
|
|
43269
|
+
camStreamId: audioStream,
|
|
43270
|
+
profile: selection.profile,
|
|
43271
|
+
selectedBy: selection.selectedBy,
|
|
43272
|
+
derived: selection.derived,
|
|
43273
|
+
pick: config.audioStreamPick ?? "auto",
|
|
43274
|
+
motionStreamId: config.motionStreamId,
|
|
43275
|
+
...selection.derived ? { note: "this pick is a derived stream — the audio subscription keeps its transcode feeder alive" } : {}
|
|
43276
|
+
}
|
|
43277
|
+
});
|
|
42981
43278
|
if ((await this.deps.probeAudioTrack(deviceId, audioStream)).kind === "absent") {
|
|
42982
43279
|
this.deps.logger.warn("audio subscription REFUSED — this stream carries no audio track", {
|
|
42983
43280
|
tags: { deviceId },
|
|
42984
43281
|
meta: {
|
|
42985
43282
|
camStreamId: audioStream,
|
|
42986
43283
|
brokerId: audioBrokerId,
|
|
42987
|
-
selectedBy:
|
|
43284
|
+
selectedBy: selection.selectedBy,
|
|
43285
|
+
profile: selection.profile,
|
|
42988
43286
|
hint: "point the camera’s audio at a stream that has an audio track — no stream is substituted automatically"
|
|
42989
43287
|
}
|
|
42990
43288
|
});
|
|
@@ -45920,6 +46218,39 @@ var pipelineOrchestratorActions = defineCustomActions({
|
|
|
45920
46218
|
var TRANSIENT_SLOT_READ = Symbol("transient-slot-read");
|
|
45921
46219
|
//#endregion
|
|
45922
46220
|
//#region src/device-detection-settings.ts
|
|
46221
|
+
/**
|
|
46222
|
+
* Pure device-detection-settings derivation for the pipeline orchestrator.
|
|
46223
|
+
*
|
|
46224
|
+
* Extracted from `index.ts`'s `resolveDeviceDetectionSettings` (S3 refactor
|
|
46225
|
+
* step — zero behavior change). That ~185-line method interleaved I/O
|
|
46226
|
+
* (device-store read, binding/feature lookups) with a 5-branch
|
|
46227
|
+
* `motionSources` default ladder and several other operator-wins→profile→
|
|
46228
|
+
* fallback resolutions. The class-side method now does ONLY the I/O
|
|
46229
|
+
* gathering (`readDeviceStore`, `hydrateSchema`, `lookupDeviceFeatures`,
|
|
46230
|
+
* `deviceHasOnboardMotionCap`, `isDetectionPipelineActive`,
|
|
46231
|
+
* `isMotionDetectionActive`) and hands the raw materials to
|
|
46232
|
+
* `resolveDetectionSettings`, the pure decision half that used to be
|
|
46233
|
+
* interleaved with that I/O.
|
|
46234
|
+
*
|
|
46235
|
+
* Also carries:
|
|
46236
|
+
* - `resolveAssignedProfile` — the profile-slot fallback chain (was already
|
|
46237
|
+
* pure; moved verbatim, no `this` usage in the original).
|
|
46238
|
+
* - `detectionConfigEquals` — the field-by-field `CameraDetectionConfig`
|
|
46239
|
+
* comparator (was already a pure `static`; moved verbatim).
|
|
46240
|
+
* - `buildDetectionConfigFromInputs` — the pure tail of
|
|
46241
|
+
* `buildDetectionConfig`: given the resolved settings + the assigned
|
|
46242
|
+
* profile-slot map, resolves motion/detection profiles and shapes the
|
|
46243
|
+
* `CameraDetectionConfig` result.
|
|
46244
|
+
* - `buildRunnerConfig` — a single-source-of-truth `RunnerCameraConfig`
|
|
46245
|
+
* literal builder (smell #10: the literal was built field-by-field in 3
|
|
46246
|
+
* places — `startDetection`, `ensureMotionWatch`,
|
|
46247
|
+
* `dispatchDetectionSession` — differing only in `motionSources` /
|
|
46248
|
+
* `detectionMode` / `audioMode` / `steps` / `audio`). Wired ONLY into
|
|
46249
|
+
* `startDetection`'s always-on/config-build branch in this step; the two
|
|
46250
|
+
* Phase-3 session-time sites intentionally still build their own literals
|
|
46251
|
+
* inline — a later refactor step migrates them so this step never touches
|
|
46252
|
+
* the session-dispatch path.
|
|
46253
|
+
*/
|
|
45923
46254
|
/** Read a required string leaf out of the hydrated `flat` schema values. */
|
|
45924
46255
|
function mustString(flat, deviceId, key) {
|
|
45925
46256
|
const v = flat[key];
|
|
@@ -45996,6 +46327,7 @@ function resolveDetectionSettings(input) {
|
|
|
45996
46327
|
}
|
|
45997
46328
|
const userDetectionMode = raw["detectionMode"];
|
|
45998
46329
|
const detectionMode = typeof userDetectionMode === "string" && isPipelinePhaseMode(userDetectionMode) ? userDetectionMode : profile?.defaults.detectionMode ?? "on-motion";
|
|
46330
|
+
const audioStreamPick = toAudioStreamPick(raw["audioStreamPick"]);
|
|
45999
46331
|
const userAudioMode = raw["audioMode"];
|
|
46000
46332
|
const audioMode = typeof userAudioMode === "string" && isPipelinePhaseMode(userAudioMode) ? userAudioMode : profile?.defaults.audioMode ?? "always-on";
|
|
46001
46333
|
const userOnboardMotionDrivesAnalyzer = raw["onboardMotionDrivesAnalyzer"];
|
|
@@ -46021,6 +46353,7 @@ function resolveDetectionSettings(input) {
|
|
|
46021
46353
|
motionCooldownMs: numberOrDefault(flat, "motionCooldownMs"),
|
|
46022
46354
|
maxSessionHoldMs: numberOrDefault(flat, "maxSessionHoldMs"),
|
|
46023
46355
|
audioMotionWindowMs: numberOrDefault(flat, "audioMotionWindowMs"),
|
|
46356
|
+
audioStreamPick,
|
|
46024
46357
|
detectionMode,
|
|
46025
46358
|
audioMode,
|
|
46026
46359
|
onboardMotionDrivesAnalyzer,
|
|
@@ -46081,7 +46414,7 @@ function buildDetectionConfigFromInputs(resolved, assigned) {
|
|
|
46081
46414
|
motionCooldownMs: resolved.motionCooldownMs,
|
|
46082
46415
|
maxSessionHoldMs: resolved.maxSessionHoldMs,
|
|
46083
46416
|
audioMotionWindowMs: resolved.audioMotionWindowMs,
|
|
46084
|
-
|
|
46417
|
+
audioStreamPick: resolved.audioStreamPick,
|
|
46085
46418
|
detectionMode: resolved.detectionMode,
|
|
46086
46419
|
audioMode: resolved.audioMode,
|
|
46087
46420
|
onboardMotionDrivesAnalyzer: resolved.onboardMotionDrivesAnalyzer,
|
|
@@ -46109,7 +46442,7 @@ function detectionConfigEquals(a, b) {
|
|
|
46109
46442
|
if (a.motionCooldownMs !== b.motionCooldownMs) return false;
|
|
46110
46443
|
if (a.maxSessionHoldMs !== b.maxSessionHoldMs) return false;
|
|
46111
46444
|
if (a.audioMotionWindowMs !== b.audioMotionWindowMs) return false;
|
|
46112
|
-
if (a.
|
|
46445
|
+
if (a.audioStreamPick !== b.audioStreamPick) return false;
|
|
46113
46446
|
if (a.onboardMotionDrivesAnalyzer !== b.onboardMotionDrivesAnalyzer) return false;
|
|
46114
46447
|
if (a.occupancyRecheckEnabled !== b.occupancyRecheckEnabled) return false;
|
|
46115
46448
|
if (a.occupancyRecheckSec !== b.occupancyRecheckSec) return false;
|
|
@@ -46139,6 +46472,7 @@ function buildRunnerConfig(base, overrides) {
|
|
|
46139
46472
|
detectionFps: config.detectionFps,
|
|
46140
46473
|
motionStreamId: config.motionStreamId,
|
|
46141
46474
|
detectionStreamId: config.detectionStreamId,
|
|
46475
|
+
...config.audioStreamPick !== void 0 ? { audioStreamPick: config.audioStreamPick } : {},
|
|
46142
46476
|
motionSources: overrides?.motionSources ?? config.motionSources,
|
|
46143
46477
|
...(() => {
|
|
46144
46478
|
const sources = overrides?.detectionSources ?? config.detectionSources;
|
|
@@ -46641,8 +46975,15 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
46641
46975
|
/**
|
|
46642
46976
|
* Derive the watchdog camera descriptor from a runner config. Captures which
|
|
46643
46977
|
* stages are expected to flow CONTINUOUSLY (always-on audio, analyzer motion,
|
|
46644
|
-
* always-on detection) → the camStreamId feeding each.
|
|
46645
|
-
*
|
|
46978
|
+
* always-on detection) → the camStreamId feeding each.
|
|
46979
|
+
*
|
|
46980
|
+
* Audio no longer shares the motion stream id. It rides the camera's lowest
|
|
46981
|
+
* NATIVE rung (`selectAudioCamStream`) because an audio-only consumer must
|
|
46982
|
+
* never cause a video transcode, and on this fleet the motion rung IS a
|
|
46983
|
+
* `derived:*` transcode. The caller therefore hands the resolved audio stream
|
|
46984
|
+
* in {@link WatchdogAudioSource} — reading `motionStreamId` here would label
|
|
46985
|
+
* and RECOVER the wrong stream, which is how camera 618's audio stage watched
|
|
46986
|
+
* a plane the analyser was not listening to.
|
|
46646
46987
|
*
|
|
46647
46988
|
* `pipelineEnabled === false` (the `object-detection` switch, D62/ADR-0109)
|
|
46648
46989
|
* arms NOTHING: `startDetection` deliberately performs no attach and opens no
|
|
@@ -46651,12 +46992,14 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
46651
46992
|
* its watchdog row stays visible — a switched-off camera must read as off, not
|
|
46652
46993
|
* as broken.
|
|
46653
46994
|
*
|
|
46654
|
-
* `
|
|
46995
|
+
* `audio.hasTrack` is the caller's evidence that the AUDIO stream actually
|
|
46655
46996
|
* carries an audio track (`BrokerStats.audio !== null`). It fails OPEN: an
|
|
46656
46997
|
* unknown or failed read passes `true`, because the audio stage is diagnostic
|
|
46657
|
-
* and a false negative silently stops watching a working camera.
|
|
46998
|
+
* and a false negative silently stops watching a working camera. A `null`
|
|
46999
|
+
* `audio.camStreamId` means the subscription itself was refused (no native
|
|
47000
|
+
* rung, or `audioMode` cannot arm the stage) — there is nothing to watch.
|
|
46658
47001
|
*/
|
|
46659
|
-
function buildWatchdogCamera(config, deviceName,
|
|
47002
|
+
function buildWatchdogCamera(config, deviceName, audio) {
|
|
46660
47003
|
const continuousStages = /* @__PURE__ */ new Map();
|
|
46661
47004
|
if (config.pipelineEnabled === false) return {
|
|
46662
47005
|
deviceId: config.deviceId,
|
|
@@ -46666,7 +47009,7 @@ function buildWatchdogCamera(config, deviceName, hasAudioSource) {
|
|
|
46666
47009
|
motionSources: config.motionSources,
|
|
46667
47010
|
continuousStages
|
|
46668
47011
|
};
|
|
46669
|
-
if (config.audioMode === "always-on" &&
|
|
47012
|
+
if (config.audioMode === "always-on" && audio.camStreamId !== null && audio.hasTrack) continuousStages.set("audio", audio.camStreamId);
|
|
46670
47013
|
if (config.motionSources.includes("analyzer")) continuousStages.set("motion", config.motionStreamId);
|
|
46671
47014
|
if (config.detectionMode === "always-on") continuousStages.set("detection", config.detectionStreamId);
|
|
46672
47015
|
return {
|
|
@@ -46851,6 +47194,11 @@ var DetectionWiringController = class {
|
|
|
46851
47194
|
* `CamProfile → sourceCamStreamId` so callers can both check which
|
|
46852
47195
|
* profiles are live AND resolve a profile to the cam-stream-keyed
|
|
46853
47196
|
* brokerId (`${deviceId}/${camStreamId}`) used by the broker.
|
|
47197
|
+
*
|
|
47198
|
+
* Public because the AUDIO plane needs the same answer, from the same read:
|
|
47199
|
+
* `selectAudioCamStream` picks the lowest NATIVE rung, and a second reader
|
|
47200
|
+
* of the slot table would be a second authority on which rung is backed by
|
|
47201
|
+
* which cam-stream.
|
|
46854
47202
|
*/
|
|
46855
47203
|
async fetchAssignedProfiles(deviceId) {
|
|
46856
47204
|
const api = this.deps.api();
|
|
@@ -47230,10 +47578,10 @@ var DetectionWiringController = class {
|
|
|
47230
47578
|
*
|
|
47231
47579
|
* Public — `PlacementService`'s `buildWatchdogCamera` closure (wired in
|
|
47232
47580
|
* `orchestrator-bootstrap.ts`) calls straight through to this method; the
|
|
47233
|
-
* caller resolves
|
|
47581
|
+
* caller resolves the {@link WatchdogAudioSource} once per dispatch.
|
|
47234
47582
|
*/
|
|
47235
|
-
buildWatchdogCamera(config, deviceName,
|
|
47236
|
-
return buildWatchdogCamera(config, deviceName,
|
|
47583
|
+
buildWatchdogCamera(config, deviceName, audio) {
|
|
47584
|
+
return buildWatchdogCamera(config, deviceName, audio);
|
|
47237
47585
|
}
|
|
47238
47586
|
/**
|
|
47239
47587
|
* Watchdog recovery callback. Invoked when a camera's continuous stage
|
|
@@ -48168,8 +48516,8 @@ var PlacementService = class {
|
|
|
48168
48516
|
await this.attachOn(targetNodeId, attachConfig);
|
|
48169
48517
|
this.deps.ledger.setConfig(runnerConfig.deviceId, attachConfig);
|
|
48170
48518
|
if (targetNodeId === this.deps.localNodeId()) {
|
|
48171
|
-
const
|
|
48172
|
-
this.deps.watchdog()?.register(this.deps.buildWatchdogCamera(runnerConfig, String(runnerConfig.deviceId),
|
|
48519
|
+
const audioSource = await this.resolveAudioSource(runnerConfig);
|
|
48520
|
+
this.deps.watchdog()?.register(this.deps.buildWatchdogCamera(runnerConfig, String(runnerConfig.deviceId), audioSource));
|
|
48173
48521
|
}
|
|
48174
48522
|
this.deps.ledger.recordAssignment(runnerConfig.deviceId, targetNodeId, reason, reason === "manual");
|
|
48175
48523
|
} finally {
|
|
@@ -48597,19 +48945,35 @@ var PlacementService = class {
|
|
|
48597
48945
|
}
|
|
48598
48946
|
await this.attachOn(nodeId, cached);
|
|
48599
48947
|
if (nodeId === this.deps.localNodeId()) {
|
|
48600
|
-
const
|
|
48601
|
-
this.deps.watchdog()?.register(this.deps.buildWatchdogCamera(cached, String(cached.deviceId),
|
|
48948
|
+
const audioSource = await this.resolveAudioSource(cached);
|
|
48949
|
+
this.deps.watchdog()?.register(this.deps.buildWatchdogCamera(cached, String(cached.deviceId), audioSource));
|
|
48602
48950
|
}
|
|
48603
48951
|
}
|
|
48604
48952
|
/**
|
|
48605
|
-
* Resolve the watchdog's audio
|
|
48606
|
-
*
|
|
48607
|
-
*
|
|
48608
|
-
* audio costs no broker round-trip.
|
|
48953
|
+
* Resolve the watchdog's audio plane for ONE dispatch: which stream audio
|
|
48954
|
+
* rides, and whether it carries a track. Skipped entirely (and answered
|
|
48955
|
+
* "nothing to watch") when `audioMode` could not arm the stage anyway, so a
|
|
48956
|
+
* camera with on-motion audio costs no broker round-trip.
|
|
48957
|
+
*
|
|
48958
|
+
* The stream comes from the SAME selection the subscription makes — reading
|
|
48959
|
+
* `motionStreamId` here would arm the stage on the derived rung the audio
|
|
48960
|
+
* analyser deliberately does not listen to, and both the label and the
|
|
48961
|
+
* recovery would name a stream nobody is on.
|
|
48609
48962
|
*/
|
|
48610
|
-
async
|
|
48611
|
-
if (config.audioMode !== "always-on") return
|
|
48612
|
-
|
|
48963
|
+
async resolveAudioSource(config) {
|
|
48964
|
+
if (config.audioMode !== "always-on") return {
|
|
48965
|
+
camStreamId: null,
|
|
48966
|
+
hasTrack: false
|
|
48967
|
+
};
|
|
48968
|
+
const camStreamId = await this.deps.audioCamStreamFor(config);
|
|
48969
|
+
if (camStreamId === null) return {
|
|
48970
|
+
camStreamId: null,
|
|
48971
|
+
hasTrack: false
|
|
48972
|
+
};
|
|
48973
|
+
return {
|
|
48974
|
+
camStreamId,
|
|
48975
|
+
hasTrack: await this.deps.hasAudioSource(config.deviceId, camStreamId)
|
|
48976
|
+
};
|
|
48613
48977
|
}
|
|
48614
48978
|
/**
|
|
48615
48979
|
* Normalizes the `decision.kind === 'pending'` → `setPendingReason` +
|
|
@@ -54568,6 +54932,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
54568
54932
|
logger: deps.ctx().logger,
|
|
54569
54933
|
readDeviceStore: async (deviceId) => await deps.ctxIfReady()?.settings?.readDeviceStore(deviceId) ?? {},
|
|
54570
54934
|
probeAudioTrack: (deviceId, camStreamId) => resolveAudioTrackProbe(deviceId, camStreamId),
|
|
54935
|
+
assignedProfiles: (deviceId) => detectionWiring.fetchAssignedProfiles(deviceId),
|
|
54571
54936
|
ingestNode: () => topology.clusterRoles.ingestNode,
|
|
54572
54937
|
localNodeId: () => localNodeId,
|
|
54573
54938
|
enabledAudioNodes: () => topology.enabledAudioNodes,
|
|
@@ -54668,6 +55033,21 @@ async function buildOrchestratorControllers(deps) {
|
|
|
54668
55033
|
* stage that never produced one signal, with one line.
|
|
54669
55034
|
*/
|
|
54670
55035
|
const resolveHasAudioSource = async (deviceId, camStreamId) => (await resolveAudioTrackProbe(deviceId, camStreamId)).kind !== "absent";
|
|
55036
|
+
/**
|
|
55037
|
+
* The cam-stream a camera's audio subscription rides — `selectAudioCamStream`
|
|
55038
|
+
* over the same profile-slot read the detection plane uses. `null` when the
|
|
55039
|
+
* selection refuses (every rung is a `derived:*` transcode, or nothing is
|
|
55040
|
+
* assigned): an audio-only consumer never causes a video transcode, so there
|
|
55041
|
+
* is genuinely no stream and the watchdog must not arm its audio stage.
|
|
55042
|
+
*/
|
|
55043
|
+
const resolveAudioCamStream = async (config) => {
|
|
55044
|
+
const selection = selectAudioCamStream({
|
|
55045
|
+
pick: config.audioStreamPick ?? "auto",
|
|
55046
|
+
motionStreamId: config.motionStreamId,
|
|
55047
|
+
assigned: await detectionWiring.fetchAssignedProfiles(config.deviceId)
|
|
55048
|
+
});
|
|
55049
|
+
return selection.kind === "selected" ? selection.camStreamId : null;
|
|
55050
|
+
};
|
|
54671
55051
|
const placement = new PlacementService({
|
|
54672
55052
|
ctx: () => deps.ctx(),
|
|
54673
55053
|
ctxIfReady: () => deps.ctxIfReady(),
|
|
@@ -54684,8 +55064,9 @@ async function buildOrchestratorControllers(deps) {
|
|
|
54684
55064
|
getInferenceDeviceCaps: (nodeId) => deps.getInferenceDeviceCaps(nodeId),
|
|
54685
55065
|
readPipelineDevicePin: (deviceId) => deps.readPipelineDevicePin(deviceId),
|
|
54686
55066
|
inferenceRotation,
|
|
54687
|
-
buildWatchdogCamera: (config, deviceName,
|
|
55067
|
+
buildWatchdogCamera: (config, deviceName, audio) => detectionWiring.buildWatchdogCamera(config, deviceName, audio),
|
|
54688
55068
|
hasAudioSource: (deviceId, camStreamId) => resolveHasAudioSource(deviceId, camStreamId),
|
|
55069
|
+
audioCamStreamFor: (config) => resolveAudioCamStream(config),
|
|
54689
55070
|
isIdleSessionCamera: (deviceId) => deps.isIdleSessionCamera(deviceId),
|
|
54690
55071
|
activeSessionNode: (deviceId) => session.activeSessionNode(deviceId),
|
|
54691
55072
|
closeActiveSession: (deviceId) => session.closeSessionForReassign(deviceId),
|
|
@@ -55488,6 +55869,34 @@ function buildDeviceSettingsSections(nodeOptions) {
|
|
|
55488
55869
|
label: "On motion (firmware/PIR/SW)"
|
|
55489
55870
|
}
|
|
55490
55871
|
]
|
|
55872
|
+
}, {
|
|
55873
|
+
key: "audioStreamPick",
|
|
55874
|
+
type: "select",
|
|
55875
|
+
label: "Audio stream",
|
|
55876
|
+
default: "auto",
|
|
55877
|
+
description: "Which camera stream the audio classifier listens on. Auto picks the lowest stream the camera serves natively — a transcoded stream is never started just to hear a camera.",
|
|
55878
|
+
options: [
|
|
55879
|
+
{
|
|
55880
|
+
value: "auto",
|
|
55881
|
+
label: "Auto (lowest native)"
|
|
55882
|
+
},
|
|
55883
|
+
{
|
|
55884
|
+
value: "high",
|
|
55885
|
+
label: "High"
|
|
55886
|
+
},
|
|
55887
|
+
{
|
|
55888
|
+
value: "mid",
|
|
55889
|
+
label: "Medium"
|
|
55890
|
+
},
|
|
55891
|
+
{
|
|
55892
|
+
value: "low",
|
|
55893
|
+
label: "Low"
|
|
55894
|
+
}
|
|
55895
|
+
],
|
|
55896
|
+
showWhen: {
|
|
55897
|
+
field: "audioMode",
|
|
55898
|
+
notEquals: "disabled"
|
|
55899
|
+
}
|
|
55491
55900
|
}]
|
|
55492
55901
|
},
|
|
55493
55902
|
{
|