@camstack/addon-pipeline 1.2.361 → 1.2.363
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-DWFL7iBE.mjs → custom-model-catalog-DHFPSu0E.mjs} +2 -2
- package/dist/{custom-model-catalog-BsuqZLaN.js → custom-model-catalog-nx2TXAGS.js} +2 -2
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-73R05PFN.mjs → dist-BX9VTm12.mjs} +278 -4
- package/dist/{dist-DFGiI5lr.js → dist-CGOb--t-.js} +278 -4
- package/dist/{model-catalogs-BefhGOAc.js → model-catalogs-DHV48cdY.js} +1 -1
- package/dist/{model-catalogs-OLyh3TdB.mjs → model-catalogs-DNL4ydAg.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-C0-Ik_JH.mjs → node-C8bbmV3c.mjs} +1 -1
- package/dist/{node-CdOP9ELN.js → node-UBMRK8SK.js} +1 -1
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-DO-ZBWdK.js → process-memory-1BwIq2Ks.js} +1 -1
- package/dist/{process-memory-DKKVQV5j.mjs → process-memory-BHG2uCeq.mjs} +1 -1
- package/dist/recorder/index.js +7 -3
- package/dist/recorder/index.mjs +7 -3
- package/dist/{segment-demux-js-C_27CmCq.mjs → segment-demux-js-BhnT__dA.mjs} +1 -1
- package/dist/{segment-demux-js-Dr3e9r4m.js → segment-demux-js-PkDP-xQg.js} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DJeFG_hB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-vdmrukBk.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ScPS8ZWM.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-Cq5NPjBX.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-Buho1uzb.mjs → hostInit-D_DBY5wu.mjs} +2 -2
- package/dist/stream-broker/index.js +247 -41
- package/dist/stream-broker/index.mjs +247 -41
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-XWaPHrfA.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-qyhFaM-7.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-CGOb--t-.js");
|
|
7
|
+
const require_node = require("../node-UBMRK8SK.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-PkDP-xQg.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
|
|
@@ -4541,15 +4630,16 @@ var RtspSession = class {
|
|
|
4541
4630
|
*
|
|
4542
4631
|
* ## Why sharing the payload is safe, and what would make it unsafe
|
|
4543
4632
|
*
|
|
4544
|
-
* The payload buffer handed here is
|
|
4545
|
-
* broker's `rtpRing` for the life of a GOP (`captureRtpForPreBuffer` pushes
|
|
4546
|
-
* the same object). A pending `socket.write` cannot outlive that, so the
|
|
4547
|
-
* lifetime requirement this adds is strictly weaker than one the broker
|
|
4548
|
-
* already meets. Its provenance is a private allocation per packet —
|
|
4633
|
+
* The payload buffer handed here is a private allocation per packet —
|
|
4549
4634
|
* `Buffer.from(readBuffer.subarray(...))` in `rtsp-client.ts`'s `onData`, for
|
|
4550
4635
|
* video and audio alike — never a view of the reader's socket buffer, which
|
|
4551
4636
|
* advances on the next read. Nothing in the broker writes into a captured
|
|
4552
4637
|
* packet: every mutation in this path targets a freshly allocated buffer.
|
|
4638
|
+
* (The broker's `rtpRing` keeps its OWN copy of a pooled packet —
|
|
4639
|
+
* `detachFromPool`, so a ring entry never pins a shared pool slab — so the
|
|
4640
|
+
* ring no longer vouches for THIS object's lifetime, and it does not need
|
|
4641
|
+
* to: a pending write holds its own reference, and the rule below is about
|
|
4642
|
+
* mutation, not retention.)
|
|
4553
4643
|
*
|
|
4554
4644
|
* The rule to keep: **`socket.write` forbids MUTATION before the write
|
|
4555
4645
|
* completes, not retention.** So the header, which IS built per call, must
|
|
@@ -11206,6 +11296,49 @@ var AudioCodecSession = class {
|
|
|
11206
11296
|
}
|
|
11207
11297
|
};
|
|
11208
11298
|
//#endregion
|
|
11299
|
+
//#region src/stream-broker/stream-broker/owned-buffer.ts
|
|
11300
|
+
/**
|
|
11301
|
+
* Buffers that enter a LONG-LIVED ring own their backing store.
|
|
11302
|
+
*
|
|
11303
|
+
* Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
|
|
11304
|
+
* below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
|
|
11305
|
+
* That is the right choice for the ingest path — a start code, a 4-byte
|
|
11306
|
+
* interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
|
|
11307
|
+
* within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
|
|
11308
|
+
* for 60 s in a clip ring pins its whole slab, and with it every short-lived
|
|
11309
|
+
* packet that happened to be allocated beside it. On the hub that held
|
|
11310
|
+
* 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
|
|
11311
|
+
* pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
|
|
11312
|
+
*
|
|
11313
|
+
* The ring byte caps counted payload bytes, so they never saw it. Both rings
|
|
11314
|
+
* now pass every entry through {@link detachFromPool} on the way in and
|
|
11315
|
+
* account it with {@link backingStoreBytes}, so the cap measures what the
|
|
11316
|
+
* entry costs. Short-lived traffic stays pooled.
|
|
11317
|
+
*/
|
|
11318
|
+
/** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
|
|
11319
|
+
function ownsBackingStore(buf) {
|
|
11320
|
+
return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
|
|
11321
|
+
}
|
|
11322
|
+
/**
|
|
11323
|
+
* What retaining `buf` really costs: its own length when it owns its
|
|
11324
|
+
* ArrayBuffer, the whole shared slab when it is a view of one.
|
|
11325
|
+
*/
|
|
11326
|
+
function backingStoreBytes(buf) {
|
|
11327
|
+
return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
|
|
11328
|
+
}
|
|
11329
|
+
/**
|
|
11330
|
+
* `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
|
|
11331
|
+
* buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
|
|
11332
|
+
* runtime detail), else a copy that does. One copy, never two: a buffer that is
|
|
11333
|
+
* already unpooled is returned as-is.
|
|
11334
|
+
*/
|
|
11335
|
+
function detachFromPool(buf) {
|
|
11336
|
+
if (ownsBackingStore(buf)) return buf;
|
|
11337
|
+
const owned = Buffer.allocUnsafeSlow(buf.length);
|
|
11338
|
+
buf.copy(owned);
|
|
11339
|
+
return owned;
|
|
11340
|
+
}
|
|
11341
|
+
//#endregion
|
|
11209
11342
|
//#region src/stream-broker/stream-broker/encoded-ring-buffer.ts
|
|
11210
11343
|
/**
|
|
11211
11344
|
* Time-based ring buffer for encoded video packets.
|
|
@@ -11216,6 +11349,12 @@ var AudioCodecSession = class {
|
|
|
11216
11349
|
*
|
|
11217
11350
|
* The buffer always starts from the most recent keyframe to ensure
|
|
11218
11351
|
* consumers can decode the returned data.
|
|
11352
|
+
*
|
|
11353
|
+
* Every entry OWNS its backing store (`owned-buffer.ts`): a sub-stream NAL
|
|
11354
|
+
* arrives as a view of a shared 8 KiB pool slab, and a ring that kept it for
|
|
11355
|
+
* 60 s pinned the slab and every short-lived packet beside it — the clip
|
|
11356
|
+
* rings of ~70 brokers were the largest holder of the hub broker's
|
|
11357
|
+
* 550–980 MB of ArrayBuffers. The byte budget charges what an entry pins.
|
|
11219
11358
|
*/
|
|
11220
11359
|
var EncodedRingBuffer = class {
|
|
11221
11360
|
packets = [];
|
|
@@ -11247,13 +11386,25 @@ var EncodedRingBuffer = class {
|
|
|
11247
11386
|
getDuration() {
|
|
11248
11387
|
return this.maxDurationMs / 1e3;
|
|
11249
11388
|
}
|
|
11250
|
-
/**
|
|
11389
|
+
/**
|
|
11390
|
+
* Push a new packet into the buffer and evict expired ones.
|
|
11391
|
+
*
|
|
11392
|
+
* A pooled payload is copied into its own allocation on the way in; one
|
|
11393
|
+
* that already owns its ArrayBuffer is stored as-is. The consumers fanned
|
|
11394
|
+
* out from the same packet keep the original, so the copy is the ring's
|
|
11395
|
+
* alone and is freed with it.
|
|
11396
|
+
*/
|
|
11251
11397
|
push(packet) {
|
|
11252
|
-
|
|
11253
|
-
|
|
11398
|
+
const data = detachFromPool(packet.data);
|
|
11399
|
+
const stored = data === packet.data ? packet : {
|
|
11400
|
+
...packet,
|
|
11401
|
+
data
|
|
11402
|
+
};
|
|
11403
|
+
this.packets.push(stored);
|
|
11404
|
+
this.bytes += backingStoreBytes(data);
|
|
11254
11405
|
this.evict();
|
|
11255
11406
|
}
|
|
11256
|
-
/** What the retained window currently costs, in bytes. */
|
|
11407
|
+
/** What the retained window currently costs, in bytes of backing store. */
|
|
11257
11408
|
getBufferedBytes() {
|
|
11258
11409
|
return this.bytes;
|
|
11259
11410
|
}
|
|
@@ -11331,14 +11482,14 @@ var EncodedRingBuffer = class {
|
|
|
11331
11482
|
let removeCount = 0;
|
|
11332
11483
|
while (removeCount < this.packets.length && (this.packets[removeCount].pts < cutoff || this.packets[removeCount].pts > newest)) removeCount++;
|
|
11333
11484
|
if (removeCount > 0) {
|
|
11334
|
-
for (let i = 0; i < removeCount; i++) this.bytes -= this.packets[i].data
|
|
11485
|
+
for (let i = 0; i < removeCount; i++) this.bytes -= backingStoreBytes(this.packets[i].data);
|
|
11335
11486
|
this.packets.splice(0, removeCount);
|
|
11336
11487
|
}
|
|
11337
11488
|
if (this.maxBytes > 0 && this.bytes > this.maxBytes) {
|
|
11338
11489
|
this.clampedByBytes = true;
|
|
11339
11490
|
let dropped = 0;
|
|
11340
11491
|
while (dropped < this.packets.length && this.bytes > this.maxBytes) {
|
|
11341
|
-
this.bytes -= this.packets[dropped].data
|
|
11492
|
+
this.bytes -= backingStoreBytes(this.packets[dropped].data);
|
|
11342
11493
|
dropped++;
|
|
11343
11494
|
}
|
|
11344
11495
|
if (dropped > 0) this.packets.splice(0, dropped);
|
|
@@ -13774,8 +13925,9 @@ var StreamBroker = class StreamBroker {
|
|
|
13774
13925
|
captureRtpForPreBuffer(rtpData) {
|
|
13775
13926
|
const marker = rtpData.length > 1 && (rtpData[1] & 128) !== 0;
|
|
13776
13927
|
if (this.rtpRingPrevMarker || this.rtpRing.length === 0) this.rtpRingCurAuStart = this.rtpRing.length;
|
|
13777
|
-
|
|
13778
|
-
this.
|
|
13928
|
+
const owned = detachFromPool(rtpData);
|
|
13929
|
+
this.rtpRing.push(owned);
|
|
13930
|
+
this.rtpRingBytes += backingStoreBytes(owned);
|
|
13779
13931
|
if (this._lastNalParamSet || this._lastNalKeyframe) {
|
|
13780
13932
|
const auStart = this.rtpRingCurAuStart;
|
|
13781
13933
|
const lastMark = this.rtpRingKeyframeMarks[this.rtpRingKeyframeMarks.length - 1];
|
|
@@ -13953,18 +14105,18 @@ var StreamBroker = class StreamBroker {
|
|
|
13953
14105
|
this.reportPreRollClamp(budget, retentionMs);
|
|
13954
14106
|
const historyEnd = this.rtpRingKeyframeMarks[this.rtpRingKeyframeMarks.length - 1].index;
|
|
13955
14107
|
let retained = 0;
|
|
13956
|
-
for (let i = cut; i < historyEnd; i++) retained += this.rtpRing[i]
|
|
14108
|
+
for (let i = cut; i < historyEnd; i++) retained += backingStoreBytes(this.rtpRing[i]);
|
|
13957
14109
|
let markIdx = this.rtpRingKeyframeMarks.findIndex((m) => m.index === cut);
|
|
13958
14110
|
while (retained > budget.bytes && markIdx >= 0 && markIdx < this.rtpRingKeyframeMarks.length - 1) {
|
|
13959
14111
|
const nextCut = this.rtpRingKeyframeMarks[markIdx + 1].index;
|
|
13960
|
-
for (let i = cut; i < nextCut; i++) retained -= this.rtpRing[i]
|
|
14112
|
+
for (let i = cut; i < nextCut; i++) retained -= backingStoreBytes(this.rtpRing[i]);
|
|
13961
14113
|
cut = nextCut;
|
|
13962
14114
|
markIdx++;
|
|
13963
14115
|
}
|
|
13964
14116
|
}
|
|
13965
14117
|
if (cut <= 0) return;
|
|
13966
14118
|
const dropped = this.rtpRing.splice(0, cut);
|
|
13967
|
-
for (const b of dropped) this.rtpRingBytes -= b
|
|
14119
|
+
for (const b of dropped) this.rtpRingBytes -= backingStoreBytes(b);
|
|
13968
14120
|
this.rtpRingKeyframeMarks = this.rtpRingKeyframeMarks.filter((mark) => mark.index >= cut).map((mark) => ({
|
|
13969
14121
|
index: mark.index - cut,
|
|
13970
14122
|
ts: mark.ts
|
|
@@ -14255,6 +14407,13 @@ var StreamBroker = class StreamBroker {
|
|
|
14255
14407
|
getLocalStreamUrl() {
|
|
14256
14408
|
return this.pipeServer.getUrl();
|
|
14257
14409
|
}
|
|
14410
|
+
/**
|
|
14411
|
+
* External RTSP restream clients playing on this broker right now — the
|
|
14412
|
+
* restream half of the per-camera live-viewer count (`live-viewers.ts`).
|
|
14413
|
+
*/
|
|
14414
|
+
countExternalRestreamViewers() {
|
|
14415
|
+
return this.rtspRestreamer.countExternalViewers();
|
|
14416
|
+
}
|
|
14258
14417
|
/** Get the RTSP restreamer instance (for RtspListenServer registration) */
|
|
14259
14418
|
getRtspRestreamer() {
|
|
14260
14419
|
return this.rtspRestreamer;
|
|
@@ -14297,11 +14456,15 @@ var StreamBroker = class StreamBroker {
|
|
|
14297
14456
|
if (clamped === this.clipRetentionSec) return;
|
|
14298
14457
|
const previous = this.clipRetentionSec;
|
|
14299
14458
|
this.clipRetentionSec = clamped;
|
|
14300
|
-
this.logger?.info(clamped === 0 ? "Clip retention off" : "Clip retention set", {
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14459
|
+
this.logger?.info(clamped === 0 ? "Clip retention off" : "Clip retention set", {
|
|
14460
|
+
tags: { deviceId: this.numericDeviceId },
|
|
14461
|
+
meta: {
|
|
14462
|
+
seconds: clamped,
|
|
14463
|
+
requested: seconds,
|
|
14464
|
+
previousSeconds: previous,
|
|
14465
|
+
capBytes: StreamBroker.CLIP_RING_MAX_BYTES
|
|
14466
|
+
}
|
|
14467
|
+
});
|
|
14305
14468
|
if (clamped === 0) {
|
|
14306
14469
|
this.clipBuffer = null;
|
|
14307
14470
|
return;
|
|
@@ -18875,6 +19038,25 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18875
19038
|
};
|
|
18876
19039
|
return broker.listClients();
|
|
18877
19040
|
}
|
|
19041
|
+
/**
|
|
19042
|
+
* People watching each camera live right now (cap `getLiveViewerCounts`).
|
|
19043
|
+
* Two planes, one definition (`live-viewers.ts`): the WebRTC server's live
|
|
19044
|
+
* viewer sessions plus every broker's external RTSP restream clients, summed
|
|
19045
|
+
* per camera over all of its profile brokers. Both are read off live session
|
|
19046
|
+
* maps — no stored counter — so an ended session cannot be left counted.
|
|
19047
|
+
*/
|
|
19048
|
+
async getLiveViewerCounts() {
|
|
19049
|
+
const rtsp = /* @__PURE__ */ new Map();
|
|
19050
|
+
for (const [brokerId, broker] of this.brokers) {
|
|
19051
|
+
const n = broker.countExternalRestreamViewers();
|
|
19052
|
+
const deviceId = deviceIdFromBrokerId$1(brokerId);
|
|
19053
|
+
if (n > 0 && deviceId !== void 0) rtsp.set(deviceId, (rtsp.get(deviceId) ?? 0) + n);
|
|
19054
|
+
}
|
|
19055
|
+
return [...mergeViewerCounts([this.webrtcServer?.countLiveViewers() ?? /* @__PURE__ */ new Map(), rtsp])].map(([deviceId, viewers]) => ({
|
|
19056
|
+
deviceId,
|
|
19057
|
+
viewers
|
|
19058
|
+
})).sort((a, b) => a.deviceId - b.deviceId);
|
|
19059
|
+
}
|
|
18878
19060
|
async killClient(input) {
|
|
18879
19061
|
const broker = this.brokers.get(input.brokerId);
|
|
18880
19062
|
if (!broker) return { killed: false };
|
|
@@ -34733,6 +34915,7 @@ var BrokerWebrtcServer = class {
|
|
|
34733
34915
|
});
|
|
34734
34916
|
const entry = {
|
|
34735
34917
|
session,
|
|
34918
|
+
consumerKind: baseAttribution.kind,
|
|
34736
34919
|
brokerId,
|
|
34737
34920
|
unsubBroker,
|
|
34738
34921
|
unsubRtp,
|
|
@@ -35159,6 +35342,29 @@ var BrokerWebrtcServer = class {
|
|
|
35159
35342
|
getSessionCount() {
|
|
35160
35343
|
return this.sessions.size;
|
|
35161
35344
|
}
|
|
35345
|
+
/**
|
|
35346
|
+
* People watching LIVE through this server, per camera (`live-viewers.ts`):
|
|
35347
|
+
* sessions of a viewer kind whose track is on the live edge. A session on a
|
|
35348
|
+
* recorded playhead is not watching live; the system's own subscriptions
|
|
35349
|
+
* (warm-up, linger ghost, egress-transcode legs) are never a SESSION here, so
|
|
35350
|
+
* they cannot count.
|
|
35351
|
+
*
|
|
35352
|
+
* Derived from `this.sessions` at call time, the map `cleanupSession`
|
|
35353
|
+
* maintains for every teardown path (explicit close, ICE failure, the peer
|
|
35354
|
+
* vanishing — all end in `session.onClosed`), so there is no second counter
|
|
35355
|
+
* that could leak.
|
|
35356
|
+
*/
|
|
35357
|
+
countLiveViewers() {
|
|
35358
|
+
const counts = /* @__PURE__ */ new Map();
|
|
35359
|
+
for (const entry of this.sessions.values()) {
|
|
35360
|
+
if (!isViewerConsumerKind(entry.consumerKind)) continue;
|
|
35361
|
+
if ((entry.timeline?.currentMode ?? "live") !== "live") continue;
|
|
35362
|
+
const deviceId = deviceIdFromBrokerId(entry.brokerId);
|
|
35363
|
+
if (deviceId === void 0) continue;
|
|
35364
|
+
counts.set(deviceId, (counts.get(deviceId) ?? 0) + 1);
|
|
35365
|
+
}
|
|
35366
|
+
return counts;
|
|
35367
|
+
}
|
|
35162
35368
|
/** Live signaling state for a session — used by the webrtc-session
|
|
35163
35369
|
* provider's getSessionState. Returns a null pending when unknown. */
|
|
35164
35370
|
getSessionRenegotiationState(sessionId) {
|