@camstack/addon-pipeline 1.2.102 → 1.2.103

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.
@@ -25455,15 +25455,32 @@ var TimelineSession = class {
25455
25455
  /**
25456
25456
  * The profile this session will actually play, given the one asked for.
25457
25457
  *
25458
- * A segment covering `epochMs` wins outright. Failing that the answer is the
25459
- * first profile the camera DEMONSTRABLY records a gap carrying a
25460
- * `nearestEdgeMs` means "footage exists on this tier, just not here", which is
25461
- * exactly what the feeder's snap-to-nearest needs. Falling back to `requested`
25462
- * when nothing has footage is only correct as a last resort: the session
25463
- * default is `mid`, and no camera on this cluster records `mid`, so returning
25464
- * it for a tap into a gap left the feeder with nothing to snap to and turned a
25465
- * recoverable crack into `seek-no-footage` the operator sees "no recording"
25466
- * on a camera that recorded seconds either side.
25458
+ * **The requested tier wins whenever it has footage AT ALL** — a segment
25459
+ * covering `epochMs`, or a gap carrying a `nearestEdgeMs`, which means
25460
+ * "footage exists on this tier, just not here" and is exactly what the
25461
+ * feeder's snap-to-nearest needs. Only a tier that records NOTHING is
25462
+ * abandoned, and then the answer is the first tier that demonstrably does.
25463
+ * Falling back to `requested` when nothing has footage anywhere is the last
25464
+ * resort: the session default is `mid`, and no camera on this cluster records
25465
+ * `mid`, so returning it for a tap into a gap left the feeder with nothing to
25466
+ * snap to and turned a recoverable crack into `seek-no-footage` the
25467
+ * operator sees "no recording" on a camera that recorded seconds either side.
25468
+ *
25469
+ * The requested tier is checked to EXHAUSTION before any other is consulted,
25470
+ * and that ordering is load-bearing twice over:
25471
+ *
25472
+ * 1. The scrub is pinned to `low` by the client, and every D163 pre-cache
25473
+ * behaviour is gated on `deps.profile === 'low'` by exact string equality
25474
+ * — the 90 s/30 s entry warm, the whole-segment read, and the 24 MB
25475
+ * fragment budget (2 MB for anything else). `profileSearchOrder` puts
25476
+ * `high` BEFORE `low`, so a momentary `low` gap used to hand the drag a
25477
+ * `high` feeder and silently turn all three off. The client cannot repair
25478
+ * that: it re-sends `setProfile` only when its own `lastSentProfile`
25479
+ * mirror disagrees, and the mirror never learns the server moved.
25480
+ * 2. `seek` and `scrubCommit` route through `playAt`, so every finger-release
25481
+ * pays this loop. locate is a cap RPC (1-8 s on cold footage); walking the
25482
+ * ladder to re-confirm a pin the caller already holds is latency the
25483
+ * operator feels as a slow commit.
25467
25484
  */
25468
25485
  async resolveProfile(requested, epochMs) {
25469
25486
  const locate = this.deps.locate;
@@ -25481,7 +25498,10 @@ var TimelineSession = class {
25481
25498
  continue;
25482
25499
  }
25483
25500
  if (loc.kind === "segment") return profile;
25484
- if (withFootage === null && loc.nearestEdgeMs !== null) withFootage = profile;
25501
+ if (withFootage === null && loc.nearestEdgeMs !== null) {
25502
+ if (profile === requested) return profile;
25503
+ withFootage = profile;
25504
+ }
25485
25505
  }
25486
25506
  return withFootage ?? requested;
25487
25507
  }
@@ -25450,15 +25450,32 @@ var TimelineSession = class {
25450
25450
  /**
25451
25451
  * The profile this session will actually play, given the one asked for.
25452
25452
  *
25453
- * A segment covering `epochMs` wins outright. Failing that the answer is the
25454
- * first profile the camera DEMONSTRABLY records a gap carrying a
25455
- * `nearestEdgeMs` means "footage exists on this tier, just not here", which is
25456
- * exactly what the feeder's snap-to-nearest needs. Falling back to `requested`
25457
- * when nothing has footage is only correct as a last resort: the session
25458
- * default is `mid`, and no camera on this cluster records `mid`, so returning
25459
- * it for a tap into a gap left the feeder with nothing to snap to and turned a
25460
- * recoverable crack into `seek-no-footage` the operator sees "no recording"
25461
- * on a camera that recorded seconds either side.
25453
+ * **The requested tier wins whenever it has footage AT ALL** — a segment
25454
+ * covering `epochMs`, or a gap carrying a `nearestEdgeMs`, which means
25455
+ * "footage exists on this tier, just not here" and is exactly what the
25456
+ * feeder's snap-to-nearest needs. Only a tier that records NOTHING is
25457
+ * abandoned, and then the answer is the first tier that demonstrably does.
25458
+ * Falling back to `requested` when nothing has footage anywhere is the last
25459
+ * resort: the session default is `mid`, and no camera on this cluster records
25460
+ * `mid`, so returning it for a tap into a gap left the feeder with nothing to
25461
+ * snap to and turned a recoverable crack into `seek-no-footage` the
25462
+ * operator sees "no recording" on a camera that recorded seconds either side.
25463
+ *
25464
+ * The requested tier is checked to EXHAUSTION before any other is consulted,
25465
+ * and that ordering is load-bearing twice over:
25466
+ *
25467
+ * 1. The scrub is pinned to `low` by the client, and every D163 pre-cache
25468
+ * behaviour is gated on `deps.profile === 'low'` by exact string equality
25469
+ * — the 90 s/30 s entry warm, the whole-segment read, and the 24 MB
25470
+ * fragment budget (2 MB for anything else). `profileSearchOrder` puts
25471
+ * `high` BEFORE `low`, so a momentary `low` gap used to hand the drag a
25472
+ * `high` feeder and silently turn all three off. The client cannot repair
25473
+ * that: it re-sends `setProfile` only when its own `lastSentProfile`
25474
+ * mirror disagrees, and the mirror never learns the server moved.
25475
+ * 2. `seek` and `scrubCommit` route through `playAt`, so every finger-release
25476
+ * pays this loop. locate is a cap RPC (1-8 s on cold footage); walking the
25477
+ * ladder to re-confirm a pin the caller already holds is latency the
25478
+ * operator feels as a slow commit.
25462
25479
  */
25463
25480
  async resolveProfile(requested, epochMs) {
25464
25481
  const locate = this.deps.locate;
@@ -25476,7 +25493,10 @@ var TimelineSession = class {
25476
25493
  continue;
25477
25494
  }
25478
25495
  if (loc.kind === "segment") return profile;
25479
- if (withFootage === null && loc.nearestEdgeMs !== null) withFootage = profile;
25496
+ if (withFootage === null && loc.nearestEdgeMs !== null) {
25497
+ if (profile === requested) return profile;
25498
+ withFootage = profile;
25499
+ }
25480
25500
  }
25481
25501
  return withFootage ?? requested;
25482
25502
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.102",
3
+ "version": "1.2.103",
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",