@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.
@@ -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
- /** Bound on the post-wake catalog nudge. Sized off the camera's ~14s awake
247
- * window: long enough for a real `onWakeTransition` catalog build to land,
248
- * short enough that a viewer is not left staring at nothing. */
249
- var STREAM_PARAMS_SETTLE_TIMEOUT_MS = 6e3;
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
- * Retries the broker resolution exactly ONCE after the wake. A second wake
261
- * would be us deciding the camera should be up, which is the proactive
262
- * behaviour the contract forbids; the operator pressing play again is a new
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 nowMs = (deps.nowMs ?? Date.now)();
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: nowMs - (facts?.lastContactAt ?? 0)
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 nudged = await deps.awaitStreamParamsChanged(deviceId, STREAM_PARAMS_SETTLE_TIMEOUT_MS).catch(() => false);
296
- await deps.reconcileCatalog(deviceId).catch((err) => {
297
- deps.logger.debug("wake-on-play: catalog reconcile failed", {
298
- tags: { deviceId },
299
- meta: { error: err instanceof Error ? err.message : String(err) }
300
- });
301
- });
302
- await deps.ensureBroker().catch((err) => {
303
- deps.logger.debug("wake-on-play: broker retry failed", {
304
- tags: { deviceId },
305
- meta: { error: err instanceof Error ? err.message : String(err) }
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
- if (deps.hasBroker()) {
309
- deps.logger.info("wake-on-play succeeded broker available after wake", {
310
- tags: { deviceId },
311
- meta: {
312
- awoke: wake.awoke,
313
- wakeMs: wake.durationMs,
314
- nudged
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
- return;
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
- nudged,
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, re-pull its catalog, and retry the resolution ONCE.
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 wakeBatteryDeviceForPlay(deviceId, {
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
- /** Bound on the post-wake catalog nudge. Sized off the camera's ~14s awake
242
- * window: long enough for a real `onWakeTransition` catalog build to land,
243
- * short enough that a viewer is not left staring at nothing. */
244
- var STREAM_PARAMS_SETTLE_TIMEOUT_MS = 6e3;
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
- * Retries the broker resolution exactly ONCE after the wake. A second wake
256
- * would be us deciding the camera should be up, which is the proactive
257
- * behaviour the contract forbids; the operator pressing play again is a new
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 nowMs = (deps.nowMs ?? Date.now)();
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: nowMs - (facts?.lastContactAt ?? 0)
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 nudged = await deps.awaitStreamParamsChanged(deviceId, STREAM_PARAMS_SETTLE_TIMEOUT_MS).catch(() => false);
291
- await deps.reconcileCatalog(deviceId).catch((err) => {
292
- deps.logger.debug("wake-on-play: catalog reconcile failed", {
293
- tags: { deviceId },
294
- meta: { error: err instanceof Error ? err.message : String(err) }
295
- });
296
- });
297
- await deps.ensureBroker().catch((err) => {
298
- deps.logger.debug("wake-on-play: broker retry failed", {
299
- tags: { deviceId },
300
- meta: { error: err instanceof Error ? err.message : String(err) }
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
- if (deps.hasBroker()) {
304
- deps.logger.info("wake-on-play succeeded broker available after wake", {
305
- tags: { deviceId },
306
- meta: {
307
- awoke: wake.awoke,
308
- wakeMs: wake.durationMs,
309
- nudged
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
- return;
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
- nudged,
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, re-pull its catalog, and retry the resolution ONCE.
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 wakeBatteryDeviceForPlay(deviceId, {
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.81",
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",