@camstack/addon-pipeline 1.2.360 → 1.2.362
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/{custom-model-catalog-ihWAlDr4.mjs → custom-model-catalog-C77XAnt9.mjs} +2 -2
- package/dist/{custom-model-catalog-K9Hx82GZ.js → custom-model-catalog-CL9Tcn2Y.js} +2 -2
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-BYCrV0VR.mjs → dist-D629dz5x.mjs} +127 -20
- package/dist/{dist-CYakbWA8.js → dist-tclsRHCk.js} +126 -19
- package/dist/{model-catalogs-CX9SJ9in.mjs → model-catalogs-BxQBJBt4.mjs} +1 -1
- package/dist/{model-catalogs-C9cyLHdH.js → model-catalogs-Y1WJrGyS.js} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-CTCm8Vtw.js → node-BYrYuZdh.js} +4 -2
- package/dist/{node-C_qDNN9f.mjs → node-D2D0N7_h.mjs} +3 -2
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-B3lkW9H-.mjs → process-memory-BR1e6q_Q.mjs} +1 -1
- package/dist/{process-memory-Ds5S3L0F.js → process-memory-eM4qLFdC.js} +1 -1
- package/dist/recorder/index.js +1679 -2
- package/dist/recorder/index.mjs +1679 -2
- package/dist/{segment-demux-js-BgDpp15L.js → segment-demux-js-B9EqRfBi.js} +1 -1
- package/dist/{segment-demux-js-W5FIgijf.mjs → segment-demux-js-C5IOs4lZ.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-v95KZ2Rt.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C4Cp_OAY.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CICWhpXS.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-qyhFaM-7.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-7VuNrr9l.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-CDaDWDva.mjs → hostInit-CZ_HBwki.mjs} +2 -2
- package/dist/stream-broker/index.js +159 -20
- package/dist/stream-broker/index.mjs +159 -20
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/inference_pool.py +12 -13
- package/python/pool_platform.py +214 -0
- package/python/test_inference_pool_windows.py +86 -0
- package/python/test_pool_platform.py +141 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-XWaPHrfA.mjs +0 -26
|
@@ -3,12 +3,12 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
6
|
+
const require_dist = require("../dist-tclsRHCk.js");
|
|
7
|
+
const require_node = require("../node-BYrYuZdh.js");
|
|
8
8
|
const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
|
|
9
9
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
10
10
|
const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
|
|
11
|
-
const require_segment_demux_js = require("../segment-demux-js-
|
|
11
|
+
const require_segment_demux_js = require("../segment-demux-js-B9EqRfBi.js");
|
|
12
12
|
let node_os = require("node:os");
|
|
13
13
|
node_os = require_chunk.__toESM(node_os);
|
|
14
14
|
let node_crypto = require("node:crypto");
|
|
@@ -565,6 +565,85 @@ var STREAM_BROKER_RETIRED_ROOT_KEYS = [
|
|
|
565
565
|
}
|
|
566
566
|
];
|
|
567
567
|
//#endregion
|
|
568
|
+
//#region src/stream-broker/live-viewers.ts
|
|
569
|
+
/**
|
|
570
|
+
* WebRTC consumer kinds that are a person (or a person's device) watching.
|
|
571
|
+
* Every other kind (`recording`, `pipeline`, `snapshot`, `warmup`,
|
|
572
|
+
* `derived-broker`, `rtsp-listen`, `unknown`) is the system holding the stream.
|
|
573
|
+
*/
|
|
574
|
+
var VIEWER_CONSUMER_KINDS = new Set([
|
|
575
|
+
"webrtc-browser",
|
|
576
|
+
"webrtc-mobile",
|
|
577
|
+
"webrtc-whep",
|
|
578
|
+
"alexa",
|
|
579
|
+
"homekit"
|
|
580
|
+
]);
|
|
581
|
+
function isViewerConsumerKind(kind) {
|
|
582
|
+
return VIEWER_CONSUMER_KINDS.has(kind);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* `User-Agent` prefixes our OWN processes put on their RTSP dials:
|
|
586
|
+
* `recorder/<id>/…` (the writer), `decode/<id>/…` (the detection decode
|
|
587
|
+
* worker), `camstack-…` (the RTSP client an agent's derived broker pulls this
|
|
588
|
+
* restream with), `prebuffer` (the prebuffer hold).
|
|
589
|
+
*/
|
|
590
|
+
var INTERNAL_RESTREAM_AGENT_PREFIXES = [
|
|
591
|
+
"recorder",
|
|
592
|
+
"decode",
|
|
593
|
+
"camstack-",
|
|
594
|
+
"prebuffer"
|
|
595
|
+
];
|
|
596
|
+
var LOOPBACK_PREFIXES = [
|
|
597
|
+
"127.",
|
|
598
|
+
"::1",
|
|
599
|
+
"::ffff:127.",
|
|
600
|
+
"[::1]"
|
|
601
|
+
];
|
|
602
|
+
/** True when the peer is this host — the system's own ffmpeg children. */
|
|
603
|
+
function isLoopbackAddr(remoteAddr) {
|
|
604
|
+
return LOOPBACK_PREFIXES.some((p) => remoteAddr.startsWith(p));
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* An RTSP restream client from OUTSIDE: playing, and none of
|
|
608
|
+
* - a declared dial purpose (detection / recording / sentry),
|
|
609
|
+
* - the muted decoder-loopback path,
|
|
610
|
+
* - one of our own `User-Agent`s,
|
|
611
|
+
* - a process on this same host (snapshot, grid compositor, the HomeKit leg —
|
|
612
|
+
* ffmpeg children that dial the restream over loopback).
|
|
613
|
+
*/
|
|
614
|
+
function isExternalRestreamClient(facts) {
|
|
615
|
+
if (!facts.playing) return false;
|
|
616
|
+
if (facts.intent !== null) return false;
|
|
617
|
+
if (facts.muted) return false;
|
|
618
|
+
const ua = facts.userAgent;
|
|
619
|
+
if (ua !== null && INTERNAL_RESTREAM_AGENT_PREFIXES.some((p) => ua.startsWith(p))) return false;
|
|
620
|
+
if (isLoopbackAddr(facts.remoteAddr)) return false;
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
/** Sum several tallies into a new one (inputs are never mutated). */
|
|
624
|
+
function mergeViewerCounts(parts) {
|
|
625
|
+
const out = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const part of parts) for (const [deviceId, n] of part) if (n > 0) out.set(deviceId, (out.get(deviceId) ?? 0) + n);
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/stream-broker/rtsp/broker-device-id.ts
|
|
631
|
+
/**
|
|
632
|
+
* Parse the numeric deviceId out of a canonical brokerId
|
|
633
|
+
* (`${deviceId}/${camStreamId}` or `${deviceId}/${profile}`). Returns
|
|
634
|
+
* `undefined` for a malformed id — so every device-scoped log line on the RTSP
|
|
635
|
+
* plane can carry `tags.deviceId` without risking a bogus tag value.
|
|
636
|
+
*
|
|
637
|
+
* Every log line about a device carries `tags: { deviceId }`, always the same
|
|
638
|
+
* key and always the numeric id: a miss rate, a media gap or a routing fault is
|
|
639
|
+
* always asked per-camera ("why is 617 worse than 615?"), and a line that omits
|
|
640
|
+
* the tag cannot answer it.
|
|
641
|
+
*/
|
|
642
|
+
function deviceIdFromBrokerId$1(brokerId) {
|
|
643
|
+
const parsed = Number(brokerId.split("/")[0]);
|
|
644
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
645
|
+
}
|
|
646
|
+
//#endregion
|
|
568
647
|
//#region src/shared/ffmpeg-decode-args.ts
|
|
569
648
|
/** Default DRM render node used for the VA-API / QSV hardware decode path. */
|
|
570
649
|
var DEFAULT_VAAPI_RENDER_DEVICE = "/dev/dri/renderD128";
|
|
@@ -2613,23 +2692,6 @@ var AnnexBDeframer = class {
|
|
|
2613
2692
|
}
|
|
2614
2693
|
};
|
|
2615
2694
|
//#endregion
|
|
2616
|
-
//#region src/stream-broker/rtsp/broker-device-id.ts
|
|
2617
|
-
/**
|
|
2618
|
-
* Parse the numeric deviceId out of a canonical brokerId
|
|
2619
|
-
* (`${deviceId}/${camStreamId}` or `${deviceId}/${profile}`). Returns
|
|
2620
|
-
* `undefined` for a malformed id — so every device-scoped log line on the RTSP
|
|
2621
|
-
* plane can carry `tags.deviceId` without risking a bogus tag value.
|
|
2622
|
-
*
|
|
2623
|
-
* Every log line about a device carries `tags: { deviceId }`, always the same
|
|
2624
|
-
* key and always the numeric id: a miss rate, a media gap or a routing fault is
|
|
2625
|
-
* always asked per-camera ("why is 617 worse than 615?"), and a line that omits
|
|
2626
|
-
* the tag cannot answer it.
|
|
2627
|
-
*/
|
|
2628
|
-
function deviceIdFromBrokerId$1(brokerId) {
|
|
2629
|
-
const parsed = Number(brokerId.split("/")[0]);
|
|
2630
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2631
|
-
}
|
|
2632
|
-
//#endregion
|
|
2633
2695
|
//#region src/stream-broker/rtsp/audio/g711-silence.ts
|
|
2634
2696
|
var SILENCE_BYTE = {
|
|
2635
2697
|
/** µ-law digital zero. */
|
|
@@ -3601,6 +3663,18 @@ var RtspRestreamer = class {
|
|
|
3601
3663
|
return this.sessions.size;
|
|
3602
3664
|
}
|
|
3603
3665
|
/**
|
|
3666
|
+
* RTSP clients from OUTSIDE that are playing right now — people with a player
|
|
3667
|
+
* open on the restream, not our recorder, decoder or agents (see
|
|
3668
|
+
* `live-viewers.ts`). Read straight off the live session map and each
|
|
3669
|
+
* session's own `playing && !closed`, so a client that vanished without a
|
|
3670
|
+
* TEARDOWN stops counting the moment its socket closes or errors.
|
|
3671
|
+
*/
|
|
3672
|
+
countExternalViewers() {
|
|
3673
|
+
let n = 0;
|
|
3674
|
+
for (const session of this.sessions.values()) if (isExternalRestreamClient(session.getViewerFacts())) n += 1;
|
|
3675
|
+
return n;
|
|
3676
|
+
}
|
|
3677
|
+
/**
|
|
3604
3678
|
* Sessions that count as LIVE INTENT — everything except a sentry dial.
|
|
3605
3679
|
*
|
|
3606
3680
|
* The split exists so a Frigate-style puller can hold a session open for ever
|
|
@@ -4382,6 +4456,21 @@ var RtspSession = class {
|
|
|
4382
4456
|
return this.userAgent;
|
|
4383
4457
|
}
|
|
4384
4458
|
/**
|
|
4459
|
+
* What the live-viewer count needs to classify this client (see
|
|
4460
|
+
* `live-viewers.ts`). `playing` is `isPlaying()`, which is false once the
|
|
4461
|
+
* socket closed or errored — so an abruptly dropped client stops counting at
|
|
4462
|
+
* the same instant, before the restreamer's own `removeSession` reconcile.
|
|
4463
|
+
*/
|
|
4464
|
+
getViewerFacts() {
|
|
4465
|
+
return {
|
|
4466
|
+
playing: this.isPlaying(),
|
|
4467
|
+
userAgent: this.userAgent,
|
|
4468
|
+
remoteAddr: `${this.socket.remoteAddress ?? "unknown"}:${String(this.socket.remotePort ?? 0)}`,
|
|
4469
|
+
muted: this.muted,
|
|
4470
|
+
intent: this.declaration.intent
|
|
4471
|
+
};
|
|
4472
|
+
}
|
|
4473
|
+
/**
|
|
4385
4474
|
* True only for a session that dialed the DETECTION path
|
|
4386
4475
|
* (`DETECTION_MUTED_PATH_SUFFIX`). Read by
|
|
4387
4476
|
* `RtspRestreamer.servePendingSessionsRtp` to exempt this session from the
|
|
@@ -14255,6 +14344,13 @@ var StreamBroker = class StreamBroker {
|
|
|
14255
14344
|
getLocalStreamUrl() {
|
|
14256
14345
|
return this.pipeServer.getUrl();
|
|
14257
14346
|
}
|
|
14347
|
+
/**
|
|
14348
|
+
* External RTSP restream clients playing on this broker right now — the
|
|
14349
|
+
* restream half of the per-camera live-viewer count (`live-viewers.ts`).
|
|
14350
|
+
*/
|
|
14351
|
+
countExternalRestreamViewers() {
|
|
14352
|
+
return this.rtspRestreamer.countExternalViewers();
|
|
14353
|
+
}
|
|
14258
14354
|
/** Get the RTSP restreamer instance (for RtspListenServer registration) */
|
|
14259
14355
|
getRtspRestreamer() {
|
|
14260
14356
|
return this.rtspRestreamer;
|
|
@@ -18875,6 +18971,25 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18875
18971
|
};
|
|
18876
18972
|
return broker.listClients();
|
|
18877
18973
|
}
|
|
18974
|
+
/**
|
|
18975
|
+
* People watching each camera live right now (cap `getLiveViewerCounts`).
|
|
18976
|
+
* Two planes, one definition (`live-viewers.ts`): the WebRTC server's live
|
|
18977
|
+
* viewer sessions plus every broker's external RTSP restream clients, summed
|
|
18978
|
+
* per camera over all of its profile brokers. Both are read off live session
|
|
18979
|
+
* maps — no stored counter — so an ended session cannot be left counted.
|
|
18980
|
+
*/
|
|
18981
|
+
async getLiveViewerCounts() {
|
|
18982
|
+
const rtsp = /* @__PURE__ */ new Map();
|
|
18983
|
+
for (const [brokerId, broker] of this.brokers) {
|
|
18984
|
+
const n = broker.countExternalRestreamViewers();
|
|
18985
|
+
const deviceId = deviceIdFromBrokerId$1(brokerId);
|
|
18986
|
+
if (n > 0 && deviceId !== void 0) rtsp.set(deviceId, (rtsp.get(deviceId) ?? 0) + n);
|
|
18987
|
+
}
|
|
18988
|
+
return [...mergeViewerCounts([this.webrtcServer?.countLiveViewers() ?? /* @__PURE__ */ new Map(), rtsp])].map(([deviceId, viewers]) => ({
|
|
18989
|
+
deviceId,
|
|
18990
|
+
viewers
|
|
18991
|
+
})).sort((a, b) => a.deviceId - b.deviceId);
|
|
18992
|
+
}
|
|
18878
18993
|
async killClient(input) {
|
|
18879
18994
|
const broker = this.brokers.get(input.brokerId);
|
|
18880
18995
|
if (!broker) return { killed: false };
|
|
@@ -34733,6 +34848,7 @@ var BrokerWebrtcServer = class {
|
|
|
34733
34848
|
});
|
|
34734
34849
|
const entry = {
|
|
34735
34850
|
session,
|
|
34851
|
+
consumerKind: baseAttribution.kind,
|
|
34736
34852
|
brokerId,
|
|
34737
34853
|
unsubBroker,
|
|
34738
34854
|
unsubRtp,
|
|
@@ -35159,6 +35275,29 @@ var BrokerWebrtcServer = class {
|
|
|
35159
35275
|
getSessionCount() {
|
|
35160
35276
|
return this.sessions.size;
|
|
35161
35277
|
}
|
|
35278
|
+
/**
|
|
35279
|
+
* People watching LIVE through this server, per camera (`live-viewers.ts`):
|
|
35280
|
+
* sessions of a viewer kind whose track is on the live edge. A session on a
|
|
35281
|
+
* recorded playhead is not watching live; the system's own subscriptions
|
|
35282
|
+
* (warm-up, linger ghost, egress-transcode legs) are never a SESSION here, so
|
|
35283
|
+
* they cannot count.
|
|
35284
|
+
*
|
|
35285
|
+
* Derived from `this.sessions` at call time, the map `cleanupSession`
|
|
35286
|
+
* maintains for every teardown path (explicit close, ICE failure, the peer
|
|
35287
|
+
* vanishing — all end in `session.onClosed`), so there is no second counter
|
|
35288
|
+
* that could leak.
|
|
35289
|
+
*/
|
|
35290
|
+
countLiveViewers() {
|
|
35291
|
+
const counts = /* @__PURE__ */ new Map();
|
|
35292
|
+
for (const entry of this.sessions.values()) {
|
|
35293
|
+
if (!isViewerConsumerKind(entry.consumerKind)) continue;
|
|
35294
|
+
if ((entry.timeline?.currentMode ?? "live") !== "live") continue;
|
|
35295
|
+
const deviceId = deviceIdFromBrokerId(entry.brokerId);
|
|
35296
|
+
if (deviceId === void 0) continue;
|
|
35297
|
+
counts.set(deviceId, (counts.get(deviceId) ?? 0) + 1);
|
|
35298
|
+
}
|
|
35299
|
+
return counts;
|
|
35300
|
+
}
|
|
35162
35301
|
/** Live signaling state for a session — used by the webrtc-session
|
|
35163
35302
|
* provider's getSessionState. Returns a null pending when unknown. */
|
|
35164
35303
|
getSessionRenegotiationState(sessionId) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { r as __toESM } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { $n as discriminatedUnion,
|
|
3
|
-
import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-
|
|
2
|
+
import { $n as discriminatedUnion, An as BaseAddon, D as G711_SCALE_CORRECTION_DB, Dn as DeviceType, Dt as gifDeriveSpeed, En as DeviceFeature, Gn as maskUrlCredentials, Hn as isEvent, I as RATE_CONTROL_RELAXED, In as createEvent, J as addonWidgetsSourceCapability, Kn as nodePin, L as RATE_CONTROL_TIGHT, Mn as CamProfileSchema, Nt as loadContributionCapability, On as errMsg, Ot as gifFromMp4Args, Pt as logChannelsCapability, Qn as boolean, Rt as memoizePinnedFfmpeg, Sn as buildFfmpegArgs, Tn as isSoftwareDecode, Tt as ffmpegRate, Un as makeProfileBrokerId, W as RingBuffer, Wn as makeSourceBrokerId, Xn as _enum, Xt as rankStreams, Zn as array, _t as egressTranscodeSharingKey, ar as record, at as clipExportFailureOf, c as CLIP_STILL_REASON_CODE_HEADER, cr as EventCategory, ct as createHwAccelCache, et as batteryCapability, ft as deriveBatteryPresence, jn as CAM_PROFILE_ORDER, kn as BOOT_RECOVERY_BACKOFF_MS, lt as createLogChannelsProvider, mn as streamBrokerCapability, nr as number, o as BatteryStatusSchema, or as string, qn as parseProfileBrokerId, rn as resolveEgressDecodeHwAccel, rr as object, rt as cameraStreamsCapability, s as CLIP_BROKER_PACED_RATES, sr as union, tr as literal, ut as declareLogChannel, vn as webrtcSessionCapability, vt as egressTransportFromRequest, w as EncodeProfileSchema, wn as invocationFromEncodeProfile, yn as AUDIO_PRESETS, zn as expandAudioChunkToF32le } from "../dist-D629dz5x.mjs";
|
|
3
|
+
import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-D2D0N7_h.mjs";
|
|
4
4
|
import { a as parseRestreamPath, d as PREBUFFER_MAX_MS, f as capPreRollRequestMs, i as isLiveEdgeOnlyDial, m as resolvePrebuffer, n as NO_DECLARATION, p as prebufferRetentionMs, s as sdpVariantFor, u as withSentryIntent } from "../restream-intent-DyGmLAU9.mjs";
|
|
5
5
|
import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
|
|
6
6
|
import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
|
|
7
|
-
import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-
|
|
7
|
+
import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-C5IOs4lZ.mjs";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import * as os$1 from "node:os";
|
|
10
10
|
import { networkInterfaces, tmpdir } from "node:os";
|
|
@@ -561,6 +561,85 @@ var STREAM_BROKER_RETIRED_ROOT_KEYS = [
|
|
|
561
561
|
}
|
|
562
562
|
];
|
|
563
563
|
//#endregion
|
|
564
|
+
//#region src/stream-broker/live-viewers.ts
|
|
565
|
+
/**
|
|
566
|
+
* WebRTC consumer kinds that are a person (or a person's device) watching.
|
|
567
|
+
* Every other kind (`recording`, `pipeline`, `snapshot`, `warmup`,
|
|
568
|
+
* `derived-broker`, `rtsp-listen`, `unknown`) is the system holding the stream.
|
|
569
|
+
*/
|
|
570
|
+
var VIEWER_CONSUMER_KINDS = new Set([
|
|
571
|
+
"webrtc-browser",
|
|
572
|
+
"webrtc-mobile",
|
|
573
|
+
"webrtc-whep",
|
|
574
|
+
"alexa",
|
|
575
|
+
"homekit"
|
|
576
|
+
]);
|
|
577
|
+
function isViewerConsumerKind(kind) {
|
|
578
|
+
return VIEWER_CONSUMER_KINDS.has(kind);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* `User-Agent` prefixes our OWN processes put on their RTSP dials:
|
|
582
|
+
* `recorder/<id>/…` (the writer), `decode/<id>/…` (the detection decode
|
|
583
|
+
* worker), `camstack-…` (the RTSP client an agent's derived broker pulls this
|
|
584
|
+
* restream with), `prebuffer` (the prebuffer hold).
|
|
585
|
+
*/
|
|
586
|
+
var INTERNAL_RESTREAM_AGENT_PREFIXES = [
|
|
587
|
+
"recorder",
|
|
588
|
+
"decode",
|
|
589
|
+
"camstack-",
|
|
590
|
+
"prebuffer"
|
|
591
|
+
];
|
|
592
|
+
var LOOPBACK_PREFIXES = [
|
|
593
|
+
"127.",
|
|
594
|
+
"::1",
|
|
595
|
+
"::ffff:127.",
|
|
596
|
+
"[::1]"
|
|
597
|
+
];
|
|
598
|
+
/** True when the peer is this host — the system's own ffmpeg children. */
|
|
599
|
+
function isLoopbackAddr(remoteAddr) {
|
|
600
|
+
return LOOPBACK_PREFIXES.some((p) => remoteAddr.startsWith(p));
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* An RTSP restream client from OUTSIDE: playing, and none of
|
|
604
|
+
* - a declared dial purpose (detection / recording / sentry),
|
|
605
|
+
* - the muted decoder-loopback path,
|
|
606
|
+
* - one of our own `User-Agent`s,
|
|
607
|
+
* - a process on this same host (snapshot, grid compositor, the HomeKit leg —
|
|
608
|
+
* ffmpeg children that dial the restream over loopback).
|
|
609
|
+
*/
|
|
610
|
+
function isExternalRestreamClient(facts) {
|
|
611
|
+
if (!facts.playing) return false;
|
|
612
|
+
if (facts.intent !== null) return false;
|
|
613
|
+
if (facts.muted) return false;
|
|
614
|
+
const ua = facts.userAgent;
|
|
615
|
+
if (ua !== null && INTERNAL_RESTREAM_AGENT_PREFIXES.some((p) => ua.startsWith(p))) return false;
|
|
616
|
+
if (isLoopbackAddr(facts.remoteAddr)) return false;
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
/** Sum several tallies into a new one (inputs are never mutated). */
|
|
620
|
+
function mergeViewerCounts(parts) {
|
|
621
|
+
const out = /* @__PURE__ */ new Map();
|
|
622
|
+
for (const part of parts) for (const [deviceId, n] of part) if (n > 0) out.set(deviceId, (out.get(deviceId) ?? 0) + n);
|
|
623
|
+
return out;
|
|
624
|
+
}
|
|
625
|
+
//#endregion
|
|
626
|
+
//#region src/stream-broker/rtsp/broker-device-id.ts
|
|
627
|
+
/**
|
|
628
|
+
* Parse the numeric deviceId out of a canonical brokerId
|
|
629
|
+
* (`${deviceId}/${camStreamId}` or `${deviceId}/${profile}`). Returns
|
|
630
|
+
* `undefined` for a malformed id — so every device-scoped log line on the RTSP
|
|
631
|
+
* plane can carry `tags.deviceId` without risking a bogus tag value.
|
|
632
|
+
*
|
|
633
|
+
* Every log line about a device carries `tags: { deviceId }`, always the same
|
|
634
|
+
* key and always the numeric id: a miss rate, a media gap or a routing fault is
|
|
635
|
+
* always asked per-camera ("why is 617 worse than 615?"), and a line that omits
|
|
636
|
+
* the tag cannot answer it.
|
|
637
|
+
*/
|
|
638
|
+
function deviceIdFromBrokerId$1(brokerId) {
|
|
639
|
+
const parsed = Number(brokerId.split("/")[0]);
|
|
640
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
641
|
+
}
|
|
642
|
+
//#endregion
|
|
564
643
|
//#region src/shared/ffmpeg-decode-args.ts
|
|
565
644
|
/** Default DRM render node used for the VA-API / QSV hardware decode path. */
|
|
566
645
|
var DEFAULT_VAAPI_RENDER_DEVICE = "/dev/dri/renderD128";
|
|
@@ -2609,23 +2688,6 @@ var AnnexBDeframer = class {
|
|
|
2609
2688
|
}
|
|
2610
2689
|
};
|
|
2611
2690
|
//#endregion
|
|
2612
|
-
//#region src/stream-broker/rtsp/broker-device-id.ts
|
|
2613
|
-
/**
|
|
2614
|
-
* Parse the numeric deviceId out of a canonical brokerId
|
|
2615
|
-
* (`${deviceId}/${camStreamId}` or `${deviceId}/${profile}`). Returns
|
|
2616
|
-
* `undefined` for a malformed id — so every device-scoped log line on the RTSP
|
|
2617
|
-
* plane can carry `tags.deviceId` without risking a bogus tag value.
|
|
2618
|
-
*
|
|
2619
|
-
* Every log line about a device carries `tags: { deviceId }`, always the same
|
|
2620
|
-
* key and always the numeric id: a miss rate, a media gap or a routing fault is
|
|
2621
|
-
* always asked per-camera ("why is 617 worse than 615?"), and a line that omits
|
|
2622
|
-
* the tag cannot answer it.
|
|
2623
|
-
*/
|
|
2624
|
-
function deviceIdFromBrokerId$1(brokerId) {
|
|
2625
|
-
const parsed = Number(brokerId.split("/")[0]);
|
|
2626
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2627
|
-
}
|
|
2628
|
-
//#endregion
|
|
2629
2691
|
//#region src/stream-broker/rtsp/audio/g711-silence.ts
|
|
2630
2692
|
var SILENCE_BYTE = {
|
|
2631
2693
|
/** µ-law digital zero. */
|
|
@@ -3597,6 +3659,18 @@ var RtspRestreamer = class {
|
|
|
3597
3659
|
return this.sessions.size;
|
|
3598
3660
|
}
|
|
3599
3661
|
/**
|
|
3662
|
+
* RTSP clients from OUTSIDE that are playing right now — people with a player
|
|
3663
|
+
* open on the restream, not our recorder, decoder or agents (see
|
|
3664
|
+
* `live-viewers.ts`). Read straight off the live session map and each
|
|
3665
|
+
* session's own `playing && !closed`, so a client that vanished without a
|
|
3666
|
+
* TEARDOWN stops counting the moment its socket closes or errors.
|
|
3667
|
+
*/
|
|
3668
|
+
countExternalViewers() {
|
|
3669
|
+
let n = 0;
|
|
3670
|
+
for (const session of this.sessions.values()) if (isExternalRestreamClient(session.getViewerFacts())) n += 1;
|
|
3671
|
+
return n;
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3600
3674
|
* Sessions that count as LIVE INTENT — everything except a sentry dial.
|
|
3601
3675
|
*
|
|
3602
3676
|
* The split exists so a Frigate-style puller can hold a session open for ever
|
|
@@ -4378,6 +4452,21 @@ var RtspSession = class {
|
|
|
4378
4452
|
return this.userAgent;
|
|
4379
4453
|
}
|
|
4380
4454
|
/**
|
|
4455
|
+
* What the live-viewer count needs to classify this client (see
|
|
4456
|
+
* `live-viewers.ts`). `playing` is `isPlaying()`, which is false once the
|
|
4457
|
+
* socket closed or errored — so an abruptly dropped client stops counting at
|
|
4458
|
+
* the same instant, before the restreamer's own `removeSession` reconcile.
|
|
4459
|
+
*/
|
|
4460
|
+
getViewerFacts() {
|
|
4461
|
+
return {
|
|
4462
|
+
playing: this.isPlaying(),
|
|
4463
|
+
userAgent: this.userAgent,
|
|
4464
|
+
remoteAddr: `${this.socket.remoteAddress ?? "unknown"}:${String(this.socket.remotePort ?? 0)}`,
|
|
4465
|
+
muted: this.muted,
|
|
4466
|
+
intent: this.declaration.intent
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
/**
|
|
4381
4470
|
* True only for a session that dialed the DETECTION path
|
|
4382
4471
|
* (`DETECTION_MUTED_PATH_SUFFIX`). Read by
|
|
4383
4472
|
* `RtspRestreamer.servePendingSessionsRtp` to exempt this session from the
|
|
@@ -14251,6 +14340,13 @@ var StreamBroker = class StreamBroker {
|
|
|
14251
14340
|
getLocalStreamUrl() {
|
|
14252
14341
|
return this.pipeServer.getUrl();
|
|
14253
14342
|
}
|
|
14343
|
+
/**
|
|
14344
|
+
* External RTSP restream clients playing on this broker right now — the
|
|
14345
|
+
* restream half of the per-camera live-viewer count (`live-viewers.ts`).
|
|
14346
|
+
*/
|
|
14347
|
+
countExternalRestreamViewers() {
|
|
14348
|
+
return this.rtspRestreamer.countExternalViewers();
|
|
14349
|
+
}
|
|
14254
14350
|
/** Get the RTSP restreamer instance (for RtspListenServer registration) */
|
|
14255
14351
|
getRtspRestreamer() {
|
|
14256
14352
|
return this.rtspRestreamer;
|
|
@@ -18871,6 +18967,25 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18871
18967
|
};
|
|
18872
18968
|
return broker.listClients();
|
|
18873
18969
|
}
|
|
18970
|
+
/**
|
|
18971
|
+
* People watching each camera live right now (cap `getLiveViewerCounts`).
|
|
18972
|
+
* Two planes, one definition (`live-viewers.ts`): the WebRTC server's live
|
|
18973
|
+
* viewer sessions plus every broker's external RTSP restream clients, summed
|
|
18974
|
+
* per camera over all of its profile brokers. Both are read off live session
|
|
18975
|
+
* maps — no stored counter — so an ended session cannot be left counted.
|
|
18976
|
+
*/
|
|
18977
|
+
async getLiveViewerCounts() {
|
|
18978
|
+
const rtsp = /* @__PURE__ */ new Map();
|
|
18979
|
+
for (const [brokerId, broker] of this.brokers) {
|
|
18980
|
+
const n = broker.countExternalRestreamViewers();
|
|
18981
|
+
const deviceId = deviceIdFromBrokerId$1(brokerId);
|
|
18982
|
+
if (n > 0 && deviceId !== void 0) rtsp.set(deviceId, (rtsp.get(deviceId) ?? 0) + n);
|
|
18983
|
+
}
|
|
18984
|
+
return [...mergeViewerCounts([this.webrtcServer?.countLiveViewers() ?? /* @__PURE__ */ new Map(), rtsp])].map(([deviceId, viewers]) => ({
|
|
18985
|
+
deviceId,
|
|
18986
|
+
viewers
|
|
18987
|
+
})).sort((a, b) => a.deviceId - b.deviceId);
|
|
18988
|
+
}
|
|
18874
18989
|
async killClient(input) {
|
|
18875
18990
|
const broker = this.brokers.get(input.brokerId);
|
|
18876
18991
|
if (!broker) return { killed: false };
|
|
@@ -34729,6 +34844,7 @@ var BrokerWebrtcServer = class {
|
|
|
34729
34844
|
});
|
|
34730
34845
|
const entry = {
|
|
34731
34846
|
session,
|
|
34847
|
+
consumerKind: baseAttribution.kind,
|
|
34732
34848
|
brokerId,
|
|
34733
34849
|
unsubBroker,
|
|
34734
34850
|
unsubRtp,
|
|
@@ -35155,6 +35271,29 @@ var BrokerWebrtcServer = class {
|
|
|
35155
35271
|
getSessionCount() {
|
|
35156
35272
|
return this.sessions.size;
|
|
35157
35273
|
}
|
|
35274
|
+
/**
|
|
35275
|
+
* People watching LIVE through this server, per camera (`live-viewers.ts`):
|
|
35276
|
+
* sessions of a viewer kind whose track is on the live edge. A session on a
|
|
35277
|
+
* recorded playhead is not watching live; the system's own subscriptions
|
|
35278
|
+
* (warm-up, linger ghost, egress-transcode legs) are never a SESSION here, so
|
|
35279
|
+
* they cannot count.
|
|
35280
|
+
*
|
|
35281
|
+
* Derived from `this.sessions` at call time, the map `cleanupSession`
|
|
35282
|
+
* maintains for every teardown path (explicit close, ICE failure, the peer
|
|
35283
|
+
* vanishing — all end in `session.onClosed`), so there is no second counter
|
|
35284
|
+
* that could leak.
|
|
35285
|
+
*/
|
|
35286
|
+
countLiveViewers() {
|
|
35287
|
+
const counts = /* @__PURE__ */ new Map();
|
|
35288
|
+
for (const entry of this.sessions.values()) {
|
|
35289
|
+
if (!isViewerConsumerKind(entry.consumerKind)) continue;
|
|
35290
|
+
if ((entry.timeline?.currentMode ?? "live") !== "live") continue;
|
|
35291
|
+
const deviceId = deviceIdFromBrokerId(entry.brokerId);
|
|
35292
|
+
if (deviceId === void 0) continue;
|
|
35293
|
+
counts.set(deviceId, (counts.get(deviceId) ?? 0) + 1);
|
|
35294
|
+
}
|
|
35295
|
+
return counts;
|
|
35296
|
+
}
|
|
35158
35297
|
/** Live signaling state for a session — used by the webrtc-session
|
|
35159
35298
|
* provider's getSessionState. Returns a null pending when unknown. */
|
|
35160
35299
|
getSessionRenegotiationState(sessionId) {
|
|
@@ -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-C4Cp_OAY.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.362",
|
|
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",
|
package/python/inference_pool.py
CHANGED
|
@@ -61,7 +61,6 @@ from __future__ import annotations
|
|
|
61
61
|
import asyncio
|
|
62
62
|
import concurrent.futures
|
|
63
63
|
import contextlib
|
|
64
|
-
import fcntl
|
|
65
64
|
import gc
|
|
66
65
|
import io
|
|
67
66
|
import json
|
|
@@ -80,6 +79,7 @@ from typing import Any, Awaitable, Callable, Optional
|
|
|
80
79
|
import numpy as np
|
|
81
80
|
from PIL import Image
|
|
82
81
|
|
|
82
|
+
import pool_platform
|
|
83
83
|
from postprocessors import POSTPROCESSORS
|
|
84
84
|
|
|
85
85
|
# ---------------------------------------------------------------------------
|
|
@@ -574,9 +574,12 @@ def compile_cache_lock(
|
|
|
574
574
|
deadline = time.monotonic() + max(0.0, timeout_sec)
|
|
575
575
|
while True:
|
|
576
576
|
try:
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
577
|
+
if pool_platform.try_lock_exclusive(handle.fileno()):
|
|
578
|
+
held = True
|
|
579
|
+
break
|
|
580
|
+
if time.monotonic() >= deadline:
|
|
581
|
+
break
|
|
582
|
+
time.sleep(_COMPILE_CACHE_LOCK_POLL_SEC)
|
|
580
583
|
except OSError:
|
|
581
584
|
if time.monotonic() >= deadline:
|
|
582
585
|
break
|
|
@@ -589,7 +592,7 @@ def compile_cache_lock(
|
|
|
589
592
|
if handle is not None:
|
|
590
593
|
try:
|
|
591
594
|
if held:
|
|
592
|
-
|
|
595
|
+
pool_platform.unlock(handle.fileno())
|
|
593
596
|
except OSError:
|
|
594
597
|
pass
|
|
595
598
|
try:
|
|
@@ -2344,6 +2347,9 @@ def _read_self_memory() -> dict:
|
|
|
2344
2347
|
Partial results are fine — an empty dict means the read failed entirely.
|
|
2345
2348
|
"""
|
|
2346
2349
|
out: dict = {}
|
|
2350
|
+
if sys.platform == "win32":
|
|
2351
|
+
# No `resource` and no /proc on Windows: psapi has the same numbers.
|
|
2352
|
+
return pool_platform.read_windows_memory()
|
|
2347
2353
|
try:
|
|
2348
2354
|
import resource
|
|
2349
2355
|
|
|
@@ -3000,14 +3006,7 @@ class CommandRouter:
|
|
|
3000
3006
|
|
|
3001
3007
|
async def _run() -> None:
|
|
3002
3008
|
loop = asyncio.get_event_loop()
|
|
3003
|
-
reader =
|
|
3004
|
-
protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
|
|
3005
|
-
await loop.connect_read_pipe(lambda: protocol, sys.stdin.buffer)
|
|
3006
|
-
|
|
3007
|
-
transport, stdout_protocol = await loop.connect_write_pipe(
|
|
3008
|
-
asyncio.streams.FlowControlMixin, sys.stdout.buffer,
|
|
3009
|
-
)
|
|
3010
|
-
stream_writer = asyncio.StreamWriter(transport, stdout_protocol, None, loop)
|
|
3009
|
+
reader, stream_writer = await pool_platform.open_stdio(loop)
|
|
3011
3010
|
writer = ResponseWriter(stream_writer)
|
|
3012
3011
|
|
|
3013
3012
|
t_start = time.perf_counter()
|