@camstack/addon-pipeline 1.2.81 → 1.2.82
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/stream-broker/index.js +101 -62
- package/dist/stream-broker/index.mjs +101 -62
- package/package.json +1 -1
|
@@ -207,7 +207,9 @@ function formatProbedSummary(declared, snapshot, opts) {
|
|
|
207
207
|
* on our own initiative, and says nothing about waking it because somebody
|
|
208
208
|
* pressed play. That asymmetry is the whole design: brokers stay AVAILABLE
|
|
209
209
|
* (definable, from the durable catalog) without being ACTIVE, and the dial is
|
|
210
|
-
* the only thing that touches the camera.
|
|
210
|
+
* the only thing that touches the camera. Concurrent/retried requests for the
|
|
211
|
+
* same waking device share one transaction; a client retry must not become a
|
|
212
|
+
* second physical wake.
|
|
211
213
|
*
|
|
212
214
|
* ## Honest states
|
|
213
215
|
*
|
|
@@ -243,10 +245,48 @@ var BatteryStreamUnavailableError = class extends Error {
|
|
|
243
245
|
};
|
|
244
246
|
/** Bound on the firmware wake. Matches the cap's own sensible range. */
|
|
245
247
|
var WAKE_FOR_STREAM_TIMEOUT_MS = 8e3;
|
|
246
|
-
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Bound AFTER firmware acknowledgement for the provider to rebuild/publish
|
|
250
|
+
* stream parameters and the broker to observe them. Field cameras can
|
|
251
|
+
* acknowledge several seconds before their media/catalog plane is usable.
|
|
252
|
+
*/
|
|
253
|
+
var BROKER_SETTLE_TIMEOUT_MS = 2e4;
|
|
254
|
+
/** Reconcile cadence while the acknowledged camera finishes waking. */
|
|
255
|
+
var BROKER_SETTLE_POLL_INTERVAL_MS = 1500;
|
|
256
|
+
function wait(timeoutMs) {
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
setTimeout(resolve, timeoutMs).unref?.();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Device-scoped single-flight for wake-on-play. A wake applies to the whole
|
|
263
|
+
* camera, not one profile/brokerId, so concurrent profile requests and client
|
|
264
|
+
* retries all await the same bounded transaction.
|
|
265
|
+
*/
|
|
266
|
+
var BatteryWakeOnPlaySingleFlight = class {
|
|
267
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
268
|
+
async run(deviceId, deps) {
|
|
269
|
+
const existing = this.inFlight.get(deviceId);
|
|
270
|
+
if (existing) {
|
|
271
|
+
await existing;
|
|
272
|
+
if (!deps.hasBroker()) await deps.ensureBroker().catch((err) => {
|
|
273
|
+
deps.logger.debug("wake-on-play: joined demand broker retry failed", {
|
|
274
|
+
tags: { deviceId },
|
|
275
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
if (!deps.hasBroker()) throw new BatteryStreamUnavailableError(deviceId, "waking", "the shared wake completed but this stream profile is not available yet — try again");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const transaction = wakeBatteryDeviceForPlay(deviceId, deps);
|
|
282
|
+
this.inFlight.set(deviceId, transaction);
|
|
283
|
+
try {
|
|
284
|
+
await transaction;
|
|
285
|
+
} finally {
|
|
286
|
+
if (this.inFlight.get(deviceId) === transaction) this.inFlight.delete(deviceId);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
250
290
|
/**
|
|
251
291
|
* Make a broker available for a battery camera that has none, by asking the
|
|
252
292
|
* camera to wake.
|
|
@@ -257,26 +297,25 @@ var STREAM_PARAMS_SETTLE_TIMEOUT_MS = 6e3;
|
|
|
257
297
|
* {@link BatteryStreamUnavailableError} only for a battery camera we could not
|
|
258
298
|
* produce a stream for — with the reason named.
|
|
259
299
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* demand and gets a new wake.
|
|
300
|
+
* After an acknowledged wake, repeatedly reconciles the provider catalog and
|
|
301
|
+
* retries broker resolution within one field-sized bound. It never issues a
|
|
302
|
+
* second wake inside that transaction.
|
|
264
303
|
*/
|
|
265
304
|
async function wakeBatteryDeviceForPlay(deviceId, deps) {
|
|
266
305
|
if (deps.hasBroker()) return;
|
|
267
306
|
if (await deps.isBatteryDevice(deviceId).catch(() => null) !== true) return;
|
|
268
307
|
const facts = await deps.readBatteryFacts(deviceId).catch(() => null);
|
|
269
|
-
const
|
|
308
|
+
const observedAt = (deps.nowMs ?? Date.now)();
|
|
270
309
|
const presence = require_dist.deriveBatteryPresence({
|
|
271
310
|
status: facts,
|
|
272
|
-
nowMs
|
|
311
|
+
nowMs: observedAt
|
|
273
312
|
});
|
|
274
313
|
if (presence === "unreachable") {
|
|
275
314
|
deps.logger.warn("wake-on-play refused — battery camera is unreachable, not asleep", {
|
|
276
315
|
tags: { deviceId },
|
|
277
316
|
meta: {
|
|
278
317
|
lastContactAt: facts?.lastContactAt ?? 0,
|
|
279
|
-
silenceMs:
|
|
318
|
+
silenceMs: observedAt - (facts?.lastContactAt ?? 0)
|
|
280
319
|
}
|
|
281
320
|
});
|
|
282
321
|
throw new BatteryStreamUnavailableError(deviceId, "unreachable", "nothing has been heard from it for hours — check the battery or its network");
|
|
@@ -292,29 +331,46 @@ async function wakeBatteryDeviceForPlay(deviceId, deps) {
|
|
|
292
331
|
awoke: false,
|
|
293
332
|
durationMs: 0
|
|
294
333
|
}));
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
334
|
+
const nowMs = deps.nowMs ?? Date.now;
|
|
335
|
+
const settleStartedAt = nowMs();
|
|
336
|
+
const settleDeadline = settleStartedAt + (wake.awoke ? BROKER_SETTLE_TIMEOUT_MS : 0);
|
|
337
|
+
const maxAttempts = wake.awoke ? Math.ceil(BROKER_SETTLE_TIMEOUT_MS / BROKER_SETTLE_POLL_INTERVAL_MS) + 1 : 1;
|
|
338
|
+
let attempts = 0;
|
|
339
|
+
while (attempts < maxAttempts) {
|
|
340
|
+
attempts += 1;
|
|
341
|
+
await deps.reconcileCatalog(deviceId).catch((err) => {
|
|
342
|
+
deps.logger.debug("wake-on-play: catalog reconcile failed", {
|
|
343
|
+
tags: { deviceId },
|
|
344
|
+
meta: {
|
|
345
|
+
attempt: attempts,
|
|
346
|
+
error: err instanceof Error ? err.message : String(err)
|
|
347
|
+
}
|
|
348
|
+
});
|
|
306
349
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
350
|
+
await deps.ensureBroker().catch((err) => {
|
|
351
|
+
deps.logger.debug("wake-on-play: broker retry failed", {
|
|
352
|
+
tags: { deviceId },
|
|
353
|
+
meta: {
|
|
354
|
+
attempt: attempts,
|
|
355
|
+
error: err instanceof Error ? err.message : String(err)
|
|
356
|
+
}
|
|
357
|
+
});
|
|
316
358
|
});
|
|
317
|
-
|
|
359
|
+
if (deps.hasBroker()) {
|
|
360
|
+
deps.logger.info("wake-on-play succeeded — broker available after wake", {
|
|
361
|
+
tags: { deviceId },
|
|
362
|
+
meta: {
|
|
363
|
+
awoke: wake.awoke,
|
|
364
|
+
wakeMs: wake.durationMs,
|
|
365
|
+
settleMs: nowMs() - settleStartedAt,
|
|
366
|
+
attempts
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const remainingMs = settleDeadline - nowMs();
|
|
372
|
+
if (remainingMs <= 0 || attempts >= maxAttempts) break;
|
|
373
|
+
await (deps.wait ?? wait)(Math.min(BROKER_SETTLE_POLL_INTERVAL_MS, remainingMs));
|
|
318
374
|
}
|
|
319
375
|
const outcome = wake.awoke ? "waking" : "sleeping";
|
|
320
376
|
deps.logger.warn("wake-on-play exhausted — no stream after the wake", {
|
|
@@ -322,7 +378,8 @@ async function wakeBatteryDeviceForPlay(deviceId, deps) {
|
|
|
322
378
|
meta: {
|
|
323
379
|
awoke: wake.awoke,
|
|
324
380
|
wakeMs: wake.durationMs,
|
|
325
|
-
|
|
381
|
+
settleMs: nowMs() - settleStartedAt,
|
|
382
|
+
attempts,
|
|
326
383
|
outcome
|
|
327
384
|
}
|
|
328
385
|
});
|
|
@@ -14401,6 +14458,13 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
14401
14458
|
*/
|
|
14402
14459
|
brokerEnsures = /* @__PURE__ */ new Map();
|
|
14403
14460
|
/**
|
|
14461
|
+
* Device-scoped wake-on-play transactions. A camera wake is shared by every
|
|
14462
|
+
* profile, so Viewer retries or concurrent sessions must join the existing
|
|
14463
|
+
* sleeping → waking → broker-settle transaction instead of issuing another
|
|
14464
|
+
* `battery.wakeForStream` mutation.
|
|
14465
|
+
*/
|
|
14466
|
+
batteryWakeOnPlay = new BatteryWakeOnPlaySingleFlight();
|
|
14467
|
+
/**
|
|
14404
14468
|
* brokerId → the profile tier a remote-ensured adapter was created for
|
|
14405
14469
|
* (Slice 3b Task 2). `this.assignments` only tracks LOCALLY-owned devices,
|
|
14406
14470
|
* so the `profileTierResolver` closure below always misses for a remote
|
|
@@ -15206,7 +15270,8 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15206
15270
|
/**
|
|
15207
15271
|
* Wake-on-play (D173). Called by `WebrtcSessionProvider` when the normal
|
|
15208
15272
|
* resolution left no broker: if the device is battery-operated, ask the
|
|
15209
|
-
* camera to wake,
|
|
15273
|
+
* camera to wake, then poll catalog reconciliation + broker resolution
|
|
15274
|
+
* through one bounded settle transaction.
|
|
15210
15275
|
*
|
|
15211
15276
|
* Throws `BatteryStreamUnavailableError` — naming `sleeping` / `waking` /
|
|
15212
15277
|
* `unreachable` — instead of letting `BrokerWebrtcServer` answer a healthy,
|
|
@@ -15219,7 +15284,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15219
15284
|
if (this.disabledDevices.has(deviceId)) return;
|
|
15220
15285
|
const api = this.api;
|
|
15221
15286
|
if (!api) return;
|
|
15222
|
-
await
|
|
15287
|
+
await this.batteryWakeOnPlay.run(deviceId, {
|
|
15223
15288
|
logger: this.logger,
|
|
15224
15289
|
isBatteryDevice: async (id) => {
|
|
15225
15290
|
const dev = await api.deviceManager.getDevice.query({ deviceId: id }).catch(() => null);
|
|
@@ -15242,38 +15307,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15242
15307
|
deviceId: id,
|
|
15243
15308
|
timeoutMs
|
|
15244
15309
|
}),
|
|
15245
|
-
awaitStreamParamsChanged: (id, timeoutMs) => this.awaitStreamParamsChanged(id, timeoutMs),
|
|
15246
15310
|
reconcileCatalog: (id) => this.reconcileDeviceCatalog(id),
|
|
15247
15311
|
ensureBroker: () => this.ensureBrokerForWebrtcSession(deviceId, brokerId, camStreamId),
|
|
15248
15312
|
hasBroker: () => this.hasRegisteredBroker(brokerId)
|
|
15249
15313
|
});
|
|
15250
15314
|
}
|
|
15251
15315
|
/**
|
|
15252
|
-
* Bounded wait for the provider's `StreamParamsChanged` nudge for one
|
|
15253
|
-
* device. Resolves `false` on timeout — a timeout is information, not a
|
|
15254
|
-
* failure: a catalog restored from the durable slice announces nothing, and
|
|
15255
|
-
* the caller reconciles either way.
|
|
15256
|
-
*/
|
|
15257
|
-
awaitStreamParamsChanged(deviceId, timeoutMs) {
|
|
15258
|
-
const bus = this.eventBus;
|
|
15259
|
-
if (!bus) return Promise.resolve(false);
|
|
15260
|
-
return new Promise((resolve) => {
|
|
15261
|
-
let settled = false;
|
|
15262
|
-
const finish = (value) => {
|
|
15263
|
-
if (settled) return;
|
|
15264
|
-
settled = true;
|
|
15265
|
-
clearTimeout(timer);
|
|
15266
|
-
unsubscribe();
|
|
15267
|
-
resolve(value);
|
|
15268
|
-
};
|
|
15269
|
-
const unsubscribe = bus.subscribe({ category: require_dist.EventCategory.StreamParamsChanged }, (event) => {
|
|
15270
|
-
if (event.data.deviceId === deviceId) finish(true);
|
|
15271
|
-
});
|
|
15272
|
-
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
15273
|
-
timer.unref?.();
|
|
15274
|
-
});
|
|
15275
|
-
}
|
|
15276
|
-
/**
|
|
15277
15316
|
* Single-flight body for `ensureBrokerForWebrtcSession`'s remote-adapter
|
|
15278
15317
|
* get-or-create — resolves the ingest owner, and for `remote-adapter` mode
|
|
15279
15318
|
* constructs the `RemoteEncodedSourceBroker`, registers it with the WebRTC
|
|
@@ -202,7 +202,9 @@ function formatProbedSummary(declared, snapshot, opts) {
|
|
|
202
202
|
* on our own initiative, and says nothing about waking it because somebody
|
|
203
203
|
* pressed play. That asymmetry is the whole design: brokers stay AVAILABLE
|
|
204
204
|
* (definable, from the durable catalog) without being ACTIVE, and the dial is
|
|
205
|
-
* the only thing that touches the camera.
|
|
205
|
+
* the only thing that touches the camera. Concurrent/retried requests for the
|
|
206
|
+
* same waking device share one transaction; a client retry must not become a
|
|
207
|
+
* second physical wake.
|
|
206
208
|
*
|
|
207
209
|
* ## Honest states
|
|
208
210
|
*
|
|
@@ -238,10 +240,48 @@ var BatteryStreamUnavailableError = class extends Error {
|
|
|
238
240
|
};
|
|
239
241
|
/** Bound on the firmware wake. Matches the cap's own sensible range. */
|
|
240
242
|
var WAKE_FOR_STREAM_TIMEOUT_MS = 8e3;
|
|
241
|
-
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Bound AFTER firmware acknowledgement for the provider to rebuild/publish
|
|
245
|
+
* stream parameters and the broker to observe them. Field cameras can
|
|
246
|
+
* acknowledge several seconds before their media/catalog plane is usable.
|
|
247
|
+
*/
|
|
248
|
+
var BROKER_SETTLE_TIMEOUT_MS = 2e4;
|
|
249
|
+
/** Reconcile cadence while the acknowledged camera finishes waking. */
|
|
250
|
+
var BROKER_SETTLE_POLL_INTERVAL_MS = 1500;
|
|
251
|
+
function wait(timeoutMs) {
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
setTimeout(resolve, timeoutMs).unref?.();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Device-scoped single-flight for wake-on-play. A wake applies to the whole
|
|
258
|
+
* camera, not one profile/brokerId, so concurrent profile requests and client
|
|
259
|
+
* retries all await the same bounded transaction.
|
|
260
|
+
*/
|
|
261
|
+
var BatteryWakeOnPlaySingleFlight = class {
|
|
262
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
263
|
+
async run(deviceId, deps) {
|
|
264
|
+
const existing = this.inFlight.get(deviceId);
|
|
265
|
+
if (existing) {
|
|
266
|
+
await existing;
|
|
267
|
+
if (!deps.hasBroker()) await deps.ensureBroker().catch((err) => {
|
|
268
|
+
deps.logger.debug("wake-on-play: joined demand broker retry failed", {
|
|
269
|
+
tags: { deviceId },
|
|
270
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
if (!deps.hasBroker()) throw new BatteryStreamUnavailableError(deviceId, "waking", "the shared wake completed but this stream profile is not available yet — try again");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const transaction = wakeBatteryDeviceForPlay(deviceId, deps);
|
|
277
|
+
this.inFlight.set(deviceId, transaction);
|
|
278
|
+
try {
|
|
279
|
+
await transaction;
|
|
280
|
+
} finally {
|
|
281
|
+
if (this.inFlight.get(deviceId) === transaction) this.inFlight.delete(deviceId);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
245
285
|
/**
|
|
246
286
|
* Make a broker available for a battery camera that has none, by asking the
|
|
247
287
|
* camera to wake.
|
|
@@ -252,26 +292,25 @@ var STREAM_PARAMS_SETTLE_TIMEOUT_MS = 6e3;
|
|
|
252
292
|
* {@link BatteryStreamUnavailableError} only for a battery camera we could not
|
|
253
293
|
* produce a stream for — with the reason named.
|
|
254
294
|
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* demand and gets a new wake.
|
|
295
|
+
* After an acknowledged wake, repeatedly reconciles the provider catalog and
|
|
296
|
+
* retries broker resolution within one field-sized bound. It never issues a
|
|
297
|
+
* second wake inside that transaction.
|
|
259
298
|
*/
|
|
260
299
|
async function wakeBatteryDeviceForPlay(deviceId, deps) {
|
|
261
300
|
if (deps.hasBroker()) return;
|
|
262
301
|
if (await deps.isBatteryDevice(deviceId).catch(() => null) !== true) return;
|
|
263
302
|
const facts = await deps.readBatteryFacts(deviceId).catch(() => null);
|
|
264
|
-
const
|
|
303
|
+
const observedAt = (deps.nowMs ?? Date.now)();
|
|
265
304
|
const presence = deriveBatteryPresence({
|
|
266
305
|
status: facts,
|
|
267
|
-
nowMs
|
|
306
|
+
nowMs: observedAt
|
|
268
307
|
});
|
|
269
308
|
if (presence === "unreachable") {
|
|
270
309
|
deps.logger.warn("wake-on-play refused — battery camera is unreachable, not asleep", {
|
|
271
310
|
tags: { deviceId },
|
|
272
311
|
meta: {
|
|
273
312
|
lastContactAt: facts?.lastContactAt ?? 0,
|
|
274
|
-
silenceMs:
|
|
313
|
+
silenceMs: observedAt - (facts?.lastContactAt ?? 0)
|
|
275
314
|
}
|
|
276
315
|
});
|
|
277
316
|
throw new BatteryStreamUnavailableError(deviceId, "unreachable", "nothing has been heard from it for hours — check the battery or its network");
|
|
@@ -287,29 +326,46 @@ async function wakeBatteryDeviceForPlay(deviceId, deps) {
|
|
|
287
326
|
awoke: false,
|
|
288
327
|
durationMs: 0
|
|
289
328
|
}));
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
329
|
+
const nowMs = deps.nowMs ?? Date.now;
|
|
330
|
+
const settleStartedAt = nowMs();
|
|
331
|
+
const settleDeadline = settleStartedAt + (wake.awoke ? BROKER_SETTLE_TIMEOUT_MS : 0);
|
|
332
|
+
const maxAttempts = wake.awoke ? Math.ceil(BROKER_SETTLE_TIMEOUT_MS / BROKER_SETTLE_POLL_INTERVAL_MS) + 1 : 1;
|
|
333
|
+
let attempts = 0;
|
|
334
|
+
while (attempts < maxAttempts) {
|
|
335
|
+
attempts += 1;
|
|
336
|
+
await deps.reconcileCatalog(deviceId).catch((err) => {
|
|
337
|
+
deps.logger.debug("wake-on-play: catalog reconcile failed", {
|
|
338
|
+
tags: { deviceId },
|
|
339
|
+
meta: {
|
|
340
|
+
attempt: attempts,
|
|
341
|
+
error: err instanceof Error ? err.message : String(err)
|
|
342
|
+
}
|
|
343
|
+
});
|
|
301
344
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
345
|
+
await deps.ensureBroker().catch((err) => {
|
|
346
|
+
deps.logger.debug("wake-on-play: broker retry failed", {
|
|
347
|
+
tags: { deviceId },
|
|
348
|
+
meta: {
|
|
349
|
+
attempt: attempts,
|
|
350
|
+
error: err instanceof Error ? err.message : String(err)
|
|
351
|
+
}
|
|
352
|
+
});
|
|
311
353
|
});
|
|
312
|
-
|
|
354
|
+
if (deps.hasBroker()) {
|
|
355
|
+
deps.logger.info("wake-on-play succeeded — broker available after wake", {
|
|
356
|
+
tags: { deviceId },
|
|
357
|
+
meta: {
|
|
358
|
+
awoke: wake.awoke,
|
|
359
|
+
wakeMs: wake.durationMs,
|
|
360
|
+
settleMs: nowMs() - settleStartedAt,
|
|
361
|
+
attempts
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const remainingMs = settleDeadline - nowMs();
|
|
367
|
+
if (remainingMs <= 0 || attempts >= maxAttempts) break;
|
|
368
|
+
await (deps.wait ?? wait)(Math.min(BROKER_SETTLE_POLL_INTERVAL_MS, remainingMs));
|
|
313
369
|
}
|
|
314
370
|
const outcome = wake.awoke ? "waking" : "sleeping";
|
|
315
371
|
deps.logger.warn("wake-on-play exhausted — no stream after the wake", {
|
|
@@ -317,7 +373,8 @@ async function wakeBatteryDeviceForPlay(deviceId, deps) {
|
|
|
317
373
|
meta: {
|
|
318
374
|
awoke: wake.awoke,
|
|
319
375
|
wakeMs: wake.durationMs,
|
|
320
|
-
|
|
376
|
+
settleMs: nowMs() - settleStartedAt,
|
|
377
|
+
attempts,
|
|
321
378
|
outcome
|
|
322
379
|
}
|
|
323
380
|
});
|
|
@@ -14396,6 +14453,13 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
14396
14453
|
*/
|
|
14397
14454
|
brokerEnsures = /* @__PURE__ */ new Map();
|
|
14398
14455
|
/**
|
|
14456
|
+
* Device-scoped wake-on-play transactions. A camera wake is shared by every
|
|
14457
|
+
* profile, so Viewer retries or concurrent sessions must join the existing
|
|
14458
|
+
* sleeping → waking → broker-settle transaction instead of issuing another
|
|
14459
|
+
* `battery.wakeForStream` mutation.
|
|
14460
|
+
*/
|
|
14461
|
+
batteryWakeOnPlay = new BatteryWakeOnPlaySingleFlight();
|
|
14462
|
+
/**
|
|
14399
14463
|
* brokerId → the profile tier a remote-ensured adapter was created for
|
|
14400
14464
|
* (Slice 3b Task 2). `this.assignments` only tracks LOCALLY-owned devices,
|
|
14401
14465
|
* so the `profileTierResolver` closure below always misses for a remote
|
|
@@ -15201,7 +15265,8 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15201
15265
|
/**
|
|
15202
15266
|
* Wake-on-play (D173). Called by `WebrtcSessionProvider` when the normal
|
|
15203
15267
|
* resolution left no broker: if the device is battery-operated, ask the
|
|
15204
|
-
* camera to wake,
|
|
15268
|
+
* camera to wake, then poll catalog reconciliation + broker resolution
|
|
15269
|
+
* through one bounded settle transaction.
|
|
15205
15270
|
*
|
|
15206
15271
|
* Throws `BatteryStreamUnavailableError` — naming `sleeping` / `waking` /
|
|
15207
15272
|
* `unreachable` — instead of letting `BrokerWebrtcServer` answer a healthy,
|
|
@@ -15214,7 +15279,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15214
15279
|
if (this.disabledDevices.has(deviceId)) return;
|
|
15215
15280
|
const api = this.api;
|
|
15216
15281
|
if (!api) return;
|
|
15217
|
-
await
|
|
15282
|
+
await this.batteryWakeOnPlay.run(deviceId, {
|
|
15218
15283
|
logger: this.logger,
|
|
15219
15284
|
isBatteryDevice: async (id) => {
|
|
15220
15285
|
const dev = await api.deviceManager.getDevice.query({ deviceId: id }).catch(() => null);
|
|
@@ -15237,38 +15302,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15237
15302
|
deviceId: id,
|
|
15238
15303
|
timeoutMs
|
|
15239
15304
|
}),
|
|
15240
|
-
awaitStreamParamsChanged: (id, timeoutMs) => this.awaitStreamParamsChanged(id, timeoutMs),
|
|
15241
15305
|
reconcileCatalog: (id) => this.reconcileDeviceCatalog(id),
|
|
15242
15306
|
ensureBroker: () => this.ensureBrokerForWebrtcSession(deviceId, brokerId, camStreamId),
|
|
15243
15307
|
hasBroker: () => this.hasRegisteredBroker(brokerId)
|
|
15244
15308
|
});
|
|
15245
15309
|
}
|
|
15246
15310
|
/**
|
|
15247
|
-
* Bounded wait for the provider's `StreamParamsChanged` nudge for one
|
|
15248
|
-
* device. Resolves `false` on timeout — a timeout is information, not a
|
|
15249
|
-
* failure: a catalog restored from the durable slice announces nothing, and
|
|
15250
|
-
* the caller reconciles either way.
|
|
15251
|
-
*/
|
|
15252
|
-
awaitStreamParamsChanged(deviceId, timeoutMs) {
|
|
15253
|
-
const bus = this.eventBus;
|
|
15254
|
-
if (!bus) return Promise.resolve(false);
|
|
15255
|
-
return new Promise((resolve) => {
|
|
15256
|
-
let settled = false;
|
|
15257
|
-
const finish = (value) => {
|
|
15258
|
-
if (settled) return;
|
|
15259
|
-
settled = true;
|
|
15260
|
-
clearTimeout(timer);
|
|
15261
|
-
unsubscribe();
|
|
15262
|
-
resolve(value);
|
|
15263
|
-
};
|
|
15264
|
-
const unsubscribe = bus.subscribe({ category: EventCategory.StreamParamsChanged }, (event) => {
|
|
15265
|
-
if (event.data.deviceId === deviceId) finish(true);
|
|
15266
|
-
});
|
|
15267
|
-
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
15268
|
-
timer.unref?.();
|
|
15269
|
-
});
|
|
15270
|
-
}
|
|
15271
|
-
/**
|
|
15272
15311
|
* Single-flight body for `ensureBrokerForWebrtcSession`'s remote-adapter
|
|
15273
15312
|
* get-or-create — resolves the ingest owner, and for `remote-adapter` mode
|
|
15274
15313
|
* constructs the `RemoteEncodedSourceBroker`, registers it with the WebRTC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.82",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|