@camstack/addon-pipeline 1.2.120 → 1.2.121
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/{addon-utils-MH-YN_oH.js → addon-utils-DpyYShEF.js} +1 -1
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +5 -5
- package/dist/detection-pipeline/index.mjs +3 -3
- package/dist/{dist-CmqBKO-v.mjs → dist-BJFxSye4.mjs} +11 -1
- package/dist/{dist-CvpyuIHg.js → dist-CDNpiyq6.js} +11 -1
- package/dist/{event-loop-stall-monitor-BOHxP7W6.js → event-loop-stall-monitor-BnqO_H2Z.js} +1 -1
- package/dist/{event-loop-stall-monitor-kh4gmJ-6.mjs → event-loop-stall-monitor-CcHh1ovi.mjs} +1 -1
- package/dist/{lazy-sharp-BzJLgOw9.js → lazy-sharp-B39XqJD6.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +4 -4
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-C2vefk7-.mjs → process-memory-BeCXh3iT.mjs} +1 -1
- package/dist/{process-memory-U6kkHBR7.js → process-memory-QlU7BsBQ.js} +1 -1
- package/dist/recorder/index.js +125 -27
- package/dist/recorder/index.mjs +124 -26
- package/dist/{segment-demux-js-xjK1LW_t.mjs → segment-demux-js-BuaascIe.mjs} +1 -1
- package/dist/{segment-demux-js-CTgNaQTI.js → segment-demux-js-DZ6bRNvA.js} +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/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BdP-MWLF.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DN_gNdAs.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-zKdlTmuo.mjs → hostInit-F493PnEJ.mjs} +1 -1
- package/dist/stream-broker/index.js +35 -5
- package/dist/stream-broker/index.mjs +35 -5
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DGYGCx9K.js → worker-protocol-BPx6jK4-.js} +1 -1
- package/dist/{worker-protocol-CElzXfEW.mjs → worker-protocol-Djzf2BTN.mjs} +1 -1
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@ 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_process_memory = require("../process-memory-
|
|
7
|
-
const require_addon_utils = require("../addon-utils-
|
|
5
|
+
const require_dist = require("../dist-CDNpiyq6.js");
|
|
6
|
+
const require_process_memory = require("../process-memory-QlU7BsBQ.js");
|
|
7
|
+
const require_addon_utils = require("../addon-utils-DpyYShEF.js");
|
|
8
8
|
let node_path = require("node:path");
|
|
9
9
|
node_path = require_dist.__toESM(node_path);
|
|
10
10
|
let node_fs = require("node:fs");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { A as audioAnalyzerCapability, Dt as hydrateSchema, J as mapAudioLabelToMacro, St as BaseAddon, _ as HF_BASE_URL, gt as errMsg, k as audioAnalysisCapability, lt as resolvePoolMemoryPolicy, n as AUDIO_BACKEND_CHOICES, s as DEFAULT_AUDIO_ANALYZER_CONFIG, x as PoolMemoryWatchdog } from "../dist-
|
|
3
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
2
|
+
import { A as audioAnalyzerCapability, Dt as hydrateSchema, J as mapAudioLabelToMacro, St as BaseAddon, _ as HF_BASE_URL, gt as errMsg, k as audioAnalysisCapability, lt as resolvePoolMemoryPolicy, n as AUDIO_BACKEND_CHOICES, s as DEFAULT_AUDIO_ANALYZER_CONFIG, x as PoolMemoryWatchdog } from "../dist-BJFxSye4.mjs";
|
|
3
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BeCXh3iT.mjs";
|
|
4
4
|
import { i as downloadFile } from "../addon-utils-CgBCF-mE.mjs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
import * as fs from "node:fs";
|
|
@@ -2,11 +2,11 @@ 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_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
7
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
8
|
-
const require_process_memory = require("../process-memory-
|
|
9
|
-
const require_addon_utils = require("../addon-utils-
|
|
5
|
+
const require_dist = require("../dist-CDNpiyq6.js");
|
|
6
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BnqO_H2Z.js");
|
|
7
|
+
const require_lazy_sharp = require("../lazy-sharp-B39XqJD6.js");
|
|
8
|
+
const require_process_memory = require("../process-memory-QlU7BsBQ.js");
|
|
9
|
+
const require_addon_utils = require("../addon-utils-DpyYShEF.js");
|
|
10
10
|
let sharp = require("sharp");
|
|
11
11
|
sharp = require_dist.__toESM(sharp);
|
|
12
12
|
let node_child_process = require("node:child_process");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { $ as pickClusterStepModels, D as YAMNET_TO_MACRO, Dt as hydrateSchema, Et as createEvent, Ft as sleep, G as evaluateZoneRules, Gt as string, I as defaultDeviceFor$1, Kt as union, Lt as array, Mt as parseJsonUnknown, St as BaseAddon, Ut as object, V as detectionPipelineCapability, W as enumerateInferenceDevices, Z as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, dt as runtimeDevices$1, et as pickClusterStepSettings, gt as errMsg, jt as nodePin, l as DEFAULT_CLUSTER_STEP_SETTINGS, lt as resolvePoolMemoryPolicy, mt as supportedRuntimes$1, p as DEVICE_BACKEND_TO_FORMAT, q as inferModelProvider, qt as EventCategory, rt as pipelineExecutorCapability, st as resolveClusterStepModelId, t as APPLE_SA_TO_MACRO, x as PoolMemoryWatchdog } from "../dist-
|
|
3
|
-
import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-
|
|
2
|
+
import { $ as pickClusterStepModels, D as YAMNET_TO_MACRO, Dt as hydrateSchema, Et as createEvent, Ft as sleep, G as evaluateZoneRules, Gt as string, I as defaultDeviceFor$1, Kt as union, Lt as array, Mt as parseJsonUnknown, St as BaseAddon, Ut as object, V as detectionPipelineCapability, W as enumerateInferenceDevices, Z as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, dt as runtimeDevices$1, et as pickClusterStepSettings, gt as errMsg, jt as nodePin, l as DEFAULT_CLUSTER_STEP_SETTINGS, lt as resolvePoolMemoryPolicy, mt as supportedRuntimes$1, p as DEVICE_BACKEND_TO_FORMAT, q as inferModelProvider, qt as EventCategory, rt as pipelineExecutorCapability, st as resolveClusterStepModelId, t as APPLE_SA_TO_MACRO, x as PoolMemoryWatchdog } from "../dist-BJFxSye4.mjs";
|
|
3
|
+
import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-CcHh1ovi.mjs";
|
|
4
4
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
5
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
5
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BeCXh3iT.mjs";
|
|
6
6
|
import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-CgBCF-mE.mjs";
|
|
7
7
|
import sharp from "sharp";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
@@ -5817,6 +5817,13 @@ var BaseAddon = class {
|
|
|
5817
5817
|
_readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
|
|
5818
5818
|
/** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
|
|
5819
5819
|
_registeredCapNames = [];
|
|
5820
|
+
/**
|
|
5821
|
+
* True only after `readAddonStore` actually answered. Constructor
|
|
5822
|
+
* defaults look like stored config when the store is down — a forked
|
|
5823
|
+
* addon that auto-starts from those defaults (cloudflare-tunnel quick
|
|
5824
|
+
* mode, 2026-08-25) is not "the operator chose this".
|
|
5825
|
+
*/
|
|
5826
|
+
settingsStoreReady = false;
|
|
5820
5827
|
/** Default config values. Provided via constructor. */
|
|
5821
5828
|
defaults;
|
|
5822
5829
|
constructor(defaults) {
|
|
@@ -6217,7 +6224,9 @@ var BaseAddon = class {
|
|
|
6217
6224
|
];
|
|
6218
6225
|
let lastErr;
|
|
6219
6226
|
for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
|
|
6220
|
-
|
|
6227
|
+
const stored = await settings.readAddonStore() ?? {};
|
|
6228
|
+
this.settingsStoreReady = true;
|
|
6229
|
+
return stored;
|
|
6221
6230
|
} catch (err) {
|
|
6222
6231
|
lastErr = err;
|
|
6223
6232
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6225,6 +6234,7 @@ var BaseAddon = class {
|
|
|
6225
6234
|
if (attempt === delaysMs.length) break;
|
|
6226
6235
|
await new Promise((r) => setTimeout(r, delaysMs[attempt]));
|
|
6227
6236
|
}
|
|
6237
|
+
this.settingsStoreReady = false;
|
|
6228
6238
|
this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
|
|
6229
6239
|
return {};
|
|
6230
6240
|
}
|
|
@@ -5840,6 +5840,13 @@ var BaseAddon = class {
|
|
|
5840
5840
|
_readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
|
|
5841
5841
|
/** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
|
|
5842
5842
|
_registeredCapNames = [];
|
|
5843
|
+
/**
|
|
5844
|
+
* True only after `readAddonStore` actually answered. Constructor
|
|
5845
|
+
* defaults look like stored config when the store is down — a forked
|
|
5846
|
+
* addon that auto-starts from those defaults (cloudflare-tunnel quick
|
|
5847
|
+
* mode, 2026-08-25) is not "the operator chose this".
|
|
5848
|
+
*/
|
|
5849
|
+
settingsStoreReady = false;
|
|
5843
5850
|
/** Default config values. Provided via constructor. */
|
|
5844
5851
|
defaults;
|
|
5845
5852
|
constructor(defaults) {
|
|
@@ -6240,7 +6247,9 @@ var BaseAddon = class {
|
|
|
6240
6247
|
];
|
|
6241
6248
|
let lastErr;
|
|
6242
6249
|
for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
|
|
6243
|
-
|
|
6250
|
+
const stored = await settings.readAddonStore() ?? {};
|
|
6251
|
+
this.settingsStoreReady = true;
|
|
6252
|
+
return stored;
|
|
6244
6253
|
} catch (err) {
|
|
6245
6254
|
lastErr = err;
|
|
6246
6255
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6248,6 +6257,7 @@ var BaseAddon = class {
|
|
|
6248
6257
|
if (attempt === delaysMs.length) break;
|
|
6249
6258
|
await new Promise((r) => setTimeout(r, delaysMs[attempt]));
|
|
6250
6259
|
}
|
|
6260
|
+
this.settingsStoreReady = false;
|
|
6251
6261
|
this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
|
|
6252
6262
|
return {};
|
|
6253
6263
|
}
|
package/dist/{event-loop-stall-monitor-kh4gmJ-6.mjs → event-loop-stall-monitor-CcHh1ovi.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { K as hfModelUrl, a as COCO_80_LABELS, j as audioKindId, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-
|
|
1
|
+
import { K as hfModelUrl, a as COCO_80_LABELS, j as audioKindId, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-BJFxSye4.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { PerformanceObserver } from "node:perf_hooks";
|
|
4
4
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
@@ -2,8 +2,8 @@ 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_lazy_sharp = require("../lazy-sharp-
|
|
5
|
+
const require_dist = require("../dist-CDNpiyq6.js");
|
|
6
|
+
const require_lazy_sharp = require("../lazy-sharp-B39XqJD6.js");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dt as hydrateSchema, G as evaluateZoneRules, St as BaseAddon, Tt as DeviceType, X as motionDetectionCapability } from "../dist-
|
|
1
|
+
import { Dt as hydrateSchema, G as evaluateZoneRules, St as BaseAddon, Tt as DeviceType, X as motionDetectionCapability } from "../dist-BJFxSye4.mjs";
|
|
2
2
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
@@ -2,11 +2,11 @@ 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_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
7
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
5
|
+
const require_dist = require("../dist-CDNpiyq6.js");
|
|
6
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BnqO_H2Z.js");
|
|
7
|
+
const require_worker_protocol = require("../worker-protocol-BPx6jK4-.js");
|
|
8
8
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
9
|
+
const require_lazy_sharp = require("../lazy-sharp-B39XqJD6.js");
|
|
10
10
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
11
11
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
12
12
|
let sharp = require("sharp");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Bt as lazy, Et as createEvent, F as customAction, Gt as string, Ht as number, It as _enum, L as defineCustomActions, Lt as array, Rt as boolean, St as BaseAddon, Ut as object, Vt as literal, gt as errMsg, it as pipelineRunnerCapability, jt as nodePin, nt as pickNativeLeaseOverride, p as DEVICE_BACKEND_TO_FORMAT, qt as EventCategory, tt as pickDetailCropConvention, u as DEFAULT_DETAIL_CROP_CONVENTION, z as deriveDetailCropRect } from "../dist-
|
|
2
|
-
import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-
|
|
3
|
-
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-
|
|
1
|
+
import { Bt as lazy, Et as createEvent, F as customAction, Gt as string, Ht as number, It as _enum, L as defineCustomActions, Lt as array, Rt as boolean, St as BaseAddon, Ut as object, Vt as literal, gt as errMsg, it as pipelineRunnerCapability, jt as nodePin, nt as pickNativeLeaseOverride, p as DEVICE_BACKEND_TO_FORMAT, qt as EventCategory, tt as pickDetailCropConvention, u as DEFAULT_DETAIL_CROP_CONVENTION, z as deriveDetailCropRect } from "../dist-BJFxSye4.mjs";
|
|
2
|
+
import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-CcHh1ovi.mjs";
|
|
3
|
+
import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-Djzf2BTN.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
6
6
|
import { n as withDetectionIntent } from "../restream-intent-B4BXZra7.mjs";
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const require_dist = require("../dist-
|
|
1
|
+
const require_dist = require("../dist-CDNpiyq6.js");
|
|
2
2
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
3
3
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
4
|
-
const require_addon_utils = require("../addon-utils-
|
|
4
|
+
const require_addon_utils = require("../addon-utils-DpyYShEF.js");
|
|
5
5
|
const require_retire_root_keys = require("../retire-root-keys-KE6D6Xh_.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
@@ -539,12 +539,38 @@ function segmentRelPath(deviceId, profile, startMs, durMs, bytes) {
|
|
|
539
539
|
return `${deviceId}/${profile}/${d.getUTCFullYear()}/${p2(d.getUTCMonth() + 1)}/${p2(d.getUTCDate())}/${p2(d.getUTCHours())}/${startMs}-${durMs}-${bytes}.m4s`;
|
|
540
540
|
}
|
|
541
541
|
var SEG_RE = /^(\d+)\/([^/]+)\/\d{4}\/\d{2}\/\d{2}\/\d{2}\/(\d+)-(\d+)-(\d+)\.m4s$/;
|
|
542
|
+
/**
|
|
543
|
+
* The profile names the recorder writes. Used ONLY to hand back a shared string
|
|
544
|
+
* instead of a fresh one — never to reject a name.
|
|
545
|
+
*
|
|
546
|
+
* `SEG_RE`'s second group is a slice of the path, so V8 materialises a new
|
|
547
|
+
* string for it on every parse. The whole fleet has three profile names and the
|
|
548
|
+
* index holds one row per segment, so that is one redundant string per segment
|
|
549
|
+
* held for the segment's whole life. Measured on the production shape (17
|
|
550
|
+
* cameras x 2 profiles x 12 days of 10 s segments = 3.53 M rows): 798 MB
|
|
551
|
+
* without this, 717 MB with it — 80.7 MB, 24 B/row, ~10% of the index.
|
|
552
|
+
*
|
|
553
|
+
* A name outside this set falls through to the parsed slice unchanged, so an
|
|
554
|
+
* unrecognised profile is never renamed, never dropped, and costs exactly what
|
|
555
|
+
* it costs today. That fall-through is the property the tests pin.
|
|
556
|
+
*/
|
|
557
|
+
var CANONICAL_PROFILES = [
|
|
558
|
+
"high",
|
|
559
|
+
"mid",
|
|
560
|
+
"low"
|
|
561
|
+
];
|
|
562
|
+
/** The shared instance of `profile` when it is one of {@link CANONICAL_PROFILES},
|
|
563
|
+
* otherwise `profile` itself. */
|
|
564
|
+
function canonicalProfile(profile) {
|
|
565
|
+
for (const known of CANONICAL_PROFILES) if (known === profile) return known;
|
|
566
|
+
return profile;
|
|
567
|
+
}
|
|
542
568
|
function parseSegmentPath(relPath) {
|
|
543
569
|
const m = SEG_RE.exec(relPath);
|
|
544
570
|
if (!m) return null;
|
|
545
571
|
return {
|
|
546
572
|
deviceId: Number(m[1]),
|
|
547
|
-
profile: m[2],
|
|
573
|
+
profile: canonicalProfile(m[2]),
|
|
548
574
|
startMs: Number(m[3]),
|
|
549
575
|
durMs: Number(m[4]),
|
|
550
576
|
bytes: Number(m[5])
|
|
@@ -590,6 +616,9 @@ var RecordingIndex = class {
|
|
|
590
616
|
sortedByDevice = /* @__PURE__ */ new Map();
|
|
591
617
|
fullRebuilds = 0;
|
|
592
618
|
incrementalInserts = 0;
|
|
619
|
+
/** See {@link HydrateReuseStats}. */
|
|
620
|
+
rowsReused = 0;
|
|
621
|
+
rowsAllocated = 0;
|
|
593
622
|
/** Hour buckets this device has been walked for; `ALL_HOURS` after a full
|
|
594
623
|
* hydrate. Empty = nothing has been looked at, which is NOT the same as
|
|
595
624
|
* "there is nothing" — see {@link hydrationOf}. */
|
|
@@ -628,6 +657,13 @@ var RecordingIndex = class {
|
|
|
628
657
|
incrementalInserts: this.incrementalInserts
|
|
629
658
|
};
|
|
630
659
|
}
|
|
660
|
+
/** See {@link HydrateReuseStats}. Cumulative over the process. */
|
|
661
|
+
hydrateReuseStats() {
|
|
662
|
+
return {
|
|
663
|
+
rowsReused: this.rowsReused,
|
|
664
|
+
rowsAllocated: this.rowsAllocated
|
|
665
|
+
};
|
|
666
|
+
}
|
|
631
667
|
/**
|
|
632
668
|
* Ordered insert of ONE NEW row into every live view it belongs to (the
|
|
633
669
|
* all-profiles view and its own profile's view). Views not yet materialised
|
|
@@ -645,25 +681,70 @@ var RecordingIndex = class {
|
|
|
645
681
|
}
|
|
646
682
|
}
|
|
647
683
|
/**
|
|
684
|
+
* The row to index for `path` at `locationId`, REUSING the held one when the
|
|
685
|
+
* index already has it.
|
|
686
|
+
*
|
|
687
|
+
* ## Why a re-walk must not re-allocate (the second copy of every path)
|
|
688
|
+
*
|
|
689
|
+
* Every field of a `SegmentRow` except `locationId` is derived from `path`,
|
|
690
|
+
* so a held row at the same location is not merely EQUAL to a rebuilt one —
|
|
691
|
+
* it is the same fact. Rebuilding it allocates a second byte-identical path
|
|
692
|
+
* string, and that second string does not stay a garbage-collectable
|
|
693
|
+
* duplicate: `SegmentHourLedger`'s mirror still holds the FIRST one, because
|
|
694
|
+
* `reconcileFromIndex` skips its `ledger.put` exactly when the contents
|
|
695
|
+
* match. The archive walk (`roots.ts`) composes a fresh `${deviceId}/${entry}`
|
|
696
|
+
* for every file it reads, so before this reuse existed the first walk after
|
|
697
|
+
* every boot doubled the retained path strings of the whole archive,
|
|
698
|
+
* permanently.
|
|
699
|
+
*
|
|
700
|
+
* Measured on the production shape (26 cameras x 2 profiles x 12 days of
|
|
701
|
+
* 10 s segments = 5.39 M rows): 1218 MB held before the walk, 1591 MB after
|
|
702
|
+
* — +373 MB, 69 B/row, one extra `SeqOneByteString` per segment.
|
|
703
|
+
*
|
|
704
|
+
* Interning the strings instead was measured and is WORSE (+597 MB): the
|
|
705
|
+
* intern table costs more than the duplicate it removes. Not allocating is
|
|
706
|
+
* the only cheap answer.
|
|
707
|
+
*
|
|
708
|
+
* `null` = not a segment path, or another device's.
|
|
709
|
+
*/
|
|
710
|
+
rowFor(held, path, deviceId, locationId) {
|
|
711
|
+
const prev = held.get(path);
|
|
712
|
+
if (prev !== void 0 && prev.locationId === locationId) {
|
|
713
|
+
this.rowsReused += 1;
|
|
714
|
+
return prev;
|
|
715
|
+
}
|
|
716
|
+
const p = parseSegmentPath(path);
|
|
717
|
+
if (!p || p.deviceId !== deviceId) return null;
|
|
718
|
+
this.rowsAllocated += 1;
|
|
719
|
+
return {
|
|
720
|
+
deviceId: p.deviceId,
|
|
721
|
+
profile: p.profile,
|
|
722
|
+
startMs: p.startMs,
|
|
723
|
+
durMs: p.durMs,
|
|
724
|
+
bytes: p.bytes,
|
|
725
|
+
path,
|
|
726
|
+
locationId
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
648
730
|
* Replace a device's segments FOR ONE LOCATION from a `storage.list` result (paths relative to that
|
|
649
731
|
* location root). Rows on other locations are preserved. Non-segment paths and other devices are ignored.
|
|
732
|
+
*
|
|
733
|
+
* Deliberately NOT delete-all-then-reinsert: the archive walk re-reports rows
|
|
734
|
+
* this index already holds, with freshly built path strings, and dropping the
|
|
735
|
+
* held rows first would make those fresh strings the permanent ones — a
|
|
736
|
+
* second copy of the whole archive's paths. See {@link retainedPathStrings}.
|
|
650
737
|
*/
|
|
651
738
|
hydrateDevice(deviceId, locationId, relPaths) {
|
|
652
739
|
const m = this.mapFor(deviceId);
|
|
653
|
-
|
|
740
|
+
const seen = /* @__PURE__ */ new Set();
|
|
654
741
|
for (const path of relPaths) {
|
|
655
|
-
const
|
|
656
|
-
if (
|
|
657
|
-
m.set(path,
|
|
658
|
-
|
|
659
|
-
profile: p.profile,
|
|
660
|
-
startMs: p.startMs,
|
|
661
|
-
durMs: p.durMs,
|
|
662
|
-
bytes: p.bytes,
|
|
663
|
-
path,
|
|
664
|
-
locationId
|
|
665
|
-
});
|
|
742
|
+
const row = this.rowFor(m, path, deviceId, locationId);
|
|
743
|
+
if (row === null) continue;
|
|
744
|
+
if (row.path !== path || m.get(path) !== row) m.set(path, row);
|
|
745
|
+
seen.add(path);
|
|
666
746
|
}
|
|
747
|
+
for (const [path, existing] of m) if (existing.locationId === locationId && !seen.has(path)) m.delete(path);
|
|
667
748
|
this.invalidateSorted(deviceId);
|
|
668
749
|
}
|
|
669
750
|
/**
|
|
@@ -708,20 +789,15 @@ var RecordingIndex = class {
|
|
|
708
789
|
*/
|
|
709
790
|
hydrateHour(deviceId, locationId, hourStartMs, relPaths) {
|
|
710
791
|
const m = this.mapFor(deviceId);
|
|
792
|
+
let changed = false;
|
|
711
793
|
for (const path of relPaths) {
|
|
712
|
-
const
|
|
713
|
-
if (
|
|
714
|
-
m.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
startMs: p.startMs,
|
|
718
|
-
durMs: p.durMs,
|
|
719
|
-
bytes: p.bytes,
|
|
720
|
-
path,
|
|
721
|
-
locationId
|
|
722
|
-
});
|
|
794
|
+
const row = this.rowFor(m, path, deviceId, locationId);
|
|
795
|
+
if (row === null) continue;
|
|
796
|
+
if (m.get(path) === row) continue;
|
|
797
|
+
m.set(path, row);
|
|
798
|
+
changed = true;
|
|
723
799
|
}
|
|
724
|
-
this.invalidateSorted(deviceId);
|
|
800
|
+
if (changed) this.invalidateSorted(deviceId);
|
|
725
801
|
this.markHydrated(deviceId, hourStartMs, hourStartMs + HOUR_MS$5);
|
|
726
802
|
}
|
|
727
803
|
/**
|
|
@@ -5847,16 +5923,22 @@ async function hydrateDeviceFromStorage(_api, index, deviceId, locations, logger
|
|
|
5847
5923
|
}
|
|
5848
5924
|
for (const location of aliases) if (!pathsByLocation.has(location.id)) pathsByLocation.set(location.id, []);
|
|
5849
5925
|
let indexed = 0;
|
|
5926
|
+
const reuseBefore = index.hydrateReuseStats();
|
|
5850
5927
|
for (const [locationId, relPaths] of pathsByLocation) {
|
|
5851
5928
|
index.hydrateDevice(deviceId, locationId, relPaths);
|
|
5852
5929
|
indexed += relPaths.length;
|
|
5853
5930
|
}
|
|
5931
|
+
const reuseAfter = index.hydrateReuseStats();
|
|
5932
|
+
const reused = reuseAfter.rowsReused - reuseBefore.rowsReused;
|
|
5933
|
+
const allocated = reuseAfter.rowsAllocated - reuseBefore.rowsAllocated;
|
|
5854
5934
|
logger.info("recorder: hydrated a device from one recordings root", {
|
|
5855
5935
|
tags: { deviceId },
|
|
5856
5936
|
meta: {
|
|
5857
5937
|
root,
|
|
5858
5938
|
entries: entries.length,
|
|
5859
5939
|
indexed,
|
|
5940
|
+
reused,
|
|
5941
|
+
allocated,
|
|
5860
5942
|
locationIds: [...pathsByLocation.keys()],
|
|
5861
5943
|
walkMs: walkedMs - startedMs,
|
|
5862
5944
|
ms: Date.now() - startedMs
|
|
@@ -5966,6 +6048,8 @@ async function hydrateWindowFromStorage(index, deviceId, fromMs, toMs, locations
|
|
|
5966
6048
|
continue;
|
|
5967
6049
|
}
|
|
5968
6050
|
const first = Math.floor(fromMs / HOUR_MS$2) * HOUR_MS$2;
|
|
6051
|
+
const reuseBefore = index.hydrateReuseStats();
|
|
6052
|
+
let hoursRead = 0;
|
|
5969
6053
|
for (let hour = first; hour < toMs; hour += HOUR_MS$2) for (const profile of profiles) {
|
|
5970
6054
|
const rel = hourDirRelPath(deviceId, profile, hour);
|
|
5971
6055
|
let files;
|
|
@@ -5974,9 +6058,23 @@ async function hydrateWindowFromStorage(index, deviceId, fromMs, toMs, locations
|
|
|
5974
6058
|
} catch {
|
|
5975
6059
|
continue;
|
|
5976
6060
|
}
|
|
6061
|
+
hoursRead += 1;
|
|
5977
6062
|
index.hydrateHour(deviceId, locationForHydratedProfile(aliases, profile).id, hour, files.filter((f) => f.endsWith(".m4s")).map((f) => `${rel}/${f}`));
|
|
5978
6063
|
}
|
|
5979
6064
|
index.markHydrated(deviceId, fromMs, toMs);
|
|
6065
|
+
if (hoursRead === 0) continue;
|
|
6066
|
+
const reuseAfter = index.hydrateReuseStats();
|
|
6067
|
+
const reused = reuseAfter.rowsReused - reuseBefore.rowsReused;
|
|
6068
|
+
const allocated = reuseAfter.rowsAllocated - reuseBefore.rowsAllocated;
|
|
6069
|
+
logger.info("recorder: hydrated a window from one recordings root", {
|
|
6070
|
+
tags: { deviceId },
|
|
6071
|
+
meta: {
|
|
6072
|
+
root,
|
|
6073
|
+
hoursRead,
|
|
6074
|
+
reused,
|
|
6075
|
+
allocated
|
|
6076
|
+
}
|
|
6077
|
+
});
|
|
5980
6078
|
}
|
|
5981
6079
|
}
|
|
5982
6080
|
//#endregion
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as deriveRecordingMode, Dt as hydrateSchema, Gt as string, Ht as number, M as batteryCapability, Pt as selectAssignedProfileSlots, R as deriveBatteryPresence, St as BaseAddon, T as RecordingConfigSchema, Tt as DeviceType, Ut as object, Wt as record, at as recordingCapability, b as OpsLogEntrySchema, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, ft as storageEvictableCapability, g as ExportRecordSchema, gt as errMsg, i as BatteryStatusSchema, jt as nodePin, m as EVENT_PAD_MS, ot as recordingExportCapability, qt as EventCategory, ut as resolveRecordingProfiles, w as RECORDING_EXPORT_MAX_READ_BYTES, wt as DeviceFeature } from "../dist-
|
|
1
|
+
import { B as deriveRecordingMode, Dt as hydrateSchema, Gt as string, Ht as number, M as batteryCapability, Pt as selectAssignedProfileSlots, R as deriveBatteryPresence, St as BaseAddon, T as RecordingConfigSchema, Tt as DeviceType, Ut as object, Wt as record, at as recordingCapability, b as OpsLogEntrySchema, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, ft as storageEvictableCapability, g as ExportRecordSchema, gt as errMsg, i as BatteryStatusSchema, jt as nodePin, m as EVENT_PAD_MS, ot as recordingExportCapability, qt as EventCategory, ut as resolveRecordingProfiles, w as RECORDING_EXPORT_MAX_READ_BYTES, wt as DeviceFeature } from "../dist-BJFxSye4.mjs";
|
|
2
2
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
3
3
|
import { r as withRecordingIntent } from "../restream-intent-B4BXZra7.mjs";
|
|
4
4
|
import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../addon-utils-CgBCF-mE.mjs";
|
|
@@ -538,12 +538,38 @@ function segmentRelPath(deviceId, profile, startMs, durMs, bytes) {
|
|
|
538
538
|
return `${deviceId}/${profile}/${d.getUTCFullYear()}/${p2(d.getUTCMonth() + 1)}/${p2(d.getUTCDate())}/${p2(d.getUTCHours())}/${startMs}-${durMs}-${bytes}.m4s`;
|
|
539
539
|
}
|
|
540
540
|
var SEG_RE = /^(\d+)\/([^/]+)\/\d{4}\/\d{2}\/\d{2}\/\d{2}\/(\d+)-(\d+)-(\d+)\.m4s$/;
|
|
541
|
+
/**
|
|
542
|
+
* The profile names the recorder writes. Used ONLY to hand back a shared string
|
|
543
|
+
* instead of a fresh one — never to reject a name.
|
|
544
|
+
*
|
|
545
|
+
* `SEG_RE`'s second group is a slice of the path, so V8 materialises a new
|
|
546
|
+
* string for it on every parse. The whole fleet has three profile names and the
|
|
547
|
+
* index holds one row per segment, so that is one redundant string per segment
|
|
548
|
+
* held for the segment's whole life. Measured on the production shape (17
|
|
549
|
+
* cameras x 2 profiles x 12 days of 10 s segments = 3.53 M rows): 798 MB
|
|
550
|
+
* without this, 717 MB with it — 80.7 MB, 24 B/row, ~10% of the index.
|
|
551
|
+
*
|
|
552
|
+
* A name outside this set falls through to the parsed slice unchanged, so an
|
|
553
|
+
* unrecognised profile is never renamed, never dropped, and costs exactly what
|
|
554
|
+
* it costs today. That fall-through is the property the tests pin.
|
|
555
|
+
*/
|
|
556
|
+
var CANONICAL_PROFILES = [
|
|
557
|
+
"high",
|
|
558
|
+
"mid",
|
|
559
|
+
"low"
|
|
560
|
+
];
|
|
561
|
+
/** The shared instance of `profile` when it is one of {@link CANONICAL_PROFILES},
|
|
562
|
+
* otherwise `profile` itself. */
|
|
563
|
+
function canonicalProfile(profile) {
|
|
564
|
+
for (const known of CANONICAL_PROFILES) if (known === profile) return known;
|
|
565
|
+
return profile;
|
|
566
|
+
}
|
|
541
567
|
function parseSegmentPath(relPath) {
|
|
542
568
|
const m = SEG_RE.exec(relPath);
|
|
543
569
|
if (!m) return null;
|
|
544
570
|
return {
|
|
545
571
|
deviceId: Number(m[1]),
|
|
546
|
-
profile: m[2],
|
|
572
|
+
profile: canonicalProfile(m[2]),
|
|
547
573
|
startMs: Number(m[3]),
|
|
548
574
|
durMs: Number(m[4]),
|
|
549
575
|
bytes: Number(m[5])
|
|
@@ -589,6 +615,9 @@ var RecordingIndex = class {
|
|
|
589
615
|
sortedByDevice = /* @__PURE__ */ new Map();
|
|
590
616
|
fullRebuilds = 0;
|
|
591
617
|
incrementalInserts = 0;
|
|
618
|
+
/** See {@link HydrateReuseStats}. */
|
|
619
|
+
rowsReused = 0;
|
|
620
|
+
rowsAllocated = 0;
|
|
592
621
|
/** Hour buckets this device has been walked for; `ALL_HOURS` after a full
|
|
593
622
|
* hydrate. Empty = nothing has been looked at, which is NOT the same as
|
|
594
623
|
* "there is nothing" — see {@link hydrationOf}. */
|
|
@@ -627,6 +656,13 @@ var RecordingIndex = class {
|
|
|
627
656
|
incrementalInserts: this.incrementalInserts
|
|
628
657
|
};
|
|
629
658
|
}
|
|
659
|
+
/** See {@link HydrateReuseStats}. Cumulative over the process. */
|
|
660
|
+
hydrateReuseStats() {
|
|
661
|
+
return {
|
|
662
|
+
rowsReused: this.rowsReused,
|
|
663
|
+
rowsAllocated: this.rowsAllocated
|
|
664
|
+
};
|
|
665
|
+
}
|
|
630
666
|
/**
|
|
631
667
|
* Ordered insert of ONE NEW row into every live view it belongs to (the
|
|
632
668
|
* all-profiles view and its own profile's view). Views not yet materialised
|
|
@@ -644,25 +680,70 @@ var RecordingIndex = class {
|
|
|
644
680
|
}
|
|
645
681
|
}
|
|
646
682
|
/**
|
|
683
|
+
* The row to index for `path` at `locationId`, REUSING the held one when the
|
|
684
|
+
* index already has it.
|
|
685
|
+
*
|
|
686
|
+
* ## Why a re-walk must not re-allocate (the second copy of every path)
|
|
687
|
+
*
|
|
688
|
+
* Every field of a `SegmentRow` except `locationId` is derived from `path`,
|
|
689
|
+
* so a held row at the same location is not merely EQUAL to a rebuilt one —
|
|
690
|
+
* it is the same fact. Rebuilding it allocates a second byte-identical path
|
|
691
|
+
* string, and that second string does not stay a garbage-collectable
|
|
692
|
+
* duplicate: `SegmentHourLedger`'s mirror still holds the FIRST one, because
|
|
693
|
+
* `reconcileFromIndex` skips its `ledger.put` exactly when the contents
|
|
694
|
+
* match. The archive walk (`roots.ts`) composes a fresh `${deviceId}/${entry}`
|
|
695
|
+
* for every file it reads, so before this reuse existed the first walk after
|
|
696
|
+
* every boot doubled the retained path strings of the whole archive,
|
|
697
|
+
* permanently.
|
|
698
|
+
*
|
|
699
|
+
* Measured on the production shape (26 cameras x 2 profiles x 12 days of
|
|
700
|
+
* 10 s segments = 5.39 M rows): 1218 MB held before the walk, 1591 MB after
|
|
701
|
+
* — +373 MB, 69 B/row, one extra `SeqOneByteString` per segment.
|
|
702
|
+
*
|
|
703
|
+
* Interning the strings instead was measured and is WORSE (+597 MB): the
|
|
704
|
+
* intern table costs more than the duplicate it removes. Not allocating is
|
|
705
|
+
* the only cheap answer.
|
|
706
|
+
*
|
|
707
|
+
* `null` = not a segment path, or another device's.
|
|
708
|
+
*/
|
|
709
|
+
rowFor(held, path, deviceId, locationId) {
|
|
710
|
+
const prev = held.get(path);
|
|
711
|
+
if (prev !== void 0 && prev.locationId === locationId) {
|
|
712
|
+
this.rowsReused += 1;
|
|
713
|
+
return prev;
|
|
714
|
+
}
|
|
715
|
+
const p = parseSegmentPath(path);
|
|
716
|
+
if (!p || p.deviceId !== deviceId) return null;
|
|
717
|
+
this.rowsAllocated += 1;
|
|
718
|
+
return {
|
|
719
|
+
deviceId: p.deviceId,
|
|
720
|
+
profile: p.profile,
|
|
721
|
+
startMs: p.startMs,
|
|
722
|
+
durMs: p.durMs,
|
|
723
|
+
bytes: p.bytes,
|
|
724
|
+
path,
|
|
725
|
+
locationId
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
647
729
|
* Replace a device's segments FOR ONE LOCATION from a `storage.list` result (paths relative to that
|
|
648
730
|
* location root). Rows on other locations are preserved. Non-segment paths and other devices are ignored.
|
|
731
|
+
*
|
|
732
|
+
* Deliberately NOT delete-all-then-reinsert: the archive walk re-reports rows
|
|
733
|
+
* this index already holds, with freshly built path strings, and dropping the
|
|
734
|
+
* held rows first would make those fresh strings the permanent ones — a
|
|
735
|
+
* second copy of the whole archive's paths. See {@link retainedPathStrings}.
|
|
649
736
|
*/
|
|
650
737
|
hydrateDevice(deviceId, locationId, relPaths) {
|
|
651
738
|
const m = this.mapFor(deviceId);
|
|
652
|
-
|
|
739
|
+
const seen = /* @__PURE__ */ new Set();
|
|
653
740
|
for (const path of relPaths) {
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
m.set(path,
|
|
657
|
-
|
|
658
|
-
profile: p.profile,
|
|
659
|
-
startMs: p.startMs,
|
|
660
|
-
durMs: p.durMs,
|
|
661
|
-
bytes: p.bytes,
|
|
662
|
-
path,
|
|
663
|
-
locationId
|
|
664
|
-
});
|
|
741
|
+
const row = this.rowFor(m, path, deviceId, locationId);
|
|
742
|
+
if (row === null) continue;
|
|
743
|
+
if (row.path !== path || m.get(path) !== row) m.set(path, row);
|
|
744
|
+
seen.add(path);
|
|
665
745
|
}
|
|
746
|
+
for (const [path, existing] of m) if (existing.locationId === locationId && !seen.has(path)) m.delete(path);
|
|
666
747
|
this.invalidateSorted(deviceId);
|
|
667
748
|
}
|
|
668
749
|
/**
|
|
@@ -707,20 +788,15 @@ var RecordingIndex = class {
|
|
|
707
788
|
*/
|
|
708
789
|
hydrateHour(deviceId, locationId, hourStartMs, relPaths) {
|
|
709
790
|
const m = this.mapFor(deviceId);
|
|
791
|
+
let changed = false;
|
|
710
792
|
for (const path of relPaths) {
|
|
711
|
-
const
|
|
712
|
-
if (
|
|
713
|
-
m.
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
startMs: p.startMs,
|
|
717
|
-
durMs: p.durMs,
|
|
718
|
-
bytes: p.bytes,
|
|
719
|
-
path,
|
|
720
|
-
locationId
|
|
721
|
-
});
|
|
793
|
+
const row = this.rowFor(m, path, deviceId, locationId);
|
|
794
|
+
if (row === null) continue;
|
|
795
|
+
if (m.get(path) === row) continue;
|
|
796
|
+
m.set(path, row);
|
|
797
|
+
changed = true;
|
|
722
798
|
}
|
|
723
|
-
this.invalidateSorted(deviceId);
|
|
799
|
+
if (changed) this.invalidateSorted(deviceId);
|
|
724
800
|
this.markHydrated(deviceId, hourStartMs, hourStartMs + HOUR_MS$5);
|
|
725
801
|
}
|
|
726
802
|
/**
|
|
@@ -5846,16 +5922,22 @@ async function hydrateDeviceFromStorage(_api, index, deviceId, locations, logger
|
|
|
5846
5922
|
}
|
|
5847
5923
|
for (const location of aliases) if (!pathsByLocation.has(location.id)) pathsByLocation.set(location.id, []);
|
|
5848
5924
|
let indexed = 0;
|
|
5925
|
+
const reuseBefore = index.hydrateReuseStats();
|
|
5849
5926
|
for (const [locationId, relPaths] of pathsByLocation) {
|
|
5850
5927
|
index.hydrateDevice(deviceId, locationId, relPaths);
|
|
5851
5928
|
indexed += relPaths.length;
|
|
5852
5929
|
}
|
|
5930
|
+
const reuseAfter = index.hydrateReuseStats();
|
|
5931
|
+
const reused = reuseAfter.rowsReused - reuseBefore.rowsReused;
|
|
5932
|
+
const allocated = reuseAfter.rowsAllocated - reuseBefore.rowsAllocated;
|
|
5853
5933
|
logger.info("recorder: hydrated a device from one recordings root", {
|
|
5854
5934
|
tags: { deviceId },
|
|
5855
5935
|
meta: {
|
|
5856
5936
|
root,
|
|
5857
5937
|
entries: entries.length,
|
|
5858
5938
|
indexed,
|
|
5939
|
+
reused,
|
|
5940
|
+
allocated,
|
|
5859
5941
|
locationIds: [...pathsByLocation.keys()],
|
|
5860
5942
|
walkMs: walkedMs - startedMs,
|
|
5861
5943
|
ms: Date.now() - startedMs
|
|
@@ -5965,6 +6047,8 @@ async function hydrateWindowFromStorage(index, deviceId, fromMs, toMs, locations
|
|
|
5965
6047
|
continue;
|
|
5966
6048
|
}
|
|
5967
6049
|
const first = Math.floor(fromMs / HOUR_MS$2) * HOUR_MS$2;
|
|
6050
|
+
const reuseBefore = index.hydrateReuseStats();
|
|
6051
|
+
let hoursRead = 0;
|
|
5968
6052
|
for (let hour = first; hour < toMs; hour += HOUR_MS$2) for (const profile of profiles) {
|
|
5969
6053
|
const rel = hourDirRelPath(deviceId, profile, hour);
|
|
5970
6054
|
let files;
|
|
@@ -5973,9 +6057,23 @@ async function hydrateWindowFromStorage(index, deviceId, fromMs, toMs, locations
|
|
|
5973
6057
|
} catch {
|
|
5974
6058
|
continue;
|
|
5975
6059
|
}
|
|
6060
|
+
hoursRead += 1;
|
|
5976
6061
|
index.hydrateHour(deviceId, locationForHydratedProfile(aliases, profile).id, hour, files.filter((f) => f.endsWith(".m4s")).map((f) => `${rel}/${f}`));
|
|
5977
6062
|
}
|
|
5978
6063
|
index.markHydrated(deviceId, fromMs, toMs);
|
|
6064
|
+
if (hoursRead === 0) continue;
|
|
6065
|
+
const reuseAfter = index.hydrateReuseStats();
|
|
6066
|
+
const reused = reuseAfter.rowsReused - reuseBefore.rowsReused;
|
|
6067
|
+
const allocated = reuseAfter.rowsAllocated - reuseBefore.rowsAllocated;
|
|
6068
|
+
logger.info("recorder: hydrated a window from one recordings root", {
|
|
6069
|
+
tags: { deviceId },
|
|
6070
|
+
meta: {
|
|
6071
|
+
root,
|
|
6072
|
+
hoursRead,
|
|
6073
|
+
reused,
|
|
6074
|
+
allocated
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
5979
6077
|
}
|
|
5980
6078
|
}
|
|
5981
6079
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { gt as errMsg } from "./dist-
|
|
2
|
+
import { gt as errMsg } from "./dist-BJFxSye4.mjs";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
//#region ../../node_modules/mp4box/dist/mp4box.all.js
|
|
5
5
|
var require_mp4box_all = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_dist = require("./dist-
|
|
1
|
+
const require_dist = require("./dist-CDNpiyq6.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) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
3
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
2
|
+
const require_worker_protocol = require("../worker-protocol-BPx6jK4-.js");
|
|
3
|
+
const require_lazy_sharp = require("../lazy-sharp-B39XqJD6.js");
|
|
4
4
|
//#region src/session-decode/color-conversion.ts
|
|
5
5
|
/**
|
|
6
6
|
* Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-
|
|
1
|
+
import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-Djzf2BTN.mjs";
|
|
2
2
|
import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
3
3
|
//#region src/session-decode/color-conversion.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_segment_demux_js = require("../segment-demux-js-
|
|
1
|
+
const require_segment_demux_js = require("../segment-demux-js-DZ6bRNvA.js");
|
|
2
2
|
let node_worker_threads = require("node:worker_threads");
|
|
3
3
|
//#region src/stream-broker/recorded/demux-worker-child.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as demuxSegmentFullJs } from "../segment-demux-js-
|
|
1
|
+
import { t as demuxSegmentFullJs } from "../segment-demux-js-BuaascIe.mjs";
|
|
2
2
|
import { parentPort } from "node:worker_threads";
|
|
3
3
|
//#region src/stream-broker/recorded/demux-worker-child.ts
|
|
4
4
|
/**
|
|
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-CDNpiyq6.js");
|
|
6
6
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_retire_root_keys = require("../retire-root-keys-KE6D6Xh_.js");
|
|
9
|
-
const require_segment_demux_js = require("../segment-demux-js-
|
|
9
|
+
const require_segment_demux_js = require("../segment-demux-js-DZ6bRNvA.js");
|
|
10
10
|
let node_crypto = require("node:crypto");
|
|
11
11
|
node_crypto = require_dist.__toESM(node_crypto, 1);
|
|
12
12
|
let node_child_process = require("node:child_process");
|
|
@@ -23928,6 +23928,23 @@ var ScrubDragStats = class {
|
|
|
23928
23928
|
//#endregion
|
|
23929
23929
|
//#region src/stream-broker/recorded/recorded-feeder.ts
|
|
23930
23930
|
var SEGMENT_TIMEOUT_MS = 8e3;
|
|
23931
|
+
/**
|
|
23932
|
+
* Deadline for the recorded-AUDIO demux on the `reusedWhole` seek path, and
|
|
23933
|
+
* the reason it is not `SEGMENT_TIMEOUT_MS`.
|
|
23934
|
+
*
|
|
23935
|
+
* That await sits BETWEEN the painted video prefix and `startAdvance`, so
|
|
23936
|
+
* every millisecond of it is a frozen picture the operator is looking at.
|
|
23937
|
+
* On the live hub (8/24–8/25) 4 of 9 `recorded:seek-ready` lines landed at
|
|
23938
|
+
* `loadMs - firstFrameMs = 8000 ± 1 ms` — the timeout, not work: first frame
|
|
23939
|
+
* at 2.3 s, then eight seconds of still image before playback advanced.
|
|
23940
|
+
*
|
|
23941
|
+
* The wait bought NOTHING in all four: the race rejects, `.catch` yields `[]`,
|
|
23942
|
+
* and the segment plays without audio anyway. So this is not audio traded for
|
|
23943
|
+
* latency — it is a wait whose only outcome is the outcome it already had.
|
|
23944
|
+
* 1500 ms is ~5x the worst demux observed in the same window (`demuxMs` p50
|
|
23945
|
+
* 2 ms, p95 202 ms, max 285 ms), so a segment that CAN yield audio still does.
|
|
23946
|
+
*/
|
|
23947
|
+
var AUDIO_DEMUX_TIMEOUT_MS = 1500;
|
|
23931
23948
|
var POSITION_INTERVAL_MS = 1e3;
|
|
23932
23949
|
/** How often `recorded:play-census` fires while the advance loop is alive. */
|
|
23933
23950
|
var PLAY_CENSUS_INTERVAL_TICKS = 5;
|
|
@@ -24515,7 +24532,20 @@ var RecordedFeeder = class {
|
|
|
24515
24532
|
let loaded;
|
|
24516
24533
|
let loadAusMs = 0;
|
|
24517
24534
|
if (reusedWhole) {
|
|
24518
|
-
|
|
24535
|
+
let audio = [];
|
|
24536
|
+
if (this.rate !== 0 && this.deps.demuxAudio && this.resolveAudioPush() !== void 0) {
|
|
24537
|
+
const audioStartedAt = Date.now();
|
|
24538
|
+
try {
|
|
24539
|
+
audio = [...await this.withTimeout(this.deps.demuxAudio(reusedWhole.bytes), AUDIO_DEMUX_TIMEOUT_MS)];
|
|
24540
|
+
} catch (err) {
|
|
24541
|
+
this.deps.onLog?.("recorded:seek-audio-dropped", {
|
|
24542
|
+
waitedMs: Date.now() - audioStartedAt,
|
|
24543
|
+
budgetMs: AUDIO_DEMUX_TIMEOUT_MS,
|
|
24544
|
+
bytes: reusedWhole.bytes.byteLength,
|
|
24545
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
24546
|
+
});
|
|
24547
|
+
}
|
|
24548
|
+
}
|
|
24519
24549
|
loaded = {
|
|
24520
24550
|
video: reusedWhole.video,
|
|
24521
24551
|
audio,
|
|
@@ -26023,10 +26053,10 @@ var RecordedFeeder = class {
|
|
|
26023
26053
|
* Race a promise against a timeout, clearing the timer the moment the inner
|
|
26024
26054
|
* promise settles so no open handle leaks or keeps the process alive.
|
|
26025
26055
|
*/
|
|
26026
|
-
async withTimeout(p) {
|
|
26056
|
+
async withTimeout(p, timeoutMs = SEGMENT_TIMEOUT_MS) {
|
|
26027
26057
|
let timer = null;
|
|
26028
26058
|
const timeout = new Promise((_resolve, reject) => {
|
|
26029
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("segment read timeout")),
|
|
26059
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("segment read timeout")), timeoutMs);
|
|
26030
26060
|
});
|
|
26031
26061
|
try {
|
|
26032
26062
|
return await Promise.race([p, timeout]);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as __toESM } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { At as makeSourceBrokerId, C as RATE_CONTROL_TIGHT, Ct as CAM_PROFILE_ORDER, E as RingBuffer, Et as createEvent, Gt as string, H as egressTranscodeSharingKey, Ht as number, It as _enum, Kt as union, Lt as array, M as batteryCapability, N as cameraStreamsCapability, Nt as parseProfileBrokerId, O as addonWidgetsSourceCapability, Ot as isEvent, P as createHwAccelCache, R as deriveBatteryPresence, Rt as boolean, S as RATE_CONTROL_RELAXED, St as BaseAddon, Tt as DeviceType, U as egressTransportFromRequest, Ut as object, Vt as literal, Wt as record, Y as maskUrlCredentials, _t as AUDIO_PRESETS, bt as invocationFromEncodeProfile, ct as resolveEgressDecodeHwAccel, gt as errMsg, h as EncodeProfileSchema, ht as webrtcSessionCapability, i as BatteryStatusSchema, jt as nodePin, kt as makeProfileBrokerId, pt as streamBrokerCapability, qt as EventCategory, vt as Fmp4BoxSplitter, wt as DeviceFeature, xt as isSoftwareDecode, yt as buildFfmpegArgs$1, zt as discriminatedUnion } from "../dist-
|
|
2
|
+
import { At as makeSourceBrokerId, C as RATE_CONTROL_TIGHT, Ct as CAM_PROFILE_ORDER, E as RingBuffer, Et as createEvent, Gt as string, H as egressTranscodeSharingKey, Ht as number, It as _enum, Kt as union, Lt as array, M as batteryCapability, N as cameraStreamsCapability, Nt as parseProfileBrokerId, O as addonWidgetsSourceCapability, Ot as isEvent, P as createHwAccelCache, R as deriveBatteryPresence, Rt as boolean, S as RATE_CONTROL_RELAXED, St as BaseAddon, Tt as DeviceType, U as egressTransportFromRequest, Ut as object, Vt as literal, Wt as record, Y as maskUrlCredentials, _t as AUDIO_PRESETS, bt as invocationFromEncodeProfile, ct as resolveEgressDecodeHwAccel, gt as errMsg, h as EncodeProfileSchema, ht as webrtcSessionCapability, i as BatteryStatusSchema, jt as nodePin, kt as makeProfileBrokerId, pt as streamBrokerCapability, qt as EventCategory, vt as Fmp4BoxSplitter, wt as DeviceFeature, xt as isSoftwareDecode, yt as buildFfmpegArgs$1, zt as discriminatedUnion } from "../dist-BJFxSye4.mjs";
|
|
3
3
|
import { t as parseRestreamPath } from "../restream-intent-B4BXZra7.mjs";
|
|
4
4
|
import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
|
|
5
5
|
import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DMolfhsP.mjs";
|
|
6
|
-
import { i as require_mp4box_all, n as demuxSegmentPreferJs, r as runFfmpegOnce, t as demuxSegmentFullJs } from "../segment-demux-js-
|
|
6
|
+
import { i as require_mp4box_all, n as demuxSegmentPreferJs, r as runFfmpegOnce, t as demuxSegmentFullJs } from "../segment-demux-js-BuaascIe.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import * as crypto$1 from "node:crypto";
|
|
9
9
|
import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -23923,6 +23923,23 @@ var ScrubDragStats = class {
|
|
|
23923
23923
|
//#endregion
|
|
23924
23924
|
//#region src/stream-broker/recorded/recorded-feeder.ts
|
|
23925
23925
|
var SEGMENT_TIMEOUT_MS = 8e3;
|
|
23926
|
+
/**
|
|
23927
|
+
* Deadline for the recorded-AUDIO demux on the `reusedWhole` seek path, and
|
|
23928
|
+
* the reason it is not `SEGMENT_TIMEOUT_MS`.
|
|
23929
|
+
*
|
|
23930
|
+
* That await sits BETWEEN the painted video prefix and `startAdvance`, so
|
|
23931
|
+
* every millisecond of it is a frozen picture the operator is looking at.
|
|
23932
|
+
* On the live hub (8/24–8/25) 4 of 9 `recorded:seek-ready` lines landed at
|
|
23933
|
+
* `loadMs - firstFrameMs = 8000 ± 1 ms` — the timeout, not work: first frame
|
|
23934
|
+
* at 2.3 s, then eight seconds of still image before playback advanced.
|
|
23935
|
+
*
|
|
23936
|
+
* The wait bought NOTHING in all four: the race rejects, `.catch` yields `[]`,
|
|
23937
|
+
* and the segment plays without audio anyway. So this is not audio traded for
|
|
23938
|
+
* latency — it is a wait whose only outcome is the outcome it already had.
|
|
23939
|
+
* 1500 ms is ~5x the worst demux observed in the same window (`demuxMs` p50
|
|
23940
|
+
* 2 ms, p95 202 ms, max 285 ms), so a segment that CAN yield audio still does.
|
|
23941
|
+
*/
|
|
23942
|
+
var AUDIO_DEMUX_TIMEOUT_MS = 1500;
|
|
23926
23943
|
var POSITION_INTERVAL_MS = 1e3;
|
|
23927
23944
|
/** How often `recorded:play-census` fires while the advance loop is alive. */
|
|
23928
23945
|
var PLAY_CENSUS_INTERVAL_TICKS = 5;
|
|
@@ -24510,7 +24527,20 @@ var RecordedFeeder = class {
|
|
|
24510
24527
|
let loaded;
|
|
24511
24528
|
let loadAusMs = 0;
|
|
24512
24529
|
if (reusedWhole) {
|
|
24513
|
-
|
|
24530
|
+
let audio = [];
|
|
24531
|
+
if (this.rate !== 0 && this.deps.demuxAudio && this.resolveAudioPush() !== void 0) {
|
|
24532
|
+
const audioStartedAt = Date.now();
|
|
24533
|
+
try {
|
|
24534
|
+
audio = [...await this.withTimeout(this.deps.demuxAudio(reusedWhole.bytes), AUDIO_DEMUX_TIMEOUT_MS)];
|
|
24535
|
+
} catch (err) {
|
|
24536
|
+
this.deps.onLog?.("recorded:seek-audio-dropped", {
|
|
24537
|
+
waitedMs: Date.now() - audioStartedAt,
|
|
24538
|
+
budgetMs: AUDIO_DEMUX_TIMEOUT_MS,
|
|
24539
|
+
bytes: reusedWhole.bytes.byteLength,
|
|
24540
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
24541
|
+
});
|
|
24542
|
+
}
|
|
24543
|
+
}
|
|
24514
24544
|
loaded = {
|
|
24515
24545
|
video: reusedWhole.video,
|
|
24516
24546
|
audio,
|
|
@@ -26018,10 +26048,10 @@ var RecordedFeeder = class {
|
|
|
26018
26048
|
* Race a promise against a timeout, clearing the timer the moment the inner
|
|
26019
26049
|
* promise settles so no open handle leaks or keeps the process alive.
|
|
26020
26050
|
*/
|
|
26021
|
-
async withTimeout(p) {
|
|
26051
|
+
async withTimeout(p, timeoutMs = SEGMENT_TIMEOUT_MS) {
|
|
26022
26052
|
let timer = null;
|
|
26023
26053
|
const timeout = new Promise((_resolve, reject) => {
|
|
26024
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("segment read timeout")),
|
|
26054
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("segment read timeout")), timeoutMs);
|
|
26025
26055
|
});
|
|
26026
26056
|
try {
|
|
26027
26057
|
return await Promise.race([p, timeout]);
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DN_gNdAs.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as DEFAULT_NATIVE_LEASE_SETTINGS, v as NativeLeaseAdmissionSchema, y as NativeLeaseSettingsSchema } from "./dist-
|
|
1
|
+
import { f as DEFAULT_NATIVE_LEASE_SETTINGS, v as NativeLeaseAdmissionSchema, y as NativeLeaseSettingsSchema } from "./dist-BJFxSye4.mjs";
|
|
2
2
|
//#region src/session-decode/native-lease-config.ts
|
|
3
3
|
/**
|
|
4
4
|
* Resolution of the native-frame hold / tile knobs for one decode worker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.121",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|