@camstack/addon-pipeline 1.2.77 → 1.2.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{addon-utils-bZcwLaVW.js → addon-utils-BZQxAJ1G.js} +1 -1
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-DQZzL0Ri.js → dist-BUIpqu3E.js} +125 -7
- package/dist/{dist-HhcoV-Ky.mjs → dist-BfTf6doa.mjs} +125 -7
- package/dist/{event-loop-stall-monitor-CBOo1oh_.js → event-loop-stall-monitor-CGLQP0by.js} +1 -1
- package/dist/{event-loop-stall-monitor-CqBEWGSZ.mjs → event-loop-stall-monitor-CmrHj0i9.mjs} +1 -1
- package/dist/{lazy-sharp-DIC1rpea.js → lazy-sharp-BZVhL9Lw.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +4 -4
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/recorder/index.js +293 -34
- package/dist/recorder/index.mjs +292 -33
- package/dist/session-decode/decode-worker-child.js +2 -2
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-De_xzvjU.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BohtrVou.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-y8ehJPoY.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-Dxr14lIu.mjs +26 -0
- package/dist/stream-broker/{hostInit-CDS5frmk.mjs → hostInit-pDANS0x0.mjs} +2 -2
- package/dist/stream-broker/index.js +347 -42
- package/dist/stream-broker/index.mjs +347 -42
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DZVGVAPU.js → worker-protocol-BaLcYcOo.js} +1 -1
- package/dist/{worker-protocol-BNlLGLBV.mjs → worker-protocol-BdOv9Ujl.mjs} +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-DHJSxHH1.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-Dg08SxUW.mjs +0 -26
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { At as string, Ct as boolean, D as cameraStreamsCapability, Dt as number, Et as literal, F as egressTranscodeSharingKey, I as egressTransportFromRequest, Mt as EventCategory, O as createHwAccelCache, Ot as object, Q as streamBrokerCapability, S as RingBuffer, St as array, V as maskUrlCredentials, Y as resolveEgressDecodeHwAccel, at as invocationFromEncodeProfile, ct as CAM_PROFILE_ORDER, dt as createEvent, et as webrtcSessionCapability, f as EncodeProfileSchema, gt as nodePin, ht as makeSourceBrokerId, it as buildFfmpegArgs$1, jt as union, kt as record, lt as DeviceFeature, mt as makeProfileBrokerId, nt as AUDIO_PRESETS, ot as isSoftwareDecode, pt as isEvent, rt as Fmp4BoxSplitter, st as BaseAddon, tt as errMsg, ut as DeviceType, v as RATE_CONTROL_RELAXED, vt as parseProfileBrokerId, w as addonWidgetsSourceCapability, wt as discriminatedUnion, xt as _enum, y as RATE_CONTROL_TIGHT } from "../dist-
|
|
2
|
+
import { At as string, Ct as boolean, D as cameraStreamsCapability, Dt as number, Et as literal, F as egressTranscodeSharingKey, I as egressTransportFromRequest, Mt as EventCategory, O as createHwAccelCache, Ot as object, Q as streamBrokerCapability, S as RingBuffer, St as array, V as maskUrlCredentials, Y as resolveEgressDecodeHwAccel, at as invocationFromEncodeProfile, ct as CAM_PROFILE_ORDER, dt as createEvent, et as webrtcSessionCapability, f as EncodeProfileSchema, gt as nodePin, ht as makeSourceBrokerId, it as buildFfmpegArgs$1, jt as union, kt as record, lt as DeviceFeature, mt as makeProfileBrokerId, nt as AUDIO_PRESETS, ot as isSoftwareDecode, pt as isEvent, rt as Fmp4BoxSplitter, st as BaseAddon, tt as errMsg, ut as DeviceType, v as RATE_CONTROL_RELAXED, vt as parseProfileBrokerId, w as addonWidgetsSourceCapability, wt as discriminatedUnion, xt as _enum, y as RATE_CONTROL_TIGHT } from "../dist-BfTf6doa.mjs";
|
|
3
3
|
import { n as profileForStreamId, r as parseRestreamPath } from "../remote-restream-Ci7RXNGb.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as crypto$1 from "node:crypto";
|
|
@@ -22534,11 +22534,31 @@ var MonotonicClock = class {
|
|
|
22534
22534
|
this.rate = rate;
|
|
22535
22535
|
}
|
|
22536
22536
|
};
|
|
22537
|
+
var SCRUB_FRAGMENT_CACHE_MAX_BYTES = 24 * 1024 * 1024;
|
|
22538
|
+
var SCRUB_FRAGMENT_CACHE_OTHER_MAX_BYTES = 2 * 1024 * 1024;
|
|
22539
|
+
/** Demuxed bytes an entry holds — what the byte bound actually counts. */
|
|
22540
|
+
function fragmentBytes(frag) {
|
|
22541
|
+
let total = 0;
|
|
22542
|
+
for (const au of frag.aus) total += au.data.length;
|
|
22543
|
+
return total;
|
|
22544
|
+
}
|
|
22537
22545
|
var ScrubFragmentCache = class {
|
|
22538
|
-
|
|
22546
|
+
maxBytes;
|
|
22547
|
+
otherMaxBytes;
|
|
22539
22548
|
map = /* @__PURE__ */ new Map();
|
|
22540
|
-
|
|
22541
|
-
|
|
22549
|
+
/**
|
|
22550
|
+
* Bytes held PER PROFILE. Budgets are per profile and so is eviction: a
|
|
22551
|
+
* `high` entry must never be able to evict the `low` segments the drag is
|
|
22552
|
+
* running on, and the reverse would be just as wrong.
|
|
22553
|
+
*/
|
|
22554
|
+
bytesByProfile = /* @__PURE__ */ new Map();
|
|
22555
|
+
constructor(maxBytes = SCRUB_FRAGMENT_CACHE_MAX_BYTES, otherMaxBytes = SCRUB_FRAGMENT_CACHE_OTHER_MAX_BYTES) {
|
|
22556
|
+
this.maxBytes = maxBytes;
|
|
22557
|
+
this.otherMaxBytes = otherMaxBytes;
|
|
22558
|
+
}
|
|
22559
|
+
/** The byte budget for a profile — the scrub profile gets the real one. */
|
|
22560
|
+
budgetFor(profile) {
|
|
22561
|
+
return profile === "low" ? this.maxBytes : this.otherMaxBytes;
|
|
22542
22562
|
}
|
|
22543
22563
|
key(profile, startMs) {
|
|
22544
22564
|
return `${profile}|${startMs}`;
|
|
@@ -22569,17 +22589,45 @@ var ScrubFragmentCache = class {
|
|
|
22569
22589
|
}
|
|
22570
22590
|
return null;
|
|
22571
22591
|
}
|
|
22572
|
-
/** Insert (or refresh) a fragment, evicting
|
|
22592
|
+
/** Insert (or refresh) a fragment, evicting that PROFILE's oldest past its bound. */
|
|
22573
22593
|
put(profile, frag) {
|
|
22574
22594
|
const k = this.key(profile, frag.startMs);
|
|
22595
|
+
const held = this.map.get(k);
|
|
22596
|
+
let bytes = this.bytesByProfile.get(profile) ?? 0;
|
|
22597
|
+
if (held) bytes -= fragmentBytes(held);
|
|
22575
22598
|
this.map.delete(k);
|
|
22576
22599
|
this.map.set(k, frag);
|
|
22577
|
-
|
|
22578
|
-
|
|
22600
|
+
bytes += fragmentBytes(frag);
|
|
22601
|
+
this.bytesByProfile.set(profile, bytes);
|
|
22602
|
+
const budget = this.budgetFor(profile);
|
|
22603
|
+
const prefix = `${profile}|`;
|
|
22604
|
+
while ((this.bytesByProfile.get(profile) ?? 0) > budget && this.entriesFor(profile) > 4) {
|
|
22605
|
+
const oldest = this.oldestKeyWithPrefix(prefix);
|
|
22579
22606
|
if (oldest === void 0) break;
|
|
22607
|
+
const evicted = this.map.get(oldest);
|
|
22580
22608
|
this.map.delete(oldest);
|
|
22609
|
+
if (evicted) this.bytesByProfile.set(profile, (this.bytesByProfile.get(profile) ?? 0) - fragmentBytes(evicted));
|
|
22581
22610
|
}
|
|
22582
22611
|
}
|
|
22612
|
+
oldestKeyWithPrefix(prefix) {
|
|
22613
|
+
for (const k of this.map.keys()) if (k.startsWith(prefix)) return k;
|
|
22614
|
+
}
|
|
22615
|
+
entriesFor(profile) {
|
|
22616
|
+
const prefix = `${profile}|`;
|
|
22617
|
+
let n = 0;
|
|
22618
|
+
for (const k of this.map.keys()) if (k.startsWith(prefix)) n += 1;
|
|
22619
|
+
return n;
|
|
22620
|
+
}
|
|
22621
|
+
/** Demuxed bytes currently held across every profile. */
|
|
22622
|
+
get byteSize() {
|
|
22623
|
+
let total = 0;
|
|
22624
|
+
for (const bytes of this.bytesByProfile.values()) total += bytes;
|
|
22625
|
+
return total;
|
|
22626
|
+
}
|
|
22627
|
+
/** Demuxed bytes held for one profile — the number its MB bound governs. */
|
|
22628
|
+
byteSizeFor(profile) {
|
|
22629
|
+
return this.bytesByProfile.get(profile) ?? 0;
|
|
22630
|
+
}
|
|
22583
22631
|
get size() {
|
|
22584
22632
|
return this.map.size;
|
|
22585
22633
|
}
|
|
@@ -22645,6 +22693,10 @@ var ScrubDragStats = class {
|
|
|
22645
22693
|
wholeReads = 0;
|
|
22646
22694
|
readBytes = 0;
|
|
22647
22695
|
parks = 0;
|
|
22696
|
+
prefetchReads = 0;
|
|
22697
|
+
prefetchBytes = 0;
|
|
22698
|
+
prefetchHits = 0;
|
|
22699
|
+
prefetchSuppressed = 0;
|
|
22648
22700
|
ackTimeouts = 0;
|
|
22649
22701
|
failures = 0;
|
|
22650
22702
|
firstPushMs = null;
|
|
@@ -22692,6 +22744,19 @@ var ScrubDragStats = class {
|
|
|
22692
22744
|
recordPark() {
|
|
22693
22745
|
this.parks += 1;
|
|
22694
22746
|
}
|
|
22747
|
+
/** One speculative GOP read and what it cost the disk. */
|
|
22748
|
+
recordPrefetchRead(bytes) {
|
|
22749
|
+
this.prefetchReads += 1;
|
|
22750
|
+
this.prefetchBytes += bytes;
|
|
22751
|
+
}
|
|
22752
|
+
/** Speculation declined: the disk is already the constraint. */
|
|
22753
|
+
recordPrefetchSuppressed() {
|
|
22754
|
+
this.prefetchSuppressed += 1;
|
|
22755
|
+
}
|
|
22756
|
+
/** A tick served by a fragment speculation had already landed. */
|
|
22757
|
+
recordPrefetchHit() {
|
|
22758
|
+
this.prefetchHits += 1;
|
|
22759
|
+
}
|
|
22695
22760
|
/** Pushes written off after the ack timeout — dropped work, never silent. */
|
|
22696
22761
|
recordAckTimeout(pushes) {
|
|
22697
22762
|
this.ackTimeouts += pushes;
|
|
@@ -22745,6 +22810,10 @@ var ScrubDragStats = class {
|
|
|
22745
22810
|
travel: this.travel,
|
|
22746
22811
|
bytes: this.bytes,
|
|
22747
22812
|
parks: this.parks,
|
|
22813
|
+
prefetchReads: this.prefetchReads,
|
|
22814
|
+
prefetchBytes: this.prefetchBytes,
|
|
22815
|
+
prefetchHits: this.prefetchHits,
|
|
22816
|
+
prefetchSuppressed: this.prefetchSuppressed,
|
|
22748
22817
|
ackTimeouts: this.ackTimeouts,
|
|
22749
22818
|
failures: this.failures,
|
|
22750
22819
|
creditEndedAt: this.creditEndedAt,
|
|
@@ -22840,6 +22909,73 @@ var SCRUB_JUMP_TRAVEL_MAX_STILLS = 3;
|
|
|
22840
22909
|
*/
|
|
22841
22910
|
var SCRUB_PREFETCH_DIR_THRESHOLD = 2;
|
|
22842
22911
|
/**
|
|
22912
|
+
* How many speculative GOP reads may be in flight at once, i.e. how many drag
|
|
22913
|
+
* steps ahead the feeder reads.
|
|
22914
|
+
*
|
|
22915
|
+
* The scrub tick IS the read: measured on salone 590 over `low`,
|
|
22916
|
+
* `tickMsP50 = 270` of which `readMsP50 = 263` — a cold ~135 KB fragment off
|
|
22917
|
+
* the HDD array through shfs/FUSE, `demuxMsP50 = 1`. One read at a time is a
|
|
22918
|
+
* hard ~3.8 push/s ceiling no cache can lift, because a drag whose steps are
|
|
22919
|
+
* tens of seconds apart revisits nothing. Reading N steps ahead is the only
|
|
22920
|
+
* thing that converts wall-clock the drag is ALREADY spending into pushes.
|
|
22921
|
+
*
|
|
22922
|
+
* 2 and not more: these reads land on the same spindles the recorder is writing
|
|
22923
|
+
* every camera to, and past a small number concurrent random reads stop
|
|
22924
|
+
* overlapping and start thrashing. Raise it only against a measured
|
|
22925
|
+
* `prefetchHits` / `readMsP50` pair — it is the knob, and the census carries
|
|
22926
|
+
* both halves of the evidence.
|
|
22927
|
+
*/
|
|
22928
|
+
var SCRUB_PREFETCH_MAX_INFLIGHT = 2;
|
|
22929
|
+
/**
|
|
22930
|
+
* The profile whose scrub read unit is the WHOLE SEGMENT.
|
|
22931
|
+
*
|
|
22932
|
+
* Every drag is pinned to `low` by the client, and a `low` segment is ~230 KB
|
|
22933
|
+
* for 10 s of timeline. Reading it whole is ONE sequential read; the GOP
|
|
22934
|
+
* byte-range it replaces was three random seeks (a 2 KB tail probe for the
|
|
22935
|
+
* `mfra` index, the `ftyp`+`moov` head, then the fragment) for ~135 KB of those
|
|
22936
|
+
* ~230 KB — and it bought nothing reusable, because the next tick inside the
|
|
22937
|
+
* same segment paid all three again. On a spindle that is also absorbing every
|
|
22938
|
+
* camera's writes, seeks are the cost and bytes are nearly free.
|
|
22939
|
+
*
|
|
22940
|
+
* The win is not the single read, it is what the read leaves behind: ten
|
|
22941
|
+
* seconds of demuxed access units. A drag inside one segment then costs zero
|
|
22942
|
+
* I/O and zero demux — which is the regime the operator spends most of a
|
|
22943
|
+
* careful gesture in.
|
|
22944
|
+
*/
|
|
22945
|
+
var SCRUB_WHOLE_SEGMENT_PROFILE = "low";
|
|
22946
|
+
/**
|
|
22947
|
+
* Safety ceiling on the whole-segment read. `low` is ~230 KB per 10 s, so this
|
|
22948
|
+
* is ~6× headroom for a camera whose substream is unusually fat — but a
|
|
22949
|
+
* segment past it (a mis-pinned `high` at ~5 MB) falls back to the GOP range,
|
|
22950
|
+
* because reading 5 MB to push one keyframe is the trade this change exists to
|
|
22951
|
+
* avoid, in the other direction.
|
|
22952
|
+
*/
|
|
22953
|
+
var SCRUB_WHOLE_SEGMENT_MAX_BYTES = 15e5;
|
|
22954
|
+
/**
|
|
22955
|
+
* How far the entry warm-up reaches, backward and forward from the playhead the
|
|
22956
|
+
* drag starts on.
|
|
22957
|
+
*
|
|
22958
|
+
* Asymmetric because the gesture is: a drag that begins at the live edge — or
|
|
22959
|
+
* at a notification's moment — is looking for what ALREADY happened. 90 s back
|
|
22960
|
+
* against 30 s forward is 12 segments ≈ 2.8 MB at `low`, read sequentially in
|
|
22961
|
+
* the background while the operator is still deciding where to drag.
|
|
22962
|
+
*/
|
|
22963
|
+
var SCRUB_WARM_BACK_MS = 9e4;
|
|
22964
|
+
var SCRUB_WARM_FWD_MS = 3e4;
|
|
22965
|
+
/** Hard bound on warm reads, whatever the spans above resolve to. */
|
|
22966
|
+
var SCRUB_WARM_MAX_SEGMENTS = 14;
|
|
22967
|
+
/**
|
|
22968
|
+
* Median recent read time at or above which the feeder STOPS speculating.
|
|
22969
|
+
*
|
|
22970
|
+
* Speculation is a bet that spare I/O exists. When reads are this slow there is
|
|
22971
|
+
* no spare I/O — the array is saturated — and a prefetch is not free work, it
|
|
22972
|
+
* is another random seek queued ahead of the read the operator is waiting on.
|
|
22973
|
+
* The field regime that forced this: 54 prefetch reads for 9 hits with the disk
|
|
22974
|
+
* at 98% busy. Suppression is counted (`prefetchSuppressed`) so the decision is
|
|
22975
|
+
* visible, and it lifts by itself as soon as reads come back.
|
|
22976
|
+
*/
|
|
22977
|
+
var SCRUB_SLOW_READ_MS = 600;
|
|
22978
|
+
/**
|
|
22843
22979
|
* Max forward TARGET delta (ms of timeline, between consecutive drag targets)
|
|
22844
22980
|
* that SWEEPS predicted frames instead of jumping. A steady forward drag is
|
|
22845
22981
|
* playback with a moving target: once a keyframe decoded, the predicted frames
|
|
@@ -23058,12 +23194,29 @@ var RecordedFeeder = class {
|
|
|
23058
23194
|
*/
|
|
23059
23195
|
scrubDirRun = 0;
|
|
23060
23196
|
/**
|
|
23061
|
-
*
|
|
23062
|
-
*
|
|
23063
|
-
* `scrubCache` —
|
|
23064
|
-
* state, and never
|
|
23065
|
-
|
|
23066
|
-
|
|
23197
|
+
* Prefetch reads in flight. Bounded by {@link SCRUB_PREFETCH_MAX_INFLIGHT}:
|
|
23198
|
+
* these are the SPECULATIVE concurrency slots, and they only fill
|
|
23199
|
+
* `scrubCache` — they never push, never touch the pending/timer/latest-wins
|
|
23200
|
+
* state, and never block the live coalesced tick.
|
|
23201
|
+
*
|
|
23202
|
+
* More than one because the read IS the tick (263 of 270 ms measured): with a
|
|
23203
|
+
* single slot the drag can never go faster than one cold read, however well
|
|
23204
|
+
* the speculation guesses.
|
|
23205
|
+
*/
|
|
23206
|
+
scrubPrefetchInFlight = 0;
|
|
23207
|
+
/** Probe instants currently being read, so two horizons never duplicate one. */
|
|
23208
|
+
scrubPrefetchProbes = /* @__PURE__ */ new Set();
|
|
23209
|
+
/**
|
|
23210
|
+
* Fragment starts this drag put in the cache SPECULATIVELY. A later tick that
|
|
23211
|
+
* hits one of them is what makes speculation provably worth its I/O
|
|
23212
|
+
* (`prefetchHits`) — without this the feeder could double the load on the disk
|
|
23213
|
+
* that is the bottleneck and the census would look calm.
|
|
23214
|
+
*/
|
|
23215
|
+
scrubPrefetched = /* @__PURE__ */ new Set();
|
|
23216
|
+
/** Recent read durations — the evidence speculation is suspended on. */
|
|
23217
|
+
scrubReadMsWindow = [];
|
|
23218
|
+
/** Warm-up runs once per drag, from the playhead the finger went down on. */
|
|
23219
|
+
scrubWarmStarted = false;
|
|
23067
23220
|
constructor(deps) {
|
|
23068
23221
|
this.deps = deps;
|
|
23069
23222
|
this.audioClock = new MonotonicClock(deps.audioClockRateHz ?? 8e3);
|
|
@@ -23353,6 +23506,7 @@ var RecordedFeeder = class {
|
|
|
23353
23506
|
this.scrubCredit = SCRUB_CREDIT_CAP;
|
|
23354
23507
|
this.scrubPrevTarget = null;
|
|
23355
23508
|
this.scrubDirRun = 0;
|
|
23509
|
+
this.scrubPrefetched = /* @__PURE__ */ new Set();
|
|
23356
23510
|
this.scrubCursor = null;
|
|
23357
23511
|
this.scrubNoFootageLogged = false;
|
|
23358
23512
|
this.scrubParkLogged = false;
|
|
@@ -23370,6 +23524,88 @@ var RecordedFeeder = class {
|
|
|
23370
23524
|
this.scrubTimer = null;
|
|
23371
23525
|
}
|
|
23372
23526
|
this.stopPositionReports();
|
|
23527
|
+
this.scrubWarmStarted = false;
|
|
23528
|
+
this.scrubReadMsWindow = [];
|
|
23529
|
+
if (this.cursorMs > 0 && this.deps.profile === SCRUB_WHOLE_SEGMENT_PROFILE) this.startScrubWarm(this.cursorMs, this.scrubToken);
|
|
23530
|
+
}
|
|
23531
|
+
/**
|
|
23532
|
+
* Warm the window the drag is about to move through: {@link SCRUB_WARM_BACK_MS}
|
|
23533
|
+
* back and {@link SCRUB_WARM_FWD_MS} forward from the entry playhead, one
|
|
23534
|
+
* segment at a time, backward first.
|
|
23535
|
+
*
|
|
23536
|
+
* Once per drag, always in the background, and never on the tick's path — a
|
|
23537
|
+
* tick that arrives against a cold cache pays its own read exactly as before.
|
|
23538
|
+
* It walks segment to segment through `locate` rather than assuming a segment
|
|
23539
|
+
* length, so a recorder configured with a different `segmentSeconds` warms the
|
|
23540
|
+
* same SPAN rather than the same count.
|
|
23541
|
+
*/
|
|
23542
|
+
startScrubWarm(centreMs, gen) {
|
|
23543
|
+
if (this.scrubWarmStarted) return;
|
|
23544
|
+
this.scrubWarmStarted = true;
|
|
23545
|
+
this.warmScrubWindow(centreMs, gen);
|
|
23546
|
+
}
|
|
23547
|
+
async warmScrubWindow(centreMs, gen) {
|
|
23548
|
+
const spent = await this.warmScrubDirection(centreMs, -1, SCRUB_WARM_BACK_MS, 0, gen);
|
|
23549
|
+
await this.warmScrubDirection(centreMs, 1, SCRUB_WARM_FWD_MS, spent, gen);
|
|
23550
|
+
}
|
|
23551
|
+
/**
|
|
23552
|
+
* Walk `dir` from `fromMs` until `spanMs` of timeline is warm (or the shared
|
|
23553
|
+
* bound is hit), reading each segment that is not already cached. Returns the
|
|
23554
|
+
* running total of segments read, so the two directions share one budget.
|
|
23555
|
+
*/
|
|
23556
|
+
async warmScrubDirection(fromMs, dir, spanMs, spent, gen) {
|
|
23557
|
+
let read = spent;
|
|
23558
|
+
let probeMs = fromMs;
|
|
23559
|
+
for (let i = 0; i < SCRUB_WARM_MAX_SEGMENTS; i++) {
|
|
23560
|
+
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return read;
|
|
23561
|
+
if (read >= SCRUB_WARM_MAX_SEGMENTS) return read;
|
|
23562
|
+
if (Math.abs(probeMs - fromMs) > spanMs) return read;
|
|
23563
|
+
if (this.scrubReadsAreSlow()) {
|
|
23564
|
+
this.scrubStats?.recordPrefetchSuppressed();
|
|
23565
|
+
return read;
|
|
23566
|
+
}
|
|
23567
|
+
const seg = await this.warmOneScrubSegment(probeMs, gen);
|
|
23568
|
+
if (seg === null) return read;
|
|
23569
|
+
if (seg.fetched) read += 1;
|
|
23570
|
+
probeMs = dir === -1 ? seg.startMs - 1 : seg.startMs + seg.durMs + 1;
|
|
23571
|
+
if (probeMs < 0) return read;
|
|
23572
|
+
}
|
|
23573
|
+
return read;
|
|
23574
|
+
}
|
|
23575
|
+
/**
|
|
23576
|
+
* One warm step: locate the segment covering `probeMs` and, unless it is
|
|
23577
|
+
* already cached, read and demux it into the cache. Returns the segment's span
|
|
23578
|
+
* so the walk can step to its neighbour, or null when the walk must stop (a
|
|
23579
|
+
* gap, a failure, or teardown).
|
|
23580
|
+
*/
|
|
23581
|
+
async warmOneScrubSegment(probeMs, gen) {
|
|
23582
|
+
try {
|
|
23583
|
+
const warm = this.scrubCache.covering(this.deps.profile, probeMs);
|
|
23584
|
+
if (warm) return {
|
|
23585
|
+
startMs: warm.startMs,
|
|
23586
|
+
durMs: warm.durMs,
|
|
23587
|
+
fetched: false
|
|
23588
|
+
};
|
|
23589
|
+
const loc = await this.withTimeout(this.deps.locate({
|
|
23590
|
+
deviceId: this.deps.deviceId,
|
|
23591
|
+
profile: this.deps.profile,
|
|
23592
|
+
epochMs: probeMs
|
|
23593
|
+
}));
|
|
23594
|
+
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return null;
|
|
23595
|
+
if (loc.kind !== "segment") return null;
|
|
23596
|
+
const load = await this.loadScrubFragment(loc, probeMs);
|
|
23597
|
+
if (load !== null) this.scrubStats?.recordPrefetchRead(load.readBytes);
|
|
23598
|
+
if (this.disposed || !this.scrubbing || gen !== this.scrubToken || load === null) return null;
|
|
23599
|
+
this.scrubCache.put(this.deps.profile, load.seg);
|
|
23600
|
+
this.scrubPrefetched.add(load.seg.startMs);
|
|
23601
|
+
return {
|
|
23602
|
+
startMs: load.seg.startMs,
|
|
23603
|
+
durMs: load.seg.durMs,
|
|
23604
|
+
fetched: true
|
|
23605
|
+
};
|
|
23606
|
+
} catch {
|
|
23607
|
+
return null;
|
|
23608
|
+
}
|
|
23373
23609
|
}
|
|
23374
23610
|
/**
|
|
23375
23611
|
* EXPERIMENTAL feeder-scrub: register a drag target. Records the newest target
|
|
@@ -23528,7 +23764,10 @@ var RecordedFeeder = class {
|
|
|
23528
23764
|
coverage = "memo";
|
|
23529
23765
|
} else {
|
|
23530
23766
|
seg = this.scrubCache.covering(this.deps.profile, target);
|
|
23531
|
-
if (seg)
|
|
23767
|
+
if (seg) {
|
|
23768
|
+
coverage = "cache";
|
|
23769
|
+
if (this.scrubPrefetched.has(seg.startMs)) this.scrubStats?.recordPrefetchHit();
|
|
23770
|
+
}
|
|
23532
23771
|
}
|
|
23533
23772
|
if (!seg) {
|
|
23534
23773
|
const locateStartMs = performance.now();
|
|
@@ -23630,7 +23869,7 @@ var RecordedFeeder = class {
|
|
|
23630
23869
|
firstIdx = this.pickScrubIndex(seg, effTarget, true);
|
|
23631
23870
|
const kf = seg.aus[firstIdx];
|
|
23632
23871
|
aus = kf === void 0 ? [] : [kf];
|
|
23633
|
-
if (prevTarget !== null && kf !== void 0) jumpTravel = this.collectWarmTravelStills(prevTarget, effTarget, seg.startMs);
|
|
23872
|
+
if (prevTarget !== null && kf !== void 0) jumpTravel = this.collectWarmTravelStills(prevTarget, effTarget, seg.startMs, firstIdx);
|
|
23634
23873
|
}
|
|
23635
23874
|
const lastAu = aus[aus.length - 1];
|
|
23636
23875
|
if (lastAu === void 0) {
|
|
@@ -23736,15 +23975,19 @@ var RecordedFeeder = class {
|
|
|
23736
23975
|
return idx;
|
|
23737
23976
|
}
|
|
23738
23977
|
/**
|
|
23739
|
-
* Load the scrub
|
|
23740
|
-
*
|
|
23741
|
-
*
|
|
23742
|
-
*
|
|
23978
|
+
* Load the scrub unit covering `effTarget`.
|
|
23979
|
+
*
|
|
23980
|
+
* For the scrub profile that unit is the WHOLE SEGMENT — one sequential read
|
|
23981
|
+
* of ~230 KB that leaves ten seconds of demuxed access units behind, so every
|
|
23982
|
+
* later tick landing in it costs nothing at all. Everything else keeps the
|
|
23983
|
+
* GOP byte-range (D31): a `high` segment is ~5 MB and reading it to push one
|
|
23984
|
+
* keyframe would be this same trade made backwards.
|
|
23985
|
+
*
|
|
23743
23986
|
* Null when superseded mid-flight. Video-only: scrub never plays audio.
|
|
23744
23987
|
*/
|
|
23745
23988
|
async loadScrubFragment(loc, effTarget) {
|
|
23746
23989
|
const readGop = this.deps.readGop;
|
|
23747
|
-
if (readGop) {
|
|
23990
|
+
if (readGop && !this.readsWholeSegment(loc)) {
|
|
23748
23991
|
const token = this.seekToken;
|
|
23749
23992
|
const readStartMs = performance.now();
|
|
23750
23993
|
const gop = await this.withTimeout(readGop({
|
|
@@ -23758,6 +24001,7 @@ var RecordedFeeder = class {
|
|
|
23758
24001
|
if (this.disposed || token !== this.seekToken) return null;
|
|
23759
24002
|
const aus = await this.withTimeout(this.deps.demux(gop.bytes));
|
|
23760
24003
|
if (this.disposed || token !== this.seekToken) return null;
|
|
24004
|
+
this.noteScrubReadMs(readEndMs - readStartMs);
|
|
23761
24005
|
return {
|
|
23762
24006
|
seg: {
|
|
23763
24007
|
startMs: gop.gopStartMs,
|
|
@@ -23770,22 +24014,60 @@ var RecordedFeeder = class {
|
|
|
23770
24014
|
wholeSegmentRead: false
|
|
23771
24015
|
};
|
|
23772
24016
|
}
|
|
23773
|
-
const
|
|
23774
|
-
const
|
|
23775
|
-
|
|
24017
|
+
const token = this.seekToken;
|
|
24018
|
+
const readStartMs = performance.now();
|
|
24019
|
+
const bytes = await this.withTimeout(this.deps.readBytes({
|
|
24020
|
+
deviceId: this.deps.deviceId,
|
|
24021
|
+
profile: this.deps.profile,
|
|
24022
|
+
startMs: loc.startMs
|
|
24023
|
+
}));
|
|
24024
|
+
const readEndMs = performance.now();
|
|
24025
|
+
if (this.disposed || token !== this.seekToken) return null;
|
|
24026
|
+
const aus = await this.withTimeout(this.deps.demux(bytes));
|
|
24027
|
+
if (this.disposed || token !== this.seekToken) return null;
|
|
24028
|
+
this.noteScrubReadMs(readEndMs - readStartMs);
|
|
23776
24029
|
return {
|
|
23777
24030
|
seg: {
|
|
23778
24031
|
startMs: loc.startMs,
|
|
23779
24032
|
durMs: loc.durMs,
|
|
23780
|
-
aus
|
|
24033
|
+
aus
|
|
23781
24034
|
},
|
|
23782
|
-
readMs:
|
|
23783
|
-
readBytes:
|
|
23784
|
-
demuxMs:
|
|
24035
|
+
readMs: readEndMs - readStartMs,
|
|
24036
|
+
readBytes: bytes.length,
|
|
24037
|
+
demuxMs: performance.now() - readEndMs,
|
|
23785
24038
|
wholeSegmentRead: true
|
|
23786
24039
|
};
|
|
23787
24040
|
}
|
|
23788
24041
|
/**
|
|
24042
|
+
* Whether this located segment is read WHOLE. The scrub profile is, up to a
|
|
24043
|
+
* safety ceiling on the byte size — see {@link SCRUB_WHOLE_SEGMENT_PROFILE}.
|
|
24044
|
+
* A `locate` that does not report a size is trusted for the scrub profile,
|
|
24045
|
+
* where the shape is known and small.
|
|
24046
|
+
*/
|
|
24047
|
+
readsWholeSegment(loc) {
|
|
24048
|
+
if (this.deps.profile !== SCRUB_WHOLE_SEGMENT_PROFILE) return false;
|
|
24049
|
+
return loc.bytes === void 0 || loc.bytes <= SCRUB_WHOLE_SEGMENT_MAX_BYTES;
|
|
24050
|
+
}
|
|
24051
|
+
/**
|
|
24052
|
+
* Fold one read into the recent-read window that governs speculation. The
|
|
24053
|
+
* window is short on purpose: suppression must track what the disk is doing
|
|
24054
|
+
* NOW, and lift as soon as it recovers.
|
|
24055
|
+
*/
|
|
24056
|
+
noteScrubReadMs(readMs) {
|
|
24057
|
+
this.scrubReadMsWindow = [...this.scrubReadMsWindow, readMs].slice(-5);
|
|
24058
|
+
}
|
|
24059
|
+
/**
|
|
24060
|
+
* Is the disk the problem right now? Median of the recent reads against
|
|
24061
|
+
* {@link SCRUB_SLOW_READ_MS}. Median and not mean because one slow read is
|
|
24062
|
+
* noise and the decision must not swing on it; and never before a few
|
|
24063
|
+
* samples, or a cold first read would suppress the whole drag.
|
|
24064
|
+
*/
|
|
24065
|
+
scrubReadsAreSlow() {
|
|
24066
|
+
if (this.scrubReadMsWindow.length < 3) return false;
|
|
24067
|
+
const sorted = this.scrubReadMsWindow.toSorted((a, b) => a - b);
|
|
24068
|
+
return (sorted[Math.floor(sorted.length / 2)] ?? 0) >= (this.deps.slowReadMs ?? SCRUB_SLOW_READ_MS);
|
|
24069
|
+
}
|
|
24070
|
+
/**
|
|
23789
24071
|
* A single slow `locate` gets its own line, throttled per drag. Reserved for
|
|
23790
24072
|
* the pathological case on purpose: D68 moved this RPC off the warm path, so
|
|
23791
24073
|
* a slow one still landing there is the finding — and if it is NOT rare, the
|
|
@@ -23917,18 +24199,21 @@ var RecordedFeeder = class {
|
|
|
23917
24199
|
* deduped by fragment, excluding the fragments that cover the endpoints
|
|
23918
24200
|
* (one is on screen, the other is the jump's own push).
|
|
23919
24201
|
*/
|
|
23920
|
-
collectWarmTravelStills(fromMs, toMs, excludeFragStartMs) {
|
|
24202
|
+
collectWarmTravelStills(fromMs, toMs, excludeFragStartMs, excludeKfIdx) {
|
|
23921
24203
|
const stills = [];
|
|
23922
|
-
const seen = new Set(
|
|
24204
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23923
24205
|
const samples = SCRUB_JUMP_TRAVEL_MAX_STILLS;
|
|
23924
24206
|
for (let i = 1; i <= samples; i++) {
|
|
23925
24207
|
const tMs = Math.round(fromMs + (toMs - fromMs) * i / 4);
|
|
23926
24208
|
const seg = this.scrubCache.covering(this.deps.profile, tMs);
|
|
23927
|
-
if (!seg ||
|
|
24209
|
+
if (!seg || fragmentCovers(seg, fromMs)) continue;
|
|
23928
24210
|
const kfIdx = this.pickScrubIndex(seg, tMs, true);
|
|
23929
24211
|
const au = seg.aus[kfIdx];
|
|
23930
24212
|
if (au === void 0 || !au.keyframe) continue;
|
|
23931
|
-
|
|
24213
|
+
if (seg.startMs === excludeFragStartMs && kfIdx === excludeKfIdx) continue;
|
|
24214
|
+
const key = `${seg.startMs}|${kfIdx}`;
|
|
24215
|
+
if (seen.has(key)) continue;
|
|
24216
|
+
seen.add(key);
|
|
23932
24217
|
stills.push({
|
|
23933
24218
|
seg,
|
|
23934
24219
|
au,
|
|
@@ -23950,28 +24235,45 @@ var RecordedFeeder = class {
|
|
|
23950
24235
|
this.scrubDirRun = 0;
|
|
23951
24236
|
return;
|
|
23952
24237
|
}
|
|
23953
|
-
const
|
|
24238
|
+
const stepMs = target - prev;
|
|
24239
|
+
const dir = stepMs > 0 ? 1 : stepMs < 0 ? -1 : 0;
|
|
23954
24240
|
if (dir === 0) {
|
|
23955
24241
|
this.scrubDirRun = 0;
|
|
23956
24242
|
return;
|
|
23957
24243
|
}
|
|
23958
24244
|
this.scrubDirRun = Math.sign(this.scrubDirRun) === dir ? this.scrubDirRun + dir : dir;
|
|
23959
24245
|
if (Math.abs(this.scrubDirRun) < SCRUB_PREFETCH_DIR_THRESHOLD) return;
|
|
23960
|
-
if (this.
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
24246
|
+
if (this.scrubReadsAreSlow()) {
|
|
24247
|
+
this.scrubStats?.recordPrefetchSuppressed();
|
|
24248
|
+
return;
|
|
24249
|
+
}
|
|
24250
|
+
const aheadMs = Math.max(Math.abs(stepMs), seg.durMs + 1);
|
|
24251
|
+
const horizons = Math.abs(stepMs) > seg.durMs ? SCRUB_PREFETCH_MAX_INFLIGHT : 1;
|
|
24252
|
+
for (let horizon = 1; horizon <= horizons; horizon++) {
|
|
24253
|
+
if (this.scrubPrefetchInFlight >= SCRUB_PREFETCH_MAX_INFLIGHT) return;
|
|
24254
|
+
const probeMs = target + dir * aheadMs * horizon;
|
|
24255
|
+
if (probeMs < 0) continue;
|
|
24256
|
+
if (this.scrubPrefetchProbes.has(probeMs)) continue;
|
|
24257
|
+
if (this.scrubCache.covering(this.deps.profile, probeMs)) continue;
|
|
24258
|
+
this.scrubPrefetch(probeMs, gen);
|
|
24259
|
+
}
|
|
23964
24260
|
}
|
|
23965
24261
|
/**
|
|
23966
|
-
*
|
|
23967
|
-
*
|
|
24262
|
+
* Speculative read: locate + read + demux the fragment covering `probeMs` into
|
|
24263
|
+
* `scrubCache`. Bounded by {@link SCRUB_PREFETCH_MAX_INFLIGHT}; aborts at any
|
|
23968
24264
|
* teardown (`disposed` / left scrub / a bumped `scrubToken`); a failure is
|
|
23969
|
-
* swallowed (the
|
|
24265
|
+
* swallowed (the fragment just demuxes on demand). Never pushes and never
|
|
23970
24266
|
* mutates latest-wins state.
|
|
24267
|
+
*
|
|
24268
|
+
* Every read it issues is COUNTED (`prefetchReads` / `prefetchBytes`), and the
|
|
24269
|
+
* fragment it lands is remembered so a later tick hitting it counts as a
|
|
24270
|
+
* `prefetchHit`. Speculation that cannot be shown to pay for itself is just
|
|
24271
|
+
* load on the disk the drag is already waiting for.
|
|
23971
24272
|
*/
|
|
23972
24273
|
async scrubPrefetch(probeMs, gen) {
|
|
23973
|
-
if (this.scrubPrefetchInFlight) return;
|
|
23974
|
-
this.scrubPrefetchInFlight
|
|
24274
|
+
if (this.scrubPrefetchInFlight >= SCRUB_PREFETCH_MAX_INFLIGHT) return;
|
|
24275
|
+
this.scrubPrefetchInFlight += 1;
|
|
24276
|
+
this.scrubPrefetchProbes.add(probeMs);
|
|
23975
24277
|
try {
|
|
23976
24278
|
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return;
|
|
23977
24279
|
if (this.scrubCache.covering(this.deps.profile, probeMs)) return;
|
|
@@ -23983,10 +24285,13 @@ var RecordedFeeder = class {
|
|
|
23983
24285
|
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return;
|
|
23984
24286
|
if (loc.kind !== "segment") return;
|
|
23985
24287
|
const load = await this.loadScrubFragment(loc, probeMs);
|
|
24288
|
+
if (load !== null) this.scrubStats?.recordPrefetchRead(load.readBytes);
|
|
23986
24289
|
if (this.disposed || !this.scrubbing || gen !== this.scrubToken || load === null) return;
|
|
23987
24290
|
this.scrubCache.put(this.deps.profile, load.seg);
|
|
24291
|
+
this.scrubPrefetched.add(load.seg.startMs);
|
|
23988
24292
|
} catch {} finally {
|
|
23989
|
-
this.scrubPrefetchInFlight
|
|
24293
|
+
this.scrubPrefetchInFlight -= 1;
|
|
24294
|
+
this.scrubPrefetchProbes.delete(probeMs);
|
|
23990
24295
|
}
|
|
23991
24296
|
}
|
|
23992
24297
|
/**
|
|
@@ -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-BohtrVou.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as NativeLeaseSettingsSchema, h as NativeLeaseAdmissionSchema, l as DEFAULT_NATIVE_LEASE_SETTINGS } from "./dist-
|
|
1
|
+
import { g as NativeLeaseSettingsSchema, h as NativeLeaseAdmissionSchema, l as DEFAULT_NATIVE_LEASE_SETTINGS } from "./dist-BfTf6doa.mjs";
|
|
2
2
|
//#region src/session-decode/native-lease-config.ts
|
|
3
3
|
/**
|
|
4
4
|
* Resolution of the native-frame hold / tile knobs for one decode worker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.79",
|
|
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",
|