@camstack/addon-pipeline 1.2.236 → 1.2.238

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.
@@ -5791,19 +5791,27 @@ var PipelineRunner = class {
5791
5791
  if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
5792
5792
  });
5793
5793
  const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
5794
+ const preRollQueue = new PreRollLane({
5795
+ capacity: this.config.maxPreRollQueueDepth ?? 12,
5796
+ clone: ownDetectionEntry,
5797
+ dispose: (entry) => {
5798
+ if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
5799
+ if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
5800
+ }
5801
+ });
5802
+ this.logger?.info("camera registered: root detectors", {
5803
+ tags: { deviceId },
5804
+ meta: {
5805
+ detectionSources: registration.detectionSources ?? null,
5806
+ ...planDetectionSources(registration.detectionSources)
5807
+ }
5808
+ });
5794
5809
  const state = {
5795
5810
  registration,
5796
5811
  onboardSlot: null,
5797
5812
  motionQueue,
5798
5813
  detectionQueue,
5799
- preRollQueue: new PreRollLane({
5800
- capacity: this.config.maxPreRollQueueDepth ?? 12,
5801
- clone: ownDetectionEntry,
5802
- dispose: (entry) => {
5803
- if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
5804
- if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
5805
- }
5806
- }),
5814
+ preRollQueue,
5807
5815
  inferenceTimes: [],
5808
5816
  shedFrames: 0,
5809
5817
  inflight: 0,
@@ -5783,19 +5783,27 @@ var PipelineRunner = class {
5783
5783
  if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
5784
5784
  });
5785
5785
  const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
5786
+ const preRollQueue = new PreRollLane({
5787
+ capacity: this.config.maxPreRollQueueDepth ?? 12,
5788
+ clone: ownDetectionEntry,
5789
+ dispose: (entry) => {
5790
+ if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
5791
+ if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
5792
+ }
5793
+ });
5794
+ this.logger?.info("camera registered: root detectors", {
5795
+ tags: { deviceId },
5796
+ meta: {
5797
+ detectionSources: registration.detectionSources ?? null,
5798
+ ...planDetectionSources(registration.detectionSources)
5799
+ }
5800
+ });
5786
5801
  const state = {
5787
5802
  registration,
5788
5803
  onboardSlot: null,
5789
5804
  motionQueue,
5790
5805
  detectionQueue,
5791
- preRollQueue: new PreRollLane({
5792
- capacity: this.config.maxPreRollQueueDepth ?? 12,
5793
- clone: ownDetectionEntry,
5794
- dispose: (entry) => {
5795
- if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
5796
- if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
5797
- }
5798
- }),
5806
+ preRollQueue,
5799
5807
  inferenceTimes: [],
5800
5808
  shedFrames: 0,
5801
5809
  inflight: 0,
@@ -12107,33 +12107,6 @@ var PrestreamPump = class {
12107
12107
  });
12108
12108
  }
12109
12109
  /**
12110
- * Prepare the picture NOW, so the next wake does not have to.
12111
- *
12112
- * The window is ~6 s wide and the preparation was measured at 2.5-5.5 s of
12113
- * it (Baby monitor, 2026-09-14) — the placeholder was ready with under a
12114
- * second left and emitted ONE frame, which is indistinguishable from the
12115
- * prestream not working, and is how the operator reported it. The encode
12116
- * cache upstream was built for exactly this and missed every time: it is
12117
- * keyed on the snapshot's digest, and a fresh snapshot is captured during
12118
- * every stream window, so the key changed on every wake.
12119
- *
12120
- * Called when the camera is ALREADY awake and nobody is waiting (the broker
12121
- * suspends), which is also the moment its snapshot is the freshest it will
12122
- * be until the next wake — so the digest that gets cached is the digest the
12123
- * next wake will compute. It opens NO window, emits nothing and arms no
12124
- * timer: the only effect it wants is the one it has upstream, a warm cache.
12125
- *
12126
- * A failure is swallowed on purpose. This is an optimisation running on
12127
- * nobody's behalf; reporting it as a fault would put a warn line on every
12128
- * camera that sleeps with a busy encoder.
12129
- */
12130
- warm(sourceUrl) {
12131
- if (this.active) return;
12132
- this.options.prepare({ sourceUrl }).then(() => {
12133
- this.options.logger?.debug("prestream: picture warmed for the next wake", { ...this.tags });
12134
- }).catch(() => {});
12135
- }
12136
- /**
12137
12110
  * Close the window. Called on the first NON-placeholder packet — the one edge
12138
12111
  * the prestream is defined by — and on the broker's own teardown paths so a
12139
12112
  * stopped broker leaves no timer behind.
@@ -13603,8 +13576,6 @@ var StreamBroker = class StreamBroker {
13603
13576
  this.placeholderTimer = void 0;
13604
13577
  }
13605
13578
  this.prestreamPump?.stop("broker suspended");
13606
- const warmUrl = this.source?.url;
13607
- if (warmUrl !== void 0 && warmUrl.length > 0) this.prestreamPump?.warm(warmUrl);
13608
13579
  this.prestreamWindowClosed = false;
13609
13580
  this.preBuffer.clear();
13610
13581
  this.decoderSeedBuffer.clear();
@@ -16315,6 +16286,8 @@ var StreamBrokerManager = class StreamBrokerManager {
16315
16286
  * was made from. One entry is one still image — kilobytes — and it is
16316
16287
  * dropped with the broker in `releaseBroker`.
16317
16288
  */
16289
+ /** Brokers with a background prestream re-encode in flight (single-flight). */
16290
+ prestreamRefreshing = /* @__PURE__ */ new Set();
16318
16291
  prestreamFrames = /* @__PURE__ */ new Map();
16319
16292
  /** Monotonic insertion counter per device — feeds `CameraStreamInternal.order`. */
16320
16293
  streamOrderSeq = /* @__PURE__ */ new Map();
@@ -17307,6 +17280,26 @@ var StreamBrokerManager = class StreamBrokerManager {
17307
17280
  });
17308
17281
  return cached.frame;
17309
17282
  }
17283
+ const cachedGeometry = cached ? cached.key.slice(cached.key.indexOf(":")) : null;
17284
+ const wantedGeometry = key.slice(key.indexOf(":"));
17285
+ if (cached && cachedGeometry === wantedGeometry) {
17286
+ logger.info("prestream: serving the previous picture while the new one encodes", {
17287
+ tags: { deviceId },
17288
+ meta: { bytes: cached.frame.annexB.length }
17289
+ });
17290
+ this.encodePrestreamInBackground(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger);
17291
+ return cached.frame;
17292
+ }
17293
+ return await this.composeAndEncodePrestream(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger);
17294
+ }
17295
+ /**
17296
+ * Compose the treatment and encode ONE IDR, then cache it under `key`.
17297
+ *
17298
+ * Split out so the wake path and the background refresh share it byte for
17299
+ * byte — two encoders that drifted apart would put two different pictures in
17300
+ * front of the operator depending on which wake they caught.
17301
+ */
17302
+ async composeAndEncodePrestream(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger) {
17310
17303
  const picture = await composePrestreamPicture({
17311
17304
  cachedJpeg,
17312
17305
  deviceName,
@@ -17350,6 +17343,20 @@ var StreamBrokerManager = class StreamBrokerManager {
17350
17343
  });
17351
17344
  return frame;
17352
17345
  }
17346
+ /**
17347
+ * Encode the CURRENT picture for the NEXT wake, off the wake path.
17348
+ *
17349
+ * Single-flight per broker: a camera that wakes twice inside one encode must
17350
+ * not spawn a second ffmpeg — that is CPU taken from the live stream the
17351
+ * operator is actually waiting for.
17352
+ */
17353
+ encodePrestreamInBackground(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger) {
17354
+ if (this.prestreamRefreshing.has(brokerId)) return;
17355
+ this.prestreamRefreshing.add(brokerId);
17356
+ this.composeAndEncodePrestream(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger).catch(() => {}).finally(() => {
17357
+ this.prestreamRefreshing.delete(brokerId);
17358
+ });
17359
+ }
17353
17360
  subscribePlaceholderStateSources(bus) {
17354
17361
  bus.subscribe({ category: require_dist.EventCategory.BatteryOnStatusChanged }, (event) => {
17355
17362
  const data = event.data;
@@ -12103,33 +12103,6 @@ var PrestreamPump = class {
12103
12103
  });
12104
12104
  }
12105
12105
  /**
12106
- * Prepare the picture NOW, so the next wake does not have to.
12107
- *
12108
- * The window is ~6 s wide and the preparation was measured at 2.5-5.5 s of
12109
- * it (Baby monitor, 2026-09-14) — the placeholder was ready with under a
12110
- * second left and emitted ONE frame, which is indistinguishable from the
12111
- * prestream not working, and is how the operator reported it. The encode
12112
- * cache upstream was built for exactly this and missed every time: it is
12113
- * keyed on the snapshot's digest, and a fresh snapshot is captured during
12114
- * every stream window, so the key changed on every wake.
12115
- *
12116
- * Called when the camera is ALREADY awake and nobody is waiting (the broker
12117
- * suspends), which is also the moment its snapshot is the freshest it will
12118
- * be until the next wake — so the digest that gets cached is the digest the
12119
- * next wake will compute. It opens NO window, emits nothing and arms no
12120
- * timer: the only effect it wants is the one it has upstream, a warm cache.
12121
- *
12122
- * A failure is swallowed on purpose. This is an optimisation running on
12123
- * nobody's behalf; reporting it as a fault would put a warn line on every
12124
- * camera that sleeps with a busy encoder.
12125
- */
12126
- warm(sourceUrl) {
12127
- if (this.active) return;
12128
- this.options.prepare({ sourceUrl }).then(() => {
12129
- this.options.logger?.debug("prestream: picture warmed for the next wake", { ...this.tags });
12130
- }).catch(() => {});
12131
- }
12132
- /**
12133
12106
  * Close the window. Called on the first NON-placeholder packet — the one edge
12134
12107
  * the prestream is defined by — and on the broker's own teardown paths so a
12135
12108
  * stopped broker leaves no timer behind.
@@ -13599,8 +13572,6 @@ var StreamBroker = class StreamBroker {
13599
13572
  this.placeholderTimer = void 0;
13600
13573
  }
13601
13574
  this.prestreamPump?.stop("broker suspended");
13602
- const warmUrl = this.source?.url;
13603
- if (warmUrl !== void 0 && warmUrl.length > 0) this.prestreamPump?.warm(warmUrl);
13604
13575
  this.prestreamWindowClosed = false;
13605
13576
  this.preBuffer.clear();
13606
13577
  this.decoderSeedBuffer.clear();
@@ -16311,6 +16282,8 @@ var StreamBrokerManager = class StreamBrokerManager {
16311
16282
  * was made from. One entry is one still image — kilobytes — and it is
16312
16283
  * dropped with the broker in `releaseBroker`.
16313
16284
  */
16285
+ /** Brokers with a background prestream re-encode in flight (single-flight). */
16286
+ prestreamRefreshing = /* @__PURE__ */ new Set();
16314
16287
  prestreamFrames = /* @__PURE__ */ new Map();
16315
16288
  /** Monotonic insertion counter per device — feeds `CameraStreamInternal.order`. */
16316
16289
  streamOrderSeq = /* @__PURE__ */ new Map();
@@ -17303,6 +17276,26 @@ var StreamBrokerManager = class StreamBrokerManager {
17303
17276
  });
17304
17277
  return cached.frame;
17305
17278
  }
17279
+ const cachedGeometry = cached ? cached.key.slice(cached.key.indexOf(":")) : null;
17280
+ const wantedGeometry = key.slice(key.indexOf(":"));
17281
+ if (cached && cachedGeometry === wantedGeometry) {
17282
+ logger.info("prestream: serving the previous picture while the new one encodes", {
17283
+ tags: { deviceId },
17284
+ meta: { bytes: cached.frame.annexB.length }
17285
+ });
17286
+ this.encodePrestreamInBackground(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger);
17287
+ return cached.frame;
17288
+ }
17289
+ return await this.composeAndEncodePrestream(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger);
17290
+ }
17291
+ /**
17292
+ * Compose the treatment and encode ONE IDR, then cache it under `key`.
17293
+ *
17294
+ * Split out so the wake path and the background refresh share it byte for
17295
+ * byte — two encoders that drifted apart would put two different pictures in
17296
+ * front of the operator depending on which wake they caught.
17297
+ */
17298
+ async composeAndEncodePrestream(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger) {
17306
17299
  const picture = await composePrestreamPicture({
17307
17300
  cachedJpeg,
17308
17301
  deviceName,
@@ -17346,6 +17339,20 @@ var StreamBrokerManager = class StreamBrokerManager {
17346
17339
  });
17347
17340
  return frame;
17348
17341
  }
17342
+ /**
17343
+ * Encode the CURRENT picture for the NEXT wake, off the wake path.
17344
+ *
17345
+ * Single-flight per broker: a camera that wakes twice inside one encode must
17346
+ * not spawn a second ffmpeg — that is CPU taken from the live stream the
17347
+ * operator is actually waiting for.
17348
+ */
17349
+ encodePrestreamInBackground(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger) {
17350
+ if (this.prestreamRefreshing.has(brokerId)) return;
17351
+ this.prestreamRefreshing.add(brokerId);
17352
+ this.composeAndEncodePrestream(deviceId, brokerId, plan, key, cachedJpeg, deviceName, logger).catch(() => {}).finally(() => {
17353
+ this.prestreamRefreshing.delete(brokerId);
17354
+ });
17355
+ }
17349
17356
  subscribePlaceholderStateSources(bus) {
17350
17357
  bus.subscribe({ category: EventCategory.BatteryOnStatusChanged }, (event) => {
17351
17358
  const data = event.data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.236",
3
+ "version": "1.2.238",
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",