@camstack/addon-pipeline 1.2.198 → 1.2.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-B5v8qEI0.js → default-detection-model-BsmxtyZx.js} +1 -1
- package/dist/{default-detection-model-CASpAESz.mjs → default-detection-model-c1LY6thP.mjs} +1 -1
- package/dist/detection-pipeline/index.js +5 -5
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-BxP0SvPz.js → dist-D2YTTkRf.js} +436 -419
- package/dist/{dist-oFIHnD88.mjs → dist-mEsrAAc3.mjs} +436 -419
- package/dist/{lazy-sharp-VKOG3WW4.js → lazy-sharp-B5bW34uC.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-B4zETsJ9.mjs → node-CUxep5tc.mjs} +1 -1
- package/dist/{node-BIKrNEtS.js → node-x3YokJkD.js} +1 -1
- package/dist/pipeline-runner/index.js +8 -7
- package/dist/pipeline-runner/index.mjs +7 -6
- package/dist/{process-memory-D9xYwm6L.mjs → process-memory-BCqpTruN.mjs} +1 -1
- package/dist/{process-memory-CMHOmj75.js → process-memory-CFc4D9A2.js} +1 -1
- package/dist/recorder/index.js +169 -334
- package/dist/recorder/index.mjs +164 -329
- package/dist/restream-intent-B1Difcnq.js +313 -0
- package/dist/restream-intent-DWe2kInY.mjs +248 -0
- package/dist/{prebuffer-DmBYRQWg.mjs → retire-root-keys-Csb8z2f3.js} +12 -64
- package/dist/{prebuffer-DqdeLSsk.js → retire-root-keys-DZvlso2l.mjs} +1 -99
- package/dist/{segment-demux-js-DLwL1vmq.js → segment-demux-js-DUZVURQd.js} +1 -1
- package/dist/{segment-demux-js-DZysrgdC.mjs → segment-demux-js-xHtB450F.mjs} +1 -1
- package/dist/session-decode/decode-worker-child.js +25 -5
- package/dist/session-decode/decode-worker-child.mjs +24 -4
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2eTdxvd.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CLXImQ3R.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-NqqfpkYF.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-Dj_qoc1e.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D62O5bRa.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-C8tu7NS4.mjs → hostInit-D_cZQhBA.mjs} +3 -3
- package/dist/stream-broker/index.js +181 -48
- package/dist/stream-broker/index.mjs +172 -39
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-D2mMjUXv.js → worker-protocol-CRDaBlJl.js} +1 -1
- package/dist/{worker-protocol-DFlZm_wr.mjs → worker-protocol-oUamwZSn.mjs} +1 -1
- package/package.json +1 -1
- package/dist/restream-intent-B4BXZra7.mjs +0 -72
- package/dist/restream-intent-Cv9x3jmu.js +0 -89
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BcjLrxhF.mjs +0 -26
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.162",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.82",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.130",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -2,12 +2,12 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_node = require("../node-
|
|
7
|
-
const require_restream_intent = require("../restream-intent-
|
|
5
|
+
const require_dist = require("../dist-D2YTTkRf.js");
|
|
6
|
+
const require_node = require("../node-x3YokJkD.js");
|
|
7
|
+
const require_restream_intent = require("../restream-intent-B1Difcnq.js");
|
|
8
8
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
9
|
-
const
|
|
10
|
-
const require_segment_demux_js = require("../segment-demux-js-
|
|
9
|
+
const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
|
|
10
|
+
const require_segment_demux_js = require("../segment-demux-js-DUZVURQd.js");
|
|
11
11
|
let node_os = require("node:os");
|
|
12
12
|
node_os = require_dist.__toESM(node_os);
|
|
13
13
|
let node_crypto = require("node:crypto");
|
|
@@ -370,7 +370,7 @@ var BROKER_PROBE_SNAPSHOTS_SPEC = {
|
|
|
370
370
|
loadLimit: BROKER_ROWS_LIMIT
|
|
371
371
|
};
|
|
372
372
|
function probeSnapshotsStore(store, logger) {
|
|
373
|
-
return new
|
|
373
|
+
return new require_retire_root_keys.RowMapStore({
|
|
374
374
|
spec: BROKER_PROBE_SNAPSHOTS_SPEC,
|
|
375
375
|
store,
|
|
376
376
|
logger
|
|
@@ -412,7 +412,7 @@ var BROKER_RTSP_TOKENS_SPEC = {
|
|
|
412
412
|
loadLimit: BROKER_ROWS_LIMIT
|
|
413
413
|
};
|
|
414
414
|
function rtspTokensStore(store, logger) {
|
|
415
|
-
return new
|
|
415
|
+
return new require_retire_root_keys.RowMapStore({
|
|
416
416
|
spec: BROKER_RTSP_TOKENS_SPEC,
|
|
417
417
|
store,
|
|
418
418
|
logger
|
|
@@ -453,7 +453,7 @@ var BROKER_PROFILE_MAP_SPEC = {
|
|
|
453
453
|
loadLimit: BROKER_ROWS_LIMIT
|
|
454
454
|
};
|
|
455
455
|
function profileMapStore(store, logger) {
|
|
456
|
-
return new
|
|
456
|
+
return new require_retire_root_keys.RowMapStore({
|
|
457
457
|
spec: BROKER_PROFILE_MAP_SPEC,
|
|
458
458
|
store,
|
|
459
459
|
logger
|
|
@@ -2070,7 +2070,7 @@ var NOTHING = {
|
|
|
2070
2070
|
function usablePreRoll(value) {
|
|
2071
2071
|
if (value === void 0) return 0;
|
|
2072
2072
|
if (!Number.isFinite(value) || value <= 0) return 0;
|
|
2073
|
-
return Math.min(
|
|
2073
|
+
return Math.min(require_restream_intent.PREBUFFER_MAX_MS, value);
|
|
2074
2074
|
}
|
|
2075
2075
|
/**
|
|
2076
2076
|
* Choose the keyframe access unit a pre-roll burst starts at.
|
|
@@ -2583,6 +2583,18 @@ var DEFAULT_PRIME_BURST_BOUND = {
|
|
|
2583
2583
|
/** Drop/withhold accounting is aggregated and reported at most this often. */
|
|
2584
2584
|
var DROP_REPORT_INTERVAL_MS$1 = 3e4;
|
|
2585
2585
|
/**
|
|
2586
|
+
* How often a HISTORY-FREE attach is reported per stream.
|
|
2587
|
+
*
|
|
2588
|
+
* The baseline population is large and uninteresting one line at a time — a
|
|
2589
|
+
* WebRTC bootstrap, a HomeKit leg, an ffprobe — but it is the control group the
|
|
2590
|
+
* pre-roll cost is measured against, so it cannot be silent either. Throttled
|
|
2591
|
+
* here and NOT throttled for an attach that carried history: that population is
|
|
2592
|
+
* rare (17 of ~130 agganci in the 2026-09-07 census) and it is the one the
|
|
2593
|
+
* question is about, so a rate limit sized for the common case would drop
|
|
2594
|
+
* exactly the samples nobody has.
|
|
2595
|
+
*/
|
|
2596
|
+
var PLAIN_ATTACH_REPORT_INTERVAL_MS = 1e4;
|
|
2597
|
+
/**
|
|
2586
2598
|
* Defensive hard bound on the per-keyframe RTP/NAL cache (both the passthrough
|
|
2587
2599
|
* `lastKeyframeRtpPackets` and the Annex-B `lastKeyframeNals`). A single
|
|
2588
2600
|
* keyframe GOP is at most a few large NALs — well under a megabyte or two even
|
|
@@ -2732,6 +2744,8 @@ var RtspRestreamer = class {
|
|
|
2732
2744
|
dropsLiveLatched = 0;
|
|
2733
2745
|
dropsLiveClosed = 0;
|
|
2734
2746
|
dropReportMs = 0;
|
|
2747
|
+
/** Last history-free attach reported — see {@link PLAIN_ATTACH_REPORT_INTERVAL_MS}. */
|
|
2748
|
+
plainAttachReportMs = 0;
|
|
2735
2749
|
/** Numeric device id parsed once from the brokerId, for `tags.deviceId`. */
|
|
2736
2750
|
deviceId;
|
|
2737
2751
|
constructor(streamName) {
|
|
@@ -2991,7 +3005,7 @@ var RtspRestreamer = class {
|
|
|
2991
3005
|
if (primePackets.length === 0) return;
|
|
2992
3006
|
const firstTs = primePackets[0].data.readUInt32BE(4);
|
|
2993
3007
|
const spanMs = (primePackets[primePackets.length - 1].data.readUInt32BE(4) - firstTs | 0) / 90;
|
|
2994
|
-
const
|
|
3008
|
+
const preRollPlans = /* @__PURE__ */ new Map();
|
|
2995
3009
|
const bound = this.currentPrimeBound();
|
|
2996
3010
|
/**
|
|
2997
3011
|
* Resolve (and memoize) the plan for one intent.
|
|
@@ -3003,28 +3017,33 @@ var RtspRestreamer = class {
|
|
|
3003
3017
|
* current GOP or a supplier bug can, and D57's remedy (join clean at the
|
|
3004
3018
|
* next live keyframe) is the right one for a burst nobody asked for.
|
|
3005
3019
|
*/
|
|
3006
|
-
const planFor = () => {
|
|
3007
|
-
|
|
3020
|
+
const planFor = (requestedMs) => {
|
|
3021
|
+
const memo = preRollPlans.get(requestedMs);
|
|
3022
|
+
if (memo) return memo;
|
|
3008
3023
|
const burst = this.preRollSupplier?.();
|
|
3009
|
-
const window = fromRing && burst && burst.preRollMs > 0 ? burst.packets() : [];
|
|
3024
|
+
const window = fromRing && burst && burst.preRollMs > 0 && requestedMs > 0 ? burst.packets(requestedMs) : [];
|
|
3010
3025
|
const packets = window.length > primePackets.length ? window.map((data) => ({
|
|
3011
3026
|
data,
|
|
3012
3027
|
marker: (data[1] & 128) !== 0
|
|
3013
3028
|
})) : [];
|
|
3014
3029
|
const windowSpanMs = packets.length > 0 ? (packets[packets.length - 1].data.readUInt32BE(4) - packets[0].data.readUInt32BE(4) | 0) / 90 : 0;
|
|
3015
|
-
const
|
|
3030
|
+
const servableMs = Math.min(requestedMs, burst?.preRollMs ?? 0);
|
|
3031
|
+
const windowBound = packets.length > 0 ? preRollSpanBound(bound, servableMs) : bound;
|
|
3016
3032
|
const plan = {
|
|
3017
3033
|
packets,
|
|
3018
3034
|
active: packets.length > 0,
|
|
3019
|
-
|
|
3035
|
+
retainedMs: burst?.preRollMs ?? 0,
|
|
3036
|
+
requestedMs,
|
|
3020
3037
|
spanMs: windowSpanMs,
|
|
3021
3038
|
overBound: packets.length > 0 && (windowSpanMs > windowBound.maxSpanMs || packets.length > windowBound.maxPackets)
|
|
3022
3039
|
};
|
|
3023
|
-
|
|
3040
|
+
preRollPlans.set(requestedMs, plan);
|
|
3024
3041
|
return plan;
|
|
3025
3042
|
};
|
|
3026
3043
|
const served = [];
|
|
3027
3044
|
const servedPreRoll = /* @__PURE__ */ new Map();
|
|
3045
|
+
/** The plan each pre-roll session was actually served, for its attach line. */
|
|
3046
|
+
const servedPlan = /* @__PURE__ */ new Map();
|
|
3028
3047
|
let withheld = 0;
|
|
3029
3048
|
let detectionIntentServed = 0;
|
|
3030
3049
|
for (const sessionId of ready) {
|
|
@@ -3032,8 +3051,9 @@ var RtspRestreamer = class {
|
|
|
3032
3051
|
if (!session) continue;
|
|
3033
3052
|
const detectionIntent = session.isDetectionIntent();
|
|
3034
3053
|
const preRollIntent = session.preRollIntent();
|
|
3035
|
-
|
|
3036
|
-
|
|
3054
|
+
const requestedMs = session.preRollRequestMs();
|
|
3055
|
+
if (preRollIntent !== null && requestedMs > 0) {
|
|
3056
|
+
const plan = planFor(requestedMs);
|
|
3037
3057
|
if (plan.active) {
|
|
3038
3058
|
if (plan.overBound) {
|
|
3039
3059
|
this.dropsWithheldSpan++;
|
|
@@ -3041,6 +3061,7 @@ var RtspRestreamer = class {
|
|
|
3041
3061
|
continue;
|
|
3042
3062
|
}
|
|
3043
3063
|
servedPreRoll.set(sessionId, preRollIntent);
|
|
3064
|
+
servedPlan.set(sessionId, plan);
|
|
3044
3065
|
served.push(sessionId);
|
|
3045
3066
|
this.logger?.info("restream pre-roll served", {
|
|
3046
3067
|
tags: { deviceId: this.deviceId },
|
|
@@ -3049,7 +3070,8 @@ var RtspRestreamer = class {
|
|
|
3049
3070
|
sessionId,
|
|
3050
3071
|
intent: preRollIntent,
|
|
3051
3072
|
consumer: session.getUserAgent() ?? "unknown",
|
|
3052
|
-
|
|
3073
|
+
requestedMs: plan.requestedMs,
|
|
3074
|
+
retainedMs: plan.retainedMs,
|
|
3053
3075
|
spanMs: Math.round(plan.spanMs),
|
|
3054
3076
|
packets: plan.packets.length
|
|
3055
3077
|
}
|
|
@@ -3078,8 +3100,16 @@ var RtspRestreamer = class {
|
|
|
3078
3100
|
}
|
|
3079
3101
|
if (withheld > 0) this.reportDrops(false);
|
|
3080
3102
|
if (served.length === 0) return;
|
|
3081
|
-
/**
|
|
3082
|
-
|
|
3103
|
+
/**
|
|
3104
|
+
* Media actually delivered — the WIDEST window any session took, or `-1`
|
|
3105
|
+
* when nobody asked for history at all.
|
|
3106
|
+
*
|
|
3107
|
+
* A max and not a single plan's span since D382: two sessions on the same
|
|
3108
|
+
* keyframe can be served different windows, and the number this aggregate
|
|
3109
|
+
* line owes its reader is the largest burst this stream let out. The
|
|
3110
|
+
* per-attach truth is on `restream attach live-edge`, one line per session.
|
|
3111
|
+
*/
|
|
3112
|
+
const servedSpanMs = preRollPlans.size === 0 ? -1 : Math.round(Math.max(...[...preRollPlans.values()].map((p) => p.spanMs)));
|
|
3083
3113
|
/** Which declared intents joined this burst — `0` is "none", not "off". */
|
|
3084
3114
|
const intentsServed = new Set(servedPreRoll.values());
|
|
3085
3115
|
this.logger?.info("restream prime (lat-probe)", {
|
|
@@ -3106,15 +3136,30 @@ var RtspRestreamer = class {
|
|
|
3106
3136
|
prime: true,
|
|
3107
3137
|
keyframeAuStart: false
|
|
3108
3138
|
}));
|
|
3109
|
-
const preRollItems =
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3139
|
+
const preRollItems = /* @__PURE__ */ new Map();
|
|
3140
|
+
const itemsFor = (plan) => {
|
|
3141
|
+
const memo = preRollItems.get(plan.requestedMs);
|
|
3142
|
+
if (memo) return memo;
|
|
3143
|
+
const built = plan.packets.map((packet) => ({
|
|
3144
|
+
packet,
|
|
3145
|
+
prime: true,
|
|
3146
|
+
keyframeAuStart: false
|
|
3147
|
+
}));
|
|
3148
|
+
preRollItems.set(plan.requestedMs, built);
|
|
3149
|
+
return built;
|
|
3150
|
+
};
|
|
3114
3151
|
for (const sessionId of served) {
|
|
3115
3152
|
this.pendingKeyframe.delete(sessionId);
|
|
3116
|
-
const intent = servedPreRoll.get(sessionId);
|
|
3117
|
-
|
|
3153
|
+
const intent = servedPreRoll.get(sessionId) ?? null;
|
|
3154
|
+
const plan = servedPlan.get(sessionId) ?? null;
|
|
3155
|
+
this.startPrimeReplay(sessionId, plan ? itemsFor(plan) : items, intent, {
|
|
3156
|
+
intent,
|
|
3157
|
+
requestedMs: plan?.requestedMs ?? 0,
|
|
3158
|
+
retainedMs: plan?.retainedMs ?? this.preRollSupplier?.().preRollMs ?? 0,
|
|
3159
|
+
preRollSpanMs: plan ? Math.round(plan.spanMs - spanMs) : 0,
|
|
3160
|
+
burstSpanMs: Math.round(plan ? plan.spanMs : spanMs),
|
|
3161
|
+
gopSpanMs: Math.round(spanMs)
|
|
3162
|
+
});
|
|
3118
3163
|
}
|
|
3119
3164
|
}
|
|
3120
3165
|
/**
|
|
@@ -3127,7 +3172,7 @@ var RtspRestreamer = class {
|
|
|
3127
3172
|
* session that went away — aborts the whole replay rather than punching holes
|
|
3128
3173
|
* in the GOP: the session is left latched and rejoins at the next keyframe AU.
|
|
3129
3174
|
*/
|
|
3130
|
-
startPrimeReplay(sessionId, items, preRollIntent) {
|
|
3175
|
+
startPrimeReplay(sessionId, items, preRollIntent, attach) {
|
|
3131
3176
|
const replay = new PacedRtpReplay((item) => {
|
|
3132
3177
|
const session = this.sessions.get(sessionId);
|
|
3133
3178
|
if (!session) {
|
|
@@ -3157,6 +3202,7 @@ var RtspRestreamer = class {
|
|
|
3157
3202
|
durationMs: stats.durationMs
|
|
3158
3203
|
}
|
|
3159
3204
|
});
|
|
3205
|
+
this.reportAttach(sessionId, attach, stats);
|
|
3160
3206
|
this.logger?.debug("restream prime drained", {
|
|
3161
3207
|
tags: { deviceId: this.deviceId },
|
|
3162
3208
|
meta: {
|
|
@@ -3175,6 +3221,58 @@ var RtspRestreamer = class {
|
|
|
3175
3221
|
this.activeReplays.set(sessionId, replay);
|
|
3176
3222
|
replay.start(items);
|
|
3177
3223
|
}
|
|
3224
|
+
/**
|
|
3225
|
+
* ONE LINE PER ATTACH: what this session asked for, what it was handed, and
|
|
3226
|
+
* how long it took to reach the live edge.
|
|
3227
|
+
*
|
|
3228
|
+
* The question this exists to answer is "does the pre-roll delay the first
|
|
3229
|
+
* inference, and by how much" — and it could not be answered before. The only
|
|
3230
|
+
* measurement in reach was `pipeline stats`, a 10 s window whose averages
|
|
3231
|
+
* dissolve a 4 s transient by construction, and the broker's own lines were
|
|
3232
|
+
* per-STREAM aggregates that named a window without naming a session or a
|
|
3233
|
+
* duration. Seventeen served bursts inside ninety minutes of ten-second
|
|
3234
|
+
* averages is not a measurement, it is a hope.
|
|
3235
|
+
*
|
|
3236
|
+
* `liveEdgeMs` is measured from PLAY, the first instant the restreamer owes
|
|
3237
|
+
* this session bytes, to the moment its burst has drained and it is on the
|
|
3238
|
+
* live feed. It therefore contains the wait for a keyframe AND the paced
|
|
3239
|
+
* drain — the two things a burst can cost a joiner on this side of the
|
|
3240
|
+
* socket. What it deliberately does NOT contain is the consumer's own decode
|
|
3241
|
+
* of that burst: that is measured where it happens, on
|
|
3242
|
+
* `session-decode: detection first frame`, and the two lines are joined by
|
|
3243
|
+
* `deviceId` plus the per-attach `consumer` tag.
|
|
3244
|
+
*
|
|
3245
|
+
* `aborted` is on the line because a burst that never reached the socket is
|
|
3246
|
+
* not a burst this session paid for, and averaging those in would understate
|
|
3247
|
+
* the cost of the ones that did.
|
|
3248
|
+
*/
|
|
3249
|
+
reportAttach(sessionId, attach, stats) {
|
|
3250
|
+
const session = this.sessions.get(sessionId);
|
|
3251
|
+
const playAt = session?.getPlayAt() ?? 0;
|
|
3252
|
+
if (!(attach.preRollSpanMs > 0)) {
|
|
3253
|
+
const now = Date.now();
|
|
3254
|
+
if (now - this.plainAttachReportMs < PLAIN_ATTACH_REPORT_INTERVAL_MS) return;
|
|
3255
|
+
this.plainAttachReportMs = now;
|
|
3256
|
+
}
|
|
3257
|
+
this.logger?.info("restream attach live-edge", {
|
|
3258
|
+
tags: { deviceId: this.deviceId },
|
|
3259
|
+
meta: {
|
|
3260
|
+
streamName: this.streamName,
|
|
3261
|
+
sessionId,
|
|
3262
|
+
consumer: session?.getUserAgent() ?? "unknown",
|
|
3263
|
+
intent: attach.intent ?? "none",
|
|
3264
|
+
requestedMs: attach.requestedMs,
|
|
3265
|
+
retainedMs: attach.retainedMs,
|
|
3266
|
+
preRollSpanMs: attach.preRollSpanMs,
|
|
3267
|
+
burstSpanMs: attach.burstSpanMs,
|
|
3268
|
+
gopSpanMs: attach.gopSpanMs,
|
|
3269
|
+
liveEdgeMs: playAt > 0 ? Date.now() - playAt : -1,
|
|
3270
|
+
drainMs: Math.round(stats.durationMs),
|
|
3271
|
+
packets: stats.sent,
|
|
3272
|
+
aborted: stats.aborted
|
|
3273
|
+
}
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3178
3276
|
/** Cancel a session's in-flight join replay (teardown / stream reset). */
|
|
3179
3277
|
abortReplay(sessionId) {
|
|
3180
3278
|
const replay = this.activeReplays.get(sessionId);
|
|
@@ -3457,15 +3555,31 @@ var RtspSession = class {
|
|
|
3457
3555
|
/** When true, this session receives video-only (no audio RTP). */
|
|
3458
3556
|
muted;
|
|
3459
3557
|
/**
|
|
3460
|
-
*
|
|
3461
|
-
*
|
|
3462
|
-
*
|
|
3558
|
+
* What this session DECLARED on its dial path (see `restream-intent.ts`): its
|
|
3559
|
+
* consumer purpose, and how many ms of retained history it asked for.
|
|
3560
|
+
* {@link NO_DECLARATION} when it declared nothing — which is every consumer
|
|
3561
|
+
* except the per-session detection decode and the recorder's writer.
|
|
3463
3562
|
*
|
|
3464
3563
|
* Kept separate from {@link muted} deliberately: `isMuted()` is shared by the
|
|
3465
3564
|
* WebRTC/HomeKit loopback legs, and the join rule that keys on it must stay
|
|
3466
3565
|
* true for them. Only an explicit declaration changes behaviour.
|
|
3566
|
+
*
|
|
3567
|
+
* One field and not two, because the purpose and the amount are one
|
|
3568
|
+
* statement (D382): a session that names a purpose without naming an amount
|
|
3569
|
+
* is asking for nothing, and that has to be as unmistakable here as it is on
|
|
3570
|
+
* the wire.
|
|
3467
3571
|
*/
|
|
3468
|
-
|
|
3572
|
+
declaration;
|
|
3573
|
+
/**
|
|
3574
|
+
* Epoch ms at which this session reached PLAY, or `0` before it does.
|
|
3575
|
+
*
|
|
3576
|
+
* The clock the join is measured against. `connectedAt` is the TCP accept,
|
|
3577
|
+
* which includes DESCRIBE/SETUP and, for a cold broker, the whole upstream
|
|
3578
|
+
* dial — so it answers "how slow was the camera", not "what did the burst
|
|
3579
|
+
* cost this session". Time to the live edge is measured from PLAY, because
|
|
3580
|
+
* PLAY is the first instant at which the restreamer owes this session bytes.
|
|
3581
|
+
*/
|
|
3582
|
+
playAt = 0;
|
|
3469
3583
|
/**
|
|
3470
3584
|
* Latch for ANY session that has started dropping video packets on socket
|
|
3471
3585
|
* backlog — it was written for the `muted` decoder loopback, which was the
|
|
@@ -3479,12 +3593,12 @@ var RtspSession = class {
|
|
|
3479
3593
|
/** Consecutive keyframe AUs at which this session was still unwritable.
|
|
3480
3594
|
* Reset by any successful write — the bound is CONSECUTIVE misses. */
|
|
3481
3595
|
missedKeyframes = 0;
|
|
3482
|
-
constructor(socket, sdp, onClose, muted,
|
|
3596
|
+
constructor(socket, sdp, onClose, muted, declaration) {
|
|
3483
3597
|
this.socket = socket;
|
|
3484
3598
|
this.sdp = sdp;
|
|
3485
3599
|
this.onClose = onClose;
|
|
3486
3600
|
this.muted = muted ?? false;
|
|
3487
|
-
this.
|
|
3601
|
+
this.declaration = declaration ?? require_restream_intent.NO_DECLARATION;
|
|
3488
3602
|
this.sessionId = (0, node_crypto.randomUUID)().replace(/-/g, "").slice(0, 16);
|
|
3489
3603
|
socket.on("data", (chunk) => {
|
|
3490
3604
|
if (this.closed) return;
|
|
@@ -3528,7 +3642,23 @@ var RtspSession = class {
|
|
|
3528
3642
|
* construction and drains the burst instead of accumulating it.
|
|
3529
3643
|
*/
|
|
3530
3644
|
isDetectionIntent() {
|
|
3531
|
-
return this.intent === "detection";
|
|
3645
|
+
return this.declaration.intent === "detection";
|
|
3646
|
+
}
|
|
3647
|
+
/**
|
|
3648
|
+
* Ms of retained history this session ASKED for on its dial. `0` = none.
|
|
3649
|
+
*
|
|
3650
|
+
* Read by `RtspRestreamer.servePendingSessionsRtp`, which serves the smaller
|
|
3651
|
+
* of this and what the ring holds. A session that declared an intent and no
|
|
3652
|
+
* amount takes the ordinary current-GOP burst — the intent still buys it
|
|
3653
|
+
* whatever else the intent buys (for detection, the live-edge exemption), and
|
|
3654
|
+
* nothing more.
|
|
3655
|
+
*/
|
|
3656
|
+
preRollRequestMs() {
|
|
3657
|
+
return this.declaration.preRollRequestMs;
|
|
3658
|
+
}
|
|
3659
|
+
/** Epoch ms at which this session reached PLAY; `0` if it has not. */
|
|
3660
|
+
getPlayAt() {
|
|
3661
|
+
return this.playAt;
|
|
3532
3662
|
}
|
|
3533
3663
|
/**
|
|
3534
3664
|
* Does this session get the PRE-ROLL window instead of the current GOP?
|
|
@@ -3545,12 +3675,14 @@ var RtspSession = class {
|
|
|
3545
3675
|
* WHICH pre-roll window this session gets, or `null` when it declared no
|
|
3546
3676
|
* intent and takes the ordinary current-GOP join burst.
|
|
3547
3677
|
*
|
|
3548
|
-
*
|
|
3549
|
-
*
|
|
3550
|
-
*
|
|
3678
|
+
* There is ONE window now (D381) and one ASK per session (D382), so what the
|
|
3679
|
+
* restreamer still needs from this is WHO joined — the identity it prints
|
|
3680
|
+
* beside the window it served, and the reason a recorder and a detector are
|
|
3681
|
+
* distinguishable on a line that reports one number.
|
|
3551
3682
|
*/
|
|
3552
3683
|
preRollIntent() {
|
|
3553
|
-
|
|
3684
|
+
const intent = this.declaration.intent;
|
|
3685
|
+
return intent === "detection" || intent === "recording" ? intent : null;
|
|
3554
3686
|
}
|
|
3555
3687
|
/**
|
|
3556
3688
|
* Diagnostic snapshot consumed by `StreamBroker.listClients` (Phase 10).
|
|
@@ -3771,6 +3903,7 @@ var RtspSession = class {
|
|
|
3771
3903
|
}
|
|
3772
3904
|
handlePlay(req) {
|
|
3773
3905
|
this.playing = true;
|
|
3906
|
+
if (this.playAt === 0) this.playAt = Date.now();
|
|
3774
3907
|
this.respond(req, 200, "OK", {
|
|
3775
3908
|
Session: this.sessionId,
|
|
3776
3909
|
Range: "npt=0.000-"
|
|
@@ -3927,7 +4060,7 @@ var RtspListenServer = class RtspListenServer {
|
|
|
3927
4060
|
async dispatch(socket, requestBuffer) {
|
|
3928
4061
|
const uriMatch = (requestBuffer.split("\r\n")[0] ?? "").match(/\s(rtsp:\/\/[^\s]+)\s/);
|
|
3929
4062
|
const pathMatch = (uriMatch ? uriMatch[1] : "").match(/rtsp:\/\/[^/]+(\/.*?)(?:\s|$)/);
|
|
3930
|
-
const { lookupPath, muted: isMuted,
|
|
4063
|
+
const { lookupPath, muted: isMuted, declaration } = require_restream_intent.parseRestreamPath(pathMatch ? pathMatch[1].replace(/^\//, "").replace(/\/trackID=\d+$/, "") : "");
|
|
3931
4064
|
const restreamer = this.restreamers.get(lookupPath);
|
|
3932
4065
|
if (!restreamer) {
|
|
3933
4066
|
socket.write(`RTSP/1.0 404 Not Found\r\nCSeq: 1\r\n\r\n`);
|
|
@@ -3956,7 +4089,7 @@ var RtspListenServer = class RtspListenServer {
|
|
|
3956
4089
|
const session = new RtspSession(socket, isMuted ? restreamer.getMutedSdp() ?? restreamer.getSdp() ?? "" : restreamer.getSdp() ?? "", () => {
|
|
3957
4090
|
restreamer.removeSession(session.getSessionId());
|
|
3958
4091
|
releaseOnce();
|
|
3959
|
-
}, isMuted,
|
|
4092
|
+
}, isMuted, declaration);
|
|
3960
4093
|
restreamer.addSession(session);
|
|
3961
4094
|
releaseOnce();
|
|
3962
4095
|
socket.unshift(Buffer.from(requestBuffer, "ascii"));
|
|
@@ -11701,7 +11834,7 @@ var StreamBroker = class StreamBroker {
|
|
|
11701
11834
|
this.rtspRestreamer.setPrimeBoundSupplier(() => this.joinBurstBound);
|
|
11702
11835
|
this.rtspRestreamer.setPreRollSupplier(() => {
|
|
11703
11836
|
return {
|
|
11704
|
-
packets: () => this.getRtpPreRollBuffer(),
|
|
11837
|
+
packets: (requestedMs) => this.getRtpPreRollBuffer(requestedMs),
|
|
11705
11838
|
preRollMs: this.prebufferMs
|
|
11706
11839
|
};
|
|
11707
11840
|
});
|
|
@@ -12778,7 +12911,7 @@ var StreamBroker = class StreamBroker {
|
|
|
12778
12911
|
* other numbers quietly sized the history that did exist.
|
|
12779
12912
|
*/
|
|
12780
12913
|
setPrebuffer(config) {
|
|
12781
|
-
const next =
|
|
12914
|
+
const next = require_restream_intent.prebufferRetentionMs(config);
|
|
12782
12915
|
this.setPreBufferEnabled(config.enabled);
|
|
12783
12916
|
this.setPreBufferDuration(config.seconds);
|
|
12784
12917
|
if (next === this.prebufferMs) return;
|
|
@@ -12913,7 +13046,7 @@ var StreamBroker = class StreamBroker {
|
|
|
12913
13046
|
*/
|
|
12914
13047
|
getRtpPreRollBuffer(requestedMs) {
|
|
12915
13048
|
if (!this.rtpRingHasKeyframe) return [];
|
|
12916
|
-
const windowMs =
|
|
13049
|
+
const windowMs = require_restream_intent.capPreRollRequestMs(requestedMs, this.prebufferMs);
|
|
12917
13050
|
if (windowMs <= 0 || this.rtpRing.length === 0) return this.getRtpPreBuffer();
|
|
12918
13051
|
const newestTs = this.rtpRing[this.rtpRing.length - 1].readUInt32BE(4);
|
|
12919
13052
|
const selection = selectPreRollStart(this.rtpPreRollMarks(newestTs), windowMs);
|
|
@@ -18295,7 +18428,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18295
18428
|
};
|
|
18296
18429
|
nextOverride.preBuffer = pb;
|
|
18297
18430
|
const broker = this.brokers.get(brokerIdFor(deviceId, camStreamId));
|
|
18298
|
-
if (broker) broker.setPrebuffer(
|
|
18431
|
+
if (broker) broker.setPrebuffer(require_restream_intent.resolvePrebuffer({
|
|
18299
18432
|
camStreamId,
|
|
18300
18433
|
clusterSeconds: this.defaultPreBufferSec,
|
|
18301
18434
|
overrides: pb,
|
|
@@ -19238,7 +19371,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19238
19371
|
* broker's own flag shipped `true`.
|
|
19239
19372
|
*/
|
|
19240
19373
|
effectivePrebuffer(deviceId, camStreamId) {
|
|
19241
|
-
return
|
|
19374
|
+
return require_restream_intent.resolvePrebuffer({
|
|
19242
19375
|
camStreamId,
|
|
19243
19376
|
clusterSeconds: this.defaultPreBufferSec,
|
|
19244
19377
|
overrides: this.deviceOverrides.get(deviceId)?.preBuffer,
|
|
@@ -30154,7 +30287,7 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
30154
30287
|
*/
|
|
30155
30288
|
async purgeFlattenedSettingsKeys() {
|
|
30156
30289
|
try {
|
|
30157
|
-
await
|
|
30290
|
+
await require_retire_root_keys.retireRootKeys({
|
|
30158
30291
|
store: this.ctx.api.settingsStore,
|
|
30159
30292
|
addonId: STREAM_BROKER_ADDON_ID,
|
|
30160
30293
|
logger: this.ctx.logger,
|