@camstack/addon-pipeline 1.1.42 → 1.1.44
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 +5 -4
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/chunk-D6vf50IK.js +28 -0
- package/dist/detection-pipeline/index.js +535 -115
- package/dist/detection-pipeline/index.mjs +529 -110
- package/dist/{dist-GFd8M6KO.mjs → dist-CgBGV988.mjs} +214 -186
- package/dist/{dist-NvwN60Fq.js → dist-CySHUkXY.js} +219 -185
- package/dist/{model-download-service-C-IHWnXx-DxM2DSns.js → model-download-service-C-IHWnXx-BLXzxpRB.js} +3 -29
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +122 -287
- package/dist/pipeline-runner/index.mjs +118 -285
- package/dist/recorder/index.js +78 -31
- package/dist/recorder/index.mjs +75 -29
- package/dist/{frame-handle-plane-E_AsR77T.mjs → remote-source-plane-B4hRwchu.mjs} +219 -2
- package/dist/{frame-handle-plane-B3Fxww8H.js → remote-source-plane-D9m0z1N9.js} +236 -1
- package/dist/session-decode/decode-worker-child.js +118 -12
- package/dist/session-decode/decode-worker-child.mjs +115 -12
- package/dist/stream-broker/_stub.js +550 -523
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CeielP5P.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-G6wtUb3G.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dlagq9X_.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BUpF966l.mjs +26 -0
- package/dist/stream-broker/{hostInit-DmjqskOR.mjs → hostInit-BSzP3BIj.mjs} +2 -2
- package/dist/stream-broker/index.js +840 -196
- package/dist/stream-broker/index.mjs +831 -188
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CEsPjwzT.js → MaskShapeCanvas-DI4BY7W2-D2ML5TPi.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-D43fIRgB.js → MotionZonesSettings-NcxxQN8r-TTWGGaqv.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-C3wJxG8V.js → PrivacyMaskSettings-APgPLF7p-CJqHU2nU.js} +1 -1
- package/embed-dist/assets/index-C1oaL9s7.js +81 -0
- package/embed-dist/assets/{index-CSFtK41z.css → index-CA4kv43T.css} +1 -1
- package/embed-dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DqJvWBKS.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-dGka8yZY.mjs +0 -26
- package/embed-dist/assets/index-CX-rILhw.js +0 -81
package/dist/recorder/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
const
|
|
2
|
-
const require_dist = require("../dist-
|
|
1
|
+
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
2
|
+
const require_dist = require("../dist-CySHUkXY.js");
|
|
3
3
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
4
|
+
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BLXzxpRB.js");
|
|
4
5
|
let node_child_process = require("node:child_process");
|
|
5
6
|
let node_fs = require("node:fs");
|
|
6
7
|
let node_path = require("node:path");
|
|
7
|
-
node_path =
|
|
8
|
+
node_path = require_chunk.__toESM(node_path);
|
|
8
9
|
//#region src/recorder/segment-path.ts
|
|
9
10
|
function segmentRelPath(deviceId, profile, startMs, durMs, bytes) {
|
|
10
11
|
if (profile.includes("/")) throw new Error(`segmentRelPath: profile must not contain '/': ${profile}`);
|
|
@@ -224,7 +225,10 @@ var SegmentStore = class SegmentStore {
|
|
|
224
225
|
try {
|
|
225
226
|
bytes = await this.deps.statBytes(input.flatAbsPath);
|
|
226
227
|
} catch (err) {
|
|
227
|
-
this.deps.logger.warn("segment stat failed",
|
|
228
|
+
this.deps.logger.warn("segment stat failed", {
|
|
229
|
+
tags: { deviceId: input.deviceId },
|
|
230
|
+
error: err
|
|
231
|
+
});
|
|
228
232
|
return null;
|
|
229
233
|
}
|
|
230
234
|
if (bytes <= 0) return null;
|
|
@@ -233,7 +237,10 @@ var SegmentStore = class SegmentStore {
|
|
|
233
237
|
try {
|
|
234
238
|
await this.deps.moveFile(input.flatAbsPath, toAbs);
|
|
235
239
|
} catch (err) {
|
|
236
|
-
this.deps.logger.warn("segment relocate failed",
|
|
240
|
+
this.deps.logger.warn("segment relocate failed", {
|
|
241
|
+
tags: { deviceId: input.deviceId },
|
|
242
|
+
error: err
|
|
243
|
+
});
|
|
237
244
|
return null;
|
|
238
245
|
}
|
|
239
246
|
const row = {
|
|
@@ -283,7 +290,10 @@ var SegmentStore = class SegmentStore {
|
|
|
283
290
|
}
|
|
284
291
|
continue;
|
|
285
292
|
} catch (err) {
|
|
286
|
-
this.deps.logger.warn("bucket removeDir failed — falling back to per-file deletes",
|
|
293
|
+
this.deps.logger.warn("bucket removeDir failed — falling back to per-file deletes", {
|
|
294
|
+
tags: { deviceId: bucketRows[0]?.deviceId },
|
|
295
|
+
error: err
|
|
296
|
+
});
|
|
287
297
|
}
|
|
288
298
|
for (const r of bucketRows) try {
|
|
289
299
|
await this.deps.deletePath({
|
|
@@ -293,7 +303,10 @@ var SegmentStore = class SegmentStore {
|
|
|
293
303
|
removed.push(r.path);
|
|
294
304
|
reclaimed += r.bytes;
|
|
295
305
|
} catch (err) {
|
|
296
|
-
this.deps.logger.warn("segment delete failed",
|
|
306
|
+
this.deps.logger.warn("segment delete failed", {
|
|
307
|
+
tags: { deviceId: r.deviceId },
|
|
308
|
+
error: err
|
|
309
|
+
});
|
|
297
310
|
}
|
|
298
311
|
}
|
|
299
312
|
if (removed.length > 0) this.deps.index.removeSegments(removed);
|
|
@@ -448,13 +461,16 @@ async function runRedundancyJanitor(deps) {
|
|
|
448
461
|
files += vrows.length;
|
|
449
462
|
bytes += reclaimed;
|
|
450
463
|
} catch (err) {
|
|
451
|
-
deps.logger.warn("janitor: evict failed (skipping)", {
|
|
452
|
-
deviceId,
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
464
|
+
deps.logger.warn("janitor: evict failed (skipping)", {
|
|
465
|
+
tags: { deviceId },
|
|
466
|
+
meta: {
|
|
467
|
+
deviceId,
|
|
468
|
+
profile,
|
|
469
|
+
location,
|
|
470
|
+
count: vrows.length,
|
|
471
|
+
error: err instanceof Error ? err.message : String(err)
|
|
472
|
+
}
|
|
473
|
+
});
|
|
458
474
|
}
|
|
459
475
|
}
|
|
460
476
|
}
|
|
@@ -1091,9 +1107,12 @@ function buildRecordingProvider(deps) {
|
|
|
1091
1107
|
return require_dist.hydrateSchema(buildRecordingDeviceSchema(), {});
|
|
1092
1108
|
},
|
|
1093
1109
|
getDeviceLiveContribution: async () => null,
|
|
1094
|
-
applyDeviceSettingsPatch: async ({ patch }) => {
|
|
1110
|
+
applyDeviceSettingsPatch: async ({ deviceId, patch }) => {
|
|
1095
1111
|
const keys = Object.keys(patch ?? {});
|
|
1096
|
-
if (keys.length > 0) deps.logger.debug("recorder: ignoring legacy settings patch (widget owns settings)", {
|
|
1112
|
+
if (keys.length > 0) deps.logger.debug("recorder: ignoring legacy settings patch (widget owns settings)", {
|
|
1113
|
+
tags: { deviceId },
|
|
1114
|
+
meta: { keys }
|
|
1115
|
+
});
|
|
1097
1116
|
return { success: true };
|
|
1098
1117
|
}
|
|
1099
1118
|
};
|
|
@@ -1857,7 +1876,7 @@ var RecordingController = class {
|
|
|
1857
1876
|
* assigned sources is ignored (record every assigned source — minimum of 1).
|
|
1858
1877
|
*/
|
|
1859
1878
|
async resolveProfiles(deviceId, override) {
|
|
1860
|
-
const assigned = require_dist.selectAssignedProfileSlots(await this.deps.brokerCall(() => this.deps.api.streamBroker.listAllProfileSlots.query()), deviceId).map((slot) => slot.profile);
|
|
1879
|
+
const assigned = require_dist.selectAssignedProfileSlots(await this.deps.brokerCall(() => this.deps.api.streamBroker.listAllProfileSlots.query(void 0, require_dist.nodePin(this.deps.ownerNodeId))), deviceId).map((slot) => slot.profile);
|
|
1861
1880
|
if (!override || override.length === 0) return assigned;
|
|
1862
1881
|
const selected = assigned.filter((p) => override.includes(p));
|
|
1863
1882
|
return selected.length > 0 ? selected : assigned;
|
|
@@ -1877,16 +1896,17 @@ var RecordingController = class {
|
|
|
1877
1896
|
profile,
|
|
1878
1897
|
...this.deps.consumerHostname !== void 0 ? { hostname: this.deps.consumerHostname } : {},
|
|
1879
1898
|
tag: `recorder:${deviceId}/${profile}`
|
|
1880
|
-
}));
|
|
1899
|
+
}, require_dist.nodePin(this.deps.ownerNodeId)));
|
|
1881
1900
|
const outDir = node_path.default.join(placement.root, STAGING_DIR_NAME, String(deviceId), profile);
|
|
1882
1901
|
await node_fs.promises.mkdir(outDir, { recursive: true });
|
|
1902
|
+
const deviceLog = this.deps.logger.withTags({ deviceId });
|
|
1883
1903
|
const writer = new SegmentWriter({
|
|
1884
1904
|
rtspUrl: source.url,
|
|
1885
1905
|
outDir,
|
|
1886
1906
|
segmentSeconds
|
|
1887
1907
|
}, {
|
|
1888
1908
|
spawn: this.deps.spawn,
|
|
1889
|
-
logger:
|
|
1909
|
+
logger: deviceLog,
|
|
1890
1910
|
onGaveUp: () => {
|
|
1891
1911
|
this.recoverWriterGaveUp(deviceId);
|
|
1892
1912
|
}
|
|
@@ -1901,7 +1921,7 @@ var RecordingController = class {
|
|
|
1901
1921
|
onFinalized: (startMs, durMs, flatAbsPath) => {
|
|
1902
1922
|
this.handleFinalizedSegment(deviceId, profile, placement, startMs, durMs, flatAbsPath);
|
|
1903
1923
|
},
|
|
1904
|
-
logger:
|
|
1924
|
+
logger: deviceLog
|
|
1905
1925
|
}),
|
|
1906
1926
|
pipelineKey: source.pipelineKey,
|
|
1907
1927
|
outDir
|
|
@@ -1962,7 +1982,7 @@ var RecordingController = class {
|
|
|
1962
1982
|
r.writer.stop();
|
|
1963
1983
|
} catch {}
|
|
1964
1984
|
try {
|
|
1965
|
-
await this.deps.api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey: r.pipelineKey });
|
|
1985
|
+
await this.deps.api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey: r.pipelineKey }, require_dist.nodePin(this.deps.ownerNodeId));
|
|
1966
1986
|
} catch (err) {
|
|
1967
1987
|
this.deps.logger.warn("recorder: releaseStreamWithCodec failed", {
|
|
1968
1988
|
tags: { deviceId },
|
|
@@ -2073,9 +2093,13 @@ var MARKER_PAD_POST_MS = require_dist.EVENT_PAD_MS.postMs;
|
|
|
2073
2093
|
* form the hub reverse-proxy client path `/addon/recorder/playback`. */
|
|
2074
2094
|
var RECORDER_ADDON_ID = "recorder";
|
|
2075
2095
|
var PLAYBACK_PREFIX = "playback";
|
|
2076
|
-
/**
|
|
2077
|
-
*
|
|
2078
|
-
|
|
2096
|
+
/** Narrows the `unknown` extras `SegmentStore`'s logger contract accepts down
|
|
2097
|
+
* to the structured `{ tags, error }` shape the device-scoped call sites
|
|
2098
|
+
* (stat/relocate failures) pass — as opposed to the raw caught error most
|
|
2099
|
+
* other call sites still pass directly. */
|
|
2100
|
+
function isSegmentStoreLogExtras(extras) {
|
|
2101
|
+
return typeof extras === "object" && extras !== null && ("tags" in extras || "error" in extras);
|
|
2102
|
+
}
|
|
2079
2103
|
var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
2080
2104
|
nodeId = "hub";
|
|
2081
2105
|
/** True when THIS node is the designated recording node (`recordingNodeId`).
|
|
@@ -2110,14 +2134,15 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
2110
2134
|
} });
|
|
2111
2135
|
return { providers: [] };
|
|
2112
2136
|
}
|
|
2113
|
-
const
|
|
2114
|
-
|
|
2137
|
+
const ingestOwner = await this.resolveIngestOwner();
|
|
2138
|
+
const consumerHostname = this.nodeId === ingestOwner.ownerNodeId ? void 0 : ingestOwner.reachableHost ?? resolveRecordingHubHostname(this.config.recordingHubHostname, process.env["CAMSTACK_HUB_URL"]);
|
|
2139
|
+
if (this.nodeId !== ingestOwner.ownerNodeId) {
|
|
2115
2140
|
this.ctx.logger.info("recorder is a REMOTE recording node — pulling broker streams cross-node", { meta: {
|
|
2116
2141
|
ownNodeId: this.nodeId,
|
|
2117
|
-
brokerHost:
|
|
2142
|
+
brokerHost: ingestOwner.ownerNodeId,
|
|
2118
2143
|
consumerHostname: consumerHostname ?? "(unresolved)"
|
|
2119
2144
|
} });
|
|
2120
|
-
if (consumerHostname === void 0) this.ctx.logger.warn("recorder: could not resolve the
|
|
2145
|
+
if (consumerHostname === void 0) this.ctx.logger.warn("recorder: could not resolve the ingest owner host for cross-node pull — set the `recordingHubHostname` global setting or CAMSTACK_HUB_URL; recording will not reach this node", { meta: { ownNodeId: this.nodeId } });
|
|
2121
2146
|
}
|
|
2122
2147
|
const segmentStoreDeps = {
|
|
2123
2148
|
index: this.index,
|
|
@@ -2142,14 +2167,21 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
2142
2167
|
force: true
|
|
2143
2168
|
});
|
|
2144
2169
|
},
|
|
2145
|
-
logger: { warn: (message, extras) =>
|
|
2170
|
+
logger: { warn: (message, extras) => {
|
|
2171
|
+
if (isSegmentStoreLogExtras(extras)) this.ctx.logger.warn(message, {
|
|
2172
|
+
tags: extras.tags,
|
|
2173
|
+
meta: { error: extras.error }
|
|
2174
|
+
});
|
|
2175
|
+
else this.ctx.logger.warn(message, { meta: { extras } });
|
|
2176
|
+
} }
|
|
2146
2177
|
};
|
|
2147
2178
|
this.segmentStore = new SegmentStore(segmentStoreDeps);
|
|
2148
|
-
const brokerHandle = this.ctx.useCapability("stream-broker", brokerScope(
|
|
2179
|
+
const brokerHandle = this.ctx.useCapability("stream-broker", brokerScope(ingestOwner.ownerNodeId));
|
|
2149
2180
|
this.controller = new RecordingController({
|
|
2150
2181
|
api: this.ctx.api,
|
|
2151
2182
|
logger: this.ctx.logger,
|
|
2152
2183
|
nodeId: this.nodeId,
|
|
2184
|
+
ownerNodeId: ingestOwner.ownerNodeId,
|
|
2153
2185
|
segmentStore: this.segmentStore,
|
|
2154
2186
|
loadConfig: (deviceId) => loadDeviceConfig(this.configStore(), deviceId),
|
|
2155
2187
|
enabledDeviceIds: async () => [...(await readDeviceConfigs(this.configStore())).keys()],
|
|
@@ -2158,7 +2190,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
2158
2190
|
segmentSeconds: this.config.segmentSeconds,
|
|
2159
2191
|
watchIntervalMs: this.config.watchIntervalMs,
|
|
2160
2192
|
...consumerHostname !== void 0 ? { consumerHostname } : {},
|
|
2161
|
-
onBrokerReady: (handler) => this.ctx.onCapabilityStateChange("stream-broker", brokerScope(
|
|
2193
|
+
onBrokerReady: (handler) => this.ctx.onCapabilityStateChange("stream-broker", brokerScope(ingestOwner.ownerNodeId), (state) => {
|
|
2162
2194
|
if (state === "ready") handler();
|
|
2163
2195
|
}),
|
|
2164
2196
|
brokerCall: makeBrokerCall(brokerHandle)
|
|
@@ -2284,6 +2316,21 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
2284
2316
|
return this.resolvedLocations;
|
|
2285
2317
|
}
|
|
2286
2318
|
/**
|
|
2319
|
+
* Resolve the cluster's camera-source owner. Graceful degrade: the
|
|
2320
|
+
* `pipeline-orchestrator` cap may not be mounted yet (pre-image-release),
|
|
2321
|
+
* in which case we fall back to `{ ownerNodeId: 'hub' }` — the same default
|
|
2322
|
+
* `getIngestOwner` itself returns absent an explicit `clusterRoles.ingestNode`,
|
|
2323
|
+
* so an unresolved owner is byte-identical to today's hub-only behaviour.
|
|
2324
|
+
*/
|
|
2325
|
+
async resolveIngestOwner() {
|
|
2326
|
+
try {
|
|
2327
|
+
return await this.ctx.api.pipelineOrchestrator.getIngestOwner.query();
|
|
2328
|
+
} catch (err) {
|
|
2329
|
+
this.ctx.logger.warn("recorder: getIngestOwner unavailable — defaulting to hub (cap not mounted yet?)", { meta: { error: require_dist.errMsg(err) } });
|
|
2330
|
+
return { ownerNodeId: "hub" };
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2287
2334
|
* Roots the playback data-plane handler searches, IN ORDER: `<dataDir>/playback`
|
|
2288
2335
|
* FIRST (the freshly-written master/variant `.m3u8` playlists), then every
|
|
2289
2336
|
* recordings location root (the segment `.m4s` files at
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as recordingCapability, B as DeviceType,
|
|
1
|
+
import { A as recordingCapability, B as DeviceType, D as nodePin, I as errMsg, L as BaseAddon, M as storageEvictableCapability, T as migrateConfigToBands, U as hydrateSchema, V as EventCategory, Y as selectAssignedProfileSlots, at as record, ot as string, s as EVENT_PAD_MS, u as RecordingConfigSchema } from "../dist-CgBGV988.mjs";
|
|
2
2
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
3
3
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -223,7 +223,10 @@ var SegmentStore = class SegmentStore {
|
|
|
223
223
|
try {
|
|
224
224
|
bytes = await this.deps.statBytes(input.flatAbsPath);
|
|
225
225
|
} catch (err) {
|
|
226
|
-
this.deps.logger.warn("segment stat failed",
|
|
226
|
+
this.deps.logger.warn("segment stat failed", {
|
|
227
|
+
tags: { deviceId: input.deviceId },
|
|
228
|
+
error: err
|
|
229
|
+
});
|
|
227
230
|
return null;
|
|
228
231
|
}
|
|
229
232
|
if (bytes <= 0) return null;
|
|
@@ -232,7 +235,10 @@ var SegmentStore = class SegmentStore {
|
|
|
232
235
|
try {
|
|
233
236
|
await this.deps.moveFile(input.flatAbsPath, toAbs);
|
|
234
237
|
} catch (err) {
|
|
235
|
-
this.deps.logger.warn("segment relocate failed",
|
|
238
|
+
this.deps.logger.warn("segment relocate failed", {
|
|
239
|
+
tags: { deviceId: input.deviceId },
|
|
240
|
+
error: err
|
|
241
|
+
});
|
|
236
242
|
return null;
|
|
237
243
|
}
|
|
238
244
|
const row = {
|
|
@@ -282,7 +288,10 @@ var SegmentStore = class SegmentStore {
|
|
|
282
288
|
}
|
|
283
289
|
continue;
|
|
284
290
|
} catch (err) {
|
|
285
|
-
this.deps.logger.warn("bucket removeDir failed — falling back to per-file deletes",
|
|
291
|
+
this.deps.logger.warn("bucket removeDir failed — falling back to per-file deletes", {
|
|
292
|
+
tags: { deviceId: bucketRows[0]?.deviceId },
|
|
293
|
+
error: err
|
|
294
|
+
});
|
|
286
295
|
}
|
|
287
296
|
for (const r of bucketRows) try {
|
|
288
297
|
await this.deps.deletePath({
|
|
@@ -292,7 +301,10 @@ var SegmentStore = class SegmentStore {
|
|
|
292
301
|
removed.push(r.path);
|
|
293
302
|
reclaimed += r.bytes;
|
|
294
303
|
} catch (err) {
|
|
295
|
-
this.deps.logger.warn("segment delete failed",
|
|
304
|
+
this.deps.logger.warn("segment delete failed", {
|
|
305
|
+
tags: { deviceId: r.deviceId },
|
|
306
|
+
error: err
|
|
307
|
+
});
|
|
296
308
|
}
|
|
297
309
|
}
|
|
298
310
|
if (removed.length > 0) this.deps.index.removeSegments(removed);
|
|
@@ -447,13 +459,16 @@ async function runRedundancyJanitor(deps) {
|
|
|
447
459
|
files += vrows.length;
|
|
448
460
|
bytes += reclaimed;
|
|
449
461
|
} catch (err) {
|
|
450
|
-
deps.logger.warn("janitor: evict failed (skipping)", {
|
|
451
|
-
deviceId,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
462
|
+
deps.logger.warn("janitor: evict failed (skipping)", {
|
|
463
|
+
tags: { deviceId },
|
|
464
|
+
meta: {
|
|
465
|
+
deviceId,
|
|
466
|
+
profile,
|
|
467
|
+
location,
|
|
468
|
+
count: vrows.length,
|
|
469
|
+
error: err instanceof Error ? err.message : String(err)
|
|
470
|
+
}
|
|
471
|
+
});
|
|
457
472
|
}
|
|
458
473
|
}
|
|
459
474
|
}
|
|
@@ -1090,9 +1105,12 @@ function buildRecordingProvider(deps) {
|
|
|
1090
1105
|
return hydrateSchema(buildRecordingDeviceSchema(), {});
|
|
1091
1106
|
},
|
|
1092
1107
|
getDeviceLiveContribution: async () => null,
|
|
1093
|
-
applyDeviceSettingsPatch: async ({ patch }) => {
|
|
1108
|
+
applyDeviceSettingsPatch: async ({ deviceId, patch }) => {
|
|
1094
1109
|
const keys = Object.keys(patch ?? {});
|
|
1095
|
-
if (keys.length > 0) deps.logger.debug("recorder: ignoring legacy settings patch (widget owns settings)", {
|
|
1110
|
+
if (keys.length > 0) deps.logger.debug("recorder: ignoring legacy settings patch (widget owns settings)", {
|
|
1111
|
+
tags: { deviceId },
|
|
1112
|
+
meta: { keys }
|
|
1113
|
+
});
|
|
1096
1114
|
return { success: true };
|
|
1097
1115
|
}
|
|
1098
1116
|
};
|
|
@@ -1856,7 +1874,7 @@ var RecordingController = class {
|
|
|
1856
1874
|
* assigned sources is ignored (record every assigned source — minimum of 1).
|
|
1857
1875
|
*/
|
|
1858
1876
|
async resolveProfiles(deviceId, override) {
|
|
1859
|
-
const assigned = selectAssignedProfileSlots(await this.deps.brokerCall(() => this.deps.api.streamBroker.listAllProfileSlots.query()), deviceId).map((slot) => slot.profile);
|
|
1877
|
+
const assigned = selectAssignedProfileSlots(await this.deps.brokerCall(() => this.deps.api.streamBroker.listAllProfileSlots.query(void 0, nodePin(this.deps.ownerNodeId))), deviceId).map((slot) => slot.profile);
|
|
1860
1878
|
if (!override || override.length === 0) return assigned;
|
|
1861
1879
|
const selected = assigned.filter((p) => override.includes(p));
|
|
1862
1880
|
return selected.length > 0 ? selected : assigned;
|
|
@@ -1876,16 +1894,17 @@ var RecordingController = class {
|
|
|
1876
1894
|
profile,
|
|
1877
1895
|
...this.deps.consumerHostname !== void 0 ? { hostname: this.deps.consumerHostname } : {},
|
|
1878
1896
|
tag: `recorder:${deviceId}/${profile}`
|
|
1879
|
-
}));
|
|
1897
|
+
}, nodePin(this.deps.ownerNodeId)));
|
|
1880
1898
|
const outDir = path.join(placement.root, STAGING_DIR_NAME, String(deviceId), profile);
|
|
1881
1899
|
await promises.mkdir(outDir, { recursive: true });
|
|
1900
|
+
const deviceLog = this.deps.logger.withTags({ deviceId });
|
|
1882
1901
|
const writer = new SegmentWriter({
|
|
1883
1902
|
rtspUrl: source.url,
|
|
1884
1903
|
outDir,
|
|
1885
1904
|
segmentSeconds
|
|
1886
1905
|
}, {
|
|
1887
1906
|
spawn: this.deps.spawn,
|
|
1888
|
-
logger:
|
|
1907
|
+
logger: deviceLog,
|
|
1889
1908
|
onGaveUp: () => {
|
|
1890
1909
|
this.recoverWriterGaveUp(deviceId);
|
|
1891
1910
|
}
|
|
@@ -1900,7 +1919,7 @@ var RecordingController = class {
|
|
|
1900
1919
|
onFinalized: (startMs, durMs, flatAbsPath) => {
|
|
1901
1920
|
this.handleFinalizedSegment(deviceId, profile, placement, startMs, durMs, flatAbsPath);
|
|
1902
1921
|
},
|
|
1903
|
-
logger:
|
|
1922
|
+
logger: deviceLog
|
|
1904
1923
|
}),
|
|
1905
1924
|
pipelineKey: source.pipelineKey,
|
|
1906
1925
|
outDir
|
|
@@ -1961,7 +1980,7 @@ var RecordingController = class {
|
|
|
1961
1980
|
r.writer.stop();
|
|
1962
1981
|
} catch {}
|
|
1963
1982
|
try {
|
|
1964
|
-
await this.deps.api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey: r.pipelineKey });
|
|
1983
|
+
await this.deps.api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey: r.pipelineKey }, nodePin(this.deps.ownerNodeId));
|
|
1965
1984
|
} catch (err) {
|
|
1966
1985
|
this.deps.logger.warn("recorder: releaseStreamWithCodec failed", {
|
|
1967
1986
|
tags: { deviceId },
|
|
@@ -2072,9 +2091,13 @@ var MARKER_PAD_POST_MS = EVENT_PAD_MS.postMs;
|
|
|
2072
2091
|
* form the hub reverse-proxy client path `/addon/recorder/playback`. */
|
|
2073
2092
|
var RECORDER_ADDON_ID = "recorder";
|
|
2074
2093
|
var PLAYBACK_PREFIX = "playback";
|
|
2075
|
-
/**
|
|
2076
|
-
*
|
|
2077
|
-
|
|
2094
|
+
/** Narrows the `unknown` extras `SegmentStore`'s logger contract accepts down
|
|
2095
|
+
* to the structured `{ tags, error }` shape the device-scoped call sites
|
|
2096
|
+
* (stat/relocate failures) pass — as opposed to the raw caught error most
|
|
2097
|
+
* other call sites still pass directly. */
|
|
2098
|
+
function isSegmentStoreLogExtras(extras) {
|
|
2099
|
+
return typeof extras === "object" && extras !== null && ("tags" in extras || "error" in extras);
|
|
2100
|
+
}
|
|
2078
2101
|
var RecorderV2Addon = class extends BaseAddon {
|
|
2079
2102
|
nodeId = "hub";
|
|
2080
2103
|
/** True when THIS node is the designated recording node (`recordingNodeId`).
|
|
@@ -2109,14 +2132,15 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
2109
2132
|
} });
|
|
2110
2133
|
return { providers: [] };
|
|
2111
2134
|
}
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2135
|
+
const ingestOwner = await this.resolveIngestOwner();
|
|
2136
|
+
const consumerHostname = this.nodeId === ingestOwner.ownerNodeId ? void 0 : ingestOwner.reachableHost ?? resolveRecordingHubHostname(this.config.recordingHubHostname, process.env["CAMSTACK_HUB_URL"]);
|
|
2137
|
+
if (this.nodeId !== ingestOwner.ownerNodeId) {
|
|
2114
2138
|
this.ctx.logger.info("recorder is a REMOTE recording node — pulling broker streams cross-node", { meta: {
|
|
2115
2139
|
ownNodeId: this.nodeId,
|
|
2116
|
-
brokerHost:
|
|
2140
|
+
brokerHost: ingestOwner.ownerNodeId,
|
|
2117
2141
|
consumerHostname: consumerHostname ?? "(unresolved)"
|
|
2118
2142
|
} });
|
|
2119
|
-
if (consumerHostname === void 0) this.ctx.logger.warn("recorder: could not resolve the
|
|
2143
|
+
if (consumerHostname === void 0) this.ctx.logger.warn("recorder: could not resolve the ingest owner host for cross-node pull — set the `recordingHubHostname` global setting or CAMSTACK_HUB_URL; recording will not reach this node", { meta: { ownNodeId: this.nodeId } });
|
|
2120
2144
|
}
|
|
2121
2145
|
const segmentStoreDeps = {
|
|
2122
2146
|
index: this.index,
|
|
@@ -2141,14 +2165,21 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
2141
2165
|
force: true
|
|
2142
2166
|
});
|
|
2143
2167
|
},
|
|
2144
|
-
logger: { warn: (message, extras) =>
|
|
2168
|
+
logger: { warn: (message, extras) => {
|
|
2169
|
+
if (isSegmentStoreLogExtras(extras)) this.ctx.logger.warn(message, {
|
|
2170
|
+
tags: extras.tags,
|
|
2171
|
+
meta: { error: extras.error }
|
|
2172
|
+
});
|
|
2173
|
+
else this.ctx.logger.warn(message, { meta: { extras } });
|
|
2174
|
+
} }
|
|
2145
2175
|
};
|
|
2146
2176
|
this.segmentStore = new SegmentStore(segmentStoreDeps);
|
|
2147
|
-
const brokerHandle = this.ctx.useCapability("stream-broker", brokerScope(
|
|
2177
|
+
const brokerHandle = this.ctx.useCapability("stream-broker", brokerScope(ingestOwner.ownerNodeId));
|
|
2148
2178
|
this.controller = new RecordingController({
|
|
2149
2179
|
api: this.ctx.api,
|
|
2150
2180
|
logger: this.ctx.logger,
|
|
2151
2181
|
nodeId: this.nodeId,
|
|
2182
|
+
ownerNodeId: ingestOwner.ownerNodeId,
|
|
2152
2183
|
segmentStore: this.segmentStore,
|
|
2153
2184
|
loadConfig: (deviceId) => loadDeviceConfig(this.configStore(), deviceId),
|
|
2154
2185
|
enabledDeviceIds: async () => [...(await readDeviceConfigs(this.configStore())).keys()],
|
|
@@ -2157,7 +2188,7 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
2157
2188
|
segmentSeconds: this.config.segmentSeconds,
|
|
2158
2189
|
watchIntervalMs: this.config.watchIntervalMs,
|
|
2159
2190
|
...consumerHostname !== void 0 ? { consumerHostname } : {},
|
|
2160
|
-
onBrokerReady: (handler) => this.ctx.onCapabilityStateChange("stream-broker", brokerScope(
|
|
2191
|
+
onBrokerReady: (handler) => this.ctx.onCapabilityStateChange("stream-broker", brokerScope(ingestOwner.ownerNodeId), (state) => {
|
|
2161
2192
|
if (state === "ready") handler();
|
|
2162
2193
|
}),
|
|
2163
2194
|
brokerCall: makeBrokerCall(brokerHandle)
|
|
@@ -2283,6 +2314,21 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
2283
2314
|
return this.resolvedLocations;
|
|
2284
2315
|
}
|
|
2285
2316
|
/**
|
|
2317
|
+
* Resolve the cluster's camera-source owner. Graceful degrade: the
|
|
2318
|
+
* `pipeline-orchestrator` cap may not be mounted yet (pre-image-release),
|
|
2319
|
+
* in which case we fall back to `{ ownerNodeId: 'hub' }` — the same default
|
|
2320
|
+
* `getIngestOwner` itself returns absent an explicit `clusterRoles.ingestNode`,
|
|
2321
|
+
* so an unresolved owner is byte-identical to today's hub-only behaviour.
|
|
2322
|
+
*/
|
|
2323
|
+
async resolveIngestOwner() {
|
|
2324
|
+
try {
|
|
2325
|
+
return await this.ctx.api.pipelineOrchestrator.getIngestOwner.query();
|
|
2326
|
+
} catch (err) {
|
|
2327
|
+
this.ctx.logger.warn("recorder: getIngestOwner unavailable — defaulting to hub (cap not mounted yet?)", { meta: { error: errMsg(err) } });
|
|
2328
|
+
return { ownerNodeId: "hub" };
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2286
2332
|
* Roots the playback data-plane handler searches, IN ORDER: `<dataDir>/playback`
|
|
2287
2333
|
* FIRST (the freshly-written master/variant `.m3u8` playlists), then every
|
|
2288
2334
|
* recordings location root (the segment `.m4s` files at
|