@camstack/addon-pipeline 1.2.45 → 1.2.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +43 -13
- package/dist/detection-pipeline/index.mjs +43 -13
- package/dist/{dist-QyZ7QSRP.js → dist-CPfFUIl9.js} +379 -3
- package/dist/{dist-BDkEhx4Z.mjs → dist-CUj9mdhh.mjs} +379 -3
- package/dist/{event-loop-stall-monitor-COEKVBQK.js → event-loop-stall-monitor-BGbgcrEN.js} +7 -1
- package/dist/{event-loop-stall-monitor-IAuNEdmG.mjs → event-loop-stall-monitor-md__iwWh.mjs} +7 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +15 -6
- package/dist/pipeline-runner/index.mjs +15 -6
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +1 -1
- 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-C68YfSUH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-aUWVCncO.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DN2e2GJL.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-B3GxBnXM.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-2D_L2y3g.mjs} +1 -1
- package/dist/stream-broker/{hostInit-BMCrULpc.mjs → hostInit-CRwWRwJH.mjs} +2 -2
- package/dist/stream-broker/index.js +262 -8
- package/dist/stream-broker/index.mjs +262 -8
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DFH_z2-q.mjs → worker-protocol-CD0M9YD7.mjs} +1 -1
- package/dist/{worker-protocol-le27Mw_8.js → worker-protocol-hdTR-F5q.js} +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C7bsz4cP.js → MaskShapeCanvas-DI4BY7W2-C8eGXtf-.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BmhLinUd.js → MotionZonesSettings-NcxxQN8r-vvEcAgxS.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BJjyGWFy.js → PrivacyMaskSettings-APgPLF7p-BLhx7VI-.js} +1 -1
- package/embed-dist/assets/{index-a4CMt-Uc.js → index-DvOgpqcE.js} +12 -12
- package/embed-dist/assets/index-ICnBEqOy.css +2 -0
- package/embed-dist/index.html +2 -2
- 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-CT2bGWqM.mjs +0 -26
- package/embed-dist/assets/index-DC63og2c.css +0 -2
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-CPfFUIl9.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_model_download_service_D8B_4ktF = require("../model-download-service-D8B-4ktF-7-YxE9Wx.js");
|
|
9
9
|
let node_crypto = require("node:crypto");
|
|
@@ -3012,6 +3012,147 @@ function buildAudioCodecCapApi(api) {
|
|
|
3012
3012
|
};
|
|
3013
3013
|
}
|
|
3014
3014
|
//#endregion
|
|
3015
|
+
//#region src/stream-broker/stream-broker/dead-decode-backends.ts
|
|
3016
|
+
/**
|
|
3017
|
+
* "This decode backend cannot create a device in THIS process" — remembered
|
|
3018
|
+
* across children, not per child.
|
|
3019
|
+
*
|
|
3020
|
+
* ## The thing this stops
|
|
3021
|
+
*
|
|
3022
|
+
* `-hwaccel vaapi` works for the decoder addon on this hub (ten concurrent 4K
|
|
3023
|
+
* decodes) and returns ENOMEM inside the stream-broker runner: `code=244`,
|
|
3024
|
+
* "Device creation failed: -12 … Cannot allocate memory". Every egress child
|
|
3025
|
+
* therefore spends ~3 s dying before its own software retry rescues it.
|
|
3026
|
+
*
|
|
3027
|
+
* That was already reported — `EgressTranscodeManager.onDecodeHwAccelFailed`
|
|
3028
|
+
* poisoned the broker's 60 s `egressHwAccelCache`. The memo is the wrong shape
|
|
3029
|
+
* for the verdict: it exists to avoid a per-session cross-process
|
|
3030
|
+
* `decoder.getInfo`, so it expires on a **read** cadence, and one minute later
|
|
3031
|
+
* the decoder answers `vaapi` again and the next cold child re-pays the death.
|
|
3032
|
+
* Measured on camera 615 on 2026-08-07: the hardware failure recurs at 22:17:07,
|
|
3033
|
+
* 22:18:42, 22:28:48, 22:30:52, 22:32:39, 22:34:07, 22:52:07 — once per memo
|
|
3034
|
+
* window, forever, on a hub where it has never once succeeded.
|
|
3035
|
+
*
|
|
3036
|
+
* ## Why this is a quarantine and not a tombstone
|
|
3037
|
+
*
|
|
3038
|
+
* A permanent verdict is wrong for the failure that is actually observed:
|
|
3039
|
+
* ENOMEM on device creation is a RESOURCE condition, and a resource condition
|
|
3040
|
+
* can clear (the operator gives the container more of the device, another
|
|
3041
|
+
* process releases it). A process that decided "never again" at 03:00 would
|
|
3042
|
+
* still be decoding in software at noon with a working accelerator, and nothing
|
|
3043
|
+
* would ever say so.
|
|
3044
|
+
*
|
|
3045
|
+
* So the verdict LASTS and escalates: each consecutive failure of the same
|
|
3046
|
+
* backend widens its quarantine — 30 min, 2 h, then 6 h — so a hub where the
|
|
3047
|
+
* backend is genuinely dead pays one probe per six hours instead of one per
|
|
3048
|
+
* minute, while a hub where it recovers still finds out.
|
|
3049
|
+
*
|
|
3050
|
+
* The FIRST rung is deliberately not a minute. One report is already a strong
|
|
3051
|
+
* signal: the manager only reports a backend after its child produced ZERO
|
|
3052
|
+
* packets *and* the software retry proved the same argv otherwise works, which
|
|
3053
|
+
* is a statement about the backend and not about the source. And the cost of
|
|
3054
|
+
* being wrong is bounded and already being paid — the egress encodes with
|
|
3055
|
+
* `libx264` either way, so an over-long quarantine costs a software DECODE on a
|
|
3056
|
+
* path that is decoding in software today, 100 % of the time.
|
|
3057
|
+
*
|
|
3058
|
+
* The invalidations are motivated, and there are exactly three:
|
|
3059
|
+
*
|
|
3060
|
+
* 1. **The window elapses.** The next acquire probes hardware once. If it
|
|
3061
|
+
* works, {@link DeadDecodeBackends.markAlive} clears the record and the
|
|
3062
|
+
* escalation starts from zero again.
|
|
3063
|
+
* 2. **The backend changes.** The record is keyed by backend name, so an
|
|
3064
|
+
* operator switching the decoder from vaapi to qsv is obeyed immediately —
|
|
3065
|
+
* qsv has no record.
|
|
3066
|
+
* 3. **The runner respawns.** This is instance state on the broker manager,
|
|
3067
|
+
* never a module global, for the same reason `HwAccelCache` is: a module
|
|
3068
|
+
* global outlives an addon respawn, and an addon respawn is exactly the
|
|
3069
|
+
* event after which a stale verdict must not survive.
|
|
3070
|
+
*
|
|
3071
|
+
* ## Why it lives here and not in `@camstack/types`
|
|
3072
|
+
*
|
|
3073
|
+
* `addon-export-hap` has the identical gap (its `failedBackend` memo forgets on
|
|
3074
|
+
* the same 60 s cadence) and this is deliberately NOT shared with it yet: a
|
|
3075
|
+
* framework package is only live once a `@camstack/server` containing it is
|
|
3076
|
+
* installed, whereas this ships with `camstack deploy packages/addon-pipeline`.
|
|
3077
|
+
* The cross-PROCESS gap D67 names — addons may not import each other and no cap
|
|
3078
|
+
* carries "this backend is dead on this node right now" — is unchanged either
|
|
3079
|
+
* way; what is closed here is the broker's own repetition of the cost.
|
|
3080
|
+
*/
|
|
3081
|
+
/**
|
|
3082
|
+
* How long a backend stays quarantined after its 1st, 2nd, 3rd+ consecutive
|
|
3083
|
+
* failure. Escalating rather than fixed, and it starts at HALF AN HOUR: a
|
|
3084
|
+
* client watchdog cycles in seconds, so any first rung measured in minutes is
|
|
3085
|
+
* re-paid by the very churn this exists to stop. The last rung makes a
|
|
3086
|
+
* permanently-broken backend cost ~nothing while still being re-probed.
|
|
3087
|
+
*/
|
|
3088
|
+
var DEAD_BACKEND_QUARANTINE_MS = [
|
|
3089
|
+
18e5,
|
|
3090
|
+
72e5,
|
|
3091
|
+
216e5
|
|
3092
|
+
];
|
|
3093
|
+
/**
|
|
3094
|
+
* The decoder's answer, overruled while the backend it names is quarantined.
|
|
3095
|
+
*
|
|
3096
|
+
* Composed rather than folded into `resolveEgressDecodeHwAccel`: that function
|
|
3097
|
+
* answers "what SHOULD decode this", which is a per-node property of the
|
|
3098
|
+
* hardware; this answers "what has been observed to work in THIS process",
|
|
3099
|
+
* which is a property of the runner. Keeping them apart is what stops a
|
|
3100
|
+
* runner-local ENOMEM from being mistaken for a node that has no accelerator —
|
|
3101
|
+
* the first is a flow bug to fix, the second a capability limit to accept.
|
|
3102
|
+
*/
|
|
3103
|
+
async function resolveWithQuarantine(input) {
|
|
3104
|
+
const backend = await input.resolve();
|
|
3105
|
+
if (backend === null || !input.dead.isQuarantined(backend)) return {
|
|
3106
|
+
backend,
|
|
3107
|
+
veto: null
|
|
3108
|
+
};
|
|
3109
|
+
return {
|
|
3110
|
+
backend: null,
|
|
3111
|
+
veto: {
|
|
3112
|
+
backend,
|
|
3113
|
+
failures: input.dead.failuresOf(backend),
|
|
3114
|
+
remainingMs: input.dead.remainingMs(backend)
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
function createDeadDecodeBackends(options = {}) {
|
|
3119
|
+
const now = options.now ?? (() => Date.now());
|
|
3120
|
+
const records = /* @__PURE__ */ new Map();
|
|
3121
|
+
const quarantineFor = (failures) => {
|
|
3122
|
+
return DEAD_BACKEND_QUARANTINE_MS[Math.min(failures, DEAD_BACKEND_QUARANTINE_MS.length) - 1] ?? DEAD_BACKEND_QUARANTINE_MS[0] ?? 0;
|
|
3123
|
+
};
|
|
3124
|
+
return {
|
|
3125
|
+
markDead(backend) {
|
|
3126
|
+
const failures = (records.get(backend)?.failures ?? 0) + 1;
|
|
3127
|
+
const quarantineMs = quarantineFor(failures);
|
|
3128
|
+
records.set(backend, {
|
|
3129
|
+
failures,
|
|
3130
|
+
until: now() + quarantineMs
|
|
3131
|
+
});
|
|
3132
|
+
return {
|
|
3133
|
+
backend,
|
|
3134
|
+
failures,
|
|
3135
|
+
quarantineMs
|
|
3136
|
+
};
|
|
3137
|
+
},
|
|
3138
|
+
markAlive(backend) {
|
|
3139
|
+
records.delete(backend);
|
|
3140
|
+
},
|
|
3141
|
+
isQuarantined(backend) {
|
|
3142
|
+
const record = records.get(backend);
|
|
3143
|
+
return record !== void 0 && now() < record.until;
|
|
3144
|
+
},
|
|
3145
|
+
remainingMs(backend) {
|
|
3146
|
+
const record = records.get(backend);
|
|
3147
|
+
if (record === void 0) return 0;
|
|
3148
|
+
return Math.max(0, record.until - now());
|
|
3149
|
+
},
|
|
3150
|
+
failuresOf(backend) {
|
|
3151
|
+
return records.get(backend)?.failures ?? 0;
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
//#endregion
|
|
3015
3156
|
//#region src/stream-broker/stream-broker/ffmpeg-invocation.ts
|
|
3016
3157
|
/** The full set of hwaccel settings (for validation + option surfaces). */
|
|
3017
3158
|
var FFMPEG_HW_ACCEL_SETTINGS = [
|
|
@@ -4360,6 +4501,7 @@ var EgressTranscodeManager = class {
|
|
|
4360
4501
|
}
|
|
4361
4502
|
const actualHwAccel = child.decodeHwAccel();
|
|
4362
4503
|
if (decodeHwAccel !== null && !require_dist.isSoftwareDecode(decodeHwAccel) && actualHwAccel === null) this.opts.onDecodeHwAccelFailed?.(decodeHwAccel);
|
|
4504
|
+
else if (actualHwAccel !== null && !require_dist.isSoftwareDecode(actualHwAccel)) this.opts.onDecodeHwAccelSucceeded?.(actualHwAccel);
|
|
4363
4505
|
const entry = {
|
|
4364
4506
|
key,
|
|
4365
4507
|
deviceId,
|
|
@@ -13316,6 +13458,15 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
13316
13458
|
*/
|
|
13317
13459
|
egressHwAccelCache = require_dist.createHwAccelCache({ ttlMs: 6e4 });
|
|
13318
13460
|
/**
|
|
13461
|
+
* Backends that could not create a device in THIS process. Separate from the
|
|
13462
|
+
* memo above on purpose: the memo answers "what should decode this" on a read
|
|
13463
|
+
* cadence and expires in a minute, so folding the failure into it meant the
|
|
13464
|
+
* next cold child one minute later re-paid the same ~3 s death — measured
|
|
13465
|
+
* seven times on 615 between 22:17 and 22:52 on 2026-08-07. See
|
|
13466
|
+
* `dead-decode-backends.ts`.
|
|
13467
|
+
*/
|
|
13468
|
+
deadDecodeBackends = createDeadDecodeBackends();
|
|
13469
|
+
/**
|
|
13319
13470
|
* Snapshot of the system `ffmpeg` config section (binaryPath / hwAccel /
|
|
13320
13471
|
* threadCount). Refreshed by the host addon via `setFfmpegConfig`; the
|
|
13321
13472
|
* transcode manager reads it live per spawn. Defaults are safe (PATH ffmpeg,
|
|
@@ -13515,8 +13666,17 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
13515
13666
|
};
|
|
13516
13667
|
},
|
|
13517
13668
|
onDecodeHwAccelFailed: (backend) => {
|
|
13518
|
-
this.
|
|
13519
|
-
this.logger.warn("egress transcode: decode backend FAILED in this process — software until
|
|
13669
|
+
const verdict = this.deadDecodeBackends.markDead(backend);
|
|
13670
|
+
this.logger.warn("egress transcode: decode backend FAILED in this process — QUARANTINED, software until it is re-probed", { meta: {
|
|
13671
|
+
backend,
|
|
13672
|
+
failures: verdict.failures,
|
|
13673
|
+
quarantineMs: verdict.quarantineMs
|
|
13674
|
+
} });
|
|
13675
|
+
},
|
|
13676
|
+
onDecodeHwAccelSucceeded: (backend) => {
|
|
13677
|
+
if (this.deadDecodeBackends.failuresOf(backend) === 0) return;
|
|
13678
|
+
this.deadDecodeBackends.markAlive(backend);
|
|
13679
|
+
this.logger.info("egress transcode: decode backend RECOVERED in this process — the quarantine is lifted", { meta: { backend } });
|
|
13520
13680
|
}
|
|
13521
13681
|
});
|
|
13522
13682
|
}
|
|
@@ -13539,6 +13699,22 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
13539
13699
|
* hub).
|
|
13540
13700
|
*/
|
|
13541
13701
|
async resolveEgressHwAccel(deviceId, override) {
|
|
13702
|
+
const resolved = await resolveWithQuarantine({
|
|
13703
|
+
resolve: () => this.readEgressHwAccel(deviceId, override),
|
|
13704
|
+
dead: this.deadDecodeBackends
|
|
13705
|
+
});
|
|
13706
|
+
if (resolved.veto !== null) this.logger.warn("egress transcode: decoding in SOFTWARE — the backend is QUARANTINED after failing in this process", {
|
|
13707
|
+
tags: { deviceId },
|
|
13708
|
+
meta: {
|
|
13709
|
+
backend: resolved.veto.backend,
|
|
13710
|
+
failures: resolved.veto.failures,
|
|
13711
|
+
remainingMs: resolved.veto.remainingMs
|
|
13712
|
+
}
|
|
13713
|
+
});
|
|
13714
|
+
return resolved.backend;
|
|
13715
|
+
}
|
|
13716
|
+
/** The decoder addon's answer for this node — before the process quarantine. */
|
|
13717
|
+
async readEgressHwAccel(deviceId, override) {
|
|
13542
13718
|
const decoderApi = this.buildDecoderCapApi(deviceId);
|
|
13543
13719
|
return require_dist.resolveEgressDecodeHwAccel({
|
|
13544
13720
|
readDecoderBackend: async () => {
|
|
@@ -18247,6 +18423,17 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
18247
18423
|
acquireTranscodeEgress;
|
|
18248
18424
|
/** The acquired egress, while this session holds a reference to it. */
|
|
18249
18425
|
transcodeEgressLeg = null;
|
|
18426
|
+
/**
|
|
18427
|
+
* An acquire started at the ANSWER and not yet consumed by the feed.
|
|
18428
|
+
*
|
|
18429
|
+
* Read-and-null-out is the whole ownership protocol: whichever of
|
|
18430
|
+
* `startTranscodeFeed` and `releaseTranscodeEgress` reads a non-null value
|
|
18431
|
+
* owns the release, and since both read it synchronously exactly one can.
|
|
18432
|
+
* Without that, a session closing while its prewarm is in flight either
|
|
18433
|
+
* double-releases (the manager logs it as a consumer bookkeeping bug) or
|
|
18434
|
+
* leaks a child the primitive has no TTL to reclaim.
|
|
18435
|
+
*/
|
|
18436
|
+
transcodeEgressPending = null;
|
|
18250
18437
|
/** Unsubscribe from the acquired egress's video plane. */
|
|
18251
18438
|
detachTranscodeEgress = null;
|
|
18252
18439
|
/** Codec actually negotiated with the browser after SDP answer. */
|
|
@@ -18886,6 +19073,7 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
18886
19073
|
negotiated: this.negotiatedCodec,
|
|
18887
19074
|
needsTranscode: this.needsTranscode
|
|
18888
19075
|
} });
|
|
19076
|
+
this.prewarmTranscodeEgress();
|
|
18889
19077
|
if (this.debug) this.logger.info("Codec negotiation detail", { meta: {
|
|
18890
19078
|
phase: "session",
|
|
18891
19079
|
sessionId: this.sessionId,
|
|
@@ -19084,6 +19272,7 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19084
19272
|
setRemoteMs,
|
|
19085
19273
|
createAnswerMs
|
|
19086
19274
|
} });
|
|
19275
|
+
this.prewarmTranscodeEgress();
|
|
19087
19276
|
if (this.debug) this.logger.info("Codec negotiation detail (client-offer)", { meta: {
|
|
19088
19277
|
phase: "session",
|
|
19089
19278
|
sessionId: this.sessionId,
|
|
@@ -19804,6 +19993,51 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19804
19993
|
* unchanged — the request asks for `audio: 'passthrough'` precisely so the
|
|
19805
19994
|
* child does not encode a plane this session never reads.
|
|
19806
19995
|
*/
|
|
19996
|
+
/**
|
|
19997
|
+
* Start the egress acquire NOW, in parallel with ICE and DTLS.
|
|
19998
|
+
*
|
|
19999
|
+
* `needsTranscode` is final the moment the answer's codec is known — the
|
|
20000
|
+
* "Codec negotiated" line already prints it — but the acquire used to happen
|
|
20001
|
+
* inside `startFeedingFrames`, which runs only after the DTLS wait. On camera
|
|
20002
|
+
* 615 that put the leg ~2 s after session creation and the first packet ~5 s
|
|
20003
|
+
* after that, against a browser watchdog that re-offers every ~5 s: the viewer
|
|
20004
|
+
* was always gone before the media existed, and the next session paid for a
|
|
20005
|
+
* cold child again. Three minutes of that on 2026-08-07 22:17–22:20.
|
|
20006
|
+
*
|
|
20007
|
+
* Only the ffmpeg moves. The ATTACH stays behind DTLS, because `sendRtp`
|
|
20008
|
+
* silently drops before the transport is connected.
|
|
20009
|
+
*
|
|
20010
|
+
* Idempotent and safe to call from any negotiation path; a session that needs
|
|
20011
|
+
* no transcode does nothing. Public for the same reason
|
|
20012
|
+
* {@link armSourceSilenceWatchdog} is — the acquire/release contract is
|
|
20013
|
+
* testable without standing up werift.
|
|
20014
|
+
*/
|
|
20015
|
+
prewarmTranscodeEgress() {
|
|
20016
|
+
if (this.closed) return;
|
|
20017
|
+
if (!this.needsTranscode) return;
|
|
20018
|
+
if (this.transcodeEgressPending !== null || this.transcodeEgressLeg !== null) return;
|
|
20019
|
+
const acquire = this.acquireTranscodeEgress;
|
|
20020
|
+
if (acquire === null) return;
|
|
20021
|
+
this.logger.info("Transcode leg: prewarming the egress while ICE/DTLS negotiate", { meta: {
|
|
20022
|
+
phase: "session",
|
|
20023
|
+
sessionId: this.sessionId,
|
|
20024
|
+
deviceId: this.deviceId,
|
|
20025
|
+
sourceCodec: this.sourceCodec
|
|
20026
|
+
} });
|
|
20027
|
+
this.transcodeEgressPending = acquire({
|
|
20028
|
+
profile: this.transcodeProfile,
|
|
20029
|
+
sessionId: this.sessionId
|
|
20030
|
+
});
|
|
20031
|
+
}
|
|
20032
|
+
/**
|
|
20033
|
+
* Take ownership of the in-flight prewarm, if there is one. Nulls the field
|
|
20034
|
+
* synchronously so the release path can never also claim it — see the field.
|
|
20035
|
+
*/
|
|
20036
|
+
claimPrewarmedEgress() {
|
|
20037
|
+
const pending = this.transcodeEgressPending;
|
|
20038
|
+
this.transcodeEgressPending = null;
|
|
20039
|
+
return pending;
|
|
20040
|
+
}
|
|
19807
20041
|
startTranscodeFeed(signal) {
|
|
19808
20042
|
const acquire = this.acquireTranscodeEgress;
|
|
19809
20043
|
if (acquire === null) {
|
|
@@ -19815,20 +20049,32 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19815
20049
|
if (!this.closed) this.close();
|
|
19816
20050
|
return;
|
|
19817
20051
|
}
|
|
20052
|
+
const prewarmed = this.claimPrewarmedEgress();
|
|
19818
20053
|
this.logger.info(`Starting ${this.sourceCodec}→H.264 Baseline transcode leg`, { meta: {
|
|
19819
20054
|
phase: "session",
|
|
19820
20055
|
sessionId: this.sessionId,
|
|
19821
20056
|
deviceId: this.deviceId,
|
|
19822
20057
|
sourceCodec: this.sourceCodec,
|
|
19823
|
-
mode: "egress-primitive"
|
|
20058
|
+
mode: "egress-primitive",
|
|
20059
|
+
prewarmed: prewarmed !== null
|
|
19824
20060
|
} });
|
|
20061
|
+
const acquireNow = () => acquire({
|
|
20062
|
+
profile: this.transcodeProfile,
|
|
20063
|
+
sessionId: this.sessionId
|
|
20064
|
+
});
|
|
19825
20065
|
(async () => {
|
|
19826
20066
|
let leg;
|
|
19827
20067
|
try {
|
|
19828
|
-
leg = await
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
|
|
20068
|
+
leg = await (prewarmed === null ? acquireNow() : prewarmed.catch((err) => {
|
|
20069
|
+
if (signal.aborted || this.closed) throw err;
|
|
20070
|
+
this.logger.warn("Transcode leg: the prewarmed acquire failed — retrying now that the feed has started", { meta: {
|
|
20071
|
+
phase: "session",
|
|
20072
|
+
sessionId: this.sessionId,
|
|
20073
|
+
deviceId: this.deviceId,
|
|
20074
|
+
error: require_dist.errMsg(err)
|
|
20075
|
+
} });
|
|
20076
|
+
return acquireNow();
|
|
20077
|
+
}));
|
|
19832
20078
|
} catch (err) {
|
|
19833
20079
|
this.logger.error("Transcode feed: acquiring the egress transcode FAILED", { meta: {
|
|
19834
20080
|
phase: "session",
|
|
@@ -19896,6 +20142,14 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19896
20142
|
try {
|
|
19897
20143
|
detach?.();
|
|
19898
20144
|
} catch {}
|
|
20145
|
+
const orphan = this.claimPrewarmedEgress();
|
|
20146
|
+
if (orphan !== null) orphan.then((prewarmedLeg) => prewarmedLeg.release()).catch((err) => {
|
|
20147
|
+
this.logger.debug("Transcode leg: the unclaimed prewarm ended without a handle", { meta: {
|
|
20148
|
+
phase: "session",
|
|
20149
|
+
sessionId: this.sessionId,
|
|
20150
|
+
error: require_dist.errMsg(err)
|
|
20151
|
+
} });
|
|
20152
|
+
});
|
|
19899
20153
|
const leg = this.transcodeEgressLeg;
|
|
19900
20154
|
this.transcodeEgressLeg = null;
|
|
19901
20155
|
if (!leg) return;
|