senza-sdk 4.4.9 → 4.4.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -17,9 +17,9 @@ import { DEFAULT_REMOTE_PLAYER_CONFIRMATION_TIMEOUT, remotePlayer } from "./remo
17
17
  import {bus, Events} from "./eventBus";
18
18
 
19
19
  // Default values for autoBackground settings. These values are used if the UIStreamer settings are not provided.
20
- const DEFAULT_AUTO_BACKGROUND_VIDEO_DELAY = 10*60;
21
- const DEFAULT_AUTO_BACKGROUND_UI_DELAY = 10*60;
22
- const DEFAULT_AUTO_BACKGROUND_ENABLED = true;
20
+ export const DEFAULT_AUTO_BACKGROUND_VIDEO_DELAY = 10*60;
21
+ export const DEFAULT_AUTO_BACKGROUND_UI_DELAY = 10*60;
22
+ export const DEFAULT_AUTO_BACKGROUND_ENABLED = true;
23
23
  // Default values for autoSuspend settings
24
24
  const DEFAULT_AUTO_SUSPEND_ENABLED = false;
25
25
  const DEFAULT_AUTO_SUSPEND_PLAYING_DELAY = 60;
@@ -101,12 +101,6 @@ class Lifecycle extends LifecycleInterface {
101
101
  */
102
102
  this._backgroundTimestamp = Date.now();
103
103
 
104
- /**
105
- * Flag to track if suspend was triggered by timer
106
- * @type {boolean}
107
- * @private
108
- */
109
- this._isSuspendTriggeredByTimer = false;
110
104
 
111
105
  this._isInTransitionToSuspended = false;
112
106
 
@@ -266,6 +260,21 @@ class Lifecycle extends LifecycleInterface {
266
260
  autoSuspend: uiStreamerSettings.autoSuspend
267
261
  });
268
262
  }
263
+ this._timersInit();
264
+ }
265
+ }
266
+
267
+ /**
268
+ * @private initialize timers based on current configuration
269
+ */
270
+ _timersInit() {
271
+ // Start background countdown if auto background is enabled and in foreground
272
+ if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
273
+ this._startCountdown();
274
+ }
275
+ // start suspend countdown if auto suspend is enabled and in background
276
+ if (this._isAutoSuspendEnabled() && this._state === this.UiState.BACKGROUND) {
277
+ this._startSuspendCountdown();
269
278
  }
270
279
  }
271
280
 
@@ -321,7 +330,7 @@ class Lifecycle extends LifecycleInterface {
321
330
  if (this._isBackgroundTriggeredByTimer && (this.state === this.UiState.BACKGROUND ||
322
331
  this.state === this.UiState.IN_TRANSITION_TO_BACKGROUND)) {
323
332
  sdkLogger.log("Moving to foreground due to user interaction after auto background");
324
- this.moveToForeground();
333
+ this.moveToForeground(true);
325
334
  } else {
326
335
  this._startCountdown();
327
336
  }
@@ -602,25 +611,9 @@ class Lifecycle extends LifecycleInterface {
602
611
  // Only start countdown if delay is positive
603
612
  if (timeoutDelay > 0) {
604
613
  sdkLogger.debug("Starting countdown timeout", timeoutDelay, isPlaying ? "(video)" : "(UI)");
605
- this._countdown = setTimeout(async () => {
606
- sdkLogger.debug("Countdown timeout reached, moving to background");
607
-
608
- // Create the event with cancelable: true
609
- const event = new Event("beforestatechange", { cancelable: true });
610
- event.state = this.UiState.BACKGROUND;
611
- // Use the event manager to dispatch the event and wait for all listeners
612
- await this._eventManager.dispatch("beforestatechange", event);
613
- // Check if any listener called preventDefault()
614
- if (event.defaultPrevented) {
615
- sdkLogger.info("moveToBackground was prevented by a listener");
616
- // Restart the countdown since the move was cancelled
617
- if (this._isForegroundOrTransitioning()) {
618
- this._startCountdown();
619
- }
620
- } else {
621
- this.moveToBackground(true);
622
- }
623
-
614
+ this._countdown = setTimeout(() => {
615
+ sdkLogger.info("Countdown timeout reached, moving to background");
616
+ this.moveToBackground(true);
624
617
  }, timeoutDelay * 1000);
625
618
  } else {
626
619
  sdkLogger.debug("Countdown not started - delay is negative or zero");
@@ -657,30 +650,14 @@ class Lifecycle extends LifecycleInterface {
657
650
  this._suspendCountdown = setTimeout(async () => {
658
651
  sdkLogger.debug("Suspend countdown timeout reached, moving to suspended");
659
652
  if (this._state === this.UiState.BACKGROUND) {
660
- // Create the event with cancelable: true
661
- const event = new Event("beforestatechange", { cancelable: true });
662
- event.state = this.UiState.SUSPENDED;
663
- // Use the event manager to dispatch the event and wait for all listeners
664
- await this._eventManager.dispatch("beforestatechange", event);
665
- // Check if any listener called preventDefault()
666
- if (event.defaultPrevented) {
667
- sdkLogger.info("moveToSuspended was prevented by a listener");
668
- // Restart the suspend countdown since the move was cancelled
669
- this._startSuspendCountdown();
670
- } else {
671
- try {
672
- // Set flag to indicate suspend was triggered by timer
673
- this._isSuspendTriggeredByTimer = true;
674
- await this.moveToSuspended();
675
- } catch (error) {
676
- sdkLogger.error("Failed to move to suspended:", error);
677
- }
653
+ try {
654
+ await this.moveToSuspended(true);
655
+ } catch (error) {
656
+ sdkLogger.error("Failed to move to suspended:", error);
678
657
  }
679
-
680
658
  } else {
681
659
  sdkLogger.warn("suspend timer was called while not in background");
682
660
  }
683
-
684
661
  }, timeoutDelay * 1000);
685
662
  } else {
686
663
  sdkLogger.debug("Suspend countdown not started - delay is zero or negative");
@@ -695,8 +672,6 @@ class Lifecycle extends LifecycleInterface {
695
672
  sdkLogger.debug("Stopping suspend countdown timer");
696
673
  clearTimeout(this._suspendCountdown);
697
674
  this._suspendCountdown = null;
698
- // Reset the timer flag when stopping countdown
699
- this._isSuspendTriggeredByTimer = false;
700
675
  }
701
676
  }
702
677
 
@@ -727,7 +702,7 @@ class Lifecycle extends LifecycleInterface {
727
702
  sdkLogger.warn("lifecycle getState is not supported if NOT running e2e");
728
703
  }
729
704
 
730
- moveToForeground() {
705
+ async moveToForeground(isSystemTriggered = false) {
731
706
  if (!_isUiReady()) {
732
707
  sdkLogger.warn("lifecycle moveToForeground: UI is not ready yet. Make sure to call senza.uiReady() before calling lifecycle.moveToForeground()");
733
708
  }
@@ -747,6 +722,19 @@ class Lifecycle extends LifecycleInterface {
747
722
  sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${inTransition}`);
748
723
  return Promise.resolve(false);
749
724
  }
725
+
726
+ // Fire beforestatechange event
727
+ const event = new Event("beforestatechange", { cancelable: true });
728
+ event.state = this.UiState.FOREGROUND;
729
+ event.isSystemTriggered = isSystemTriggered;
730
+ await this._eventManager.dispatch("beforestatechange", event);
731
+
732
+ // Check if any listener called preventDefault()
733
+ if (event.defaultPrevented) {
734
+ sdkLogger.info("moveToForeground was prevented by a listener");
735
+ return Promise.resolve(false);
736
+ }
737
+
750
738
  this._inTransitionToForeground = true;
751
739
  bus.dispatchEvent(new Event(Events.LifecycleForeground));
752
740
  const FCID = getFCID();
@@ -918,13 +906,13 @@ class Lifecycle extends LifecycleInterface {
918
906
  return Promise.resolve(false);
919
907
  }
920
908
 
921
- moveToBackground(isTriggeredByTimer = false) {
909
+ async moveToBackground(isSystemTriggered = false) {
922
910
  if (this._isInTransitionToSuspended) {
923
911
  sdkLogger.error("lifecycle moveToBackground: Currently in transition to suspend, cannot move to background");
924
912
  return Promise.resolve(false);
925
913
  }
926
914
  // If the background transition is triggered by the auto background timer, set the flag
927
- this._isBackgroundTriggeredByTimer = isTriggeredByTimer;
915
+ this._isBackgroundTriggeredByTimer = isSystemTriggered;
928
916
 
929
917
  if (window.cefQuery) {
930
918
  const inTransition = this._isInTransition();
@@ -932,6 +920,24 @@ class Lifecycle extends LifecycleInterface {
932
920
  sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${inTransition}`);
933
921
  return Promise.resolve(false);
934
922
  }
923
+
924
+ // Fire beforestatechange event
925
+ const event = new Event("beforestatechange", { cancelable: true });
926
+ event.state = this.UiState.BACKGROUND;
927
+ event.isSystemTriggered = isSystemTriggered;
928
+ await this._eventManager.dispatch("beforestatechange", event);
929
+
930
+ // Check if any listener called preventDefault()
931
+ if (event.defaultPrevented) {
932
+ sdkLogger.info("moveToBackground was prevented by a listener");
933
+ // Clear the timer flag if the move was prevented
934
+ this._isBackgroundTriggeredByTimer = false;
935
+ // Restart the countdown if it was triggered by timer
936
+ if (isSystemTriggered && this._isForegroundOrTransitioning()) {
937
+ this._startCountdown();
938
+ }
939
+ return Promise.resolve(false);
940
+ }
935
941
  if (remotePlayer._isSeekingByApplication) {
936
942
  remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
937
943
  return Promise.resolve(true);
@@ -1076,7 +1082,7 @@ class Lifecycle extends LifecycleInterface {
1076
1082
  return Promise.reject("disconnect is not supported if NOT running e2e");
1077
1083
  }
1078
1084
 
1079
- moveToSuspended() {
1085
+ async moveToSuspended(isSystemTriggered = false) {
1080
1086
  // Check if the current state is BACKGROUND
1081
1087
  if (this._state !== this.UiState.BACKGROUND) {
1082
1088
  const errorMsg = `moveToSuspended can only be called from BACKGROUND state. Current state: ${this._state}`;
@@ -1084,20 +1090,35 @@ class Lifecycle extends LifecycleInterface {
1084
1090
  return Promise.reject(errorMsg);
1085
1091
  }
1086
1092
 
1093
+ // Fire beforestatechange event
1094
+ const event = new Event("beforestatechange", { cancelable: true });
1095
+ event.state = this.UiState.SUSPENDED;
1096
+ event.isSystemTriggered = isSystemTriggered;
1097
+ await this._eventManager.dispatch("beforestatechange", event);
1098
+
1099
+ // Check if any listener called preventDefault()
1100
+ if (event.defaultPrevented) {
1101
+ sdkLogger.info("moveToSuspended was prevented by a listener");
1102
+ // Restart the suspend countdown if it was triggered by timer
1103
+ if (isSystemTriggered && this._isAutoSuspendEnabled()) {
1104
+ this._startSuspendCountdown();
1105
+ }
1106
+ return Promise.resolve(false);
1107
+ }
1108
+
1087
1109
  this._isInTransitionToSuspended = true;
1088
1110
  // Report metrics for time between background and suspend
1089
1111
  const duration = (Date.now() - this._backgroundTimestamp) / 1000;
1090
1112
 
1091
1113
  sdkLogger.metrics({
1092
1114
  type: "suspendTransitions",
1093
- isAutoSuspend: this._isSuspendTriggeredByTimer,
1115
+ isAutoSuspend: isSystemTriggered,
1094
1116
  isPlaying: remotePlayer._isPlaying,
1095
1117
  duration
1096
1118
  });
1097
1119
 
1098
1120
  // Reset the timer flag after reporting metrics
1099
- const reason = this._isSuspendTriggeredByTimer ? "timer" : "appInitiated";
1100
- this._isSuspendTriggeredByTimer = false;
1121
+ const reason = isSystemTriggered ? "timer" : "appInitiated";
1101
1122
 
1102
1123
  if (window.cefQuery) {
1103
1124
  return new Promise((resolve, reject) => {
@@ -254,6 +254,13 @@ class RemotePlayer extends RemotePlayerInterface {
254
254
  typeof document !== "undefined" && document.addEventListener("hs/ui_inactive", () => {
255
255
  /* istanbul ignore next */
256
256
  sdkLogger.info("Got hs/ui_inactive event");
257
+
258
+ // this logic is specific for customers that use autoTune. When a playback session is auto tuned, we do not need the guard time before releasing the UI
259
+ // calling moveToSuspended right away, will release the UI faster.
260
+ if (this._isPlaying && this._autoTune) {
261
+ sdkLogger.info("Moving to suspended state, in autoTune playback, due to ui_inactive event");
262
+ lifecycle.moveToSuspended();
263
+ }
257
264
  });
258
265
  typeof document !== "undefined" && document.addEventListener("hs/senzaPlayerSetRate", (event) => {
259
266
  if (!this._videoElement) return;
@@ -16,18 +16,32 @@ import {
16
16
  /**
17
17
  * @event Lifecycle#beforestatechange
18
18
  * @description Fired before transitioning to a new state. This event is cancelable.<br>
19
- * Currently only fired when transitioning to BACKGROUND and SUSPENDED state (e.g., from autoBackground feature).<br>
19
+ * Fired when transitioning to FOREGROUND, BACKGROUND, or SUSPENDED state.<br>
20
20
  * The actual state transition will occur after all event listeners have completed processing.
21
- * Can be used to prevent automatic transitions to background state when using autoBackground feature, or for suspneded from autoSuspended feature .
22
- * @property {UiState} state - Indicates the target state the lifecycle is trying to transition to.
21
+ * Can be used to prevent automatic transitions (e.g., from autoBackground or autoSuspend features) or
22
+ * to perform cleanup operations before state changes.
23
+ * @property {UiState} state - Indicates the target state the lifecycle is trying to transition to (FOREGROUND, BACKGROUND, or SUSPENDED).
24
+ * @property {boolean} isSystemTriggered - Indicates whether the transition was triggered by the system (e.g., auto background timer, auto suspend timer) or by the application.
23
25
  * @property {boolean} cancelable - true, indicating the event can be cancelled using preventDefault()
24
26
  * @example
27
+ * // Prevent automatic background transition when user is actively interacting
25
28
  * lifecycle.addEventListener("beforestatechange", (e) => {
26
- * if (e.state === lifecycle.UiState.BACKGROUND && userIsInteracting) {
27
- * // Prevent transition to background
29
+ * if (e.state === lifecycle.UiState.BACKGROUND && e.isSystemTriggered && userIsInteracting) {
30
+ * console.log("Preventing automatic background transition");
28
31
  * e.preventDefault();
29
32
  * }
30
33
  * });
34
+ *
35
+ * @example
36
+ * // Save application state before moving to suspended
37
+ * lifecycle.addEventListener("beforestatechange", async (e) => {
38
+ * if (e.state === lifecycle.UiState.SUSPENDED) {
39
+ * console.log("Saving application state before suspend");
40
+ * await saveApplicationState();
41
+ * }
42
+ * });
43
+ *
44
+
31
45
  */
32
46
 
33
47
  /**
@@ -110,7 +110,7 @@ export class RemotePlayerError extends Error {
110
110
  * | 3403 | Player | Problem accessing segments, server authentication issue |
111
111
  * | 3404 | Player | Problem accessing segments, server returned not found |
112
112
  * | 3900-3999 | Player | Internal player error |
113
- * | 6000 | Player | The remote player api call has reached the configurable timeout with no response from the remote player |
113
+ * | 6000 | Player | The remote player api call has reached a timeout with no response from the remote player |
114
114
  * | 6001 | Player | play() was called while the remote player is not loaded |
115
115
  * | 6002 | Player | load() was called while the application was in state 'background' or 'inTransitionToBackground' |
116
116
  * | 6500 | Player | remotePlayer api was called before initializing remotePlayer |
@@ -1 +1 @@
1
- export const version = "4.4.9";
1
+ export const version = "4.4.11";