@camstack/addon-decoder-nodeav 1.1.1 → 1.1.2
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/index.js +16 -114
- package/dist/index.mjs +16 -114
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13315,7 +13315,9 @@ var decoderCapability = {
|
|
|
13315
13315
|
id: string(),
|
|
13316
13316
|
name: string(),
|
|
13317
13317
|
isPullMode: boolean().optional(),
|
|
13318
|
-
priority: number().optional()
|
|
13318
|
+
priority: number().optional(),
|
|
13319
|
+
hwaccel: string().optional(),
|
|
13320
|
+
probedBestHwaccel: string().optional()
|
|
13319
13321
|
})),
|
|
13320
13322
|
createSession: method(DecoderSessionConfigSchema, object({
|
|
13321
13323
|
sessionId: string(),
|
|
@@ -23131,79 +23133,10 @@ function normalizeDecoderNodeId(rawNodeId) {
|
|
|
23131
23133
|
}
|
|
23132
23134
|
//#endregion
|
|
23133
23135
|
//#region src/shared/decoder-backend.ts
|
|
23134
|
-
/**
|
|
23135
|
-
*
|
|
23136
|
-
*
|
|
23137
|
-
|
|
23138
|
-
* Two decoder addons ship in this bundle: `decoder-ffmpeg` (subprocess
|
|
23139
|
-
* decode; a decode crash is isolated to the child) and `decoder-nodeav`
|
|
23140
|
-
* (in-process decode via FFmpeg native bindings; no subprocess lifecycle).
|
|
23141
|
-
* Both are installed on every node, but only ONE may provide the `decoder`
|
|
23142
|
-
* cap at a time.
|
|
23143
|
-
*
|
|
23144
|
-
* ## Why selection happens at REGISTRATION time (race-free by construction)
|
|
23145
|
-
*
|
|
23146
|
-
* The `decoder` cap is a singleton. Historically both decoder addons
|
|
23147
|
-
* registered a provider and the active slot was resolved AFTER the fact —
|
|
23148
|
-
* which exposed a boot-order race: whichever addon's runner initialised
|
|
23149
|
-
* first briefly held the active slot, and any consumer that resolved the
|
|
23150
|
-
* cap inside that window was dispatched to the wrong backend (the
|
|
23151
|
-
* cap-declared `preferredProvider` override only corrects the slot when
|
|
23152
|
-
* the preferred addon's registration eventually lands).
|
|
23153
|
-
*
|
|
23154
|
-
* This module removes the race by construction instead of arbitrating it:
|
|
23155
|
-
* each decoder addon calls {@link resolveDecoderBackend} at the START of
|
|
23156
|
-
* its `onInitialize` and registers its provider ONLY when it is the
|
|
23157
|
-
* selected backend — the other addon returns no registrations at all. At
|
|
23158
|
-
* most one `decoder` provider ever exists per node, so no resolution layer
|
|
23159
|
-
* (kernel `CapabilityRegistry`, route resolver, UDS child preference) can
|
|
23160
|
-
* pick a wrong one, regardless of boot order or timing.
|
|
23161
|
-
*
|
|
23162
|
-
* ## The setting
|
|
23163
|
-
*
|
|
23164
|
-
* The per-node selection lives in the `decoder-ffmpeg` OWNER addon's global
|
|
23165
|
-
* settings — the standard `addon-settings` surface, visible in the admin UI
|
|
23166
|
-
* as the owner's `backend` select field. The store is hub-central and the
|
|
23167
|
-
* value is persisted node-scoped as `backend@<nodeId>`
|
|
23168
|
-
* (`decoder-backend-keys.ts`); the owner's `getGlobalSettings` projects the
|
|
23169
|
-
* requested node's value onto the bare `backend` field.
|
|
23170
|
-
*
|
|
23171
|
-
* Both decoder addons read it the SAME way, placement-agnostically: via
|
|
23172
|
-
* `ctx.api.addonSettings.getGlobalSettings({ addonId: 'decoder-ffmpeg',
|
|
23173
|
-
* nodeId })`. The `addon-settings` cap is hub-routed at the system level
|
|
23174
|
-
* (`AddonCallGateway.classify` sends settings calls to the addon's base
|
|
23175
|
-
* node — the hub instance answers for every node), so an agent addon
|
|
23176
|
-
* transparently reads the hub-central store with no placement branching.
|
|
23177
|
-
* NOT used: `ctx.settings.getSection` (node-local — empty on an agent, the
|
|
23178
|
-
* original bug), the raw settings-store cap, or any custom side channel.
|
|
23179
|
-
*
|
|
23180
|
-
* Resolution: the node's scoped value → the built-in default
|
|
23181
|
-
* {@link DEFAULT_DECODER_BACKEND} (`'nodeav'` — node-av is primary everywhere;
|
|
23182
|
-
* ffmpeg is an explicit opt-in fallback only). There is deliberately NO
|
|
23183
|
-
* bare-key fallback (see `decoder-backend-keys.ts`), and any read failure
|
|
23184
|
-
* resolves to the default so a decoder still comes up — and, because the
|
|
23185
|
-
* default IS the primary backend, a stale/failed read can never leave the node
|
|
23186
|
-
* with NO decoder (the old `'ffmpeg'` default made node-av stand down on a
|
|
23187
|
-
* missed read → both addons down).
|
|
23188
|
-
*
|
|
23189
|
-
* ## Switching backends
|
|
23190
|
-
*
|
|
23191
|
-
* Because registration is decided once at addon init, changing the setting
|
|
23192
|
-
* takes effect on the next restart of the two decoder addons on the
|
|
23193
|
-
* affected node (addon restart / redeploy / node reboot). This is the
|
|
23194
|
-
* price of race-freedom: there is deliberately NO live re-arbitration
|
|
23195
|
-
* path — a live hand-off would reintroduce a window with two providers.
|
|
23196
|
-
*/
|
|
23197
|
-
/** The addon whose global settings OWN the per-node `backend` field. */
|
|
23198
|
-
var DECODER_OWNER_ADDON_ID = "decoder-ffmpeg";
|
|
23199
|
-
/** Fail-safe when the owner's hwaccel can't be read: defer to the local probe. */
|
|
23200
|
-
var DEFAULT_DECODER_HWACCEL = "auto";
|
|
23201
|
-
/** Narrow an arbitrary stored value to a known {@link HwAccelChoice}, else null. */
|
|
23202
|
-
function parseDecoderHwAccel(raw) {
|
|
23203
|
-
if (typeof raw !== "string") return null;
|
|
23204
|
-
const match = HWACCEL_OPTIONS.find((o) => o.value === raw);
|
|
23205
|
-
return match ? match.value : null;
|
|
23206
|
-
}
|
|
23136
|
+
/** The NEUTRAL addon (pipeline-orchestrator, hub-resident) whose global
|
|
23137
|
+
* settings OWN the per-node `backend` selector. Neither decoder addon owns
|
|
23138
|
+
* it, so decoder-nodeav / decoder-ffmpeg stay fully independent. */
|
|
23139
|
+
var DECODER_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
23207
23140
|
function isHydratedField(entry) {
|
|
23208
23141
|
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
23209
23142
|
}
|
|
@@ -23294,37 +23227,6 @@ async function resolveDecoderBackend(api, nodeId, logger) {
|
|
|
23294
23227
|
} });
|
|
23295
23228
|
return DEFAULT_DECODER_BACKEND;
|
|
23296
23229
|
}
|
|
23297
|
-
/**
|
|
23298
|
-
* Pure selection of the owner's `hwaccel` field from an already-read hydrated
|
|
23299
|
-
* settings payload. Missing/invalid/null → {@link DEFAULT_DECODER_HWACCEL}.
|
|
23300
|
-
*/
|
|
23301
|
-
function pickDecoderHwAccelFromSettings(view) {
|
|
23302
|
-
if (view === null) return DEFAULT_DECODER_HWACCEL;
|
|
23303
|
-
for (const section of view.sections) for (const entry of section.fields) {
|
|
23304
|
-
if (!isHydratedField(entry) || entry.key !== "hwaccel") continue;
|
|
23305
|
-
return parseDecoderHwAccel(entry.value) ?? "auto";
|
|
23306
|
-
}
|
|
23307
|
-
return DEFAULT_DECODER_HWACCEL;
|
|
23308
|
-
}
|
|
23309
|
-
/**
|
|
23310
|
-
* Resolve the effective decoder hwaccel override for a node by reading the
|
|
23311
|
-
* `decoder-ffmpeg` OWNER's `hwaccel@<node>` setting — the exact same
|
|
23312
|
-
* hub-routed, placement-agnostic read as {@link resolveDecoderBackend}, so
|
|
23313
|
-
* node-av and ffmpeg honour ONE per-node value (the one the UI edits). Fails
|
|
23314
|
-
* safe to {@link DEFAULT_DECODER_HWACCEL} (`'auto'` → the session's local probe).
|
|
23315
|
-
*/
|
|
23316
|
-
async function resolveDecoderHwAccel(api, nodeId, logger) {
|
|
23317
|
-
if (!api) return DEFAULT_DECODER_HWACCEL;
|
|
23318
|
-
try {
|
|
23319
|
-
return pickDecoderHwAccelFromSettings(await api.addonSettings.getGlobalSettings.query({
|
|
23320
|
-
addonId: DECODER_OWNER_ADDON_ID,
|
|
23321
|
-
nodeId: normalizeDecoderNodeId(nodeId)
|
|
23322
|
-
}));
|
|
23323
|
-
} catch (err) {
|
|
23324
|
-
logger.warn("decoder-hwaccel: owner settings read failed — deferring to local probe", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
23325
|
-
return DEFAULT_DECODER_HWACCEL;
|
|
23326
|
-
}
|
|
23327
|
-
}
|
|
23328
23230
|
//#endregion
|
|
23329
23231
|
//#region src/scaler-geometry.ts
|
|
23330
23232
|
/**
|
|
@@ -24473,16 +24375,14 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
24473
24375
|
}];
|
|
24474
24376
|
}
|
|
24475
24377
|
/**
|
|
24476
|
-
* Resolve the effective hwaccel backend for a new session
|
|
24477
|
-
* per-node
|
|
24478
|
-
*
|
|
24479
|
-
*
|
|
24480
|
-
*
|
|
24481
|
-
* the UI). `'auto'` defers to the session's local resolver
|
|
24482
|
-
* (`ctx.kernel.hwaccel`). Fails safe to `'auto'`.
|
|
24378
|
+
* Resolve the effective hwaccel backend for a new session — from THIS addon's
|
|
24379
|
+
* OWN per-node `hwaccel` config (edited via the Pipeline UI when node-av is
|
|
24380
|
+
* the active decoder). No cross-read of another addon's store. `'auto'`
|
|
24381
|
+
* defers to the session's local resolver (`ctx.kernel.hwaccel`); fails safe
|
|
24382
|
+
* to `'auto'`.
|
|
24483
24383
|
*/
|
|
24484
24384
|
resolveHwAccelPref() {
|
|
24485
|
-
return
|
|
24385
|
+
return Promise.resolve(this.config.hwaccel ?? "auto");
|
|
24486
24386
|
}
|
|
24487
24387
|
/**
|
|
24488
24388
|
* Re-run the platform probe on this host and persist the detected
|
|
@@ -24524,7 +24424,9 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
24524
24424
|
id: "decoder-nodeav",
|
|
24525
24425
|
name: "Decoder (node-av)",
|
|
24526
24426
|
isPullMode: true,
|
|
24527
|
-
priority: 10
|
|
24427
|
+
priority: 10,
|
|
24428
|
+
hwaccel: this.config.hwaccel,
|
|
24429
|
+
probedBestHwaccel: this.config.probedBestHwaccel || void 0
|
|
24528
24430
|
};
|
|
24529
24431
|
}
|
|
24530
24432
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -13311,7 +13311,9 @@ var decoderCapability = {
|
|
|
13311
13311
|
id: string(),
|
|
13312
13312
|
name: string(),
|
|
13313
13313
|
isPullMode: boolean().optional(),
|
|
13314
|
-
priority: number().optional()
|
|
13314
|
+
priority: number().optional(),
|
|
13315
|
+
hwaccel: string().optional(),
|
|
13316
|
+
probedBestHwaccel: string().optional()
|
|
13315
13317
|
})),
|
|
13316
13318
|
createSession: method(DecoderSessionConfigSchema, object({
|
|
13317
13319
|
sessionId: string(),
|
|
@@ -23127,79 +23129,10 @@ function normalizeDecoderNodeId(rawNodeId) {
|
|
|
23127
23129
|
}
|
|
23128
23130
|
//#endregion
|
|
23129
23131
|
//#region src/shared/decoder-backend.ts
|
|
23130
|
-
/**
|
|
23131
|
-
*
|
|
23132
|
-
*
|
|
23133
|
-
|
|
23134
|
-
* Two decoder addons ship in this bundle: `decoder-ffmpeg` (subprocess
|
|
23135
|
-
* decode; a decode crash is isolated to the child) and `decoder-nodeav`
|
|
23136
|
-
* (in-process decode via FFmpeg native bindings; no subprocess lifecycle).
|
|
23137
|
-
* Both are installed on every node, but only ONE may provide the `decoder`
|
|
23138
|
-
* cap at a time.
|
|
23139
|
-
*
|
|
23140
|
-
* ## Why selection happens at REGISTRATION time (race-free by construction)
|
|
23141
|
-
*
|
|
23142
|
-
* The `decoder` cap is a singleton. Historically both decoder addons
|
|
23143
|
-
* registered a provider and the active slot was resolved AFTER the fact —
|
|
23144
|
-
* which exposed a boot-order race: whichever addon's runner initialised
|
|
23145
|
-
* first briefly held the active slot, and any consumer that resolved the
|
|
23146
|
-
* cap inside that window was dispatched to the wrong backend (the
|
|
23147
|
-
* cap-declared `preferredProvider` override only corrects the slot when
|
|
23148
|
-
* the preferred addon's registration eventually lands).
|
|
23149
|
-
*
|
|
23150
|
-
* This module removes the race by construction instead of arbitrating it:
|
|
23151
|
-
* each decoder addon calls {@link resolveDecoderBackend} at the START of
|
|
23152
|
-
* its `onInitialize` and registers its provider ONLY when it is the
|
|
23153
|
-
* selected backend — the other addon returns no registrations at all. At
|
|
23154
|
-
* most one `decoder` provider ever exists per node, so no resolution layer
|
|
23155
|
-
* (kernel `CapabilityRegistry`, route resolver, UDS child preference) can
|
|
23156
|
-
* pick a wrong one, regardless of boot order or timing.
|
|
23157
|
-
*
|
|
23158
|
-
* ## The setting
|
|
23159
|
-
*
|
|
23160
|
-
* The per-node selection lives in the `decoder-ffmpeg` OWNER addon's global
|
|
23161
|
-
* settings — the standard `addon-settings` surface, visible in the admin UI
|
|
23162
|
-
* as the owner's `backend` select field. The store is hub-central and the
|
|
23163
|
-
* value is persisted node-scoped as `backend@<nodeId>`
|
|
23164
|
-
* (`decoder-backend-keys.ts`); the owner's `getGlobalSettings` projects the
|
|
23165
|
-
* requested node's value onto the bare `backend` field.
|
|
23166
|
-
*
|
|
23167
|
-
* Both decoder addons read it the SAME way, placement-agnostically: via
|
|
23168
|
-
* `ctx.api.addonSettings.getGlobalSettings({ addonId: 'decoder-ffmpeg',
|
|
23169
|
-
* nodeId })`. The `addon-settings` cap is hub-routed at the system level
|
|
23170
|
-
* (`AddonCallGateway.classify` sends settings calls to the addon's base
|
|
23171
|
-
* node — the hub instance answers for every node), so an agent addon
|
|
23172
|
-
* transparently reads the hub-central store with no placement branching.
|
|
23173
|
-
* NOT used: `ctx.settings.getSection` (node-local — empty on an agent, the
|
|
23174
|
-
* original bug), the raw settings-store cap, or any custom side channel.
|
|
23175
|
-
*
|
|
23176
|
-
* Resolution: the node's scoped value → the built-in default
|
|
23177
|
-
* {@link DEFAULT_DECODER_BACKEND} (`'nodeav'` — node-av is primary everywhere;
|
|
23178
|
-
* ffmpeg is an explicit opt-in fallback only). There is deliberately NO
|
|
23179
|
-
* bare-key fallback (see `decoder-backend-keys.ts`), and any read failure
|
|
23180
|
-
* resolves to the default so a decoder still comes up — and, because the
|
|
23181
|
-
* default IS the primary backend, a stale/failed read can never leave the node
|
|
23182
|
-
* with NO decoder (the old `'ffmpeg'` default made node-av stand down on a
|
|
23183
|
-
* missed read → both addons down).
|
|
23184
|
-
*
|
|
23185
|
-
* ## Switching backends
|
|
23186
|
-
*
|
|
23187
|
-
* Because registration is decided once at addon init, changing the setting
|
|
23188
|
-
* takes effect on the next restart of the two decoder addons on the
|
|
23189
|
-
* affected node (addon restart / redeploy / node reboot). This is the
|
|
23190
|
-
* price of race-freedom: there is deliberately NO live re-arbitration
|
|
23191
|
-
* path — a live hand-off would reintroduce a window with two providers.
|
|
23192
|
-
*/
|
|
23193
|
-
/** The addon whose global settings OWN the per-node `backend` field. */
|
|
23194
|
-
var DECODER_OWNER_ADDON_ID = "decoder-ffmpeg";
|
|
23195
|
-
/** Fail-safe when the owner's hwaccel can't be read: defer to the local probe. */
|
|
23196
|
-
var DEFAULT_DECODER_HWACCEL = "auto";
|
|
23197
|
-
/** Narrow an arbitrary stored value to a known {@link HwAccelChoice}, else null. */
|
|
23198
|
-
function parseDecoderHwAccel(raw) {
|
|
23199
|
-
if (typeof raw !== "string") return null;
|
|
23200
|
-
const match = HWACCEL_OPTIONS.find((o) => o.value === raw);
|
|
23201
|
-
return match ? match.value : null;
|
|
23202
|
-
}
|
|
23132
|
+
/** The NEUTRAL addon (pipeline-orchestrator, hub-resident) whose global
|
|
23133
|
+
* settings OWN the per-node `backend` selector. Neither decoder addon owns
|
|
23134
|
+
* it, so decoder-nodeav / decoder-ffmpeg stay fully independent. */
|
|
23135
|
+
var DECODER_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
23203
23136
|
function isHydratedField(entry) {
|
|
23204
23137
|
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
23205
23138
|
}
|
|
@@ -23290,37 +23223,6 @@ async function resolveDecoderBackend(api, nodeId, logger) {
|
|
|
23290
23223
|
} });
|
|
23291
23224
|
return DEFAULT_DECODER_BACKEND;
|
|
23292
23225
|
}
|
|
23293
|
-
/**
|
|
23294
|
-
* Pure selection of the owner's `hwaccel` field from an already-read hydrated
|
|
23295
|
-
* settings payload. Missing/invalid/null → {@link DEFAULT_DECODER_HWACCEL}.
|
|
23296
|
-
*/
|
|
23297
|
-
function pickDecoderHwAccelFromSettings(view) {
|
|
23298
|
-
if (view === null) return DEFAULT_DECODER_HWACCEL;
|
|
23299
|
-
for (const section of view.sections) for (const entry of section.fields) {
|
|
23300
|
-
if (!isHydratedField(entry) || entry.key !== "hwaccel") continue;
|
|
23301
|
-
return parseDecoderHwAccel(entry.value) ?? "auto";
|
|
23302
|
-
}
|
|
23303
|
-
return DEFAULT_DECODER_HWACCEL;
|
|
23304
|
-
}
|
|
23305
|
-
/**
|
|
23306
|
-
* Resolve the effective decoder hwaccel override for a node by reading the
|
|
23307
|
-
* `decoder-ffmpeg` OWNER's `hwaccel@<node>` setting — the exact same
|
|
23308
|
-
* hub-routed, placement-agnostic read as {@link resolveDecoderBackend}, so
|
|
23309
|
-
* node-av and ffmpeg honour ONE per-node value (the one the UI edits). Fails
|
|
23310
|
-
* safe to {@link DEFAULT_DECODER_HWACCEL} (`'auto'` → the session's local probe).
|
|
23311
|
-
*/
|
|
23312
|
-
async function resolveDecoderHwAccel(api, nodeId, logger) {
|
|
23313
|
-
if (!api) return DEFAULT_DECODER_HWACCEL;
|
|
23314
|
-
try {
|
|
23315
|
-
return pickDecoderHwAccelFromSettings(await api.addonSettings.getGlobalSettings.query({
|
|
23316
|
-
addonId: DECODER_OWNER_ADDON_ID,
|
|
23317
|
-
nodeId: normalizeDecoderNodeId(nodeId)
|
|
23318
|
-
}));
|
|
23319
|
-
} catch (err) {
|
|
23320
|
-
logger.warn("decoder-hwaccel: owner settings read failed — deferring to local probe", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
23321
|
-
return DEFAULT_DECODER_HWACCEL;
|
|
23322
|
-
}
|
|
23323
|
-
}
|
|
23324
23226
|
//#endregion
|
|
23325
23227
|
//#region src/scaler-geometry.ts
|
|
23326
23228
|
/**
|
|
@@ -24469,16 +24371,14 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
24469
24371
|
}];
|
|
24470
24372
|
}
|
|
24471
24373
|
/**
|
|
24472
|
-
* Resolve the effective hwaccel backend for a new session
|
|
24473
|
-
* per-node
|
|
24474
|
-
*
|
|
24475
|
-
*
|
|
24476
|
-
*
|
|
24477
|
-
* the UI). `'auto'` defers to the session's local resolver
|
|
24478
|
-
* (`ctx.kernel.hwaccel`). Fails safe to `'auto'`.
|
|
24374
|
+
* Resolve the effective hwaccel backend for a new session — from THIS addon's
|
|
24375
|
+
* OWN per-node `hwaccel` config (edited via the Pipeline UI when node-av is
|
|
24376
|
+
* the active decoder). No cross-read of another addon's store. `'auto'`
|
|
24377
|
+
* defers to the session's local resolver (`ctx.kernel.hwaccel`); fails safe
|
|
24378
|
+
* to `'auto'`.
|
|
24479
24379
|
*/
|
|
24480
24380
|
resolveHwAccelPref() {
|
|
24481
|
-
return
|
|
24381
|
+
return Promise.resolve(this.config.hwaccel ?? "auto");
|
|
24482
24382
|
}
|
|
24483
24383
|
/**
|
|
24484
24384
|
* Re-run the platform probe on this host and persist the detected
|
|
@@ -24520,7 +24420,9 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
24520
24420
|
id: "decoder-nodeav",
|
|
24521
24421
|
name: "Decoder (node-av)",
|
|
24522
24422
|
isPullMode: true,
|
|
24523
|
-
priority: 10
|
|
24423
|
+
priority: 10,
|
|
24424
|
+
hwaccel: this.config.hwaccel,
|
|
24425
|
+
probedBestHwaccel: this.config.probedBestHwaccel || void 0
|
|
24524
24426
|
};
|
|
24525
24427
|
}
|
|
24526
24428
|
/**
|