@camstack/addon-pipeline 1.2.90 → 1.2.93
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-yc-chXuC.js → addon-utils-CTRpEiSd.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-BJYwWEZ9.mjs → dist-6POVIYmC.mjs} +91 -4
- package/dist/{dist-n4Nbnxx5.js → dist-D9To_r_d.js} +96 -3
- package/dist/{event-loop-stall-monitor-BiPffk0D.mjs → event-loop-stall-monitor-C-VvWOxS.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-DG7QY00k.js → event-loop-stall-monitor-CGacbzXq.js} +1 -1
- package/dist/{lazy-sharp-EoSOHgoj.js → lazy-sharp-C3zOnmiU.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 +134 -21
- package/dist/recorder/index.mjs +133 -20
- 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-CsrMZYLB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2woB6bY.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dut56EeQ.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-DgivrPsH.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DsIi4G5Q.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DoFovQmc.mjs → hostInit-VCURcZLt.mjs} +2 -2
- package/dist/stream-broker/index.js +194 -30
- package/dist/stream-broker/index.mjs +194 -30
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DjuxhGo3.js → worker-protocol-Bq28qobd.js} +1 -1
- package/dist/{worker-protocol-XZ1e36rM.mjs → worker-protocol-PzAKg8mC.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-DNSELPGi.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-D9To_r_d.js");
|
|
6
6
|
const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_dist.__toESM(node_crypto, 1);
|
|
@@ -1298,6 +1298,36 @@ function buildDerivedTranscodeArgs(input) {
|
|
|
1298
1298
|
audio
|
|
1299
1299
|
};
|
|
1300
1300
|
}
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region src/stream-broker/fill-empty-profile-slots.ts
|
|
1303
|
+
/**
|
|
1304
|
+
* When `assignProfile` locks `auto: false` (putting a derived on `low`), a
|
|
1305
|
+
* later catalog blip can empty the other slots. Auto-rank must not overwrite
|
|
1306
|
+
* the operator's slot, but EMPTY slots should still take unused eligible
|
|
1307
|
+
* natives — otherwise the camera stays dark until someone clicks the
|
|
1308
|
+
* dropdowns again (618 / 1438).
|
|
1309
|
+
*/
|
|
1310
|
+
function fillEmptyProfileSlots(current, autoRanked) {
|
|
1311
|
+
const used = /* @__PURE__ */ new Set();
|
|
1312
|
+
for (const profile of require_dist.CAM_PROFILE_ORDER) {
|
|
1313
|
+
const id = current[profile];
|
|
1314
|
+
if (id) used.add(id);
|
|
1315
|
+
}
|
|
1316
|
+
const ranked = [];
|
|
1317
|
+
for (const profile of require_dist.CAM_PROFILE_ORDER) {
|
|
1318
|
+
const id = autoRanked[profile];
|
|
1319
|
+
if (id && !ranked.includes(id)) ranked.push(id);
|
|
1320
|
+
}
|
|
1321
|
+
const next = { ...current };
|
|
1322
|
+
for (const profile of require_dist.CAM_PROFILE_ORDER) {
|
|
1323
|
+
if (next[profile]) continue;
|
|
1324
|
+
const candidate = ranked.find((id) => !used.has(id));
|
|
1325
|
+
if (!candidate) continue;
|
|
1326
|
+
next[profile] = candidate;
|
|
1327
|
+
used.add(candidate);
|
|
1328
|
+
}
|
|
1329
|
+
return next;
|
|
1330
|
+
}
|
|
1301
1331
|
/**
|
|
1302
1332
|
* Ceiling for a configured pre-roll.
|
|
1303
1333
|
*
|
|
@@ -13715,8 +13745,7 @@ var TranscodePipelineManager = class {
|
|
|
13715
13745
|
const outputArgs = input.outputArgs ?? [];
|
|
13716
13746
|
const audioEgress = input.audioEgress === true;
|
|
13717
13747
|
const sourceStream = input.profile !== void 0 ? this.resolveTargetedStream(input.deviceId, input.profile) : input.targetResolution !== void 0 ? this.pickClosestSource(input.deviceId, input.targetResolution) ?? this.firstPublished(input.deviceId) : this.firstPublished(input.deviceId);
|
|
13718
|
-
if (input.profile === void 0
|
|
13719
|
-
if (!sourceStream || !sourceStream.url) throw new Error(`getStreamWithCodec: no assigned source for device ${input.deviceId}`);
|
|
13748
|
+
if (!sourceStream) throw new Error(input.profile === void 0 ? `getStreamWithCodec: no published stream for device ${input.deviceId}` : `getStreamWithCodec: no assigned source for device ${input.deviceId}`);
|
|
13720
13749
|
const sourceCodec = normaliseCodec(sourceStream.codec);
|
|
13721
13750
|
const outResolution = input.targetResolution ?? sourceStream.resolution ?? {
|
|
13722
13751
|
width: 1280,
|
|
@@ -13901,11 +13930,17 @@ var TranscodePipelineManager = class {
|
|
|
13901
13930
|
* Throws when the profile has no assignment or the assigned source is not
|
|
13902
13931
|
* currently published — a targeted request must not silently fall back to
|
|
13903
13932
|
* a different source.
|
|
13933
|
+
*
|
|
13934
|
+
* Publication is "in the catalog", NOT `!!stream.url`. A `derived:*` stream
|
|
13935
|
+
* is broker-owned metadata with no camera pull URL; consumers ride the
|
|
13936
|
+
* broker restream (`resolveSourceUrl`), and the broker starts the transcode
|
|
13937
|
+
* feeder on demand. Requiring a catalog `url` made recording/WebRTC throw
|
|
13938
|
+
* `not published` for a live derived assignment (618, 2026-08-17).
|
|
13904
13939
|
*/
|
|
13905
13940
|
resolveTargetedStream(deviceId, profile) {
|
|
13906
13941
|
const camStreamId = this.resolveProfileSource?.(deviceId, profile) ?? null;
|
|
13907
13942
|
if (!camStreamId) throw new Error(`getStreamWithCodec: profile "${profile}" has no assigned source for device ${deviceId}`);
|
|
13908
|
-
const stream = this.cameraStreamLookup(deviceId).find((s) => s.camStreamId === camStreamId
|
|
13943
|
+
const stream = this.cameraStreamLookup(deviceId).find((s) => s.camStreamId === camStreamId);
|
|
13909
13944
|
if (!stream) throw new Error(`getStreamWithCodec: profile "${profile}" source "${camStreamId}" not published for device ${deviceId}`);
|
|
13910
13945
|
return stream;
|
|
13911
13946
|
}
|
|
@@ -14571,6 +14606,10 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
14571
14606
|
return this.transcodeManager;
|
|
14572
14607
|
}
|
|
14573
14608
|
async getStreamWithCodec(input) {
|
|
14609
|
+
if (input.profile !== void 0) {
|
|
14610
|
+
const camStreamId = this.assignments.get(input.deviceId)?.map[input.profile];
|
|
14611
|
+
if (camStreamId) await this.ensureDerivedReady(input.deviceId, camStreamId);
|
|
14612
|
+
}
|
|
14574
14613
|
const source = await this.getTranscodeManager().acquire(input);
|
|
14575
14614
|
if (input.hostname !== void 0 && input.hostname.length > 0) return {
|
|
14576
14615
|
...source,
|
|
@@ -15237,8 +15276,14 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15237
15276
|
*/
|
|
15238
15277
|
async ensureBrokerForWebrtcSession(deviceId, brokerId, camStreamId, tier) {
|
|
15239
15278
|
if (this.disabledDevices.has(deviceId)) return;
|
|
15240
|
-
if (this.brokers.has(brokerId)) return;
|
|
15241
15279
|
if (this.remoteBrokerAdapters.has(brokerId)) return;
|
|
15280
|
+
if (camStreamId !== void 0) {
|
|
15281
|
+
if ((this.cameraStreams.get(deviceId)?.get(camStreamId))?.kind === "derived") {
|
|
15282
|
+
await this.ensureDerivedReady(deviceId, camStreamId);
|
|
15283
|
+
if (this.brokers.has(brokerId)) return;
|
|
15284
|
+
}
|
|
15285
|
+
}
|
|
15286
|
+
if (this.brokers.has(brokerId)) return;
|
|
15242
15287
|
if (camStreamId === void 0) return;
|
|
15243
15288
|
const published = this.cameraStreams.get(deviceId)?.get(camStreamId);
|
|
15244
15289
|
if (published !== void 0 && published.kind !== "derived") {
|
|
@@ -15593,7 +15638,16 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15593
15638
|
camStreamId: input.sourceCamStreamId
|
|
15594
15639
|
} : void 0);
|
|
15595
15640
|
if (!derivedSource || !encodeProfile) throw new Error("kind: 'derived' requires a source (profile|camStream) + encodeProfile");
|
|
15596
|
-
|
|
15641
|
+
const existingDerived = this.cameraStreams.get(deviceId)?.get(camStreamId);
|
|
15642
|
+
if (existingDerived) {
|
|
15643
|
+
this.cameraStreams.get(deviceId)?.set(camStreamId, {
|
|
15644
|
+
...existingDerived,
|
|
15645
|
+
derivedSource,
|
|
15646
|
+
encodeProfile
|
|
15647
|
+
});
|
|
15648
|
+
await this.ensureDerivedReady(deviceId, camStreamId);
|
|
15649
|
+
return { success: true };
|
|
15650
|
+
}
|
|
15597
15651
|
if (derivedSource.kind === "camStream") {
|
|
15598
15652
|
const sourceBrokerId = brokerIdFor(deviceId, derivedSource.camStreamId);
|
|
15599
15653
|
if (!this.brokers.has(sourceBrokerId)) throw new Error(`SOURCE_NOT_FOUND: ${sourceBrokerId}`);
|
|
@@ -15619,11 +15673,14 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15619
15673
|
order,
|
|
15620
15674
|
deviceFeatures: deviceFeaturesArr,
|
|
15621
15675
|
autoEligible: input.autoEligible ?? false,
|
|
15622
|
-
brokerOwned: true
|
|
15676
|
+
brokerOwned: true,
|
|
15677
|
+
derivedSource,
|
|
15678
|
+
encodeProfile
|
|
15623
15679
|
};
|
|
15624
15680
|
derivedStreamMap.set(camStreamId, derivedCam);
|
|
15625
15681
|
await this.ensureBroker(deviceId, camStreamId);
|
|
15626
15682
|
this.attachDerivedFeeder(deviceId, camStreamId, derivedSource, encodeProfile);
|
|
15683
|
+
await this.reconcileAutoAssignment(deviceId, "publish", true);
|
|
15627
15684
|
this.emitCamStreamsChanged(deviceId);
|
|
15628
15685
|
return { success: true };
|
|
15629
15686
|
}
|
|
@@ -17784,14 +17841,15 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
17784
17841
|
*
|
|
17785
17842
|
* Streams marked `autoEligible: false` are skipped — they remain
|
|
17786
17843
|
* published (visible + manually assignable via `assignProfile`) but
|
|
17787
|
-
* never become the default. If every stream is non-eligible the
|
|
17788
|
-
*
|
|
17789
|
-
*
|
|
17844
|
+
* never become the default. If every stream is non-eligible the map
|
|
17845
|
+
* stays empty: ranking ineligible streams (a derived that beat the
|
|
17846
|
+
* catalog to boot) parked a transcode on `high` and left the natives
|
|
17847
|
+
* around it (618, 2026-08-17).
|
|
17790
17848
|
*/
|
|
17791
17849
|
computeInitialAssignment(streams) {
|
|
17792
|
-
if (streams.length === 0) return {};
|
|
17793
17850
|
const eligible = streams.filter((s) => s.autoEligible);
|
|
17794
|
-
|
|
17851
|
+
if (eligible.length === 0) return {};
|
|
17852
|
+
const ranked = [...eligible].toSorted((a, b) => {
|
|
17795
17853
|
const aPx = a.resolution ? a.resolution.width * a.resolution.height : -1;
|
|
17796
17854
|
const bPx = b.resolution ? b.resolution.width * b.resolution.height : -1;
|
|
17797
17855
|
if (aPx !== bPx) return bPx - aPx;
|
|
@@ -17814,13 +17872,19 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
17814
17872
|
* `publishCameraStream` so a multi-stream catalog publish can upsert
|
|
17815
17873
|
* every stream first and rank ONCE (no transient intermediate
|
|
17816
17874
|
* assignment). When the device's assignment is operator-locked
|
|
17817
|
-
* (`auto === false`)
|
|
17875
|
+
* (`auto === false`) occupied slots stay put (a derived on `low` must
|
|
17876
|
+
* not be kicked off). Empty slots refill from unused natives when the
|
|
17877
|
+
* device has a derived stream (618 / 1438) or the whole map is empty —
|
|
17878
|
+
* a camera without deriveds still honours an operator unassign.
|
|
17818
17879
|
*/
|
|
17819
17880
|
async reconcileAutoAssignment(deviceId, reason, streamAdded = false) {
|
|
17820
17881
|
const current = this.assignments.get(deviceId);
|
|
17821
17882
|
const streams = [...this.cameraStreams.get(deviceId)?.values() ?? []].toSorted((a, b) => a.order - b.order);
|
|
17822
17883
|
const isAuto = current?.auto ?? true;
|
|
17823
|
-
const
|
|
17884
|
+
const ranked = this.computeInitialAssignment(streams);
|
|
17885
|
+
const hasDerived = streams.some((s) => s.kind === "derived");
|
|
17886
|
+
const lockedEmpty = current !== void 0 && !isAuto && require_dist.CAM_PROFILE_ORDER.every((profile) => current.map[profile] === void 0);
|
|
17887
|
+
const newMap = !current ? ranked : isAuto ? this.wouldChangeAutoMap(current.map, streams) ? ranked : current.map : hasDerived || lockedEmpty ? fillEmptyProfileSlots(current.map, ranked) : current.map;
|
|
17824
17888
|
await this.applyAssignmentUpdate(deviceId, newMap, {
|
|
17825
17889
|
auto: isAuto,
|
|
17826
17890
|
reason,
|
|
@@ -18219,6 +18283,10 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18219
18283
|
if (!existingToken) this.rtspProvider.persistTokens();
|
|
18220
18284
|
await this.fanOutRegistration(deviceId, brokerId, cam, broker);
|
|
18221
18285
|
this.syncProfileRestreamers(deviceId);
|
|
18286
|
+
if (cam.kind === "derived") {
|
|
18287
|
+
const def = this.resolveDerivedDef(deviceId, camStreamId);
|
|
18288
|
+
if (def) this.attachDerivedFeeder(deviceId, camStreamId, def.source, def.encodeProfile);
|
|
18289
|
+
}
|
|
18222
18290
|
}
|
|
18223
18291
|
/**
|
|
18224
18292
|
* Tear down the broker for a `(deviceId, camStreamId)` pair. No-op
|
|
@@ -18250,6 +18318,40 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
18250
18318
|
this.syncProfileRestreamers(deviceId);
|
|
18251
18319
|
}
|
|
18252
18320
|
/**
|
|
18321
|
+
* Ensure a derived stream's push broker exists AND its transcode feeder is
|
|
18322
|
+
* attached. Consumers (recording `getStreamWithCodec`, WebRTC session setup,
|
|
18323
|
+
* profile restream DESCRIBE) address the PROFILE; the broker is responsible
|
|
18324
|
+
* for starting the derived. No-op for non-derived camStreams.
|
|
18325
|
+
*/
|
|
18326
|
+
async ensureDerivedReady(deviceId, camStreamId) {
|
|
18327
|
+
if ((this.cameraStreams.get(deviceId)?.get(camStreamId))?.kind !== "derived") return;
|
|
18328
|
+
const def = this.resolveDerivedDef(deviceId, camStreamId);
|
|
18329
|
+
if (!def) return;
|
|
18330
|
+
await this.ensureBroker(deviceId, camStreamId);
|
|
18331
|
+
this.attachDerivedFeeder(deviceId, camStreamId, def.source, def.encodeProfile);
|
|
18332
|
+
}
|
|
18333
|
+
/**
|
|
18334
|
+
* Source + encode profile for a derived camStream. Prefers the fields stored
|
|
18335
|
+
* on the catalog entry at publish; falls back to the persisted device
|
|
18336
|
+
* override (`derivedStreams[<name>]`) so a consumer can re-attach after a
|
|
18337
|
+
* broker release even if the in-memory record predates those fields.
|
|
18338
|
+
*/
|
|
18339
|
+
resolveDerivedDef(deviceId, camStreamId) {
|
|
18340
|
+
const published = this.cameraStreams.get(deviceId)?.get(camStreamId);
|
|
18341
|
+
if (published?.derivedSource && published.encodeProfile) return {
|
|
18342
|
+
source: published.derivedSource,
|
|
18343
|
+
encodeProfile: published.encodeProfile
|
|
18344
|
+
};
|
|
18345
|
+
if (!camStreamId.startsWith("derived:")) return null;
|
|
18346
|
+
const name = camStreamId.slice(8);
|
|
18347
|
+
const def = this.deviceOverrides.get(deviceId)?.derivedStreams?.[name];
|
|
18348
|
+
if (!def?.source || !def.encodeProfile) return null;
|
|
18349
|
+
return {
|
|
18350
|
+
source: def.source,
|
|
18351
|
+
encodeProfile: def.encodeProfile
|
|
18352
|
+
};
|
|
18353
|
+
}
|
|
18354
|
+
/**
|
|
18253
18355
|
* Attach the on-demand transcode-feeder lifecycle to a freshly-minted derived
|
|
18254
18356
|
* push broker. The feeder runs ONLY while the derived broker is consumed — it
|
|
18255
18357
|
* is spawned on the broker's demand edge (`onDemandChange(true)`) and torn
|
|
@@ -25006,6 +25108,22 @@ var RecordedFeeder = class {
|
|
|
25006
25108
|
}
|
|
25007
25109
|
};
|
|
25008
25110
|
//#endregion
|
|
25111
|
+
//#region src/stream-broker/recorded/playback-profile-fallback.ts
|
|
25112
|
+
/**
|
|
25113
|
+
* Recorded playback is pinned to one profile (no adaptive ladder). When the
|
|
25114
|
+
* viewer asks for `mid` (auto/adaptive default) but the camera only kept
|
|
25115
|
+
* `high` — typical of events-mode — locate must try the other tiers before
|
|
25116
|
+
* reporting a gap.
|
|
25117
|
+
*/
|
|
25118
|
+
var PLAYBACK_PROFILE_FALLBACK_ORDER = [
|
|
25119
|
+
"high",
|
|
25120
|
+
"mid",
|
|
25121
|
+
"low"
|
|
25122
|
+
];
|
|
25123
|
+
function profileSearchOrder(requested) {
|
|
25124
|
+
return [requested, ...PLAYBACK_PROFILE_FALLBACK_ORDER.filter((p) => p !== requested)];
|
|
25125
|
+
}
|
|
25126
|
+
//#endregion
|
|
25009
25127
|
//#region src/stream-broker/recorded/timeline-session.ts
|
|
25010
25128
|
/**
|
|
25011
25129
|
* Per-viewer coordinator over one broker WebRTC session. Owns the playback mode
|
|
@@ -25052,17 +25170,14 @@ var TimelineSession = class {
|
|
|
25052
25170
|
async handleControl(msg) {
|
|
25053
25171
|
this.emitLiveProfile();
|
|
25054
25172
|
switch (msg.t) {
|
|
25055
|
-
case "playRecorded":
|
|
25056
|
-
|
|
25057
|
-
this.profile = msg.profile;
|
|
25058
|
-
await (this.mode === "recorded" && this.feeder && !profileChanged ? this.feeder : this.enterRecorded()).seek(msg.epoch, { travelFromMs: this.lastPositionMs });
|
|
25173
|
+
case "playRecorded":
|
|
25174
|
+
await this.playAt(msg.epoch, msg.profile, { travelFromMs: this.lastPositionMs });
|
|
25059
25175
|
break;
|
|
25060
|
-
}
|
|
25061
25176
|
case "seek":
|
|
25062
|
-
await
|
|
25177
|
+
await this.playAt(msg.epoch, this.profile, { travelFromMs: this.lastPositionMs });
|
|
25063
25178
|
break;
|
|
25064
25179
|
case "scrubCommit":
|
|
25065
|
-
await
|
|
25180
|
+
await this.playAt(msg.epoch, this.profile);
|
|
25066
25181
|
break;
|
|
25067
25182
|
case "scrubStart":
|
|
25068
25183
|
(this.mode === "recorded" && this.feeder ? this.feeder : this.enterRecorded()).beginScrub();
|
|
@@ -25108,6 +25223,29 @@ var TimelineSession = class {
|
|
|
25108
25223
|
this.feeder = feeder;
|
|
25109
25224
|
return feeder;
|
|
25110
25225
|
}
|
|
25226
|
+
/**
|
|
25227
|
+
* Play recorded footage at `epochMs`. If `locate` is wired and the requested
|
|
25228
|
+
* profile has no segment, try high → mid → low so events-mode cameras that
|
|
25229
|
+
* only kept one tier still play.
|
|
25230
|
+
*/
|
|
25231
|
+
async playAt(epochMs, requestedProfile, opts) {
|
|
25232
|
+
const profile = await this.resolveProfile(requestedProfile, epochMs);
|
|
25233
|
+
const profileChanged = profile !== this.profile;
|
|
25234
|
+
this.profile = profile;
|
|
25235
|
+
const feeder = this.mode === "recorded" && this.feeder && !profileChanged ? this.feeder : this.enterRecorded();
|
|
25236
|
+
if (opts) await feeder.seek(epochMs, opts);
|
|
25237
|
+
else await feeder.seek(epochMs);
|
|
25238
|
+
}
|
|
25239
|
+
async resolveProfile(requested, epochMs) {
|
|
25240
|
+
const locate = this.deps.locate;
|
|
25241
|
+
if (!locate) return requested;
|
|
25242
|
+
for (const profile of profileSearchOrder(requested)) if ((await locate({
|
|
25243
|
+
deviceId: this.deps.deviceId,
|
|
25244
|
+
profile,
|
|
25245
|
+
epochMs
|
|
25246
|
+
})).kind === "segment") return profile;
|
|
25247
|
+
return requested;
|
|
25248
|
+
}
|
|
25111
25249
|
goLive() {
|
|
25112
25250
|
this.feeder?.dispose();
|
|
25113
25251
|
this.feeder = null;
|
|
@@ -32786,6 +32924,30 @@ async function demuxSegmentAudioOpusPreferJs(bytes, opts = {}) {
|
|
|
32786
32924
|
}
|
|
32787
32925
|
}
|
|
32788
32926
|
//#endregion
|
|
32927
|
+
//#region src/stream-broker/webrtc/prefer-live-adaptive-candidates.ts
|
|
32928
|
+
function isDerivedBrokerId(brokerId) {
|
|
32929
|
+
const slash = brokerId.indexOf("/");
|
|
32930
|
+
if (slash < 0) return false;
|
|
32931
|
+
return brokerId.slice(slash + 1).startsWith("derived:");
|
|
32932
|
+
}
|
|
32933
|
+
/**
|
|
32934
|
+
* Adaptive live walks every registered broker for a device. When no profile
|
|
32935
|
+
* slot is bound, Map insertion order wins — and a derived materialized at
|
|
32936
|
+
* boot is often first, so the picture attaches to a transcode whose source
|
|
32937
|
+
* profile is empty (618, 2026-08-17).
|
|
32938
|
+
*
|
|
32939
|
+
* Prefer brokers that currently own a profile tier. If none do, drop
|
|
32940
|
+
* unassigned deriveds so a native pull can win. If the derived is the only
|
|
32941
|
+
* broker, keep it (better a stream than a throw).
|
|
32942
|
+
*/
|
|
32943
|
+
function preferLiveAdaptiveCandidates(candidates) {
|
|
32944
|
+
if (candidates.length <= 1) return [...candidates];
|
|
32945
|
+
const assigned = candidates.filter((c) => c.tier !== null);
|
|
32946
|
+
if (assigned.length > 0) return assigned;
|
|
32947
|
+
const natives = candidates.filter((c) => !isDerivedBrokerId(c.brokerId));
|
|
32948
|
+
return natives.length > 0 ? natives : [...candidates];
|
|
32949
|
+
}
|
|
32950
|
+
//#endregion
|
|
32789
32951
|
//#region src/stream-broker/webrtc/broker-webrtc-server.ts
|
|
32790
32952
|
/**
|
|
32791
32953
|
* Broker-integrated WebRTC server.
|
|
@@ -33741,6 +33903,7 @@ var BrokerWebrtcServer = class {
|
|
|
33741
33903
|
timeline = new TimelineSession({
|
|
33742
33904
|
session,
|
|
33743
33905
|
deviceId,
|
|
33906
|
+
locate: locateRecorded,
|
|
33744
33907
|
...initialLiveProfile !== null ? { initialLiveProfile } : {},
|
|
33745
33908
|
warm: readGop ? (profile, epochMs) => {
|
|
33746
33909
|
warmFragment({
|
|
@@ -34110,12 +34273,13 @@ var BrokerWebrtcServer = class {
|
|
|
34110
34273
|
broker
|
|
34111
34274
|
});
|
|
34112
34275
|
}
|
|
34113
|
-
|
|
34114
|
-
if (
|
|
34276
|
+
const pool = preferLiveAdaptiveCandidates(candidates);
|
|
34277
|
+
if (pool.length === 0) return `${deviceKey}/high`;
|
|
34278
|
+
if (pool.length === 1) return pool[0].brokerId;
|
|
34115
34279
|
const adaptiveDeviceIdTag = Number(deviceKey.split("/")[0]);
|
|
34116
34280
|
const deviceIdTag = Number.isFinite(adaptiveDeviceIdTag) ? { tags: { deviceId: adaptiveDeviceIdTag } } : {};
|
|
34117
34281
|
if (hints.prefersTier === "high" || hints.prefersTier === "mid" || hints.prefersTier === "low") {
|
|
34118
|
-
const explicit =
|
|
34282
|
+
const explicit = pool.find((c) => c.tier === hints.prefersTier);
|
|
34119
34283
|
if (explicit) {
|
|
34120
34284
|
this.logger.info("Adaptive source selected (prefersTier exact)", {
|
|
34121
34285
|
...deviceIdTag,
|
|
@@ -34129,7 +34293,7 @@ var BrokerWebrtcServer = class {
|
|
|
34129
34293
|
return explicit.brokerId;
|
|
34130
34294
|
}
|
|
34131
34295
|
for (const tier of SMALLEST_FIRST_TIER_PREFERENCE) {
|
|
34132
|
-
const match =
|
|
34296
|
+
const match = pool.find((c) => c.tier === tier);
|
|
34133
34297
|
if (match) {
|
|
34134
34298
|
this.logger.info("Adaptive source selected (prefersTier fallback → smallest available)", {
|
|
34135
34299
|
...deviceIdTag,
|
|
@@ -34146,7 +34310,7 @@ var BrokerWebrtcServer = class {
|
|
|
34146
34310
|
}
|
|
34147
34311
|
if (!(hints.viewportWidth !== void 0 || hints.viewportHeight !== void 0 || hints.downlinkMbps !== void 0)) {
|
|
34148
34312
|
for (const tier of TIER_PREFERENCE) {
|
|
34149
|
-
const match =
|
|
34313
|
+
const match = pool.find((c) => c.tier === tier);
|
|
34150
34314
|
if (match) {
|
|
34151
34315
|
this.logger.info("Adaptive source selected (no client hints — tier preference)", {
|
|
34152
34316
|
...deviceIdTag,
|
|
@@ -34159,12 +34323,12 @@ var BrokerWebrtcServer = class {
|
|
|
34159
34323
|
return match.brokerId;
|
|
34160
34324
|
}
|
|
34161
34325
|
}
|
|
34162
|
-
return
|
|
34326
|
+
return pool[0].brokerId;
|
|
34163
34327
|
}
|
|
34164
|
-
let bestId =
|
|
34165
|
-
let bestTier =
|
|
34328
|
+
let bestId = pool[0].brokerId;
|
|
34329
|
+
let bestTier = pool[0].tier;
|
|
34166
34330
|
let bestScore = Infinity;
|
|
34167
|
-
for (const c of
|
|
34331
|
+
for (const c of pool) {
|
|
34168
34332
|
const score = scoreBroker(c.tier, c.broker, hints);
|
|
34169
34333
|
const cRank = c.tier ? TIER_PREFERENCE.indexOf(c.tier) : TIER_PREFERENCE.length;
|
|
34170
34334
|
const bestRank = bestTier ? TIER_PREFERENCE.indexOf(bestTier) : TIER_PREFERENCE.length;
|