@camstack/addon-pipeline 1.2.269 → 1.2.270

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.
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.226",
21
+ version: "1.2.227",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.226",
39
+ version: "1.2.227",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -13088,6 +13088,15 @@ var StreamBroker = class StreamBroker {
13088
13088
  * H.264 video is 30fps → 80pps minimum on a working stream) while a
13089
13089
  * real stall is detected before Chrome's ~15s ICE-disconnect timer
13090
13090
  * has any chance to fire. */
13091
+ /** How many times a resume re-asks the registry before it stops asking. */
13092
+ static RESUME_RETRY_MAX = 5;
13093
+ /** Spacing of those asks — long enough for a catalog reconcile to land. */
13094
+ static RESUME_RETRY_DELAY_MS = 3e3;
13095
+ resumeRetryTimer;
13096
+ resumeRetryAttempts = 0;
13097
+ resumeRetryExhausted = false;
13098
+ /** One line per suspension when packets arrive with no watchdog armed. */
13099
+ suspendedActivityNoted = false;
13091
13100
  static RTP_ACTIVITY_TIMEOUT_MS = 8e3;
13092
13101
  /**
13093
13102
  * How often the single sweep timer checks {@link lastRtpActivityAt}.
@@ -13598,6 +13607,54 @@ var StreamBroker = class StreamBroker {
13598
13607
  this.scheduleReconnect();
13599
13608
  }
13600
13609
  /**
13610
+ * Ask again for a source the registry did not have yet — bounded.
13611
+ *
13612
+ * Demand exists and nothing can be dialled, so waiting for the NEXT demand
13613
+ * edge is waiting for something that may never come: the consumer is already
13614
+ * attached. The retry is what turns "nothing happened" back into either a
13615
+ * stream or a stated failure, and the bound is what stops a camera that
13616
+ * genuinely publishes nothing from spinning for the life of the process.
13617
+ */
13618
+ scheduleResumeRetry() {
13619
+ if (this.resumeRetryTimer !== void 0) return;
13620
+ if (this.resumeRetryAttempts >= StreamBroker.RESUME_RETRY_MAX) {
13621
+ if (!this.resumeRetryExhausted) {
13622
+ this.resumeRetryExhausted = true;
13623
+ this.logger?.warn("demand is waiting and this broker still has no source — giving up until something changes", { meta: {
13624
+ brokerId: this.deviceId,
13625
+ attempts: this.resumeRetryAttempts,
13626
+ ...this.numericDeviceId === null ? {} : { deviceId: this.numericDeviceId }
13627
+ } });
13628
+ }
13629
+ return;
13630
+ }
13631
+ this.resumeRetryAttempts += 1;
13632
+ this.logger?.warn("demand detected but no source could be resolved — retrying", {
13633
+ ...this.numericDeviceId === null ? {} : { tags: { deviceId: this.numericDeviceId } },
13634
+ meta: {
13635
+ brokerId: this.deviceId,
13636
+ attempt: this.resumeRetryAttempts,
13637
+ ofMax: StreamBroker.RESUME_RETRY_MAX
13638
+ }
13639
+ });
13640
+ this.resumeRetryTimer = setTimeout(() => {
13641
+ this.resumeRetryTimer = void 0;
13642
+ if (this.manualStop || this.stopping || !this._suspended) return;
13643
+ if (!this.hasLiveIntent()) return;
13644
+ this.checkDemand();
13645
+ }, StreamBroker.RESUME_RETRY_DELAY_MS);
13646
+ this.resumeRetryTimer.unref?.();
13647
+ }
13648
+ /** A resume that SUCCEEDED clears the episode, counter included. */
13649
+ cancelResumeRetry() {
13650
+ if (this.resumeRetryTimer !== void 0) {
13651
+ clearTimeout(this.resumeRetryTimer);
13652
+ this.resumeRetryTimer = void 0;
13653
+ }
13654
+ this.resumeRetryAttempts = 0;
13655
+ this.resumeRetryExhausted = false;
13656
+ }
13657
+ /**
13601
13658
  * Resolve the source to use for the next dial attempt. Prefers the
13602
13659
  * manager-supplied resolver — re-derives from the cam-stream
13603
13660
  * registry, so a re-publish that updated the URL is honoured on the
@@ -14106,6 +14163,7 @@ var StreamBroker = class StreamBroker {
14106
14163
  if (this._suspended) {
14107
14164
  const fresh = this.resolveCurrentSource();
14108
14165
  if (fresh) {
14166
+ this.cancelResumeRetry();
14109
14167
  this._suspended = false;
14110
14168
  this.logger?.info("demand detected — resuming stream");
14111
14169
  this.start(fresh).catch((err) => {
@@ -14113,7 +14171,7 @@ var StreamBroker = class StreamBroker {
14113
14171
  this._suspended = true;
14114
14172
  this._status = "idle";
14115
14173
  });
14116
- }
14174
+ } else this.scheduleResumeRetry();
14117
14175
  }
14118
14176
  } else if (!this._suspended) {
14119
14177
  if (!this.suspendTimer) this.suspendTimer = setTimeout(() => {
@@ -15813,7 +15871,20 @@ var StreamBroker = class StreamBroker {
15813
15871
  * operations on the highest-frequency path in the busiest process.
15814
15872
  */
15815
15873
  kickRtpActivityWatchdog() {
15816
- if (this.manualStop || this.stopping || this._suspended) return;
15874
+ if (this.manualStop || this.stopping || this._suspended) {
15875
+ if (this._suspended && !this.suspendedActivityNoted) {
15876
+ this.suspendedActivityNoted = true;
15877
+ this.logger?.warn("source RTP is arriving while this broker is suspended — the stall watchdog is NOT armed for it", {
15878
+ ...this.numericDeviceId === null ? {} : { tags: { deviceId: this.numericDeviceId } },
15879
+ meta: {
15880
+ brokerId: this.deviceId,
15881
+ status: this._status
15882
+ }
15883
+ });
15884
+ }
15885
+ return;
15886
+ }
15887
+ this.suspendedActivityNoted = false;
15817
15888
  this.lastRtpActivityAt = Date.now();
15818
15889
  if (this.rtpActivitySweep !== void 0) return;
15819
15890
  this.rtpActivitySweep = setInterval(() => {
@@ -15853,7 +15924,17 @@ var StreamBroker = class StreamBroker {
15853
15924
  */
15854
15925
  onRtpActivityWatchdogExpired() {
15855
15926
  this.rtpActivityWatchdogFiredCount++;
15856
- if (this.manualStop || this.stopping || this._suspended) return;
15927
+ if (this.manualStop || this.stopping || this._suspended) {
15928
+ if (this._suspended) this.logger?.warn("source RTP went silent on a suspended broker — the watchdog is disarmed and nothing will reconnect it", {
15929
+ ...this.numericDeviceId === null ? {} : { tags: { deviceId: this.numericDeviceId } },
15930
+ meta: {
15931
+ brokerId: this.deviceId,
15932
+ timeoutMs: StreamBroker.RTP_ACTIVITY_TIMEOUT_MS,
15933
+ firedCount: this.rtpActivityWatchdogFiredCount
15934
+ }
15935
+ });
15936
+ return;
15937
+ }
15857
15938
  const meta = {
15858
15939
  timeoutMs: StreamBroker.RTP_ACTIVITY_TIMEOUT_MS,
15859
15940
  ...this.sourceMetaForLog()
@@ -13084,6 +13084,15 @@ var StreamBroker = class StreamBroker {
13084
13084
  * H.264 video is 30fps → 80pps minimum on a working stream) while a
13085
13085
  * real stall is detected before Chrome's ~15s ICE-disconnect timer
13086
13086
  * has any chance to fire. */
13087
+ /** How many times a resume re-asks the registry before it stops asking. */
13088
+ static RESUME_RETRY_MAX = 5;
13089
+ /** Spacing of those asks — long enough for a catalog reconcile to land. */
13090
+ static RESUME_RETRY_DELAY_MS = 3e3;
13091
+ resumeRetryTimer;
13092
+ resumeRetryAttempts = 0;
13093
+ resumeRetryExhausted = false;
13094
+ /** One line per suspension when packets arrive with no watchdog armed. */
13095
+ suspendedActivityNoted = false;
13087
13096
  static RTP_ACTIVITY_TIMEOUT_MS = 8e3;
13088
13097
  /**
13089
13098
  * How often the single sweep timer checks {@link lastRtpActivityAt}.
@@ -13594,6 +13603,54 @@ var StreamBroker = class StreamBroker {
13594
13603
  this.scheduleReconnect();
13595
13604
  }
13596
13605
  /**
13606
+ * Ask again for a source the registry did not have yet — bounded.
13607
+ *
13608
+ * Demand exists and nothing can be dialled, so waiting for the NEXT demand
13609
+ * edge is waiting for something that may never come: the consumer is already
13610
+ * attached. The retry is what turns "nothing happened" back into either a
13611
+ * stream or a stated failure, and the bound is what stops a camera that
13612
+ * genuinely publishes nothing from spinning for the life of the process.
13613
+ */
13614
+ scheduleResumeRetry() {
13615
+ if (this.resumeRetryTimer !== void 0) return;
13616
+ if (this.resumeRetryAttempts >= StreamBroker.RESUME_RETRY_MAX) {
13617
+ if (!this.resumeRetryExhausted) {
13618
+ this.resumeRetryExhausted = true;
13619
+ this.logger?.warn("demand is waiting and this broker still has no source — giving up until something changes", { meta: {
13620
+ brokerId: this.deviceId,
13621
+ attempts: this.resumeRetryAttempts,
13622
+ ...this.numericDeviceId === null ? {} : { deviceId: this.numericDeviceId }
13623
+ } });
13624
+ }
13625
+ return;
13626
+ }
13627
+ this.resumeRetryAttempts += 1;
13628
+ this.logger?.warn("demand detected but no source could be resolved — retrying", {
13629
+ ...this.numericDeviceId === null ? {} : { tags: { deviceId: this.numericDeviceId } },
13630
+ meta: {
13631
+ brokerId: this.deviceId,
13632
+ attempt: this.resumeRetryAttempts,
13633
+ ofMax: StreamBroker.RESUME_RETRY_MAX
13634
+ }
13635
+ });
13636
+ this.resumeRetryTimer = setTimeout(() => {
13637
+ this.resumeRetryTimer = void 0;
13638
+ if (this.manualStop || this.stopping || !this._suspended) return;
13639
+ if (!this.hasLiveIntent()) return;
13640
+ this.checkDemand();
13641
+ }, StreamBroker.RESUME_RETRY_DELAY_MS);
13642
+ this.resumeRetryTimer.unref?.();
13643
+ }
13644
+ /** A resume that SUCCEEDED clears the episode, counter included. */
13645
+ cancelResumeRetry() {
13646
+ if (this.resumeRetryTimer !== void 0) {
13647
+ clearTimeout(this.resumeRetryTimer);
13648
+ this.resumeRetryTimer = void 0;
13649
+ }
13650
+ this.resumeRetryAttempts = 0;
13651
+ this.resumeRetryExhausted = false;
13652
+ }
13653
+ /**
13597
13654
  * Resolve the source to use for the next dial attempt. Prefers the
13598
13655
  * manager-supplied resolver — re-derives from the cam-stream
13599
13656
  * registry, so a re-publish that updated the URL is honoured on the
@@ -14102,6 +14159,7 @@ var StreamBroker = class StreamBroker {
14102
14159
  if (this._suspended) {
14103
14160
  const fresh = this.resolveCurrentSource();
14104
14161
  if (fresh) {
14162
+ this.cancelResumeRetry();
14105
14163
  this._suspended = false;
14106
14164
  this.logger?.info("demand detected — resuming stream");
14107
14165
  this.start(fresh).catch((err) => {
@@ -14109,7 +14167,7 @@ var StreamBroker = class StreamBroker {
14109
14167
  this._suspended = true;
14110
14168
  this._status = "idle";
14111
14169
  });
14112
- }
14170
+ } else this.scheduleResumeRetry();
14113
14171
  }
14114
14172
  } else if (!this._suspended) {
14115
14173
  if (!this.suspendTimer) this.suspendTimer = setTimeout(() => {
@@ -15809,7 +15867,20 @@ var StreamBroker = class StreamBroker {
15809
15867
  * operations on the highest-frequency path in the busiest process.
15810
15868
  */
15811
15869
  kickRtpActivityWatchdog() {
15812
- if (this.manualStop || this.stopping || this._suspended) return;
15870
+ if (this.manualStop || this.stopping || this._suspended) {
15871
+ if (this._suspended && !this.suspendedActivityNoted) {
15872
+ this.suspendedActivityNoted = true;
15873
+ this.logger?.warn("source RTP is arriving while this broker is suspended — the stall watchdog is NOT armed for it", {
15874
+ ...this.numericDeviceId === null ? {} : { tags: { deviceId: this.numericDeviceId } },
15875
+ meta: {
15876
+ brokerId: this.deviceId,
15877
+ status: this._status
15878
+ }
15879
+ });
15880
+ }
15881
+ return;
15882
+ }
15883
+ this.suspendedActivityNoted = false;
15813
15884
  this.lastRtpActivityAt = Date.now();
15814
15885
  if (this.rtpActivitySweep !== void 0) return;
15815
15886
  this.rtpActivitySweep = setInterval(() => {
@@ -15849,7 +15920,17 @@ var StreamBroker = class StreamBroker {
15849
15920
  */
15850
15921
  onRtpActivityWatchdogExpired() {
15851
15922
  this.rtpActivityWatchdogFiredCount++;
15852
- if (this.manualStop || this.stopping || this._suspended) return;
15923
+ if (this.manualStop || this.stopping || this._suspended) {
15924
+ if (this._suspended) this.logger?.warn("source RTP went silent on a suspended broker — the watchdog is disarmed and nothing will reconnect it", {
15925
+ ...this.numericDeviceId === null ? {} : { tags: { deviceId: this.numericDeviceId } },
15926
+ meta: {
15927
+ brokerId: this.deviceId,
15928
+ timeoutMs: StreamBroker.RTP_ACTIVITY_TIMEOUT_MS,
15929
+ firedCount: this.rtpActivityWatchdogFiredCount
15930
+ }
15931
+ });
15932
+ return;
15933
+ }
15853
15934
  const meta = {
15854
15935
  timeoutMs: StreamBroker.RTP_ACTIVITY_TIMEOUT_MS,
15855
15936
  ...this.sourceMetaForLog()
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CbUvu4Jn.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DZlxPoTV.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.269",
3
+ "version": "1.2.270",
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",