@camstack/addon-pipeline 1.2.106 → 1.2.115
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-BEzVJ0xg.js → addon-utils-C0ht2KPO.js} +51 -15
- package/dist/{addon-utils-A2S9D7pu.mjs → addon-utils-CgBCF-mE.mjs} +50 -14
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/detection-pipeline/index.js +198 -38
- package/dist/detection-pipeline/index.mjs +197 -37
- package/dist/{dist-BSeR_hVv.js → dist-BqIbYW1A.js} +290 -17
- package/dist/{dist-lITI1Bn5.mjs → dist-DGQkkWc6.mjs} +273 -18
- package/dist/{event-loop-stall-monitor-0VvjTRDP.mjs → event-loop-stall-monitor-D5jK5v4Z.mjs} +223 -2
- package/dist/{event-loop-stall-monitor-BNsp9k8r.js → event-loop-stall-monitor-ylMGpz48.js} +234 -1
- package/dist/{lazy-sharp-CBohM8D8.js → lazy-sharp-cOsy7l_P.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 +915 -107
- package/dist/pipeline-runner/index.mjs +914 -106
- package/dist/{process-memory-C33u3tQz.js → process-memory-45juuHpN.js} +1 -1
- package/dist/{process-memory-CEKVKfwg.mjs → process-memory-BtT7WfbJ.mjs} +1 -1
- package/dist/recorder/index.js +187 -47
- package/dist/recorder/index.mjs +187 -47
- package/dist/session-decode/decode-worker-child.js +30 -10
- package/dist/session-decode/decode-worker-child.mjs +29 -9
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-w2XLD3uI.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_SFFNGT.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.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-DsIi4G5Q.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs} +1 -1
- package/dist/stream-broker/{hostInit-B-h24cDy.mjs → hostInit-CG28NvlV.mjs} +3 -3
- package/dist/stream-broker/index.js +279 -198
- package/dist/stream-broker/index.mjs +279 -198
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-CiKfal2P.js → worker-protocol-CIrqGbpG.js} +6 -3
- package/dist/{worker-protocol-BDiL8TBm.mjs → worker-protocol-DyocTIx_.mjs} +6 -3
- package/package.json +1 -1
- package/python/inference_pool.py +4 -1
- package/python/test_inference_pool_preprocess.py +87 -16
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DvZWLWk1.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BqIbYW1A.js");
|
|
6
6
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_retire_root_keys = require("../retire-root-keys-KE6D6Xh_.js");
|
|
@@ -23631,7 +23631,7 @@ var MonotonicClock = class {
|
|
|
23631
23631
|
this.rate = rate;
|
|
23632
23632
|
}
|
|
23633
23633
|
};
|
|
23634
|
-
var SCRUB_FRAGMENT_CACHE_MAX_BYTES =
|
|
23634
|
+
var SCRUB_FRAGMENT_CACHE_MAX_BYTES = 48 * 1024 * 1024;
|
|
23635
23635
|
var SCRUB_FRAGMENT_CACHE_OTHER_MAX_BYTES = 2 * 1024 * 1024;
|
|
23636
23636
|
/** Demuxed bytes an entry holds — what the byte bound actually counts. */
|
|
23637
23637
|
function fragmentBytes(frag) {
|
|
@@ -24053,14 +24053,22 @@ var SCRUB_WHOLE_SEGMENT_MAX_BYTES = 15e5;
|
|
|
24053
24053
|
* drag starts on.
|
|
24054
24054
|
*
|
|
24055
24055
|
* Asymmetric because the gesture is: a drag that begins at the live edge — or
|
|
24056
|
-
* at a notification's moment — is looking for what ALREADY happened.
|
|
24057
|
-
* against
|
|
24058
|
-
*
|
|
24056
|
+
* at a notification's moment — is looking for what ALREADY happened. 15 min
|
|
24057
|
+
* back against 5 min forward fills the 48 MB window where the finger will
|
|
24058
|
+
* actually move, read sequentially in the background while the first ticks
|
|
24059
|
+
* still pay their own demand-read.
|
|
24059
24060
|
*/
|
|
24060
|
-
var SCRUB_WARM_BACK_MS =
|
|
24061
|
-
var SCRUB_WARM_FWD_MS =
|
|
24062
|
-
/**
|
|
24063
|
-
var
|
|
24061
|
+
var SCRUB_WARM_BACK_MS = 15 * 6e4;
|
|
24062
|
+
var SCRUB_WARM_FWD_MS = 5 * 6e4;
|
|
24063
|
+
/** Symmetric span used after a jump: scrub around the landing, both ways. */
|
|
24064
|
+
var SCRUB_WARM_AROUND_MS = 15 * 6e4;
|
|
24065
|
+
/**
|
|
24066
|
+
* Re-centre the fill when a tick lands this far from the window we are filling.
|
|
24067
|
+
* A slow drag stays inside this; a jump to 8am does not.
|
|
24068
|
+
*/
|
|
24069
|
+
var SCRUB_RECENTER_MS = 12e4;
|
|
24070
|
+
/** Hard bound on warm reads, derived from the session RAM budget. */
|
|
24071
|
+
var SCRUB_WARM_MAX_SEGMENTS = Math.max(1, Math.floor(SCRUB_FRAGMENT_CACHE_MAX_BYTES / 2e5));
|
|
24064
24072
|
/**
|
|
24065
24073
|
* Median recent read time at or above which the feeder STOPS speculating.
|
|
24066
24074
|
*
|
|
@@ -24314,6 +24322,16 @@ var RecordedFeeder = class {
|
|
|
24314
24322
|
scrubReadMsWindow = [];
|
|
24315
24323
|
/** Warm-up runs once per drag, from the playhead the finger went down on. */
|
|
24316
24324
|
scrubWarmStarted = false;
|
|
24325
|
+
/** Centre of the fill currently in flight (or last completed). */
|
|
24326
|
+
scrubWarmCentreMs = null;
|
|
24327
|
+
/** Bumped to abort a fill without aborting the drag tick (`scrubToken`). */
|
|
24328
|
+
scrubWarmGen = 0;
|
|
24329
|
+
/** Tick demand-read in flight: the fill waits so the finger wins the spindle. */
|
|
24330
|
+
scrubTickReadInFlight = false;
|
|
24331
|
+
/** One fill walker per drag — a fling retargets it, it does not spawn another. */
|
|
24332
|
+
scrubFillLoopActive = false;
|
|
24333
|
+
scrubWarmBackMs = SCRUB_WARM_BACK_MS;
|
|
24334
|
+
scrubWarmFwdMs = SCRUB_WARM_FWD_MS;
|
|
24317
24335
|
constructor(deps) {
|
|
24318
24336
|
this.deps = deps;
|
|
24319
24337
|
this.audioClock = new MonotonicClock(deps.audioClockRateHz ?? 8e3);
|
|
@@ -24435,6 +24453,7 @@ var RecordedFeeder = class {
|
|
|
24435
24453
|
let prefixFed = false;
|
|
24436
24454
|
let gopFirst = false;
|
|
24437
24455
|
let stateAnnounced = false;
|
|
24456
|
+
let reusedWhole = null;
|
|
24438
24457
|
if (this.deps.readGop) {
|
|
24439
24458
|
const cached = this.scrubCache.covering(this.deps.profile, landMs);
|
|
24440
24459
|
let fragment;
|
|
@@ -24444,6 +24463,10 @@ var RecordedFeeder = class {
|
|
|
24444
24463
|
if (fragmentLoad === null) return;
|
|
24445
24464
|
fragment = fragmentLoad.seg;
|
|
24446
24465
|
this.scrubCache.put(this.deps.profile, fragment);
|
|
24466
|
+
if (fragmentLoad.wholeSegmentRead && fragmentLoad.rawBytes) reusedWhole = {
|
|
24467
|
+
video: fragment.aus,
|
|
24468
|
+
bytes: fragmentLoad.rawBytes
|
|
24469
|
+
};
|
|
24447
24470
|
}
|
|
24448
24471
|
if (this.disposed || token !== this.seekToken) return;
|
|
24449
24472
|
const fragmentLoc = {
|
|
@@ -24461,14 +24484,23 @@ var RecordedFeeder = class {
|
|
|
24461
24484
|
this.startPositionReports();
|
|
24462
24485
|
stateAnnounced = true;
|
|
24463
24486
|
}
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
this.
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24487
|
+
let loaded;
|
|
24488
|
+
if (reusedWhole) {
|
|
24489
|
+
const audio = this.rate !== 0 && this.deps.demuxAudio && this.resolveAudioPush() !== void 0 ? await this.withTimeout(this.deps.demuxAudio(reusedWhole.bytes)).catch(() => []) : [];
|
|
24490
|
+
loaded = {
|
|
24491
|
+
video: reusedWhole.video,
|
|
24492
|
+
audio
|
|
24493
|
+
};
|
|
24494
|
+
} else {
|
|
24495
|
+
const fullLoad = this.loadAus(loc.startMs, this.rate !== 0, this.deps.readGop ? void 0 : (video) => {
|
|
24496
|
+
if (this.disposed || token !== this.seekToken) return;
|
|
24497
|
+
this.feedPrefix(loc, video, landMs);
|
|
24498
|
+
prefixFed = true;
|
|
24499
|
+
firstFrameMs = performance.now() - seekStartedAt;
|
|
24500
|
+
});
|
|
24501
|
+
fullLoad.catch(() => {});
|
|
24502
|
+
loaded = await fullLoad;
|
|
24503
|
+
}
|
|
24472
24504
|
loadMs = performance.now() - loadStartedAt;
|
|
24473
24505
|
if (this.disposed || token !== this.seekToken || loaded === null) return;
|
|
24474
24506
|
if (!prefixFed) this.feedPrefix(loc, loaded.video, landMs);
|
|
@@ -24630,38 +24662,92 @@ var RecordedFeeder = class {
|
|
|
24630
24662
|
* back and {@link SCRUB_WARM_FWD_MS} forward from the entry playhead, one
|
|
24631
24663
|
* segment at a time, backward first.
|
|
24632
24664
|
*
|
|
24633
|
-
*
|
|
24634
|
-
*
|
|
24665
|
+
* Fire-and-forget, never on the tick's path — a tick that arrives against a
|
|
24666
|
+
* cold cache pays its own read exactly as before. A later jump more than
|
|
24667
|
+
* {@link SCRUB_RECENTER_MS} from this centre aborts the fill (via
|
|
24668
|
+
* {@link scrubWarmGen}, not the drag token) and starts a new window.
|
|
24669
|
+
*
|
|
24635
24670
|
* It walks segment to segment through `locate` rather than assuming a segment
|
|
24636
24671
|
* length, so a recorder configured with a different `segmentSeconds` warms the
|
|
24637
24672
|
* same SPAN rather than the same count.
|
|
24638
24673
|
*/
|
|
24639
|
-
startScrubWarm(centreMs,
|
|
24640
|
-
if (this.scrubWarmStarted) return;
|
|
24674
|
+
startScrubWarm(centreMs, dragGen, opts) {
|
|
24675
|
+
if (this.scrubWarmStarted && !opts?.force) return;
|
|
24641
24676
|
this.scrubWarmStarted = true;
|
|
24642
|
-
this.
|
|
24677
|
+
this.scrubWarmGen += 1;
|
|
24678
|
+
this.scrubWarmCentreMs = centreMs;
|
|
24679
|
+
this.scrubWarmBackMs = opts?.backMs ?? SCRUB_WARM_BACK_MS;
|
|
24680
|
+
this.scrubWarmFwdMs = opts?.fwdMs ?? SCRUB_WARM_FWD_MS;
|
|
24681
|
+
this.deps.onLog?.("scrub-feeder:warm-start", {
|
|
24682
|
+
centreMs,
|
|
24683
|
+
fillGen: this.scrubWarmGen
|
|
24684
|
+
});
|
|
24685
|
+
if (this.scrubFillLoopActive) return;
|
|
24686
|
+
this.scrubFillLoopActive = true;
|
|
24687
|
+
this.runScrubFillLoop(dragGen);
|
|
24688
|
+
}
|
|
24689
|
+
/**
|
|
24690
|
+
* A single sequential walk. Recentre bumps {@link scrubWarmGen} and this loop
|
|
24691
|
+
* restarts around the new cursor — it must never `void` a second walker, or a
|
|
24692
|
+
* coarse fling stacks one USB walk per tick and play/scrub starve (1.2.113:
|
|
24693
|
+
* 16 walkers, seek `loadMs=16652`, drags with `ticks=0`).
|
|
24694
|
+
*/
|
|
24695
|
+
async runScrubFillLoop(dragGen) {
|
|
24696
|
+
let lastWorked = -1;
|
|
24697
|
+
try {
|
|
24698
|
+
while (!this.disposed && this.scrubbing && dragGen === this.scrubToken) {
|
|
24699
|
+
const gen = this.scrubWarmGen;
|
|
24700
|
+
const centre = this.scrubWarmCentreMs;
|
|
24701
|
+
if (centre === null) return;
|
|
24702
|
+
await this.warmScrubWindow(centre, dragGen, gen, this.scrubWarmBackMs, this.scrubWarmFwdMs);
|
|
24703
|
+
lastWorked = gen;
|
|
24704
|
+
if (gen === this.scrubWarmGen) return;
|
|
24705
|
+
}
|
|
24706
|
+
} finally {
|
|
24707
|
+
this.scrubFillLoopActive = false;
|
|
24708
|
+
if (!this.disposed && this.scrubbing && dragGen === this.scrubToken && this.scrubWarmCentreMs !== null && this.scrubWarmGen !== lastWorked) {
|
|
24709
|
+
this.scrubFillLoopActive = true;
|
|
24710
|
+
this.runScrubFillLoop(dragGen);
|
|
24711
|
+
}
|
|
24712
|
+
}
|
|
24713
|
+
}
|
|
24714
|
+
/**
|
|
24715
|
+
* Abort the current fill and start a new ±15 min window around `centreMs`.
|
|
24716
|
+
* Does not bump `scrubToken` — the in-flight tick must still land.
|
|
24717
|
+
*/
|
|
24718
|
+
recenterScrubWarm(centreMs) {
|
|
24719
|
+
if (this.deps.profile !== SCRUB_WHOLE_SEGMENT_PROFILE) return;
|
|
24720
|
+
this.startScrubWarm(centreMs, this.scrubToken, {
|
|
24721
|
+
force: true,
|
|
24722
|
+
backMs: SCRUB_WARM_AROUND_MS,
|
|
24723
|
+
fwdMs: SCRUB_WARM_AROUND_MS
|
|
24724
|
+
});
|
|
24725
|
+
}
|
|
24726
|
+
maybeRecenterScrubWarm(targetMs) {
|
|
24727
|
+
if (this.deps.profile !== SCRUB_WHOLE_SEGMENT_PROFILE) return;
|
|
24728
|
+
const centre = this.scrubWarmCentreMs;
|
|
24729
|
+
if (centre === null) return;
|
|
24730
|
+
if (Math.abs(targetMs - centre) <= SCRUB_RECENTER_MS) return;
|
|
24731
|
+
this.recenterScrubWarm(targetMs);
|
|
24643
24732
|
}
|
|
24644
|
-
async warmScrubWindow(centreMs,
|
|
24645
|
-
const spent = await this.warmScrubDirection(centreMs, -1,
|
|
24646
|
-
await this.warmScrubDirection(centreMs, 1,
|
|
24733
|
+
async warmScrubWindow(centreMs, dragGen, fillGen, backMs, fwdMs) {
|
|
24734
|
+
const spent = await this.warmScrubDirection(centreMs, -1, backMs, 0, dragGen, fillGen);
|
|
24735
|
+
await this.warmScrubDirection(centreMs, 1, fwdMs, spent, dragGen, fillGen);
|
|
24647
24736
|
}
|
|
24648
24737
|
/**
|
|
24649
24738
|
* Walk `dir` from `fromMs` until `spanMs` of timeline is warm (or the shared
|
|
24650
24739
|
* bound is hit), reading each segment that is not already cached. Returns the
|
|
24651
24740
|
* running total of segments read, so the two directions share one budget.
|
|
24652
24741
|
*/
|
|
24653
|
-
async warmScrubDirection(fromMs, dir, spanMs, spent,
|
|
24742
|
+
async warmScrubDirection(fromMs, dir, spanMs, spent, dragGen, fillGen) {
|
|
24654
24743
|
let read = spent;
|
|
24655
24744
|
let probeMs = fromMs;
|
|
24656
24745
|
for (let i = 0; i < SCRUB_WARM_MAX_SEGMENTS; i++) {
|
|
24657
|
-
if (this.disposed || !this.scrubbing ||
|
|
24746
|
+
if (this.disposed || !this.scrubbing || dragGen !== this.scrubToken) return read;
|
|
24747
|
+
if (fillGen !== this.scrubWarmGen) return read;
|
|
24658
24748
|
if (read >= SCRUB_WARM_MAX_SEGMENTS) return read;
|
|
24659
24749
|
if (Math.abs(probeMs - fromMs) > spanMs) return read;
|
|
24660
|
-
|
|
24661
|
-
this.scrubStats?.recordPrefetchSuppressed();
|
|
24662
|
-
return read;
|
|
24663
|
-
}
|
|
24664
|
-
const seg = await this.warmOneScrubSegment(probeMs, gen);
|
|
24750
|
+
const seg = await this.warmOneScrubSegment(probeMs, dragGen, fillGen);
|
|
24665
24751
|
if (seg === null) return read;
|
|
24666
24752
|
if (seg.fetched) read += 1;
|
|
24667
24753
|
probeMs = dir === -1 ? seg.startMs - 1 : seg.startMs + seg.durMs + 1;
|
|
@@ -24675,7 +24761,7 @@ var RecordedFeeder = class {
|
|
|
24675
24761
|
* so the walk can step to its neighbour, or null when the walk must stop (a
|
|
24676
24762
|
* gap, a failure, or teardown).
|
|
24677
24763
|
*/
|
|
24678
|
-
async warmOneScrubSegment(probeMs,
|
|
24764
|
+
async warmOneScrubSegment(probeMs, dragGen, fillGen) {
|
|
24679
24765
|
try {
|
|
24680
24766
|
const warm = this.scrubCache.covering(this.deps.profile, probeMs);
|
|
24681
24767
|
if (warm) return {
|
|
@@ -24683,16 +24769,20 @@ var RecordedFeeder = class {
|
|
|
24683
24769
|
durMs: warm.durMs,
|
|
24684
24770
|
fetched: false
|
|
24685
24771
|
};
|
|
24772
|
+
if (!await this.waitForTickReadIdle(fillGen)) return null;
|
|
24686
24773
|
const loc = await this.withTimeout(this.deps.locate({
|
|
24687
24774
|
deviceId: this.deps.deviceId,
|
|
24688
24775
|
profile: this.deps.profile,
|
|
24689
24776
|
epochMs: probeMs
|
|
24690
24777
|
}));
|
|
24691
|
-
if (this.disposed || !this.scrubbing ||
|
|
24778
|
+
if (this.disposed || !this.scrubbing || dragGen !== this.scrubToken) return null;
|
|
24779
|
+
if (fillGen !== this.scrubWarmGen) return null;
|
|
24692
24780
|
if (loc.kind !== "segment") return null;
|
|
24781
|
+
if (!await this.waitForTickReadIdle(fillGen)) return null;
|
|
24693
24782
|
const load = await this.loadScrubFragment(loc, probeMs);
|
|
24694
24783
|
if (load !== null) this.scrubStats?.recordPrefetchRead(load.readBytes);
|
|
24695
|
-
if (this.disposed || !this.scrubbing ||
|
|
24784
|
+
if (this.disposed || !this.scrubbing || dragGen !== this.scrubToken || load === null) return null;
|
|
24785
|
+
if (fillGen !== this.scrubWarmGen) return null;
|
|
24696
24786
|
this.scrubCache.put(this.deps.profile, load.seg);
|
|
24697
24787
|
this.scrubPrefetched.add(load.seg.startMs);
|
|
24698
24788
|
return {
|
|
@@ -24704,6 +24794,14 @@ var RecordedFeeder = class {
|
|
|
24704
24794
|
return null;
|
|
24705
24795
|
}
|
|
24706
24796
|
}
|
|
24797
|
+
/** Yield the spindle to an in-flight finger tick. Returns false if this fill died. */
|
|
24798
|
+
async waitForTickReadIdle(fillGen) {
|
|
24799
|
+
while (this.scrubTickReadInFlight) {
|
|
24800
|
+
await new Promise((r) => setTimeout(r, 4));
|
|
24801
|
+
if (this.disposed || !this.scrubbing || fillGen !== this.scrubWarmGen) return false;
|
|
24802
|
+
}
|
|
24803
|
+
return true;
|
|
24804
|
+
}
|
|
24707
24805
|
/**
|
|
24708
24806
|
* EXPERIMENTAL feeder-scrub: register a drag target. Records the newest target
|
|
24709
24807
|
* and, if no tick cycle is live, starts a leading-edge tick. A burst of moves
|
|
@@ -24867,63 +24965,69 @@ var RecordedFeeder = class {
|
|
|
24867
24965
|
}
|
|
24868
24966
|
}
|
|
24869
24967
|
if (!seg) {
|
|
24870
|
-
|
|
24871
|
-
|
|
24872
|
-
|
|
24873
|
-
|
|
24874
|
-
|
|
24875
|
-
}));
|
|
24876
|
-
locateCalls += 1;
|
|
24877
|
-
locateMs = performance.now() - locateStartMs;
|
|
24878
|
-
this.maybeLogSlowLocate(target, locateMs);
|
|
24879
|
-
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return;
|
|
24880
|
-
if (loc.kind === "gap") {
|
|
24881
|
-
const decision = resolveSnapTarget(loc, target);
|
|
24882
|
-
if (decision.kind !== "snap") {
|
|
24883
|
-
if (!this.scrubNoFootageLogged) {
|
|
24884
|
-
this.scrubNoFootageLogged = true;
|
|
24885
|
-
if (decision.kind === "too-far") this.deps.onLog?.("scrub-feeder:snap-too-far", {
|
|
24886
|
-
epochMs: target,
|
|
24887
|
-
nearestMs: decision.snapMs,
|
|
24888
|
-
distanceMs: decision.distanceMs
|
|
24889
|
-
});
|
|
24890
|
-
else this.deps.onLog?.("scrub-feeder:no-footage", { epochMs: target });
|
|
24891
|
-
}
|
|
24892
|
-
return;
|
|
24893
|
-
}
|
|
24894
|
-
const snapStartMs = performance.now();
|
|
24895
|
-
const snapped = await this.withTimeout(this.deps.locate({
|
|
24968
|
+
this.scrubTickReadInFlight = true;
|
|
24969
|
+
try {
|
|
24970
|
+
this.maybeRecenterScrubWarm(target);
|
|
24971
|
+
const locateStartMs = performance.now();
|
|
24972
|
+
let loc = await this.withTimeout(this.deps.locate({
|
|
24896
24973
|
deviceId: this.deps.deviceId,
|
|
24897
24974
|
profile: this.deps.profile,
|
|
24898
|
-
epochMs:
|
|
24975
|
+
epochMs: target
|
|
24899
24976
|
}));
|
|
24900
24977
|
locateCalls += 1;
|
|
24901
|
-
locateMs =
|
|
24978
|
+
locateMs = performance.now() - locateStartMs;
|
|
24979
|
+
this.maybeLogSlowLocate(target, locateMs);
|
|
24902
24980
|
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return;
|
|
24903
|
-
if (
|
|
24904
|
-
|
|
24905
|
-
|
|
24906
|
-
this.
|
|
24981
|
+
if (loc.kind === "gap") {
|
|
24982
|
+
const decision = resolveSnapTarget(loc, target);
|
|
24983
|
+
if (decision.kind !== "snap") {
|
|
24984
|
+
if (!this.scrubNoFootageLogged) {
|
|
24985
|
+
this.scrubNoFootageLogged = true;
|
|
24986
|
+
if (decision.kind === "too-far") this.deps.onLog?.("scrub-feeder:snap-too-far", {
|
|
24987
|
+
epochMs: target,
|
|
24988
|
+
nearestMs: decision.snapMs,
|
|
24989
|
+
distanceMs: decision.distanceMs
|
|
24990
|
+
});
|
|
24991
|
+
else this.deps.onLog?.("scrub-feeder:no-footage", { epochMs: target });
|
|
24992
|
+
}
|
|
24993
|
+
return;
|
|
24907
24994
|
}
|
|
24908
|
-
|
|
24995
|
+
const snapStartMs = performance.now();
|
|
24996
|
+
const snapped = await this.withTimeout(this.deps.locate({
|
|
24997
|
+
deviceId: this.deps.deviceId,
|
|
24998
|
+
profile: this.deps.profile,
|
|
24999
|
+
epochMs: decision.snapMs
|
|
25000
|
+
}));
|
|
25001
|
+
locateCalls += 1;
|
|
25002
|
+
locateMs = (locateMs ?? 0) + (performance.now() - snapStartMs);
|
|
25003
|
+
if (this.disposed || !this.scrubbing || gen !== this.scrubToken) return;
|
|
25004
|
+
if (snapped.kind !== "segment") {
|
|
25005
|
+
if (!this.scrubNoFootageLogged) {
|
|
25006
|
+
this.scrubNoFootageLogged = true;
|
|
25007
|
+
this.deps.onLog?.("scrub-feeder:no-footage", { epochMs: target });
|
|
25008
|
+
}
|
|
25009
|
+
return;
|
|
25010
|
+
}
|
|
25011
|
+
this.deps.onLog?.("scrub-feeder:snapped", {
|
|
25012
|
+
requestedMs: target,
|
|
25013
|
+
snappedMs: decision.snapMs
|
|
25014
|
+
});
|
|
25015
|
+
loc = snapped;
|
|
25016
|
+
effTarget = decision.snapMs;
|
|
24909
25017
|
}
|
|
24910
|
-
this.deps.
|
|
24911
|
-
|
|
24912
|
-
|
|
24913
|
-
|
|
24914
|
-
|
|
24915
|
-
|
|
24916
|
-
|
|
24917
|
-
|
|
24918
|
-
|
|
24919
|
-
|
|
24920
|
-
|
|
24921
|
-
|
|
24922
|
-
|
|
24923
|
-
readBytes = load.readBytes;
|
|
24924
|
-
demuxMs = load.demuxMs;
|
|
24925
|
-
wholeSegmentRead = load.wholeSegmentRead;
|
|
24926
|
-
this.scrubCache.put(this.deps.profile, seg);
|
|
25018
|
+
seg = this.scrubCache.covering(this.deps.profile, effTarget);
|
|
25019
|
+
if (!seg) {
|
|
25020
|
+
const load = await this.loadScrubFragment(loc, effTarget);
|
|
25021
|
+
if (this.disposed || !this.scrubbing || gen !== this.scrubToken || load === null) return;
|
|
25022
|
+
seg = load.seg;
|
|
25023
|
+
readMs = load.readMs;
|
|
25024
|
+
readBytes = load.readBytes;
|
|
25025
|
+
demuxMs = load.demuxMs;
|
|
25026
|
+
wholeSegmentRead = load.wholeSegmentRead;
|
|
25027
|
+
this.scrubCache.put(this.deps.profile, seg);
|
|
25028
|
+
}
|
|
25029
|
+
} finally {
|
|
25030
|
+
this.scrubTickReadInFlight = false;
|
|
24927
25031
|
}
|
|
24928
25032
|
}
|
|
24929
25033
|
this.scrubSegment = seg;
|
|
@@ -25132,7 +25236,8 @@ var RecordedFeeder = class {
|
|
|
25132
25236
|
readMs: readEndMs - readStartMs,
|
|
25133
25237
|
readBytes: bytes.length,
|
|
25134
25238
|
demuxMs: performance.now() - readEndMs,
|
|
25135
|
-
wholeSegmentRead: true
|
|
25239
|
+
wholeSegmentRead: true,
|
|
25240
|
+
rawBytes: bytes
|
|
25136
25241
|
};
|
|
25137
25242
|
}
|
|
25138
25243
|
/**
|
|
@@ -25344,6 +25449,7 @@ var RecordedFeeder = class {
|
|
|
25344
25449
|
this.scrubStats?.recordPrefetchSuppressed();
|
|
25345
25450
|
return;
|
|
25346
25451
|
}
|
|
25452
|
+
if (this.scrubFillLoopActive) return;
|
|
25347
25453
|
const aheadMs = Math.max(Math.abs(stepMs), seg.durMs + 1);
|
|
25348
25454
|
const horizons = Math.abs(stepMs) > seg.durMs ? SCRUB_PREFETCH_MAX_INFLIGHT : 1;
|
|
25349
25455
|
for (let horizon = 1; horizon <= horizons; horizon++) {
|
|
@@ -25352,6 +25458,7 @@ var RecordedFeeder = class {
|
|
|
25352
25458
|
if (probeMs < 0) continue;
|
|
25353
25459
|
if (this.scrubPrefetchProbes.has(probeMs)) continue;
|
|
25354
25460
|
if (this.scrubCache.covering(this.deps.profile, probeMs)) continue;
|
|
25461
|
+
if (this.scrubWarmCentreMs !== null && Math.abs(probeMs - this.scrubWarmCentreMs) > SCRUB_WARM_AROUND_MS) continue;
|
|
25355
25462
|
this.scrubPrefetch(probeMs, gen);
|
|
25356
25463
|
}
|
|
25357
25464
|
}
|
|
@@ -25974,7 +26081,7 @@ var TimelineSession = class {
|
|
|
25974
26081
|
*
|
|
25975
26082
|
* 1. The scrub is pinned to `low` by the client, and every D163 pre-cache
|
|
25976
26083
|
* behaviour is gated on `deps.profile === 'low'` by exact string equality
|
|
25977
|
-
* — the
|
|
26084
|
+
* — the ±15 min entry warm, the whole-segment read, and the 48 MB
|
|
25978
26085
|
* fragment budget (2 MB for anything else). `profileSearchOrder` puts
|
|
25979
26086
|
* `high` BEFORE `low`, so a momentary `low` gap used to hand the drag a
|
|
25980
26087
|
* `high` feeder and silently turn all three off. The client cannot repair
|
|
@@ -33710,6 +33817,70 @@ function preferLiveAdaptiveCandidates(candidates) {
|
|
|
33710
33817
|
return natives.length > 0 ? natives : [...candidates];
|
|
33711
33818
|
}
|
|
33712
33819
|
//#endregion
|
|
33820
|
+
//#region src/stream-broker/webrtc/adaptive-tier.ts
|
|
33821
|
+
var RANK = {
|
|
33822
|
+
low: 1,
|
|
33823
|
+
mid: 2,
|
|
33824
|
+
high: 3
|
|
33825
|
+
};
|
|
33826
|
+
var QUALITY_FIRST = [
|
|
33827
|
+
"high",
|
|
33828
|
+
"mid",
|
|
33829
|
+
"low"
|
|
33830
|
+
];
|
|
33831
|
+
var SMALLEST_FIRST = [
|
|
33832
|
+
"low",
|
|
33833
|
+
"mid",
|
|
33834
|
+
"high"
|
|
33835
|
+
];
|
|
33836
|
+
function isTier(value) {
|
|
33837
|
+
return value === "high" || value === "mid" || value === "low";
|
|
33838
|
+
}
|
|
33839
|
+
function firstAvailable(order, available) {
|
|
33840
|
+
const set = new Set(available);
|
|
33841
|
+
return order.find((t) => set.has(t));
|
|
33842
|
+
}
|
|
33843
|
+
function capTo(tier, available) {
|
|
33844
|
+
const max = RANK[tier];
|
|
33845
|
+
return firstAvailable(QUALITY_FIRST.filter((t) => RANK[t] <= max), available);
|
|
33846
|
+
}
|
|
33847
|
+
function pickAdaptiveTier(input) {
|
|
33848
|
+
const available = input.available.filter((t) => t === "high" || t === "mid" || t === "low");
|
|
33849
|
+
const fallback = firstAvailable(QUALITY_FIRST, available) ?? "high";
|
|
33850
|
+
if (isTier(input.prefersTier)) {
|
|
33851
|
+
const exact = available.includes(input.prefersTier) ? input.prefersTier : void 0;
|
|
33852
|
+
if (exact) return {
|
|
33853
|
+
tier: exact,
|
|
33854
|
+
reason: "prefers-tier"
|
|
33855
|
+
};
|
|
33856
|
+
return {
|
|
33857
|
+
tier: firstAvailable(SMALLEST_FIRST, available) ?? fallback,
|
|
33858
|
+
reason: "prefers-tier-fallback"
|
|
33859
|
+
};
|
|
33860
|
+
}
|
|
33861
|
+
let tier = fallback;
|
|
33862
|
+
let reason = "default-high";
|
|
33863
|
+
if (input.downlinkMbps !== void 0 && Number.isFinite(input.downlinkMbps) && input.downlinkMbps >= 0) {
|
|
33864
|
+
tier = capTo(input.downlinkMbps >= 8 ? "high" : input.downlinkMbps >= 2.5 ? "mid" : "low", available) ?? fallback;
|
|
33865
|
+
reason = "downlink";
|
|
33866
|
+
}
|
|
33867
|
+
const width = input.viewportWidth;
|
|
33868
|
+
const height = input.viewportHeight;
|
|
33869
|
+
if (width !== void 0 && height !== void 0 && width > 0 && height > 0) {
|
|
33870
|
+
if (Math.max(width, height) < 240) {
|
|
33871
|
+
const tiled = capTo("low", available) ?? fallback;
|
|
33872
|
+
if (RANK[tiled] < RANK[tier]) return {
|
|
33873
|
+
tier: tiled,
|
|
33874
|
+
reason: "viewport-ceiling"
|
|
33875
|
+
};
|
|
33876
|
+
}
|
|
33877
|
+
}
|
|
33878
|
+
return {
|
|
33879
|
+
tier,
|
|
33880
|
+
reason
|
|
33881
|
+
};
|
|
33882
|
+
}
|
|
33883
|
+
//#endregion
|
|
33713
33884
|
//#region src/stream-broker/webrtc/broker-webrtc-server.ts
|
|
33714
33885
|
/**
|
|
33715
33886
|
* Broker-integrated WebRTC server.
|
|
@@ -33825,36 +33996,12 @@ function deriveH264ProfileLevelId(sdpParameterSets, preBuffer) {
|
|
|
33825
33996
|
if (profileLevelId) return profileLevelId;
|
|
33826
33997
|
}
|
|
33827
33998
|
}
|
|
33828
|
-
|
|
33829
|
-
|
|
33830
|
-
|
|
33831
|
-
|
|
33832
|
-
|
|
33833
|
-
|
|
33834
|
-
mid: {
|
|
33835
|
-
pixels: 1280 * 720,
|
|
33836
|
-
bitrateKbps: 1200
|
|
33837
|
-
},
|
|
33838
|
-
low: {
|
|
33839
|
-
pixels: 640 * 360,
|
|
33840
|
-
bitrateKbps: 400
|
|
33841
|
-
}
|
|
33842
|
-
};
|
|
33843
|
-
var TIER_PREFERENCE = [
|
|
33844
|
-
"high",
|
|
33845
|
-
"mid",
|
|
33846
|
-
"low"
|
|
33847
|
-
];
|
|
33848
|
-
/** Smallest-keyframe-first tier order. Used as the fallback when an explicit
|
|
33849
|
-
* `prefersTier` (e.g. the hub's `remote`-viewer bias to `low`) has no exact
|
|
33850
|
-
* assigned slot: prefer the SMALLEST available tier rather than the
|
|
33851
|
-
* quality-first default, so a remote-biased request never silently lands back
|
|
33852
|
-
* on `high`. */
|
|
33853
|
-
var SMALLEST_FIRST_TIER_PREFERENCE = [
|
|
33854
|
-
"low",
|
|
33855
|
-
"mid",
|
|
33856
|
-
"high"
|
|
33857
|
-
];
|
|
33999
|
+
function adaptivePickLogLine(reason) {
|
|
34000
|
+
if (reason === "prefers-tier") return "Adaptive source selected (prefersTier exact)";
|
|
34001
|
+
if (reason === "prefers-tier-fallback") return "Adaptive source selected (prefersTier fallback → smallest available)";
|
|
34002
|
+
if (reason === "default-high") return "Adaptive source selected (no client hints — tier preference)";
|
|
34003
|
+
return "Adaptive source selected";
|
|
34004
|
+
}
|
|
33858
34005
|
/** Push-queue overflow purges are aggregated and reported at most this often. */
|
|
33859
34006
|
var FRAME_DROP_LOG_INTERVAL_MS = 3e4;
|
|
33860
34007
|
/**
|
|
@@ -33895,20 +34042,6 @@ function reduceProfileForTranscodeDown(base) {
|
|
|
33895
34042
|
}
|
|
33896
34043
|
};
|
|
33897
34044
|
}
|
|
33898
|
-
function scoreBroker(tier, broker, hints) {
|
|
33899
|
-
const stats = broker.getStats();
|
|
33900
|
-
const fallback = tier ? LABEL_DEFAULTS[tier] : void 0;
|
|
33901
|
-
const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : fallback?.bitrateKbps ?? 2e3;
|
|
33902
|
-
const srcPixels = fallback?.pixels ?? 1920 * 1080;
|
|
33903
|
-
const targetPixels = (hints.viewportWidth ?? 1920) * (hints.viewportHeight ?? 1080);
|
|
33904
|
-
const pixelDistance = Math.abs(srcPixels - targetPixels) / 1e6;
|
|
33905
|
-
let bandwidthPenalty = 0;
|
|
33906
|
-
if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {
|
|
33907
|
-
const availableKbps = hints.downlinkMbps * 1e3 * .6;
|
|
33908
|
-
if (bitrateKbps > availableKbps) bandwidthPenalty = (bitrateKbps - availableKbps) / availableKbps * 100;
|
|
33909
|
-
}
|
|
33910
|
-
return pixelDistance + bandwidthPenalty;
|
|
33911
|
-
}
|
|
33912
34045
|
var BrokerWebrtcServer = class {
|
|
33913
34046
|
logger;
|
|
33914
34047
|
getIceServersFn;
|
|
@@ -35040,78 +35173,26 @@ var BrokerWebrtcServer = class {
|
|
|
35040
35173
|
if (pool.length === 1) return pool[0].brokerId;
|
|
35041
35174
|
const adaptiveDeviceIdTag = Number(deviceKey.split("/")[0]);
|
|
35042
35175
|
const deviceIdTag = Number.isFinite(adaptiveDeviceIdTag) ? { tags: { deviceId: adaptiveDeviceIdTag } } : {};
|
|
35043
|
-
|
|
35044
|
-
|
|
35045
|
-
|
|
35046
|
-
|
|
35047
|
-
|
|
35048
|
-
|
|
35049
|
-
|
|
35050
|
-
|
|
35051
|
-
|
|
35052
|
-
|
|
35053
|
-
}
|
|
35054
|
-
});
|
|
35055
|
-
return explicit.brokerId;
|
|
35056
|
-
}
|
|
35057
|
-
for (const tier of SMALLEST_FIRST_TIER_PREFERENCE) {
|
|
35058
|
-
const match = pool.find((c) => c.tier === tier);
|
|
35059
|
-
if (match) {
|
|
35060
|
-
this.logger.info("Adaptive source selected (prefersTier fallback → smallest available)", {
|
|
35061
|
-
...deviceIdTag,
|
|
35062
|
-
meta: {
|
|
35063
|
-
deviceKey,
|
|
35064
|
-
selected: match.brokerId,
|
|
35065
|
-
tier: match.tier,
|
|
35066
|
-
prefersTier: hints.prefersTier
|
|
35067
|
-
}
|
|
35068
|
-
});
|
|
35069
|
-
return match.brokerId;
|
|
35070
|
-
}
|
|
35071
|
-
}
|
|
35072
|
-
}
|
|
35073
|
-
if (!(hints.viewportWidth !== void 0 || hints.viewportHeight !== void 0 || hints.downlinkMbps !== void 0)) {
|
|
35074
|
-
for (const tier of TIER_PREFERENCE) {
|
|
35075
|
-
const match = pool.find((c) => c.tier === tier);
|
|
35076
|
-
if (match) {
|
|
35077
|
-
this.logger.info("Adaptive source selected (no client hints — tier preference)", {
|
|
35078
|
-
...deviceIdTag,
|
|
35079
|
-
meta: {
|
|
35080
|
-
deviceKey,
|
|
35081
|
-
selected: match.brokerId,
|
|
35082
|
-
tier: match.tier
|
|
35083
|
-
}
|
|
35084
|
-
});
|
|
35085
|
-
return match.brokerId;
|
|
35086
|
-
}
|
|
35087
|
-
}
|
|
35088
|
-
return pool[0].brokerId;
|
|
35089
|
-
}
|
|
35090
|
-
let bestId = pool[0].brokerId;
|
|
35091
|
-
let bestTier = pool[0].tier;
|
|
35092
|
-
let bestScore = Infinity;
|
|
35093
|
-
for (const c of pool) {
|
|
35094
|
-
const score = scoreBroker(c.tier, c.broker, hints);
|
|
35095
|
-
const cRank = c.tier ? TIER_PREFERENCE.indexOf(c.tier) : TIER_PREFERENCE.length;
|
|
35096
|
-
const bestRank = bestTier ? TIER_PREFERENCE.indexOf(bestTier) : TIER_PREFERENCE.length;
|
|
35097
|
-
if (score < bestScore || score === bestScore && cRank < bestRank) {
|
|
35098
|
-
bestId = c.brokerId;
|
|
35099
|
-
bestTier = c.tier;
|
|
35100
|
-
bestScore = score;
|
|
35101
|
-
}
|
|
35102
|
-
}
|
|
35103
|
-
const adaptiveDeviceId = Number(deviceKey.split("/")[0]);
|
|
35104
|
-
this.logger.debug("Adaptive source selected", {
|
|
35105
|
-
...Number.isFinite(adaptiveDeviceId) ? { tags: { deviceId: adaptiveDeviceId } } : {},
|
|
35176
|
+
const pick = pickAdaptiveTier({
|
|
35177
|
+
available: pool.map((c) => c.tier),
|
|
35178
|
+
prefersTier: hints.prefersTier,
|
|
35179
|
+
downlinkMbps: hints.downlinkMbps,
|
|
35180
|
+
viewportWidth: hints.viewportWidth,
|
|
35181
|
+
viewportHeight: hints.viewportHeight
|
|
35182
|
+
});
|
|
35183
|
+
const selected = pool.find((c) => c.tier === pick.tier) ?? pool[0];
|
|
35184
|
+
this.logger.info(adaptivePickLogLine(pick.reason), {
|
|
35185
|
+
...deviceIdTag,
|
|
35106
35186
|
meta: {
|
|
35107
35187
|
deviceKey,
|
|
35108
|
-
selected:
|
|
35109
|
-
tier:
|
|
35110
|
-
|
|
35188
|
+
selected: selected.brokerId,
|
|
35189
|
+
tier: selected.tier,
|
|
35190
|
+
reason: pick.reason,
|
|
35191
|
+
prefersTier: hints.prefersTier,
|
|
35111
35192
|
hints
|
|
35112
35193
|
}
|
|
35113
35194
|
});
|
|
35114
|
-
return
|
|
35195
|
+
return selected.brokerId;
|
|
35115
35196
|
}
|
|
35116
35197
|
async resolveIceServers() {
|
|
35117
35198
|
if (this.getIceServersFn) try {
|