@camstack/addon-pipeline 1.2.374 → 1.2.601
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/{custom-model-catalog-1LDuDw8S.mjs → custom-model-catalog-CK9GK3N5.mjs} +2 -2
- package/dist/{custom-model-catalog-W0DHDhfo.js → custom-model-catalog-DgyafyuW.js} +2 -2
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-CmdLCb_F.mjs → dist-BaHsDOWa.mjs} +1124 -29
- package/dist/{dist-CBEUVjTS.js → dist-BqI10eLV.js} +1129 -28
- package/dist/{model-catalogs-BnCdB2A3.mjs → model-catalogs-DM5sVj1T.mjs} +1 -1
- package/dist/{model-catalogs-D5YksA5A.js → model-catalogs-DhGVt_sH.js} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-C3r8kiOo.mjs → node-B0a0dXhg.mjs} +38 -3
- package/dist/{node-DMgi6pND.js → node-CgwqjGz0.js} +42 -1
- package/dist/pipeline-runner/index.js +4 -4
- package/dist/pipeline-runner/index.mjs +4 -4
- package/dist/{process-memory-GFR7fxpJ.js → process-memory-C0zbWvwD.js} +1 -1
- package/dist/{process-memory-X0ws1SGJ.mjs → process-memory-sfdcLVkW.mjs} +1 -1
- package/dist/recorder/index.js +357 -516
- package/dist/recorder/index.mjs +357 -516
- package/dist/{segment-demux-js-CSQVhbFI.js → segment-demux-js-B9lL2VCC.js} +1 -1
- package/dist/{segment-demux-js-IKEO1XBz.mjs → segment-demux-js-BB4CUAB5.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DPFTlVoW.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CbN9DjoK.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Boq8JqyD.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B25q1OKG.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-BOp1_Hhk.mjs → hostInit-BSYHAfPt.mjs} +3 -3
- package/dist/stream-broker/index.js +556 -32
- package/dist/stream-broker/index.mjs +556 -32
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B7wsxLrK.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BohOb2Cl.mjs +0 -26
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as __toESM } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { An as errMsg, At as gifFromMp4Args, Bt as memoizePinnedFfmpeg, D as G711_SCALE_CORRECTION_DB, Dn as isSoftwareDecode, Dt as ffmpegRate, En as invocationFromEncodeProfile, Ft as loadContributionCapability, Gn as
|
|
3
|
-
import { i as NO_COST_CLAIM, n as Fmp4FragmentChild,
|
|
2
|
+
import { An as errMsg, At as gifFromMp4Args, Bt as memoizePinnedFfmpeg, D as G711_SCALE_CORRECTION_DB, Dn as isSoftwareDecode, Dt as ffmpegRate, En as invocationFromEncodeProfile, Ft as loadContributionCapability, Gn as isEvent, Hn as expandAudioChunkToF32le, I as RATE_CONTROL_RELAXED, It as logChannelsCapability, J as addonWidgetsSourceCapability, Jn as makeSourceBrokerId, Kn as isSecretMarker, L as RATE_CONTROL_TIGHT, Mn as BaseAddon, Nn as CAM_PROFILE_ORDER, On as DeviceFeature, Pn as CamProfileSchema, Qt as rankStreams, Rn as createEvent, W as RingBuffer, Xn as nodePin, Yn as maskUrlCredentials, Z as armRemovalReconcile, Zn as parseProfileBrokerId, an as resolveEgressDecodeHwAccel, bn as webrtcSessionCapability, bt as egressTransportFromRequest, c as CLIP_STILL_REASON_CODE_HEADER, cr as object, dr as string, dt as declareLogChannel, fr as union, gn as streamBrokerCapability, ir as discriminatedUnion, it as cameraStreamsCapability, jn as BOOT_RECOVERY_BACKOFF_MS, kn as DeviceType, kt as gifDeriveSpeed, lt as createHwAccelCache, nr as array, o as BatteryStatusSchema, or as literal, ot as clipExportFailureOf, pr as EventCategory, pt as deriveBatteryPresence, qn as makeProfileBrokerId, rr as boolean, s as CLIP_BROKER_PACED_RATES, sr as number, tr as _enum, tt as batteryCapability, ur as record, ut as createLogChannelsProvider, vt as deviceRemovalHookCapability, w as EncodeProfileSchema, wn as buildFfmpegArgs, xn as AUDIO_PRESETS, yt as egressTranscodeSharingKey } from "../dist-BaHsDOWa.mjs";
|
|
3
|
+
import { a as defaultFfmpegSpawn, i as NO_COST_CLAIM, n as Fmp4FragmentChild, r as Fmp4FragmentPlane, s as probeFfmpegBinary, t as ChildCostRegistry } from "../node-B0a0dXhg.mjs";
|
|
4
4
|
import { a as parseRestreamPath, d as PREBUFFER_MAX_MS, f as capPreRollRequestMs, i as isLiveEdgeOnlyDial, m as resolvePrebuffer, n as NO_DECLARATION, p as prebufferRetentionMs, s as sdpVariantFor, u as withSentryIntent } from "../restream-intent-DyGmLAU9.mjs";
|
|
5
5
|
import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
|
|
6
|
-
import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-
|
|
6
|
+
import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-BB4CUAB5.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import * as os$1 from "node:os";
|
|
9
9
|
import { networkInterfaces, tmpdir } from "node:os";
|
|
@@ -854,6 +854,131 @@ function healthEventIsOperatorFacing(camStreamId) {
|
|
|
854
854
|
return !PRIVATE_STREAM_PREFIXES.some((prefix) => camStreamId.startsWith(prefix));
|
|
855
855
|
}
|
|
856
856
|
//#endregion
|
|
857
|
+
//#region src/stream-broker/stream-broker/stream-health-policy.ts
|
|
858
|
+
/**
|
|
859
|
+
* WHAT a stale stream means for a battery camera (D883).
|
|
860
|
+
*
|
|
861
|
+
* A mains camera whose stream goes quiet for the stale window has lost its
|
|
862
|
+
* stream: that is an outage. A battery camera whose stream goes quiet has,
|
|
863
|
+
* ordinarily, gone to sleep — the designed steady state, not a fault. The
|
|
864
|
+
* broker used to tell the two apart by nothing, so every sleep produced a
|
|
865
|
+
* `stream.offline` per profile and every wake a `stream.online`, and the
|
|
866
|
+
* operator's system-event rule pushed each one.
|
|
867
|
+
*
|
|
868
|
+
* The verdict is made HERE, at the emitter, from the battery's own sleep
|
|
869
|
+
* mirror — never by a filter downstream:
|
|
870
|
+
*
|
|
871
|
+
* - asleep and nobody asked for the stream — NOT an outage (`asleep`);
|
|
872
|
+
* - asleep, a stream was DEMANDED and none arrived — `wake-timeout`;
|
|
873
|
+
* - reported awake and the stream is gone — `connection-error`;
|
|
874
|
+
* - silent past the battery refresh window — `battery-unreachable`.
|
|
875
|
+
*
|
|
876
|
+
* A sleep verdict needs a sleep state that was actually READ. A mirror that
|
|
877
|
+
* was never fed (a restart, a dropped event) is not "asleep": the caller passes
|
|
878
|
+
* `battery: null` and the stream is judged exactly as a mains one (D49 —
|
|
879
|
+
* unknown excludes nothing, and a silent suppression on an unread mirror hid
|
|
880
|
+
* a camera that slept and then died).
|
|
881
|
+
*/
|
|
882
|
+
var OUTAGE_REASONS = [
|
|
883
|
+
"stale-timeout",
|
|
884
|
+
"wake-timeout",
|
|
885
|
+
"connection-error",
|
|
886
|
+
"battery-unreachable"
|
|
887
|
+
];
|
|
888
|
+
/**
|
|
889
|
+
* How long a demand may wait for a sleeping camera's stream before it counts
|
|
890
|
+
* as a failed wake: the firmware wake bound plus the broker settle window of
|
|
891
|
+
* wake-on-play (8 s + 20 s), with margin for the sweep cadence.
|
|
892
|
+
*/
|
|
893
|
+
var WAKE_DEMAND_BOUND_MS = 45e3;
|
|
894
|
+
function isStreamOutageReason(reason) {
|
|
895
|
+
return reason !== void 0 && OUTAGE_REASONS.includes(reason);
|
|
896
|
+
}
|
|
897
|
+
function judgeStaleStream(input) {
|
|
898
|
+
if (input.battery === null) return {
|
|
899
|
+
kind: "outage",
|
|
900
|
+
reason: "stale-timeout"
|
|
901
|
+
};
|
|
902
|
+
const presence = deriveBatteryPresence({
|
|
903
|
+
status: input.battery.facts,
|
|
904
|
+
nowMs: input.nowMs
|
|
905
|
+
});
|
|
906
|
+
if (presence === "unreachable") return {
|
|
907
|
+
kind: "outage",
|
|
908
|
+
reason: "battery-unreachable"
|
|
909
|
+
};
|
|
910
|
+
if (presence === "awake") return {
|
|
911
|
+
kind: "outage",
|
|
912
|
+
reason: "connection-error"
|
|
913
|
+
};
|
|
914
|
+
if (input.demanded) return {
|
|
915
|
+
kind: "outage",
|
|
916
|
+
reason: "wake-timeout"
|
|
917
|
+
};
|
|
918
|
+
return {
|
|
919
|
+
kind: "suppressed",
|
|
920
|
+
presence
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
//#endregion
|
|
924
|
+
//#region src/stream-broker/stream-broker/device-stream-outages.ts
|
|
925
|
+
var DeviceStreamOutages = class {
|
|
926
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
927
|
+
get(deviceId) {
|
|
928
|
+
return this.byDevice.get(deviceId);
|
|
929
|
+
}
|
|
930
|
+
/** Open the device's outage, or add this stream to the one already open. */
|
|
931
|
+
open(deviceId, camStreamId, reason, now) {
|
|
932
|
+
const existing = this.byDevice.get(deviceId);
|
|
933
|
+
if (existing) {
|
|
934
|
+
existing.camStreamIds.add(camStreamId);
|
|
935
|
+
existing.wakeOnly = false;
|
|
936
|
+
existing.lastSignalAt = now;
|
|
937
|
+
return existing;
|
|
938
|
+
}
|
|
939
|
+
const created = {
|
|
940
|
+
reason,
|
|
941
|
+
since: now,
|
|
942
|
+
camStreamIds: new Set([camStreamId]),
|
|
943
|
+
announced: false,
|
|
944
|
+
wakeOnly: false,
|
|
945
|
+
lastSignalAt: now
|
|
946
|
+
};
|
|
947
|
+
this.byDevice.set(deviceId, created);
|
|
948
|
+
return created;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* A failed wake with no broker to carry it. Joins the device's open outage
|
|
952
|
+
* (one outage per device) or opens a wake-only one over its REAL streams.
|
|
953
|
+
*/
|
|
954
|
+
openWake(deviceId, camStreamIds, reason, now) {
|
|
955
|
+
const existing = this.byDevice.get(deviceId);
|
|
956
|
+
if (existing) {
|
|
957
|
+
existing.lastSignalAt = now;
|
|
958
|
+
return existing;
|
|
959
|
+
}
|
|
960
|
+
const created = {
|
|
961
|
+
reason,
|
|
962
|
+
since: now,
|
|
963
|
+
camStreamIds: new Set(camStreamIds),
|
|
964
|
+
announced: false,
|
|
965
|
+
wakeOnly: true,
|
|
966
|
+
lastSignalAt: now
|
|
967
|
+
};
|
|
968
|
+
this.byDevice.set(deviceId, created);
|
|
969
|
+
return created;
|
|
970
|
+
}
|
|
971
|
+
entries() {
|
|
972
|
+
return this.byDevice.entries();
|
|
973
|
+
}
|
|
974
|
+
close(deviceId) {
|
|
975
|
+
this.byDevice.delete(deviceId);
|
|
976
|
+
}
|
|
977
|
+
clear() {
|
|
978
|
+
this.byDevice.clear();
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
//#endregion
|
|
857
982
|
//#region src/stream-broker/stream-broker/sentry-eligibility.ts
|
|
858
983
|
function sentryEligible(input) {
|
|
859
984
|
if (input.batteryOperated === null || input.declaresSignals === null) return {
|
|
@@ -1737,7 +1862,7 @@ var FfmpegPushReader = class {
|
|
|
1737
1862
|
this.ffmpegPath = options.ffmpegPath;
|
|
1738
1863
|
this.args = options.args;
|
|
1739
1864
|
this.logger = options.logger;
|
|
1740
|
-
this.spawnFn = options.spawnFn ??
|
|
1865
|
+
this.spawnFn = options.spawnFn ?? defaultFfmpegSpawn;
|
|
1741
1866
|
this.callbacks = callbacks;
|
|
1742
1867
|
this.logLabel = logLabel;
|
|
1743
1868
|
this.claimCost = options.claimCost;
|
|
@@ -7646,7 +7771,7 @@ var EgressTranscodeManager = class {
|
|
|
7646
7771
|
const egress = new TranscodeEgress({
|
|
7647
7772
|
logger: this.opts.logger.child("egress"),
|
|
7648
7773
|
ffmpegBinaryPath,
|
|
7649
|
-
spawnFn: this.opts.spawnFn ??
|
|
7774
|
+
spawnFn: this.opts.spawnFn ?? defaultFfmpegSpawn,
|
|
7650
7775
|
acquireChannel: () => channel,
|
|
7651
7776
|
...spec.onDied ? { onChildExit: spec.onDied } : {},
|
|
7652
7777
|
claimCost: (pid) => this.opts.costRegistry?.claim({
|
|
@@ -7681,7 +7806,7 @@ var EgressTranscodeManager = class {
|
|
|
7681
7806
|
const child = new Fmp4FragmentChild({
|
|
7682
7807
|
logger: this.opts.logger.child("egress-fmp4"),
|
|
7683
7808
|
ffmpegBinaryPath,
|
|
7684
|
-
spawnFn: this.opts.spawnFn ??
|
|
7809
|
+
spawnFn: this.opts.spawnFn ?? defaultFfmpegSpawn,
|
|
7685
7810
|
...this.opts.fragmentFirstUnitTimeoutMs === void 0 ? {} : { firstUnitTimeoutMs: this.opts.fragmentFirstUnitTimeoutMs },
|
|
7686
7811
|
...spec.onDied ? { onChildExit: spec.onDied } : {}
|
|
7687
7812
|
}, {
|
|
@@ -14551,6 +14676,15 @@ var StreamBroker = class StreamBroker {
|
|
|
14551
14676
|
return false;
|
|
14552
14677
|
}
|
|
14553
14678
|
/**
|
|
14679
|
+
* Does a consumer want this stream right now (a viewer, a recording, a
|
|
14680
|
+
* detection decode, a camera-reported wake hold)? Sentry sessions and idle
|
|
14681
|
+
* prestream do not count. The stream-health sweep reads it to tell a battery
|
|
14682
|
+
* camera that slept unasked from one that failed to wake for somebody (D883).
|
|
14683
|
+
*/
|
|
14684
|
+
hasLiveDemand() {
|
|
14685
|
+
return this.hasLiveIntent();
|
|
14686
|
+
}
|
|
14687
|
+
/**
|
|
14554
14688
|
* True when at least one consumer needs DECODED audio. The audio-specific
|
|
14555
14689
|
* subset of {@link hasDemand} — deliberately excludes video-only
|
|
14556
14690
|
* consumers so a video-only viewer never spins up the AAC/Opus decode
|
|
@@ -16780,7 +16914,7 @@ var TranscodePipelineManager = class {
|
|
|
16780
16914
|
this.onFfmpegMissing = opts.onFfmpegMissing ?? null;
|
|
16781
16915
|
this.probeFfmpeg = opts.probeFfmpeg ?? probeFfmpegBinary;
|
|
16782
16916
|
this.resolveStreamHwAccel = opts.resolveStreamHwAccel ?? null;
|
|
16783
|
-
this.spawnFn = opts.spawnFn ??
|
|
16917
|
+
this.spawnFn = opts.spawnFn ?? defaultFfmpegSpawn;
|
|
16784
16918
|
this.acquireChannel = opts.acquireChannel ?? null;
|
|
16785
16919
|
this.sentryOffered = opts.sentryOffered ?? null;
|
|
16786
16920
|
}
|
|
@@ -17450,6 +17584,10 @@ function resolveBrokerTier(brokerId, localLookup, remoteBrokerTiers) {
|
|
|
17450
17584
|
*/
|
|
17451
17585
|
var STREAM_STALE_TIMEOUT_MS = 12e4;
|
|
17452
17586
|
var STREAM_HEALTH_POLL_MS = 15e3;
|
|
17587
|
+
/** How often an unanswered / stale battery probe is asked again. */
|
|
17588
|
+
var BATTERY_PROBE_RETRY_MS = 6e4;
|
|
17589
|
+
/** A wake-only outage with no further failed wake for this long is over: the demand is gone. */
|
|
17590
|
+
var WAKE_ONLY_OUTAGE_TTL_MS = 5 * 6e4;
|
|
17453
17591
|
/** Compose a SOURCE broker id from a `(deviceId, camStreamId)` tuple. Thin
|
|
17454
17592
|
* wrapper over the canonical `makeSourceBrokerId` — single source of truth. */
|
|
17455
17593
|
function brokerIdFor(deviceId, camStreamId) {
|
|
@@ -17642,6 +17780,25 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
17642
17780
|
* guess which one it was looking at.
|
|
17643
17781
|
*/
|
|
17644
17782
|
streamOfflineReasonByBroker = /* @__PURE__ */ new Map();
|
|
17783
|
+
/**
|
|
17784
|
+
* Brokers whose stream went quiet because their BATTERY camera is asleep.
|
|
17785
|
+
* Not an outage and never announced (D883); remembered so the wake that ends
|
|
17786
|
+
* it is recognised as a plain wake instead of a `recovered`.
|
|
17787
|
+
*/
|
|
17788
|
+
sleepSuppressedBrokers = /* @__PURE__ */ new Set();
|
|
17789
|
+
/**
|
|
17790
|
+
* What the sweep knows of a device's battery: is it one, and what did its
|
|
17791
|
+
* `battery` slice last say. Fed by `BatteryOnStatusChanged` (droppable, empty
|
|
17792
|
+
* after a restart) AND read from the device registry / slice by the sweep
|
|
17793
|
+
* itself — the same two reads wake-on-play makes — so a restart or a lost
|
|
17794
|
+
* event never leaves the verdict standing on an unread mirror (D49).
|
|
17795
|
+
*/
|
|
17796
|
+
batteryProbe = /* @__PURE__ */ new Map();
|
|
17797
|
+
batteryProbeInFlight = /* @__PURE__ */ new Set();
|
|
17798
|
+
/** When a consumer's demand on a quiet battery stream was first seen (the wake bound starts there). */
|
|
17799
|
+
demandSince = /* @__PURE__ */ new Map();
|
|
17800
|
+
/** The stream outage a DEVICE is in (one entry per device, however many profiles). */
|
|
17801
|
+
deviceOutages = new DeviceStreamOutages();
|
|
17645
17802
|
streamHealthTimer;
|
|
17646
17803
|
/**
|
|
17647
17804
|
* Routes an audio-chunk `subscriptionId` (Phase 5 / D9) back to its owning
|
|
@@ -18582,6 +18739,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18582
18739
|
const deviceId = typeof data.deviceId === "number" ? data.deviceId : null;
|
|
18583
18740
|
if (deviceId === null) return;
|
|
18584
18741
|
const sleeping = data.status?.sleeping === true;
|
|
18742
|
+
this.recordBatterySleep(deviceId, data.status);
|
|
18585
18743
|
this.applyPlaceholderReasonToDevice(deviceId, sleeping ? "sleeping" : "reconnecting");
|
|
18586
18744
|
});
|
|
18587
18745
|
bus.subscribe({ category: EventCategory.BatteryOnWakeStarted }, (event) => {
|
|
@@ -18745,18 +18903,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18745
18903
|
await this.batteryWakeOnPlay.run(deviceId, {
|
|
18746
18904
|
logger: this.logger,
|
|
18747
18905
|
isBatteryDevice: (id) => this.isBatteryOperated(id),
|
|
18748
|
-
readBatteryFacts:
|
|
18749
|
-
const slice = await api.deviceState.getCapSlice.query({
|
|
18750
|
-
deviceId: id,
|
|
18751
|
-
capName: batteryCapability.name
|
|
18752
|
-
}).catch(() => null);
|
|
18753
|
-
const parsed = BatteryStatusSchema.safeParse(slice);
|
|
18754
|
-
if (!parsed.success) return null;
|
|
18755
|
-
return {
|
|
18756
|
-
sleeping: parsed.data.sleeping,
|
|
18757
|
-
lastContactAt: parsed.data.lastContactAt
|
|
18758
|
-
};
|
|
18759
|
-
},
|
|
18906
|
+
readBatteryFacts: (id) => this.readBatterySleepFacts(id),
|
|
18760
18907
|
wakeForStream: async (id, timeoutMs) => api.battery.wakeForStream.mutate({
|
|
18761
18908
|
deviceId: id,
|
|
18762
18909
|
timeoutMs
|
|
@@ -18764,6 +18911,9 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18764
18911
|
reconcileCatalog: (id) => this.reconcileDeviceCatalog(id),
|
|
18765
18912
|
ensureBroker: () => this.ensureBrokerForWebrtcSession(deviceId, brokerId, camStreamId),
|
|
18766
18913
|
hasBroker: demandHasBroker
|
|
18914
|
+
}).catch((err) => {
|
|
18915
|
+
if (err instanceof BatteryStreamUnavailableError) this.announceBatteryWakeFailure(deviceId, camStreamId, err.presence === "unreachable" ? "battery-unreachable" : "wake-timeout");
|
|
18916
|
+
throw err;
|
|
18767
18917
|
});
|
|
18768
18918
|
}
|
|
18769
18919
|
/** Any broker (local or remote-adapter) registered for this device? The
|
|
@@ -20387,6 +20537,10 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
20387
20537
|
this.brokers.clear();
|
|
20388
20538
|
this.streamHealthByBroker.clear();
|
|
20389
20539
|
this.streamOfflineReasonByBroker.clear();
|
|
20540
|
+
this.sleepSuppressedBrokers.clear();
|
|
20541
|
+
this.batteryProbe.clear();
|
|
20542
|
+
this.demandSince.clear();
|
|
20543
|
+
this.deviceOutages.clear();
|
|
20390
20544
|
this.audioSubscriptionBroker.clear();
|
|
20391
20545
|
await this.rtspServer.stop();
|
|
20392
20546
|
}
|
|
@@ -20409,6 +20563,8 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
20409
20563
|
evaluateStreamHealth() {
|
|
20410
20564
|
if (!this.eventBus) return;
|
|
20411
20565
|
const now = Date.now();
|
|
20566
|
+
const outageOpenedOn = /* @__PURE__ */ new Map();
|
|
20567
|
+
const cameBackOn = /* @__PURE__ */ new Map();
|
|
20412
20568
|
for (const [brokerId, broker] of this.brokers) {
|
|
20413
20569
|
const parsed = this.parseBrokerId(brokerId);
|
|
20414
20570
|
if (!parsed) continue;
|
|
@@ -20418,30 +20574,386 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
20418
20574
|
const wasHealthy = this.streamHealthByBroker.get(brokerId);
|
|
20419
20575
|
const isHealthy = lastPacketAt > 0 && now - lastPacketAt <= STREAM_STALE_TIMEOUT_MS;
|
|
20420
20576
|
if (!isHealthy && broker.getStats().status === "streaming") broker.redial("stream-health sweep: phantom streaming, no packets within stale window");
|
|
20421
|
-
|
|
20577
|
+
const context = {
|
|
20578
|
+
deviceId,
|
|
20579
|
+
camStreamId,
|
|
20580
|
+
brokerId,
|
|
20581
|
+
sourceType: broker.getActiveSourceType(),
|
|
20582
|
+
lastPacketAt
|
|
20583
|
+
};
|
|
20584
|
+
if (wasHealthy === isHealthy) {
|
|
20585
|
+
if (!isHealthy) this.rejudgeQuietBroker(brokerId, broker, context, now, outageOpenedOn);
|
|
20586
|
+
continue;
|
|
20587
|
+
}
|
|
20422
20588
|
if (wasHealthy === void 0 && !isHealthy && !broker.suspended) {
|
|
20423
20589
|
this.streamHealthByBroker.set(brokerId, false);
|
|
20424
20590
|
continue;
|
|
20425
20591
|
}
|
|
20592
|
+
let verdict = null;
|
|
20593
|
+
if (!isHealthy && !broker.suspended) {
|
|
20594
|
+
verdict = this.judgeQuietStream(brokerId, deviceId, broker, now);
|
|
20595
|
+
if (verdict === "defer") continue;
|
|
20596
|
+
}
|
|
20426
20597
|
this.streamHealthByBroker.set(brokerId, isHealthy);
|
|
20427
|
-
const
|
|
20428
|
-
|
|
20429
|
-
|
|
20430
|
-
|
|
20598
|
+
const wasSleepSuppressed = this.sleepSuppressedBrokers.delete(brokerId);
|
|
20599
|
+
const previousReason = this.streamOfflineReasonByBroker.get(brokerId);
|
|
20600
|
+
if (verdict !== null) {
|
|
20601
|
+
if (verdict.kind === "suppressed") {
|
|
20602
|
+
this.markSleepSuppressed(brokerId, context, verdict.presence, lastPacketAt);
|
|
20603
|
+
continue;
|
|
20604
|
+
}
|
|
20605
|
+
this.openStreamOutage(brokerId, context, verdict.reason, now, outageOpenedOn);
|
|
20606
|
+
continue;
|
|
20607
|
+
}
|
|
20608
|
+
if (!isHealthy) {
|
|
20609
|
+
this.demandSince.delete(brokerId);
|
|
20610
|
+
this.streamOfflineReasonByBroker.set(brokerId, "suspended");
|
|
20611
|
+
this.emitStreamHealth(false, {
|
|
20612
|
+
...context,
|
|
20613
|
+
reason: "suspended"
|
|
20614
|
+
});
|
|
20615
|
+
continue;
|
|
20616
|
+
}
|
|
20617
|
+
this.streamOfflineReasonByBroker.delete(brokerId);
|
|
20618
|
+
this.demandSince.delete(brokerId);
|
|
20619
|
+
if (wasSleepSuppressed) this.logger.info("battery camera woke and delivers a stream — plain wake, no event", {
|
|
20620
|
+
tags: { deviceId },
|
|
20621
|
+
meta: { camStreamId }
|
|
20622
|
+
});
|
|
20623
|
+
if (this.deviceOutages.get(deviceId) !== void 0) {
|
|
20624
|
+
if (!cameBackOn.has(deviceId)) cameBackOn.set(deviceId, context);
|
|
20625
|
+
continue;
|
|
20626
|
+
}
|
|
20627
|
+
if (wasSleepSuppressed) {
|
|
20628
|
+
this.writeCameraStreamsSlice(deviceId, this.snapshotProfileSlots(deviceId));
|
|
20629
|
+
continue;
|
|
20630
|
+
}
|
|
20631
|
+
const reason = isStreamOutageReason(previousReason) ? "recovered" : previousReason === "suspended" ? "resumed" : "first-packet";
|
|
20632
|
+
this.emitStreamHealth(true, {
|
|
20633
|
+
...context,
|
|
20634
|
+
reason
|
|
20635
|
+
});
|
|
20636
|
+
}
|
|
20637
|
+
for (const [deviceId, context] of outageOpenedOn) this.announceDeviceOutage(deviceId, context);
|
|
20638
|
+
for (const [deviceId, context] of cameBackOn) this.settleDeviceOutage(deviceId, context);
|
|
20639
|
+
this.expireWakeOnlyOutages(now);
|
|
20640
|
+
}
|
|
20641
|
+
/** Record one stream as part of its device's outage (announced after the pass). */
|
|
20642
|
+
openStreamOutage(brokerId, context, reason, now, outageOpenedOn) {
|
|
20643
|
+
this.sleepSuppressedBrokers.delete(brokerId);
|
|
20644
|
+
this.streamOfflineReasonByBroker.set(brokerId, reason);
|
|
20645
|
+
this.deviceOutages.open(context.deviceId, context.camStreamId, reason, now);
|
|
20646
|
+
if (!outageOpenedOn.has(context.deviceId)) outageOpenedOn.set(context.deviceId, context);
|
|
20647
|
+
}
|
|
20648
|
+
markSleepSuppressed(brokerId, context, presence, lastPacketAt) {
|
|
20649
|
+
this.sleepSuppressedBrokers.add(brokerId);
|
|
20650
|
+
this.streamOfflineReasonByBroker.delete(brokerId);
|
|
20651
|
+
this.logger.info("stream quiet on a sleeping battery camera — not an outage", {
|
|
20652
|
+
tags: { deviceId: context.deviceId },
|
|
20653
|
+
meta: {
|
|
20654
|
+
camStreamId: context.camStreamId,
|
|
20655
|
+
presence,
|
|
20656
|
+
lastPacketAt
|
|
20657
|
+
}
|
|
20658
|
+
});
|
|
20659
|
+
this.writeCameraStreamsSlice(context.deviceId, this.snapshotProfileSlots(context.deviceId));
|
|
20660
|
+
}
|
|
20661
|
+
/**
|
|
20662
|
+
* The verdict on a quiet, non-idle stream. `'defer'` = the device's battery
|
|
20663
|
+
* class is still being asked of the registry; the edge is kept and judged
|
|
20664
|
+
* again. A class that could not be answered, or a slice never read, judges as
|
|
20665
|
+
* a mains camera — never as a sleeper (D49).
|
|
20666
|
+
*/
|
|
20667
|
+
judgeQuietStream(brokerId, deviceId, broker, now) {
|
|
20668
|
+
const probe = this.ensureBatteryProbe(deviceId, now);
|
|
20669
|
+
if (probe.state === "pending") return "defer";
|
|
20670
|
+
return judgeStaleStream({
|
|
20671
|
+
battery: probe.isBattery === true && probe.facts !== null ? { facts: probe.facts } : null,
|
|
20672
|
+
demanded: this.demandHeldPastBound(brokerId, broker, now),
|
|
20673
|
+
nowMs: now
|
|
20674
|
+
});
|
|
20675
|
+
}
|
|
20676
|
+
demandHeldPastBound(brokerId, broker, now) {
|
|
20677
|
+
if (!broker.hasLiveDemand()) {
|
|
20678
|
+
this.demandSince.delete(brokerId);
|
|
20679
|
+
return false;
|
|
20680
|
+
}
|
|
20681
|
+
const since = this.demandSince.get(brokerId) ?? now;
|
|
20682
|
+
this.demandSince.set(brokerId, since);
|
|
20683
|
+
return now - since >= WAKE_DEMAND_BOUND_MS;
|
|
20684
|
+
}
|
|
20685
|
+
/**
|
|
20686
|
+
* A broker that stays quiet, pass after pass. Three things can be true of it:
|
|
20687
|
+
* it was suppressed as a sleeper (the camera may have died, or a wake may
|
|
20688
|
+
* have been demanded since); it went idle while holding an outage open; or it
|
|
20689
|
+
* resumed from an idle suspend on a battery camera that never answered.
|
|
20690
|
+
*/
|
|
20691
|
+
rejudgeQuietBroker(brokerId, broker, context, now, outageOpenedOn) {
|
|
20692
|
+
const { deviceId } = context;
|
|
20693
|
+
const reason = this.streamOfflineReasonByBroker.get(brokerId);
|
|
20694
|
+
if (isStreamOutageReason(reason) && broker.suspended) {
|
|
20695
|
+
this.dropSuspendedOutage(brokerId, context);
|
|
20696
|
+
return;
|
|
20697
|
+
}
|
|
20698
|
+
const suppressed = this.sleepSuppressedBrokers.has(brokerId);
|
|
20699
|
+
const resumedQuiet = reason === "suspended" && !broker.suspended;
|
|
20700
|
+
if (!suppressed && !resumedQuiet) return;
|
|
20701
|
+
if (broker.suspended) {
|
|
20702
|
+
this.demandSince.delete(brokerId);
|
|
20703
|
+
return;
|
|
20704
|
+
}
|
|
20705
|
+
this.refreshBatteryFactsIfDue(deviceId, now);
|
|
20706
|
+
const probe = this.batteryProbe.get(deviceId);
|
|
20707
|
+
if (probe?.state !== "done" || probe.isBattery !== true || probe.facts === null) return;
|
|
20708
|
+
const verdict = judgeStaleStream({
|
|
20709
|
+
battery: { facts: probe.facts },
|
|
20710
|
+
demanded: this.demandHeldPastBound(brokerId, broker, now),
|
|
20711
|
+
nowMs: now
|
|
20712
|
+
});
|
|
20713
|
+
if (verdict.kind === "suppressed") return;
|
|
20714
|
+
this.logger.warn("quiet battery stream is now an outage", {
|
|
20715
|
+
tags: { deviceId },
|
|
20716
|
+
meta: {
|
|
20717
|
+
camStreamId: context.camStreamId,
|
|
20718
|
+
reason: verdict.reason
|
|
20719
|
+
}
|
|
20720
|
+
});
|
|
20721
|
+
this.openStreamOutage(brokerId, context, verdict.reason, now, outageOpenedOn);
|
|
20722
|
+
}
|
|
20723
|
+
/**
|
|
20724
|
+
* An outage stream whose last consumer left (the broker went idle). Nobody is
|
|
20725
|
+
* waiting for it any more, so the outage ends there — closed cleanly, with
|
|
20726
|
+
* the one online that lets every consumer clear its row (reason `suspended`,
|
|
20727
|
+
* which the notification gate discards).
|
|
20728
|
+
*/
|
|
20729
|
+
dropSuspendedOutage(brokerId, context) {
|
|
20730
|
+
const { deviceId, camStreamId } = context;
|
|
20731
|
+
this.demandSince.delete(brokerId);
|
|
20732
|
+
this.streamOfflineReasonByBroker.set(brokerId, "suspended");
|
|
20733
|
+
const outage = this.deviceOutages.get(deviceId);
|
|
20734
|
+
this.logger.info("stream outage dropped — the broker went idle, nobody is waiting", {
|
|
20735
|
+
tags: { deviceId },
|
|
20736
|
+
meta: { camStreamId }
|
|
20737
|
+
});
|
|
20738
|
+
if (!outage) return;
|
|
20739
|
+
outage.camStreamIds.delete(camStreamId);
|
|
20740
|
+
if (outage.camStreamIds.size > 0 || outage.wakeOnly) return;
|
|
20741
|
+
this.closeDeviceOutage(deviceId, outage, "suspended", context);
|
|
20742
|
+
}
|
|
20743
|
+
/** Close a device outage and, when it was announced, say so once (any online closes the rows). */
|
|
20744
|
+
closeDeviceOutage(deviceId, outage, reason, context) {
|
|
20745
|
+
this.deviceOutages.close(deviceId);
|
|
20746
|
+
if (!outage.announced) return;
|
|
20747
|
+
this.emitDeviceStreamHealth(true, deviceId, reason, /* @__PURE__ */ new Set(), context);
|
|
20748
|
+
}
|
|
20749
|
+
/** Wake-only outages end when the failed wakes stop (demand released). */
|
|
20750
|
+
expireWakeOnlyOutages(now) {
|
|
20751
|
+
for (const [deviceId, outage] of [...this.deviceOutages.entries()]) {
|
|
20752
|
+
if (!outage.wakeOnly || now - outage.lastSignalAt < WAKE_ONLY_OUTAGE_TTL_MS) continue;
|
|
20753
|
+
this.logger.info("wake failure outage ended — no further demand", {
|
|
20754
|
+
tags: { deviceId },
|
|
20755
|
+
meta: { reason: outage.reason }
|
|
20756
|
+
});
|
|
20757
|
+
this.closeDeviceOutage(deviceId, outage, "suspended", {
|
|
20431
20758
|
deviceId,
|
|
20759
|
+
brokerId: String(deviceId),
|
|
20760
|
+
sourceType: "battery-wake",
|
|
20761
|
+
lastPacketAt: 0
|
|
20762
|
+
});
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
/** The device's battery probe; starts the read when there is none (or a retry is due). */
|
|
20766
|
+
ensureBatteryProbe(deviceId, now) {
|
|
20767
|
+
const existing = this.batteryProbe.get(deviceId);
|
|
20768
|
+
if (existing === void 0) {
|
|
20769
|
+
if (!this.api) {
|
|
20770
|
+
const none = {
|
|
20771
|
+
state: "done",
|
|
20772
|
+
isBattery: null,
|
|
20773
|
+
facts: null,
|
|
20774
|
+
readAt: now
|
|
20775
|
+
};
|
|
20776
|
+
this.batteryProbe.set(deviceId, none);
|
|
20777
|
+
return none;
|
|
20778
|
+
}
|
|
20779
|
+
const pending = {
|
|
20780
|
+
state: "pending",
|
|
20781
|
+
isBattery: null,
|
|
20782
|
+
facts: null,
|
|
20783
|
+
readAt: now
|
|
20784
|
+
};
|
|
20785
|
+
this.batteryProbe.set(deviceId, pending);
|
|
20786
|
+
this.probeBattery(deviceId);
|
|
20787
|
+
return pending;
|
|
20788
|
+
}
|
|
20789
|
+
if (existing.state === "done" && existing.isBattery === null && this.api && now - existing.readAt >= BATTERY_PROBE_RETRY_MS && !this.batteryProbeInFlight.has(deviceId)) this.probeBattery(deviceId);
|
|
20790
|
+
return existing;
|
|
20791
|
+
}
|
|
20792
|
+
async probeBattery(deviceId) {
|
|
20793
|
+
this.batteryProbeInFlight.add(deviceId);
|
|
20794
|
+
try {
|
|
20795
|
+
const isBattery = await this.isBatteryOperated(deviceId);
|
|
20796
|
+
const facts = isBattery === true ? await this.readBatterySleepFacts(deviceId) : null;
|
|
20797
|
+
const current = this.batteryProbe.get(deviceId);
|
|
20798
|
+
this.batteryProbe.set(deviceId, {
|
|
20799
|
+
state: "done",
|
|
20800
|
+
isBattery: current?.isBattery === true ? true : isBattery,
|
|
20801
|
+
facts: current?.facts ?? facts,
|
|
20802
|
+
readAt: Date.now()
|
|
20803
|
+
});
|
|
20804
|
+
} finally {
|
|
20805
|
+
this.batteryProbeInFlight.delete(deviceId);
|
|
20806
|
+
}
|
|
20807
|
+
}
|
|
20808
|
+
refreshBatteryFactsIfDue(deviceId, now) {
|
|
20809
|
+
const probe = this.batteryProbe.get(deviceId);
|
|
20810
|
+
if (probe?.state !== "done" || probe.isBattery !== true) return;
|
|
20811
|
+
if (now - probe.readAt < BATTERY_PROBE_RETRY_MS || this.batteryProbeInFlight.has(deviceId)) return;
|
|
20812
|
+
this.batteryProbeInFlight.add(deviceId);
|
|
20813
|
+
this.readBatterySleepFacts(deviceId).then((facts) => {
|
|
20814
|
+
const current = this.batteryProbe.get(deviceId);
|
|
20815
|
+
this.batteryProbe.set(deviceId, {
|
|
20816
|
+
state: "done",
|
|
20817
|
+
isBattery: true,
|
|
20818
|
+
facts: facts ?? current?.facts ?? null,
|
|
20819
|
+
readAt: Date.now()
|
|
20820
|
+
});
|
|
20821
|
+
}).finally(() => this.batteryProbeInFlight.delete(deviceId));
|
|
20822
|
+
}
|
|
20823
|
+
/** The device's `battery` slice as sleep facts, or `null` when it cannot be read. */
|
|
20824
|
+
async readBatterySleepFacts(deviceId) {
|
|
20825
|
+
const api = this.api;
|
|
20826
|
+
if (!api) return null;
|
|
20827
|
+
const slice = await api.deviceState.getCapSlice.query({
|
|
20828
|
+
deviceId,
|
|
20829
|
+
capName: batteryCapability.name
|
|
20830
|
+
}).catch(() => null);
|
|
20831
|
+
const parsed = BatteryStatusSchema.safeParse(slice);
|
|
20832
|
+
if (!parsed.success) return null;
|
|
20833
|
+
return {
|
|
20834
|
+
sleeping: parsed.data.sleeping,
|
|
20835
|
+
lastContactAt: parsed.data.lastContactAt
|
|
20836
|
+
};
|
|
20837
|
+
}
|
|
20838
|
+
recordBatterySleep(deviceId, status) {
|
|
20839
|
+
const previous = this.batteryProbe.get(deviceId);
|
|
20840
|
+
const facts = status && typeof status.sleeping === "boolean" ? {
|
|
20841
|
+
sleeping: status.sleeping,
|
|
20842
|
+
...typeof status.lastContactAt === "number" ? { lastContactAt: status.lastContactAt } : {}
|
|
20843
|
+
} : null;
|
|
20844
|
+
this.batteryProbe.set(deviceId, {
|
|
20845
|
+
state: "done",
|
|
20846
|
+
isBattery: true,
|
|
20847
|
+
facts: facts ?? previous?.facts ?? null,
|
|
20848
|
+
readAt: Date.now()
|
|
20849
|
+
});
|
|
20850
|
+
}
|
|
20851
|
+
/**
|
|
20852
|
+
* A released broker can no longer carry an outage. When it was the last
|
|
20853
|
+
* stream holding an ANNOUNCED outage open, the outage is closed with an
|
|
20854
|
+
* online (`broker-destroyed`, which the notification gate discards but every
|
|
20855
|
+
* ledger and alert card clears on) — otherwise the row stays open for ever.
|
|
20856
|
+
*/
|
|
20857
|
+
forgetReleasedStreamOutage(context) {
|
|
20858
|
+
const { deviceId, camStreamId } = context;
|
|
20859
|
+
const outage = this.deviceOutages.get(deviceId);
|
|
20860
|
+
if (!outage || !outage.camStreamIds.delete(camStreamId)) return;
|
|
20861
|
+
if (outage.camStreamIds.size > 0) return;
|
|
20862
|
+
this.logger.info("stream outage closed — its last stream was released", {
|
|
20863
|
+
tags: { deviceId },
|
|
20864
|
+
meta: {
|
|
20865
|
+
reason: outage.reason,
|
|
20432
20866
|
camStreamId,
|
|
20433
|
-
|
|
20434
|
-
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
|
|
20867
|
+
announced: outage.announced
|
|
20868
|
+
}
|
|
20869
|
+
});
|
|
20870
|
+
this.closeDeviceOutage(deviceId, outage, "broker-destroyed", context);
|
|
20871
|
+
}
|
|
20872
|
+
/** The ONE `stream.offline` of a device outage, naming every stream it covers. */
|
|
20873
|
+
announceDeviceOutage(deviceId, context) {
|
|
20874
|
+
const outage = this.deviceOutages.get(deviceId);
|
|
20875
|
+
if (!outage) return;
|
|
20876
|
+
if (outage.announced) {
|
|
20877
|
+
this.logger.debug("stream outage joined an already announced one — no second event", {
|
|
20878
|
+
tags: { deviceId },
|
|
20879
|
+
meta: {
|
|
20880
|
+
camStreamIds: [...outage.camStreamIds],
|
|
20881
|
+
reason: outage.reason
|
|
20882
|
+
}
|
|
20438
20883
|
});
|
|
20884
|
+
return;
|
|
20885
|
+
}
|
|
20886
|
+
outage.announced = true;
|
|
20887
|
+
this.logger.warn("stream outage", {
|
|
20888
|
+
tags: { deviceId },
|
|
20889
|
+
meta: {
|
|
20890
|
+
reason: outage.reason,
|
|
20891
|
+
camStreamIds: [...outage.camStreamIds]
|
|
20892
|
+
}
|
|
20893
|
+
});
|
|
20894
|
+
this.emitDeviceStreamHealth(false, deviceId, outage.reason, outage.camStreamIds, context);
|
|
20895
|
+
}
|
|
20896
|
+
/**
|
|
20897
|
+
* A broker of a device in outage is delivering again. The device is back when
|
|
20898
|
+
* no stream of it is still down for an outage reason; then, and only then,
|
|
20899
|
+
* the single `stream.online` (`recovered`) goes out.
|
|
20900
|
+
*/
|
|
20901
|
+
settleDeviceOutage(deviceId, context) {
|
|
20902
|
+
const outage = this.deviceOutages.get(deviceId);
|
|
20903
|
+
if (!outage) return;
|
|
20904
|
+
for (const camStreamId of [...outage.camStreamIds]) {
|
|
20905
|
+
const brokerId = brokerIdFor(deviceId, camStreamId);
|
|
20906
|
+
if (!isStreamOutageReason(this.streamOfflineReasonByBroker.get(brokerId))) outage.camStreamIds.delete(camStreamId);
|
|
20439
20907
|
}
|
|
20908
|
+
if (outage.camStreamIds.size > 0) return;
|
|
20909
|
+
this.deviceOutages.close(deviceId);
|
|
20910
|
+
if (!outage.announced) return;
|
|
20911
|
+
this.logger.info("stream outage over — device delivers again", {
|
|
20912
|
+
tags: { deviceId },
|
|
20913
|
+
meta: {
|
|
20914
|
+
reason: outage.reason,
|
|
20915
|
+
outageMs: Date.now() - outage.since
|
|
20916
|
+
}
|
|
20917
|
+
});
|
|
20918
|
+
this.emitDeviceStreamHealth(true, deviceId, "recovered", new Set(context.camStreamId !== void 0 ? [context.camStreamId] : []), context);
|
|
20919
|
+
}
|
|
20920
|
+
/**
|
|
20921
|
+
* A wake somebody ASKED for did not produce a stream (the viewer's play
|
|
20922
|
+
* button, found no broker and a wake that settled nothing). This is the one
|
|
20923
|
+
* failure with no broker to carry it, so it opens the device outage itself.
|
|
20924
|
+
*/
|
|
20925
|
+
announceBatteryWakeFailure(deviceId, camStreamId, reason) {
|
|
20926
|
+
if (!this.eventBus) return;
|
|
20927
|
+
const published = [...this.cameraStreams.get(deviceId)?.keys() ?? []];
|
|
20928
|
+
const streamIds = camStreamId !== void 0 && published.includes(camStreamId) ? [camStreamId] : published;
|
|
20929
|
+
this.deviceOutages.openWake(deviceId, streamIds, reason, Date.now());
|
|
20930
|
+
this.announceDeviceOutage(deviceId, {
|
|
20931
|
+
deviceId,
|
|
20932
|
+
...streamIds[0] !== void 0 ? { camStreamId: streamIds[0] } : {},
|
|
20933
|
+
brokerId: streamIds[0] !== void 0 ? brokerIdFor(deviceId, streamIds[0]) : String(deviceId),
|
|
20934
|
+
sourceType: "battery-wake",
|
|
20935
|
+
lastPacketAt: 0
|
|
20936
|
+
});
|
|
20937
|
+
}
|
|
20938
|
+
emitDeviceStreamHealth(online, deviceId, reason, camStreamIds, context) {
|
|
20939
|
+
const ids = [...camStreamIds];
|
|
20940
|
+
const first = ids[0] ?? context.camStreamId;
|
|
20941
|
+
const assignments = this.assignments.get(deviceId)?.map ?? {};
|
|
20942
|
+
this.emitStreamHealth(online, {
|
|
20943
|
+
...context,
|
|
20944
|
+
deviceId,
|
|
20945
|
+
...first !== void 0 ? { camStreamId: first } : {},
|
|
20946
|
+
brokerId: first !== void 0 ? brokerIdFor(deviceId, first) : context.brokerId,
|
|
20947
|
+
profile: first !== void 0 ? findProfileForStream(assignments, first) : null,
|
|
20948
|
+
reason,
|
|
20949
|
+
camStreamIds: ids
|
|
20950
|
+
});
|
|
20440
20951
|
}
|
|
20441
20952
|
emitStreamHealth(online, payload) {
|
|
20442
20953
|
const bus = this.eventBus;
|
|
20443
20954
|
if (!bus) return;
|
|
20444
20955
|
const category = online ? "stream.online" : "stream.offline";
|
|
20956
|
+
const profile = payload.profile !== void 0 ? payload.profile : payload.camStreamId !== void 0 ? findProfileForStream(this.assignments.get(payload.deviceId)?.map ?? {}, payload.camStreamId) : null;
|
|
20445
20957
|
bus.emit({
|
|
20446
20958
|
id: `${category}-${payload.brokerId}-${Date.now()}`,
|
|
20447
20959
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -20450,7 +20962,10 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
20450
20962
|
type: "stream-broker",
|
|
20451
20963
|
id: payload.brokerId
|
|
20452
20964
|
},
|
|
20453
|
-
data:
|
|
20965
|
+
data: {
|
|
20966
|
+
...payload,
|
|
20967
|
+
profile
|
|
20968
|
+
}
|
|
20454
20969
|
});
|
|
20455
20970
|
this.writeCameraStreamsSlice(payload.deviceId, this.snapshotProfileSlots(payload.deviceId));
|
|
20456
20971
|
}
|
|
@@ -22266,6 +22781,14 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
22266
22781
|
});
|
|
22267
22782
|
this.streamHealthByBroker.delete(brokerId);
|
|
22268
22783
|
this.streamOfflineReasonByBroker.delete(brokerId);
|
|
22784
|
+
this.sleepSuppressedBrokers.delete(brokerId);
|
|
22785
|
+
this.forgetReleasedStreamOutage({
|
|
22786
|
+
deviceId,
|
|
22787
|
+
camStreamId,
|
|
22788
|
+
brokerId,
|
|
22789
|
+
sourceType: broker.getActiveSourceType(),
|
|
22790
|
+
lastPacketAt: broker.getLastPacketAt()
|
|
22791
|
+
});
|
|
22269
22792
|
await broker.stop();
|
|
22270
22793
|
this.brokers.delete(brokerId);
|
|
22271
22794
|
this.sentryFrames.delete(brokerId);
|
|
@@ -33773,9 +34296,10 @@ var ClipStreamFeeder = class {
|
|
|
33773
34296
|
asked: "high",
|
|
33774
34297
|
served: answer.served
|
|
33775
34298
|
});
|
|
33776
|
-
|
|
34299
|
+
const askedProfile = this.deps.handle.profile;
|
|
34300
|
+
if (askedProfile !== void 0 && askedProfile !== null && askedProfile !== "high" && answer.served === "high") this.deps.onLog?.("clip stream: only the main copy exists — serving it for a cheaper ask", {
|
|
33777
34301
|
deviceId: this.deps.handle.deviceId,
|
|
33778
|
-
asked:
|
|
34302
|
+
asked: askedProfile,
|
|
33779
34303
|
served: answer.served
|
|
33780
34304
|
});
|
|
33781
34305
|
if (this.audioAsk !== "none" && answer.servedAudio !== this.audioAsk) {
|