@camstack/addon-pipeline 1.2.196 → 1.2.198
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 +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-CJdc8j83.js → default-detection-model-B5v8qEI0.js} +1 -1
- package/dist/{default-detection-model-BGBAmtEp.mjs → default-detection-model-CASpAESz.mjs} +1 -1
- package/dist/detection-pipeline/index.js +5 -5
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-HG5geL9t.js → dist-BxP0SvPz.js} +215 -58
- package/dist/{dist-mYDK58Jp.mjs → dist-oFIHnD88.mjs} +198 -59
- package/dist/{lazy-sharp-DmAwZ0s9.js → lazy-sharp-VKOG3WW4.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-xRsaPwBa.mjs → node-B4zETsJ9.mjs} +1 -1
- package/dist/{node--my8JTOW.js → node-BIKrNEtS.js} +1 -1
- package/dist/pipeline-runner/index.js +363 -6
- package/dist/pipeline-runner/index.mjs +362 -5
- package/dist/{retire-root-keys-KE6D6Xh_.js → prebuffer-DmBYRQWg.mjs} +83 -12
- package/dist/{retire-root-keys-DMolfhsP.mjs → prebuffer-DqdeLSsk.js} +118 -1
- package/dist/{process-memory-CoCSce63.js → process-memory-CMHOmj75.js} +1 -1
- package/dist/{process-memory-BDHR3lxe.mjs → process-memory-D9xYwm6L.mjs} +1 -1
- package/dist/recorder/index.js +152 -31
- package/dist/recorder/index.mjs +148 -27
- package/dist/{segment-demux-js-CBynn9Gh.js → segment-demux-js-DLwL1vmq.js} +1 -1
- package/dist/{segment-demux-js-CXvgKfsn.mjs → segment-demux-js-DZysrgdC.mjs} +1 -1
- package/dist/session-decode/decode-worker-child.js +2 -2
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +29 -32
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BMsB2MVD.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2eTdxvd.mjs} +3 -3
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B_NVJXAy.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BcjLrxhF.mjs} +1 -1
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CBxobiZW.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dj_qoc1e.mjs} +1 -1
- 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-CZig_taJ.mjs → hostInit-C8tu7NS4.mjs} +3 -3
- package/dist/stream-broker/index.js +452 -541
- package/dist/stream-broker/index.mjs +448 -537
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DCsi4YS1.js → worker-protocol-D2mMjUXv.js} +1 -1
- package/dist/{worker-protocol-C0R1DDC0.mjs → worker-protocol-DFlZm_wr.mjs} +1 -1
- package/package.json +1 -1
package/dist/recorder/index.js
CHANGED
|
@@ -2,12 +2,12 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_node = require("../node
|
|
5
|
+
const require_dist = require("../dist-BxP0SvPz.js");
|
|
6
|
+
const require_node = require("../node-BIKrNEtS.js");
|
|
7
7
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
8
8
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
9
9
|
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DaUBcb13.js");
|
|
10
|
-
const
|
|
10
|
+
const require_prebuffer = require("../prebuffer-DqdeLSsk.js");
|
|
11
11
|
let node_crypto = require("node:crypto");
|
|
12
12
|
let node_fs = require("node:fs");
|
|
13
13
|
let node_path = require("node:path");
|
|
@@ -3884,6 +3884,31 @@ var CalendarIndex = class {
|
|
|
3884
3884
|
//#endregion
|
|
3885
3885
|
//#region src/recorder/addon/config-store.ts
|
|
3886
3886
|
/**
|
|
3887
|
+
* Per-device recording-config persistence for the recorder addon.
|
|
3888
|
+
*
|
|
3889
|
+
* ONE ROW PER CAMERA in `recorder:recording-configs`, through
|
|
3890
|
+
* {@link RowMapStore}. Asking what camera 617 records is a primary-key lookup;
|
|
3891
|
+
* saving its config writes that row and touches no other.
|
|
3892
|
+
*
|
|
3893
|
+
* ## What this replaced, and why
|
|
3894
|
+
*
|
|
3895
|
+
* Until the 2026-08-19 flatten every camera's `RecordingConfig` lived in ONE
|
|
3896
|
+
* key of the recorder's `addon-settings` / `root` blob — a row that was 201 833
|
|
3897
|
+
* bytes on the reference hub, because the ops-log and the export history shared
|
|
3898
|
+
* it. So `loadDeviceConfig(617)` read and `JSON.parse`d 201 KB, and
|
|
3899
|
+
* `saveDeviceConfig` read that, merged, and rewrote it. The staging walk did it
|
|
3900
|
+
* once per device and lost 45–267 s of a boot to it (2026-08-18); teaching the
|
|
3901
|
+
* walk to read the map once capped the symptom, and this removes the cause.
|
|
3902
|
+
*
|
|
3903
|
+
* ## Failure direction
|
|
3904
|
+
*
|
|
3905
|
+
* `RecordingConfigSchema` rejects unknown keys so a stale caller fails loudly,
|
|
3906
|
+
* but a row written by an older build may still carry retired ones — `.strip()`
|
|
3907
|
+
* drops them. And validation is now PER ROW: one unparseable camera costs that
|
|
3908
|
+
* camera, where the blob's single `safeParse` took every camera down to the
|
|
3909
|
+
* empty fallback (= the whole fleet silently unconfigured).
|
|
3910
|
+
*/
|
|
3911
|
+
/**
|
|
3887
3912
|
* @durable class=config owner=recorder
|
|
3888
3913
|
* write="one row per camera, written by `saveDeviceConfig` (the `recording` cap's
|
|
3889
3914
|
* setConfig / band authoring) — the only writer"
|
|
@@ -3893,7 +3918,7 @@ var CalendarIndex = class {
|
|
|
3893
3918
|
var RECORDING_CONFIGS_COLLECTION = "recorder:recording-configs";
|
|
3894
3919
|
var RECORDING_CONFIGS_SPEC = {
|
|
3895
3920
|
collection: RECORDING_CONFIGS_COLLECTION,
|
|
3896
|
-
schema: require_dist.RecordingConfigSchema.strip(),
|
|
3921
|
+
schema: require_dist.preprocess((raw) => require_dist.migrateLegacyDeviceSignalConfig(raw).value, require_dist.RecordingConfigSchema.strip()).transform((config) => require_dist.stripRetiredBandPreBufferSec(config).config),
|
|
3897
3922
|
columns: [
|
|
3898
3923
|
{
|
|
3899
3924
|
name: "deviceId",
|
|
@@ -3925,7 +3950,7 @@ var DEFAULT_DEVICE_CONFIG = {
|
|
|
3925
3950
|
};
|
|
3926
3951
|
/** Build the handle. `declare()` MUST run once at boot before any read/write. */
|
|
3927
3952
|
function recordingConfigsState(store, logger) {
|
|
3928
|
-
return new
|
|
3953
|
+
return new require_prebuffer.RowMapStore({
|
|
3929
3954
|
spec: RECORDING_CONFIGS_SPEC,
|
|
3930
3955
|
store,
|
|
3931
3956
|
logger
|
|
@@ -3959,7 +3984,10 @@ async function loadDeviceConfig(state, deviceId) {
|
|
|
3959
3984
|
*/
|
|
3960
3985
|
function deviceIdsWithLostBands(configs) {
|
|
3961
3986
|
const lost = [];
|
|
3962
|
-
for (const [deviceId, config] of configs)
|
|
3987
|
+
for (const [deviceId, config] of configs) {
|
|
3988
|
+
if (config.deviceDecision === true) continue;
|
|
3989
|
+
if (config.enabled && config.bands.length === 0 && config.mode && config.mode !== "off") lost.push(deviceId);
|
|
3990
|
+
}
|
|
3963
3991
|
return lost.toSorted((a, b) => a - b);
|
|
3964
3992
|
}
|
|
3965
3993
|
/** Boot-time check: log the {@link deviceIdsWithLostBands} once, if any. */
|
|
@@ -3969,6 +3997,48 @@ async function warnLostBands(state, logger) {
|
|
|
3969
3997
|
logger.warn(`recording config: device(s) ${lost.join(", ")} claim a recording mode but have NO bands — a pre-retirement config that lost its legacy authoring fields. Nothing is recorded for them until their bands are re-authored.`);
|
|
3970
3998
|
}
|
|
3971
3999
|
/**
|
|
4000
|
+
* Persist the D371→D380 shape for every stored row — the migration, in code,
|
|
4001
|
+
* run once per boot and IDEMPOTENT: a second run finds no legacy key and
|
|
4002
|
+
* writes nothing.
|
|
4003
|
+
*
|
|
4004
|
+
* It reads the rows RAW ({@link RowMapStore.readAllRaw}) on purpose. The read
|
|
4005
|
+
* schema already migrates in memory, so by the time `readAll` answers there is
|
|
4006
|
+
* nothing left to recognise — and the retired key is stripped by a non-strict
|
|
4007
|
+
* `z.object`, so the migration would look like it had nothing to do on exactly
|
|
4008
|
+
* the row it exists for.
|
|
4009
|
+
*
|
|
4010
|
+
* Both outcomes are logged, because both change what a camera records and
|
|
4011
|
+
* silence would read as "nothing happened" — which is what a stripped key
|
|
4012
|
+
* looks like from the outside.
|
|
4013
|
+
*/
|
|
4014
|
+
async function migrateLegacyDeviceSignalRows(state, logger) {
|
|
4015
|
+
const migrated = [];
|
|
4016
|
+
const stripped = [];
|
|
4017
|
+
for (const [key, raw] of await state.readAllRaw()) {
|
|
4018
|
+
const outcome = require_dist.migrateLegacyDeviceSignalConfig(raw);
|
|
4019
|
+
if (!outcome.migrated && !outcome.strippedOnly) continue;
|
|
4020
|
+
const deviceId = Number(key);
|
|
4021
|
+
if (!Number.isInteger(deviceId)) continue;
|
|
4022
|
+
const parsed = require_dist.RecordingConfigSchema.strip().safeParse(outcome.value);
|
|
4023
|
+
if (!parsed.success) {
|
|
4024
|
+
logger.warn("recording config: legacy device-signal row could not be migrated", {
|
|
4025
|
+
tags: { deviceId },
|
|
4026
|
+
meta: { error: parsed.error.message }
|
|
4027
|
+
});
|
|
4028
|
+
continue;
|
|
4029
|
+
}
|
|
4030
|
+
await saveDeviceConfig(state, deviceId, parsed.data);
|
|
4031
|
+
if (outcome.migrated) migrated.push(deviceId);
|
|
4032
|
+
else stripped.push(deviceId);
|
|
4033
|
+
}
|
|
4034
|
+
if (migrated.length > 0) logger.info("recording config: migrated to the on-device-decision mode (the retired band trigger)", { meta: { deviceIds: migrated } });
|
|
4035
|
+
if (stripped.length > 0) logger.warn("recording config: a band mixed the retired device-signal trigger with others — the key was dropped and the band kept. These cameras no longer follow their own signal until the on-device-decision mode is selected for them.", { meta: { deviceIds: stripped } });
|
|
4036
|
+
return {
|
|
4037
|
+
migrated,
|
|
4038
|
+
stripped
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
/**
|
|
3972
4042
|
* Persist (insert or replace) a device's config — ONE row. Validates against
|
|
3973
4043
|
* `RecordingConfigSchema` at the boundary (untrusted cap input). Returns the
|
|
3974
4044
|
* validated config that was persisted.
|
|
@@ -5171,7 +5241,7 @@ var RECORDING_EXPORTS_SPEC = {
|
|
|
5171
5241
|
};
|
|
5172
5242
|
/** Build the handle. `declare()` MUST run once at boot before any read/write. */
|
|
5173
5243
|
function recordingExportsState(store, logger) {
|
|
5174
|
-
return new
|
|
5244
|
+
return new require_prebuffer.RowMapStore({
|
|
5175
5245
|
spec: RECORDING_EXPORTS_SPEC,
|
|
5176
5246
|
store,
|
|
5177
5247
|
logger
|
|
@@ -11014,7 +11084,7 @@ var RECORDING_OPS_LOG_SPEC = {
|
|
|
11014
11084
|
};
|
|
11015
11085
|
/** Build the handle. `declare()` MUST run once at boot before any read/write. */
|
|
11016
11086
|
function recordingOpsLogState(store, logger) {
|
|
11017
|
-
return new
|
|
11087
|
+
return new require_prebuffer.RowMapStore({
|
|
11018
11088
|
spec: RECORDING_OPS_LOG_SPEC,
|
|
11019
11089
|
store,
|
|
11020
11090
|
logger
|
|
@@ -12462,6 +12532,13 @@ function bandActiveAt(band, date) {
|
|
|
12462
12532
|
if (s > e) return applies(wd) && t >= s || applies(prevWd) && t < e;
|
|
12463
12533
|
return applies(wd);
|
|
12464
12534
|
}
|
|
12535
|
+
/**
|
|
12536
|
+
* The band covering `date`, or null. GENERIC over the band type so the caller
|
|
12537
|
+
* gets back exactly what it put in: the recorder decides on `EffectiveBand`s
|
|
12538
|
+
* (`band-decision.ts`), which carry the one non-authored fact — that the DEVICE
|
|
12539
|
+
* decides — and a return type widened to `RecordingBand` would erase it right
|
|
12540
|
+
* where the gates read it.
|
|
12541
|
+
*/
|
|
12465
12542
|
function activeBandAt(config, date) {
|
|
12466
12543
|
return config.bands.find((b) => bandActiveAt(b, date)) ?? null;
|
|
12467
12544
|
}
|
|
@@ -12486,18 +12563,57 @@ function activeBandAt(config, date) {
|
|
|
12486
12563
|
* unit-testable without ffmpeg, the broker, the event bus, or a real wall clock.
|
|
12487
12564
|
*/
|
|
12488
12565
|
/**
|
|
12489
|
-
* THE
|
|
12490
|
-
*
|
|
12491
|
-
*
|
|
12492
|
-
*
|
|
12493
|
-
*
|
|
12494
|
-
*
|
|
12495
|
-
*
|
|
12496
|
-
*
|
|
12566
|
+
* THE band a `deviceDecision` config decides through: always on (every day,
|
|
12567
|
+
* `start === end` — the band engine's always-on shape), `events`, and listening
|
|
12568
|
+
* to exactly one source, the camera's own level.
|
|
12569
|
+
*
|
|
12570
|
+
* Its buffers are the canonical pad ({@link DEFAULT_EVENTS_BAND_BUFFER_SEC}),
|
|
12571
|
+
* not per-camera knobs: this mode's whole contract with the operator is ONE
|
|
12572
|
+
* flag. The pad only ever retains footage around an episode the device itself
|
|
12573
|
+
* delimited, so there is nothing here for an operator to tune.
|
|
12574
|
+
*/
|
|
12575
|
+
var DEVICE_DECISION_BAND = Object.freeze({
|
|
12576
|
+
days: [],
|
|
12577
|
+
start: "00:00",
|
|
12578
|
+
end: "00:00",
|
|
12579
|
+
mode: "events",
|
|
12580
|
+
deviceSignal: true,
|
|
12581
|
+
postBufferSec: require_dist.DEFAULT_EVENTS_BAND_BUFFER_SEC.postBufferSec
|
|
12582
|
+
});
|
|
12583
|
+
/**
|
|
12584
|
+
* THE bands the recorder decides on for a config — the ONE place
|
|
12585
|
+
* `config.bands` is turned into what the gates read.
|
|
12586
|
+
*
|
|
12587
|
+
* `deviceDecision` resolves to {@link DEVICE_DECISION_BAND} and to nothing
|
|
12588
|
+
* else; every other config is its own authored bands, byte for byte. Because
|
|
12589
|
+
* the schema makes the two exclusive, this is a choice and never a merge.
|
|
12590
|
+
*/
|
|
12591
|
+
function effectiveBands(config) {
|
|
12592
|
+
if (config.deviceDecision === true) return [DEVICE_DECISION_BAND];
|
|
12593
|
+
return config.bands ?? [];
|
|
12594
|
+
}
|
|
12595
|
+
/**
|
|
12596
|
+
* THE one place a band's buffers are read.
|
|
12597
|
+
*
|
|
12598
|
+
* `postMs` is the LIVE demand window and stays a per-band setting: it decides
|
|
12599
|
+
* how long a writer keeps running, which is a policy about this camera at this
|
|
12600
|
+
* hour. An ABSENT field takes {@link DEFAULT_EVENTS_BAND_BUFFER_SEC}; an
|
|
12601
|
+
* explicit value — including `0` — is honoured verbatim, because both callers
|
|
12602
|
+
* used to inline `?? 0` and read "the operator omitted the field" as "the
|
|
12603
|
+
* operator asked for a zero-length window", which for the live gate is not a
|
|
12604
|
+
* policy but an impossibility. Two cameras sat in exactly that state.
|
|
12605
|
+
*
|
|
12606
|
+
* `preMs` is NOT a setting any more (D381). It is the retroactive KEEP bound —
|
|
12607
|
+
* which already-written segments survive a trigger — and its only correct
|
|
12608
|
+
* value was ever "at least as wide as the widest history the broker can hand
|
|
12609
|
+
* the writer". So it IS that ceiling, read from the one place that defines it:
|
|
12610
|
+
* a keep gate can no longer be configured narrower than the media it is
|
|
12611
|
+
* supposed to keep, which is the disagreement D191 was written against and
|
|
12612
|
+
* which a per-band number could only ever warn about.
|
|
12497
12613
|
*/
|
|
12498
12614
|
function resolveBandBufferMs(band) {
|
|
12499
12615
|
return {
|
|
12500
|
-
preMs:
|
|
12616
|
+
preMs: require_prebuffer.PREBUFFER_MAX_MS,
|
|
12501
12617
|
postMs: (band.postBufferSec ?? require_dist.DEFAULT_EVENTS_BAND_BUFFER_SEC.postBufferSec) * 1e3
|
|
12502
12618
|
};
|
|
12503
12619
|
}
|
|
@@ -12526,6 +12642,9 @@ function triggerKey(source) {
|
|
|
12526
12642
|
case "signal": return "signal";
|
|
12527
12643
|
}
|
|
12528
12644
|
}
|
|
12645
|
+
/** The single source {@link DEVICE_DECISION_BAND} listens to, shared so the
|
|
12646
|
+
* band never allocates a new one per call. */
|
|
12647
|
+
var SIGNAL_SOURCES = Object.freeze([{ kind: "signal" }]);
|
|
12529
12648
|
/**
|
|
12530
12649
|
* LE sorgenti che questa banda ascolta — l'unico posto dove i `triggers` di
|
|
12531
12650
|
* una banda diventano `TriggerSource[]`.
|
|
@@ -12541,6 +12660,7 @@ function triggerKey(source) {
|
|
|
12541
12660
|
* denuncia invece di restare zitto.
|
|
12542
12661
|
*/
|
|
12543
12662
|
function bandTriggerSources(band) {
|
|
12663
|
+
if (band.deviceSignal === true) return SIGNAL_SOURCES;
|
|
12544
12664
|
const t = band.triggers;
|
|
12545
12665
|
if (t === void 0) return [];
|
|
12546
12666
|
const sources = [];
|
|
@@ -12554,7 +12674,6 @@ function bandTriggerSources(band) {
|
|
|
12554
12674
|
kind: "sensor",
|
|
12555
12675
|
sourceDeviceId
|
|
12556
12676
|
});
|
|
12557
|
-
if (t.deviceSignal === true) sources.push({ kind: "signal" });
|
|
12558
12677
|
return sources;
|
|
12559
12678
|
}
|
|
12560
12679
|
/**
|
|
@@ -12740,8 +12859,8 @@ function eventsBandDemanded(band, triggers, atMs) {
|
|
|
12740
12859
|
*/
|
|
12741
12860
|
function shouldRecordAt(config, triggers, at) {
|
|
12742
12861
|
if (!config.enabled) return false;
|
|
12743
|
-
const bands = config
|
|
12744
|
-
if (
|
|
12862
|
+
const bands = effectiveBands(config);
|
|
12863
|
+
if (bands.length === 0) return false;
|
|
12745
12864
|
const band = activeBandAt({ bands }, at);
|
|
12746
12865
|
if (!band) return false;
|
|
12747
12866
|
if (band.mode === "continuous") return true;
|
|
@@ -12996,8 +13115,8 @@ function candidateEdgesMs(bands, nowMs) {
|
|
|
12996
13115
|
* costs one wasted wake-up and never costs footage.
|
|
12997
13116
|
*/
|
|
12998
13117
|
function nextBandEdgeMs(config, nowMs) {
|
|
12999
|
-
const bands = config
|
|
13000
|
-
if (
|
|
13118
|
+
const bands = effectiveBands(config);
|
|
13119
|
+
if (bands.length === 0) return null;
|
|
13001
13120
|
const current = activeBandAt({ bands }, new Date(nowMs));
|
|
13002
13121
|
const candidates = [...new Set(candidateEdgesMs(bands, nowMs))].filter((ms) => ms > nowMs).toSorted((a, b) => a - b);
|
|
13003
13122
|
for (const ms of candidates) if (activeBandAt({ bands }, new Date(ms)) !== current) return ms;
|
|
@@ -13032,8 +13151,8 @@ function eventsWindowCloseMs(band, triggers, nowMs) {
|
|
|
13032
13151
|
function nextDecisionChangeAt(input) {
|
|
13033
13152
|
const { config, triggers, nowMs } = input;
|
|
13034
13153
|
if (!config.enabled) return null;
|
|
13035
|
-
const bands = config
|
|
13036
|
-
if (
|
|
13154
|
+
const bands = effectiveBands(config);
|
|
13155
|
+
if (bands.length === 0) return null;
|
|
13037
13156
|
const band = activeBandAt({ bands }, new Date(nowMs));
|
|
13038
13157
|
const edgeMs = nextBandEdgeMs(config, nowMs);
|
|
13039
13158
|
const closeMs = band === null ? null : eventsWindowCloseMs(band, triggers, nowMs);
|
|
@@ -14503,7 +14622,7 @@ var RecordingController = class {
|
|
|
14503
14622
|
const config = await this.deps.loadConfig(deviceId);
|
|
14504
14623
|
if (config.enabled !== true) return;
|
|
14505
14624
|
const nowMs = this.now();
|
|
14506
|
-
const band = activeBandAt({ bands: config
|
|
14625
|
+
const band = activeBandAt({ bands: effectiveBands(config) }, new Date(nowMs));
|
|
14507
14626
|
if (!band || band.mode !== "events") return;
|
|
14508
14627
|
const { postMs } = resolveBandBufferMs(band);
|
|
14509
14628
|
const sinceMs = nowMs - postMs - TRIGGER_HISTORY_MARGIN_MS;
|
|
@@ -14910,7 +15029,7 @@ var RecordingController = class {
|
|
|
14910
15029
|
if (this.stopped) return;
|
|
14911
15030
|
let band;
|
|
14912
15031
|
try {
|
|
14913
|
-
band = activeBandAt({ bands: (await this.deps.loadConfig(deviceId))
|
|
15032
|
+
band = activeBandAt({ bands: effectiveBands(await this.deps.loadConfig(deviceId)) }, new Date(atMs));
|
|
14914
15033
|
} catch (err) {
|
|
14915
15034
|
this.deps.logger.warn("recorder: audio trigger qualification skipped — config read failed", {
|
|
14916
15035
|
tags: { deviceId },
|
|
@@ -14935,7 +15054,7 @@ var RecordingController = class {
|
|
|
14935
15054
|
*/
|
|
14936
15055
|
async postBufferMsFor(deviceId, atMs) {
|
|
14937
15056
|
try {
|
|
14938
|
-
const band = activeBandAt({ bands: (await this.deps.loadConfig(deviceId))
|
|
15057
|
+
const band = activeBandAt({ bands: effectiveBands(await this.deps.loadConfig(deviceId)) }, new Date(atMs));
|
|
14939
15058
|
if (band) return resolveBandBufferMs(band).postMs;
|
|
14940
15059
|
} catch (err) {
|
|
14941
15060
|
this.deps.logger.warn("recorder: trigger history using the DEFAULT post-buffer — config read failed", {
|
|
@@ -15026,7 +15145,7 @@ var RecordingController = class {
|
|
|
15026
15145
|
reportUnrecordable(deviceId, config) {
|
|
15027
15146
|
if (!config.enabled) return;
|
|
15028
15147
|
if (this.unrecordableReported.has(deviceId)) return;
|
|
15029
|
-
const bands = config
|
|
15148
|
+
const bands = effectiveBands(config);
|
|
15030
15149
|
if (bands.length === 0) {
|
|
15031
15150
|
this.unrecordableReported.add(deviceId);
|
|
15032
15151
|
this.deps.logger.warn("recorder: device is enabled but has no recording bands — nothing will ever be recorded", { tags: { deviceId } });
|
|
@@ -15363,7 +15482,7 @@ var RecordingController = class {
|
|
|
15363
15482
|
*/
|
|
15364
15483
|
async handleFinalizedSegment(deviceId, profile, placement, attach, startMs, durMs, flatAbsPath) {
|
|
15365
15484
|
try {
|
|
15366
|
-
const band = activeBandAt({ bands: (await this.deps.loadConfig(deviceId))
|
|
15485
|
+
const band = activeBandAt({ bands: effectiveBands(await this.deps.loadConfig(deviceId)) }, new Date(startMs + durMs));
|
|
15367
15486
|
const triggers = this.triggersFor(deviceId);
|
|
15368
15487
|
if (band?.mode === "events" && !segmentRetainedForEvents(startMs, startMs + durMs, band, triggers, attach)) {
|
|
15369
15488
|
this.reportDiscardedSegment(deviceId, profile, attach, band, triggers, startMs, durMs);
|
|
@@ -17367,7 +17486,9 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
17367
17486
|
watchIntervalMs: this.config.watchIntervalMs
|
|
17368
17487
|
}
|
|
17369
17488
|
});
|
|
17370
|
-
|
|
17489
|
+
migrateLegacyDeviceSignalRows(this.configStore(), this.ctx.logger).catch((err) => {
|
|
17490
|
+
this.ctx.logger.warn("recorder: device-signal config migration failed", { meta: { error: require_dist.errMsg(err) } });
|
|
17491
|
+
}).then(() => warnLostBands(this.configStore(), this.ctx.logger));
|
|
17371
17492
|
const providers = [
|
|
17372
17493
|
{
|
|
17373
17494
|
capability: require_dist.recordingCapability,
|
|
@@ -18289,7 +18410,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
18289
18410
|
*/
|
|
18290
18411
|
async purgeFlattenedSettingsKeys() {
|
|
18291
18412
|
try {
|
|
18292
|
-
await
|
|
18413
|
+
await require_prebuffer.retireRootKeys({
|
|
18293
18414
|
store: this.ctx.api.settingsStore,
|
|
18294
18415
|
addonId: RECORDER_ADDON_ID,
|
|
18295
18416
|
logger: this.ctx.logger,
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-
|
|
1
|
+
import { Bt as hydrateSchema, Ct as storageEvictableCapability, D as RecordingSignalStatusSchema, E as RecordingConfigSchema, Ft as BaseAddon, H as deriveBatteryPresence, Jt as sleep$1, Lt as DeviceFeature, Ot as errMsg, P as batteryCapability, R as customAction, Rt as DeviceType, T as RECORDING_EXPORT_MAX_READ_BYTES, Tt as stripRetiredBandPreBufferSec, V as defineCustomActions, W as deriveRecordingMode, Wt as nodePin, Xt as array, Y as evaluateSensorEdge, Yt as _enum, Zt as boolean, _t as recordingSignalCapability, an as string, at as migrateLegacyDeviceSignalConfig, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, et as isFirstLevelMacroClass, g as ExportRecordSchema, gt as recordingExportCapability, ht as recordingCapability, i as BatteryStatusSchema, in as record, nn as object, qt as selectAssignedProfileSlots, rn as preprocess, sn as EventCategory, tn as number, tt as loadContributionCapability, x as OpsLogEntrySchema, xt as resolveRecordingProfiles } from "../dist-oFIHnD88.mjs";
|
|
2
|
+
import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-B4zETsJ9.mjs";
|
|
3
3
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
4
4
|
import { r as withRecordingIntent } from "../restream-intent-B4BXZra7.mjs";
|
|
5
5
|
import { n as startEventLoopStallMonitor } from "../event-loop-stall-monitor-DXLNBMkY.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { a as retireRootKeys, o as RowMapStore, t as PREBUFFER_MAX_MS } from "../prebuffer-DmBYRQWg.mjs";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { createReadStream, promises, realpathSync } from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
@@ -3879,6 +3879,31 @@ var CalendarIndex = class {
|
|
|
3879
3879
|
//#endregion
|
|
3880
3880
|
//#region src/recorder/addon/config-store.ts
|
|
3881
3881
|
/**
|
|
3882
|
+
* Per-device recording-config persistence for the recorder addon.
|
|
3883
|
+
*
|
|
3884
|
+
* ONE ROW PER CAMERA in `recorder:recording-configs`, through
|
|
3885
|
+
* {@link RowMapStore}. Asking what camera 617 records is a primary-key lookup;
|
|
3886
|
+
* saving its config writes that row and touches no other.
|
|
3887
|
+
*
|
|
3888
|
+
* ## What this replaced, and why
|
|
3889
|
+
*
|
|
3890
|
+
* Until the 2026-08-19 flatten every camera's `RecordingConfig` lived in ONE
|
|
3891
|
+
* key of the recorder's `addon-settings` / `root` blob — a row that was 201 833
|
|
3892
|
+
* bytes on the reference hub, because the ops-log and the export history shared
|
|
3893
|
+
* it. So `loadDeviceConfig(617)` read and `JSON.parse`d 201 KB, and
|
|
3894
|
+
* `saveDeviceConfig` read that, merged, and rewrote it. The staging walk did it
|
|
3895
|
+
* once per device and lost 45–267 s of a boot to it (2026-08-18); teaching the
|
|
3896
|
+
* walk to read the map once capped the symptom, and this removes the cause.
|
|
3897
|
+
*
|
|
3898
|
+
* ## Failure direction
|
|
3899
|
+
*
|
|
3900
|
+
* `RecordingConfigSchema` rejects unknown keys so a stale caller fails loudly,
|
|
3901
|
+
* but a row written by an older build may still carry retired ones — `.strip()`
|
|
3902
|
+
* drops them. And validation is now PER ROW: one unparseable camera costs that
|
|
3903
|
+
* camera, where the blob's single `safeParse` took every camera down to the
|
|
3904
|
+
* empty fallback (= the whole fleet silently unconfigured).
|
|
3905
|
+
*/
|
|
3906
|
+
/**
|
|
3882
3907
|
* @durable class=config owner=recorder
|
|
3883
3908
|
* write="one row per camera, written by `saveDeviceConfig` (the `recording` cap's
|
|
3884
3909
|
* setConfig / band authoring) — the only writer"
|
|
@@ -3888,7 +3913,7 @@ var CalendarIndex = class {
|
|
|
3888
3913
|
var RECORDING_CONFIGS_COLLECTION = "recorder:recording-configs";
|
|
3889
3914
|
var RECORDING_CONFIGS_SPEC = {
|
|
3890
3915
|
collection: RECORDING_CONFIGS_COLLECTION,
|
|
3891
|
-
schema: RecordingConfigSchema.strip(),
|
|
3916
|
+
schema: preprocess((raw) => migrateLegacyDeviceSignalConfig(raw).value, RecordingConfigSchema.strip()).transform((config) => stripRetiredBandPreBufferSec(config).config),
|
|
3892
3917
|
columns: [
|
|
3893
3918
|
{
|
|
3894
3919
|
name: "deviceId",
|
|
@@ -3954,7 +3979,10 @@ async function loadDeviceConfig(state, deviceId) {
|
|
|
3954
3979
|
*/
|
|
3955
3980
|
function deviceIdsWithLostBands(configs) {
|
|
3956
3981
|
const lost = [];
|
|
3957
|
-
for (const [deviceId, config] of configs)
|
|
3982
|
+
for (const [deviceId, config] of configs) {
|
|
3983
|
+
if (config.deviceDecision === true) continue;
|
|
3984
|
+
if (config.enabled && config.bands.length === 0 && config.mode && config.mode !== "off") lost.push(deviceId);
|
|
3985
|
+
}
|
|
3958
3986
|
return lost.toSorted((a, b) => a - b);
|
|
3959
3987
|
}
|
|
3960
3988
|
/** Boot-time check: log the {@link deviceIdsWithLostBands} once, if any. */
|
|
@@ -3964,6 +3992,48 @@ async function warnLostBands(state, logger) {
|
|
|
3964
3992
|
logger.warn(`recording config: device(s) ${lost.join(", ")} claim a recording mode but have NO bands — a pre-retirement config that lost its legacy authoring fields. Nothing is recorded for them until their bands are re-authored.`);
|
|
3965
3993
|
}
|
|
3966
3994
|
/**
|
|
3995
|
+
* Persist the D371→D380 shape for every stored row — the migration, in code,
|
|
3996
|
+
* run once per boot and IDEMPOTENT: a second run finds no legacy key and
|
|
3997
|
+
* writes nothing.
|
|
3998
|
+
*
|
|
3999
|
+
* It reads the rows RAW ({@link RowMapStore.readAllRaw}) on purpose. The read
|
|
4000
|
+
* schema already migrates in memory, so by the time `readAll` answers there is
|
|
4001
|
+
* nothing left to recognise — and the retired key is stripped by a non-strict
|
|
4002
|
+
* `z.object`, so the migration would look like it had nothing to do on exactly
|
|
4003
|
+
* the row it exists for.
|
|
4004
|
+
*
|
|
4005
|
+
* Both outcomes are logged, because both change what a camera records and
|
|
4006
|
+
* silence would read as "nothing happened" — which is what a stripped key
|
|
4007
|
+
* looks like from the outside.
|
|
4008
|
+
*/
|
|
4009
|
+
async function migrateLegacyDeviceSignalRows(state, logger) {
|
|
4010
|
+
const migrated = [];
|
|
4011
|
+
const stripped = [];
|
|
4012
|
+
for (const [key, raw] of await state.readAllRaw()) {
|
|
4013
|
+
const outcome = migrateLegacyDeviceSignalConfig(raw);
|
|
4014
|
+
if (!outcome.migrated && !outcome.strippedOnly) continue;
|
|
4015
|
+
const deviceId = Number(key);
|
|
4016
|
+
if (!Number.isInteger(deviceId)) continue;
|
|
4017
|
+
const parsed = RecordingConfigSchema.strip().safeParse(outcome.value);
|
|
4018
|
+
if (!parsed.success) {
|
|
4019
|
+
logger.warn("recording config: legacy device-signal row could not be migrated", {
|
|
4020
|
+
tags: { deviceId },
|
|
4021
|
+
meta: { error: parsed.error.message }
|
|
4022
|
+
});
|
|
4023
|
+
continue;
|
|
4024
|
+
}
|
|
4025
|
+
await saveDeviceConfig(state, deviceId, parsed.data);
|
|
4026
|
+
if (outcome.migrated) migrated.push(deviceId);
|
|
4027
|
+
else stripped.push(deviceId);
|
|
4028
|
+
}
|
|
4029
|
+
if (migrated.length > 0) logger.info("recording config: migrated to the on-device-decision mode (the retired band trigger)", { meta: { deviceIds: migrated } });
|
|
4030
|
+
if (stripped.length > 0) logger.warn("recording config: a band mixed the retired device-signal trigger with others — the key was dropped and the band kept. These cameras no longer follow their own signal until the on-device-decision mode is selected for them.", { meta: { deviceIds: stripped } });
|
|
4031
|
+
return {
|
|
4032
|
+
migrated,
|
|
4033
|
+
stripped
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
/**
|
|
3967
4037
|
* Persist (insert or replace) a device's config — ONE row. Validates against
|
|
3968
4038
|
* `RecordingConfigSchema` at the boundary (untrusted cap input). Returns the
|
|
3969
4039
|
* validated config that was persisted.
|
|
@@ -12456,6 +12526,13 @@ function bandActiveAt(band, date) {
|
|
|
12456
12526
|
if (s > e) return applies(wd) && t >= s || applies(prevWd) && t < e;
|
|
12457
12527
|
return applies(wd);
|
|
12458
12528
|
}
|
|
12529
|
+
/**
|
|
12530
|
+
* The band covering `date`, or null. GENERIC over the band type so the caller
|
|
12531
|
+
* gets back exactly what it put in: the recorder decides on `EffectiveBand`s
|
|
12532
|
+
* (`band-decision.ts`), which carry the one non-authored fact — that the DEVICE
|
|
12533
|
+
* decides — and a return type widened to `RecordingBand` would erase it right
|
|
12534
|
+
* where the gates read it.
|
|
12535
|
+
*/
|
|
12459
12536
|
function activeBandAt(config, date) {
|
|
12460
12537
|
return config.bands.find((b) => bandActiveAt(b, date)) ?? null;
|
|
12461
12538
|
}
|
|
@@ -12480,18 +12557,57 @@ function activeBandAt(config, date) {
|
|
|
12480
12557
|
* unit-testable without ffmpeg, the broker, the event bus, or a real wall clock.
|
|
12481
12558
|
*/
|
|
12482
12559
|
/**
|
|
12483
|
-
* THE
|
|
12484
|
-
*
|
|
12485
|
-
*
|
|
12486
|
-
*
|
|
12487
|
-
*
|
|
12488
|
-
*
|
|
12489
|
-
*
|
|
12490
|
-
*
|
|
12560
|
+
* THE band a `deviceDecision` config decides through: always on (every day,
|
|
12561
|
+
* `start === end` — the band engine's always-on shape), `events`, and listening
|
|
12562
|
+
* to exactly one source, the camera's own level.
|
|
12563
|
+
*
|
|
12564
|
+
* Its buffers are the canonical pad ({@link DEFAULT_EVENTS_BAND_BUFFER_SEC}),
|
|
12565
|
+
* not per-camera knobs: this mode's whole contract with the operator is ONE
|
|
12566
|
+
* flag. The pad only ever retains footage around an episode the device itself
|
|
12567
|
+
* delimited, so there is nothing here for an operator to tune.
|
|
12568
|
+
*/
|
|
12569
|
+
var DEVICE_DECISION_BAND = Object.freeze({
|
|
12570
|
+
days: [],
|
|
12571
|
+
start: "00:00",
|
|
12572
|
+
end: "00:00",
|
|
12573
|
+
mode: "events",
|
|
12574
|
+
deviceSignal: true,
|
|
12575
|
+
postBufferSec: DEFAULT_EVENTS_BAND_BUFFER_SEC.postBufferSec
|
|
12576
|
+
});
|
|
12577
|
+
/**
|
|
12578
|
+
* THE bands the recorder decides on for a config — the ONE place
|
|
12579
|
+
* `config.bands` is turned into what the gates read.
|
|
12580
|
+
*
|
|
12581
|
+
* `deviceDecision` resolves to {@link DEVICE_DECISION_BAND} and to nothing
|
|
12582
|
+
* else; every other config is its own authored bands, byte for byte. Because
|
|
12583
|
+
* the schema makes the two exclusive, this is a choice and never a merge.
|
|
12584
|
+
*/
|
|
12585
|
+
function effectiveBands(config) {
|
|
12586
|
+
if (config.deviceDecision === true) return [DEVICE_DECISION_BAND];
|
|
12587
|
+
return config.bands ?? [];
|
|
12588
|
+
}
|
|
12589
|
+
/**
|
|
12590
|
+
* THE one place a band's buffers are read.
|
|
12591
|
+
*
|
|
12592
|
+
* `postMs` is the LIVE demand window and stays a per-band setting: it decides
|
|
12593
|
+
* how long a writer keeps running, which is a policy about this camera at this
|
|
12594
|
+
* hour. An ABSENT field takes {@link DEFAULT_EVENTS_BAND_BUFFER_SEC}; an
|
|
12595
|
+
* explicit value — including `0` — is honoured verbatim, because both callers
|
|
12596
|
+
* used to inline `?? 0` and read "the operator omitted the field" as "the
|
|
12597
|
+
* operator asked for a zero-length window", which for the live gate is not a
|
|
12598
|
+
* policy but an impossibility. Two cameras sat in exactly that state.
|
|
12599
|
+
*
|
|
12600
|
+
* `preMs` is NOT a setting any more (D381). It is the retroactive KEEP bound —
|
|
12601
|
+
* which already-written segments survive a trigger — and its only correct
|
|
12602
|
+
* value was ever "at least as wide as the widest history the broker can hand
|
|
12603
|
+
* the writer". So it IS that ceiling, read from the one place that defines it:
|
|
12604
|
+
* a keep gate can no longer be configured narrower than the media it is
|
|
12605
|
+
* supposed to keep, which is the disagreement D191 was written against and
|
|
12606
|
+
* which a per-band number could only ever warn about.
|
|
12491
12607
|
*/
|
|
12492
12608
|
function resolveBandBufferMs(band) {
|
|
12493
12609
|
return {
|
|
12494
|
-
preMs:
|
|
12610
|
+
preMs: PREBUFFER_MAX_MS,
|
|
12495
12611
|
postMs: (band.postBufferSec ?? DEFAULT_EVENTS_BAND_BUFFER_SEC.postBufferSec) * 1e3
|
|
12496
12612
|
};
|
|
12497
12613
|
}
|
|
@@ -12520,6 +12636,9 @@ function triggerKey(source) {
|
|
|
12520
12636
|
case "signal": return "signal";
|
|
12521
12637
|
}
|
|
12522
12638
|
}
|
|
12639
|
+
/** The single source {@link DEVICE_DECISION_BAND} listens to, shared so the
|
|
12640
|
+
* band never allocates a new one per call. */
|
|
12641
|
+
var SIGNAL_SOURCES = Object.freeze([{ kind: "signal" }]);
|
|
12523
12642
|
/**
|
|
12524
12643
|
* LE sorgenti che questa banda ascolta — l'unico posto dove i `triggers` di
|
|
12525
12644
|
* una banda diventano `TriggerSource[]`.
|
|
@@ -12535,6 +12654,7 @@ function triggerKey(source) {
|
|
|
12535
12654
|
* denuncia invece di restare zitto.
|
|
12536
12655
|
*/
|
|
12537
12656
|
function bandTriggerSources(band) {
|
|
12657
|
+
if (band.deviceSignal === true) return SIGNAL_SOURCES;
|
|
12538
12658
|
const t = band.triggers;
|
|
12539
12659
|
if (t === void 0) return [];
|
|
12540
12660
|
const sources = [];
|
|
@@ -12548,7 +12668,6 @@ function bandTriggerSources(band) {
|
|
|
12548
12668
|
kind: "sensor",
|
|
12549
12669
|
sourceDeviceId
|
|
12550
12670
|
});
|
|
12551
|
-
if (t.deviceSignal === true) sources.push({ kind: "signal" });
|
|
12552
12671
|
return sources;
|
|
12553
12672
|
}
|
|
12554
12673
|
/**
|
|
@@ -12734,8 +12853,8 @@ function eventsBandDemanded(band, triggers, atMs) {
|
|
|
12734
12853
|
*/
|
|
12735
12854
|
function shouldRecordAt(config, triggers, at) {
|
|
12736
12855
|
if (!config.enabled) return false;
|
|
12737
|
-
const bands = config
|
|
12738
|
-
if (
|
|
12856
|
+
const bands = effectiveBands(config);
|
|
12857
|
+
if (bands.length === 0) return false;
|
|
12739
12858
|
const band = activeBandAt({ bands }, at);
|
|
12740
12859
|
if (!band) return false;
|
|
12741
12860
|
if (band.mode === "continuous") return true;
|
|
@@ -12990,8 +13109,8 @@ function candidateEdgesMs(bands, nowMs) {
|
|
|
12990
13109
|
* costs one wasted wake-up and never costs footage.
|
|
12991
13110
|
*/
|
|
12992
13111
|
function nextBandEdgeMs(config, nowMs) {
|
|
12993
|
-
const bands = config
|
|
12994
|
-
if (
|
|
13112
|
+
const bands = effectiveBands(config);
|
|
13113
|
+
if (bands.length === 0) return null;
|
|
12995
13114
|
const current = activeBandAt({ bands }, new Date(nowMs));
|
|
12996
13115
|
const candidates = [...new Set(candidateEdgesMs(bands, nowMs))].filter((ms) => ms > nowMs).toSorted((a, b) => a - b);
|
|
12997
13116
|
for (const ms of candidates) if (activeBandAt({ bands }, new Date(ms)) !== current) return ms;
|
|
@@ -13026,8 +13145,8 @@ function eventsWindowCloseMs(band, triggers, nowMs) {
|
|
|
13026
13145
|
function nextDecisionChangeAt(input) {
|
|
13027
13146
|
const { config, triggers, nowMs } = input;
|
|
13028
13147
|
if (!config.enabled) return null;
|
|
13029
|
-
const bands = config
|
|
13030
|
-
if (
|
|
13148
|
+
const bands = effectiveBands(config);
|
|
13149
|
+
if (bands.length === 0) return null;
|
|
13031
13150
|
const band = activeBandAt({ bands }, new Date(nowMs));
|
|
13032
13151
|
const edgeMs = nextBandEdgeMs(config, nowMs);
|
|
13033
13152
|
const closeMs = band === null ? null : eventsWindowCloseMs(band, triggers, nowMs);
|
|
@@ -14497,7 +14616,7 @@ var RecordingController = class {
|
|
|
14497
14616
|
const config = await this.deps.loadConfig(deviceId);
|
|
14498
14617
|
if (config.enabled !== true) return;
|
|
14499
14618
|
const nowMs = this.now();
|
|
14500
|
-
const band = activeBandAt({ bands: config
|
|
14619
|
+
const band = activeBandAt({ bands: effectiveBands(config) }, new Date(nowMs));
|
|
14501
14620
|
if (!band || band.mode !== "events") return;
|
|
14502
14621
|
const { postMs } = resolveBandBufferMs(band);
|
|
14503
14622
|
const sinceMs = nowMs - postMs - TRIGGER_HISTORY_MARGIN_MS;
|
|
@@ -14904,7 +15023,7 @@ var RecordingController = class {
|
|
|
14904
15023
|
if (this.stopped) return;
|
|
14905
15024
|
let band;
|
|
14906
15025
|
try {
|
|
14907
|
-
band = activeBandAt({ bands: (await this.deps.loadConfig(deviceId))
|
|
15026
|
+
band = activeBandAt({ bands: effectiveBands(await this.deps.loadConfig(deviceId)) }, new Date(atMs));
|
|
14908
15027
|
} catch (err) {
|
|
14909
15028
|
this.deps.logger.warn("recorder: audio trigger qualification skipped — config read failed", {
|
|
14910
15029
|
tags: { deviceId },
|
|
@@ -14929,7 +15048,7 @@ var RecordingController = class {
|
|
|
14929
15048
|
*/
|
|
14930
15049
|
async postBufferMsFor(deviceId, atMs) {
|
|
14931
15050
|
try {
|
|
14932
|
-
const band = activeBandAt({ bands: (await this.deps.loadConfig(deviceId))
|
|
15051
|
+
const band = activeBandAt({ bands: effectiveBands(await this.deps.loadConfig(deviceId)) }, new Date(atMs));
|
|
14933
15052
|
if (band) return resolveBandBufferMs(band).postMs;
|
|
14934
15053
|
} catch (err) {
|
|
14935
15054
|
this.deps.logger.warn("recorder: trigger history using the DEFAULT post-buffer — config read failed", {
|
|
@@ -15020,7 +15139,7 @@ var RecordingController = class {
|
|
|
15020
15139
|
reportUnrecordable(deviceId, config) {
|
|
15021
15140
|
if (!config.enabled) return;
|
|
15022
15141
|
if (this.unrecordableReported.has(deviceId)) return;
|
|
15023
|
-
const bands = config
|
|
15142
|
+
const bands = effectiveBands(config);
|
|
15024
15143
|
if (bands.length === 0) {
|
|
15025
15144
|
this.unrecordableReported.add(deviceId);
|
|
15026
15145
|
this.deps.logger.warn("recorder: device is enabled but has no recording bands — nothing will ever be recorded", { tags: { deviceId } });
|
|
@@ -15357,7 +15476,7 @@ var RecordingController = class {
|
|
|
15357
15476
|
*/
|
|
15358
15477
|
async handleFinalizedSegment(deviceId, profile, placement, attach, startMs, durMs, flatAbsPath) {
|
|
15359
15478
|
try {
|
|
15360
|
-
const band = activeBandAt({ bands: (await this.deps.loadConfig(deviceId))
|
|
15479
|
+
const band = activeBandAt({ bands: effectiveBands(await this.deps.loadConfig(deviceId)) }, new Date(startMs + durMs));
|
|
15361
15480
|
const triggers = this.triggersFor(deviceId);
|
|
15362
15481
|
if (band?.mode === "events" && !segmentRetainedForEvents(startMs, startMs + durMs, band, triggers, attach)) {
|
|
15363
15482
|
this.reportDiscardedSegment(deviceId, profile, attach, band, triggers, startMs, durMs);
|
|
@@ -17361,7 +17480,9 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
17361
17480
|
watchIntervalMs: this.config.watchIntervalMs
|
|
17362
17481
|
}
|
|
17363
17482
|
});
|
|
17364
|
-
|
|
17483
|
+
migrateLegacyDeviceSignalRows(this.configStore(), this.ctx.logger).catch((err) => {
|
|
17484
|
+
this.ctx.logger.warn("recorder: device-signal config migration failed", { meta: { error: errMsg(err) } });
|
|
17485
|
+
}).then(() => warnLostBands(this.configStore(), this.ctx.logger));
|
|
17365
17486
|
const providers = [
|
|
17366
17487
|
{
|
|
17367
17488
|
capability: recordingCapability,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_dist = require("./dist-
|
|
1
|
+
const require_dist = require("./dist-BxP0SvPz.js");
|
|
2
2
|
let node_child_process = require("node:child_process");
|
|
3
3
|
//#region ../../node_modules/mp4box/dist/mp4box.all.js
|
|
4
4
|
var require_mp4box_all = /* @__PURE__ */ require_dist.__commonJSMin(((exports) => {
|