@camstack/system 1.2.259 → 1.2.260
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.
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asking again for a source that has not republished yet.
|
|
3
|
+
*
|
|
4
|
+
* A camera provider re-announces its streams a moment after the hub comes up,
|
|
5
|
+
* and a grid opened inside that window used to fail outright:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* camera grid: could not acquire every source — releasing the ones already taken
|
|
9
|
+
* {acquired=0 error="getStreamWithCodec: profile \"low\" source \"native:sub\"
|
|
10
|
+
* not published for device 592"}
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Observed twice on the hub at 12:02 on 2026-09-18 and gone by the next attempt.
|
|
14
|
+
* It is the same shape as a WebRTC broker being rebuilt: the answer changes with
|
|
15
|
+
* TIME, and the consumer was deciding on the first ask.
|
|
16
|
+
*
|
|
17
|
+
* The retry is BOUNDED and it changes no verdict — a camera that really has no
|
|
18
|
+
* such profile still fails, with the provider's own error, after a wait that is
|
|
19
|
+
* reported. What it stops is a grid refusing to open for the first few seconds
|
|
20
|
+
* of every hub's life.
|
|
21
|
+
*/
|
|
22
|
+
export interface AcquireRetryDeps {
|
|
23
|
+
/** Wall-clock ceiling for the whole acquire, across every attempt. */
|
|
24
|
+
readonly budgetMs: number;
|
|
25
|
+
readonly now: () => number;
|
|
26
|
+
readonly sleep: () => Promise<void>;
|
|
27
|
+
/** Called before each retry with the ms spent so far — for the log line. */
|
|
28
|
+
readonly onRetry?: (waitedMs: number) => void;
|
|
29
|
+
}
|
|
30
|
+
export declare function acquireWithRetry<T>(attempt: () => Promise<T>, deps: AcquireRetryDeps): Promise<T>;
|
|
@@ -209,6 +209,19 @@ var GridCameraDevice = class extends require_dist.BaseDevice {
|
|
|
209
209
|
}
|
|
210
210
|
};
|
|
211
211
|
//#endregion
|
|
212
|
+
//#region src/builtins/camera-grid/acquire-retry.ts
|
|
213
|
+
async function acquireWithRetry(attempt, deps) {
|
|
214
|
+
const startedAt = deps.now();
|
|
215
|
+
for (;;) try {
|
|
216
|
+
return await attempt();
|
|
217
|
+
} catch (error) {
|
|
218
|
+
const waited = deps.now() - startedAt;
|
|
219
|
+
if (waited >= deps.budgetMs) throw error;
|
|
220
|
+
deps.onRetry?.(waited);
|
|
221
|
+
await deps.sleep();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
212
225
|
//#region src/builtins/camera-grid/grid-instances.ts
|
|
213
226
|
/**
|
|
214
227
|
* The operator-written grid rows.
|
|
@@ -1701,6 +1714,13 @@ async function startGridChild(ctx, plan, sources, onEnded) {
|
|
|
1701
1714
|
//#region src/builtins/camera-grid/addon.ts
|
|
1702
1715
|
var INTEGRATION_NAME = "Camera Grids";
|
|
1703
1716
|
var RECONCILE_MS = 6e4;
|
|
1717
|
+
/**
|
|
1718
|
+
* How long a source acquire may keep asking while a provider is still coming up.
|
|
1719
|
+
* Long enough to cover the republish window after a hub restart, short enough
|
|
1720
|
+
* that a genuinely missing profile fails while the operator is still looking.
|
|
1721
|
+
*/
|
|
1722
|
+
var SOURCE_ACQUIRE_BUDGET_MS = 8e3;
|
|
1723
|
+
var SOURCE_ACQUIRE_PAUSE_MS = 700;
|
|
1704
1724
|
var DEFAULTS = {
|
|
1705
1725
|
cameraGrids: [],
|
|
1706
1726
|
cameraGridTombstones: []
|
|
@@ -2013,12 +2033,27 @@ var CameraGridAddon = class extends require_dist.BaseAddon {
|
|
|
2013
2033
|
deps: {
|
|
2014
2034
|
logger: this.ctx.logger,
|
|
2015
2035
|
acquireSource: async (sourceDeviceId) => {
|
|
2016
|
-
const dial = gridSourceDial(await this.ctx.api.streamBroker.getStreamWithCodec.mutate({
|
|
2036
|
+
const dial = gridSourceDial(await acquireWithRetry(() => this.ctx.api.streamBroker.getStreamWithCodec.mutate({
|
|
2017
2037
|
deviceId: sourceDeviceId,
|
|
2018
2038
|
video: "copy",
|
|
2019
2039
|
audio: "none",
|
|
2020
2040
|
profile,
|
|
2021
2041
|
tag: `camera-grid:${String(deviceId)}:${profile}`
|
|
2042
|
+
}), {
|
|
2043
|
+
budgetMs: SOURCE_ACQUIRE_BUDGET_MS,
|
|
2044
|
+
now: () => Date.now(),
|
|
2045
|
+
sleep: () => new Promise((resolve) => setTimeout(resolve, SOURCE_ACQUIRE_PAUSE_MS)),
|
|
2046
|
+
onRetry: (waitedMs) => {
|
|
2047
|
+
this.ctx.logger.info("camera grid: a source has not republished yet — asking again", {
|
|
2048
|
+
tags: { deviceId },
|
|
2049
|
+
meta: {
|
|
2050
|
+
instanceId,
|
|
2051
|
+
profile,
|
|
2052
|
+
sourceDeviceId,
|
|
2053
|
+
waitedMs
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2022
2057
|
}));
|
|
2023
2058
|
this.ctx.logger.info("camera grid: source acquired", {
|
|
2024
2059
|
tags: { deviceId },
|
|
@@ -204,6 +204,19 @@ var GridCameraDevice = class extends BaseDevice {
|
|
|
204
204
|
}
|
|
205
205
|
};
|
|
206
206
|
//#endregion
|
|
207
|
+
//#region src/builtins/camera-grid/acquire-retry.ts
|
|
208
|
+
async function acquireWithRetry(attempt, deps) {
|
|
209
|
+
const startedAt = deps.now();
|
|
210
|
+
for (;;) try {
|
|
211
|
+
return await attempt();
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const waited = deps.now() - startedAt;
|
|
214
|
+
if (waited >= deps.budgetMs) throw error;
|
|
215
|
+
deps.onRetry?.(waited);
|
|
216
|
+
await deps.sleep();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
207
220
|
//#region src/builtins/camera-grid/grid-instances.ts
|
|
208
221
|
/**
|
|
209
222
|
* The operator-written grid rows.
|
|
@@ -1696,6 +1709,13 @@ async function startGridChild(ctx, plan, sources, onEnded) {
|
|
|
1696
1709
|
//#region src/builtins/camera-grid/addon.ts
|
|
1697
1710
|
var INTEGRATION_NAME = "Camera Grids";
|
|
1698
1711
|
var RECONCILE_MS = 6e4;
|
|
1712
|
+
/**
|
|
1713
|
+
* How long a source acquire may keep asking while a provider is still coming up.
|
|
1714
|
+
* Long enough to cover the republish window after a hub restart, short enough
|
|
1715
|
+
* that a genuinely missing profile fails while the operator is still looking.
|
|
1716
|
+
*/
|
|
1717
|
+
var SOURCE_ACQUIRE_BUDGET_MS = 8e3;
|
|
1718
|
+
var SOURCE_ACQUIRE_PAUSE_MS = 700;
|
|
1699
1719
|
var DEFAULTS = {
|
|
1700
1720
|
cameraGrids: [],
|
|
1701
1721
|
cameraGridTombstones: []
|
|
@@ -2008,12 +2028,27 @@ var CameraGridAddon = class extends BaseAddon {
|
|
|
2008
2028
|
deps: {
|
|
2009
2029
|
logger: this.ctx.logger,
|
|
2010
2030
|
acquireSource: async (sourceDeviceId) => {
|
|
2011
|
-
const dial = gridSourceDial(await this.ctx.api.streamBroker.getStreamWithCodec.mutate({
|
|
2031
|
+
const dial = gridSourceDial(await acquireWithRetry(() => this.ctx.api.streamBroker.getStreamWithCodec.mutate({
|
|
2012
2032
|
deviceId: sourceDeviceId,
|
|
2013
2033
|
video: "copy",
|
|
2014
2034
|
audio: "none",
|
|
2015
2035
|
profile,
|
|
2016
2036
|
tag: `camera-grid:${String(deviceId)}:${profile}`
|
|
2037
|
+
}), {
|
|
2038
|
+
budgetMs: SOURCE_ACQUIRE_BUDGET_MS,
|
|
2039
|
+
now: () => Date.now(),
|
|
2040
|
+
sleep: () => new Promise((resolve) => setTimeout(resolve, SOURCE_ACQUIRE_PAUSE_MS)),
|
|
2041
|
+
onRetry: (waitedMs) => {
|
|
2042
|
+
this.ctx.logger.info("camera grid: a source has not republished yet — asking again", {
|
|
2043
|
+
tags: { deviceId },
|
|
2044
|
+
meta: {
|
|
2045
|
+
instanceId,
|
|
2046
|
+
profile,
|
|
2047
|
+
sourceDeviceId,
|
|
2048
|
+
waitedMs
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2017
2052
|
}));
|
|
2018
2053
|
this.ctx.logger.info("camera grid: source acquired", {
|
|
2019
2054
|
tags: { deviceId },
|