senza-sdk 4.2.46-fdbcc4c.0 → 4.2.47-a34cd6b.0

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.2.46-fdbcc4c.0",
3
+ "version": "4.2.47-a34cd6b.0",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -1,6 +1,5 @@
1
1
  import { getFCID, sdkLogger } from "./utils";
2
2
  import { lifecycle } from "./lifecycle";
3
- import { getTriggerEvent } from "./api";
4
3
 
5
4
  /**
6
5
  * AlarmManager is a singleton class that manages the alarms in the application. It fires events whose types are the names of the alarms.
@@ -27,7 +26,6 @@ class AlarmManager extends EventTarget {
27
26
  constructor() {
28
27
  super();
29
28
  this._eventListeners = {};
30
- this._isFirstAlarm = true;
31
29
  typeof document !== "undefined" && document.addEventListener("hs/alarmFiredEvent", (e) => {
32
30
  const logger = sdkLogger.withFields({alarmName: e.detail?.alarmName});
33
31
  logger.log("Got hs/alarmFiredEvent", JSON.stringify(e.detail));
@@ -35,23 +33,20 @@ class AlarmManager extends EventTarget {
35
33
  if (this._eventListeners[e.detail.alarmName]) {
36
34
  const event = new CustomEvent(e.detail.alarmName, {detail: e.detail.payload});
37
35
  const timeBeforeAlarmHandling = Date.now();
38
- let uiStateBeforeAlarmHandling;
36
+ let sourceState;
39
37
  if (lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND) {
40
- uiStateBeforeAlarmHandling = "foreground";
38
+ sourceState = "foreground";
41
39
  } else {
42
- uiStateBeforeAlarmHandling = "background";
40
+ sourceState = "background";
43
41
  }
42
+ this._targetState = sourceState;
44
43
  Promise.allSettled(Array.from(this._eventListeners[e.detail.alarmName], cb => cb(event))).then(() => {
45
44
  const alarmHandlingDuration = Date.now() - timeBeforeAlarmHandling;
46
45
  logger.log(`All callbacks for alarm '${e.detail.alarmName}' are finished within ${alarmHandlingDuration}ms`);
47
- // If the application was triggered due to an alarm, and after all callbacks are handled, the application is still in the background and
48
- // moveToForeground has not been called as a result of the alarm, we want to intentionally terminate the ui in order to save resources.
49
- let isTriggering = false;
50
- if (getTriggerEvent().type === "alarmFiredEvent" && lifecycle.state === lifecycle.UiState.BACKGROUND) {
51
- if (this._isFirstAlarm) {
52
- isTriggering = true;
53
- this._isFirstAlarm = false;
54
- }
46
+ // If this alarm triggered the application, and moveToForeground has not been called as a result of the alarm
47
+ // (i.e. the application is still in the background), we want to intentionally terminate the ui in order to save resources.
48
+ const isTriggering = lifecycle.triggerEvent.type === "alarmFiredEvent" && lifecycle._triggerEventFcid && lifecycle._triggerEventFcid === e.detail.fcid;
49
+ if (isTriggering) {
55
50
  if (!this._moveToForegroundHasBeenCalled && window.cefQuery) {
56
51
  logger.log("Application is about to be terminated since didn't move to foreground");
57
52
  const message = { type: "terminating" };
@@ -70,18 +65,12 @@ class AlarmManager extends EventTarget {
70
65
  }
71
66
  // For monitoring purposes, log a json with all the necessary labels and a unique prefix [hs-sdk][metrics]
72
67
  // The ui-streamer will recognize this prefix and handle the json accordingly.
73
- let uiStateAfterAlarmHandling;
74
- if (lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND) {
75
- uiStateAfterAlarmHandling = "foreground";
76
- } else {
77
- uiStateAfterAlarmHandling = "background";
78
- }
79
68
  logger.metrics({
80
69
  type: "monitorAlarm",
81
70
  alarmName: e.detail.alarmName,
82
71
  isTriggering,
83
- uiStateBefore: uiStateBeforeAlarmHandling,
84
- uiStateAfter: uiStateAfterAlarmHandling,
72
+ sourceState,
73
+ targetState: this._targetState,
85
74
  alarmHandlingDuration
86
75
  });
87
76
  });
@@ -105,6 +94,11 @@ class AlarmManager extends EventTarget {
105
94
 
106
95
  _moveToForegroundCalled() {
107
96
  this._moveToForegroundHasBeenCalled = true;
97
+ this._targetState = "foreground";
98
+ }
99
+
100
+ _moveToBackgroundCalled() {
101
+ this._targetState = "background";
108
102
  }
109
103
 
110
104
  /** Set alarm to be fired at the specified time, event when ui is released
package/src/api.js CHANGED
@@ -30,6 +30,7 @@ export const auth = {
30
30
  };
31
31
 
32
32
  /**
33
+ * @deprecated use lifecycle.ConnectReason instead.
33
34
  * @typedef {Object} ConnectReason - The reason the ui app has been loaded
34
35
  * @property {string} UNKNOWN
35
36
  * @property {string} INITIAL_CONNECTION - Indicates that ui app has been loaded for the first time
@@ -49,9 +50,6 @@ export const ConnectReason = Object.freeze({
49
50
  UI_WATCHDOG: "ui_watchdog"
50
51
  });
51
52
 
52
- let connectReason = ConnectReason.UNKNOWN;
53
- let triggerEvent = {};
54
-
55
53
  import { remotePlayer } from "./remotePlayer";
56
54
  export { remotePlayer };
57
55
 
@@ -112,33 +110,7 @@ export async function init() {
112
110
  sdkLogger.log(`onUpdateSessionEvent: token = ${authToken}`);
113
111
  });
114
112
 
115
- // Get connection reason
116
- connectReason = await new Promise((resolve) => {
117
- const FCID = getFCID();
118
- const logger = sdkLogger.withFields({ FCID });
119
- const message = { type: "connectReason", fcid: FCID };
120
- const request = { target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message) };
121
- window.cefQuery({
122
- request: JSON.stringify(request),
123
- persistent: false,
124
- onSuccess: (response) => {
125
- logger.log(`connectReason request successfully returned '${response}'`);
126
- resolve(response);
127
- },
128
- onFailure: (code, msg) => {
129
- logger.error(`connectReason request failed: ${code} ${msg}`);
130
- resolve(ConnectReason.UNKNOWN);
131
- }
132
- });
133
- });
134
-
135
113
  // Get trigger event
136
- // Currently the known types (and details per type) are:
137
- // keyPressEvent (keyValue)
138
- // alarmFiredEvent (alarmName, payload)
139
- // userAlertEvent (eventCode, message)
140
- // externalEvent (eventName, payload)
141
- // videoPlaybackEvent (eventCode, errorCode)
142
114
  const triggerEventStr = await new Promise((resolve) => {
143
115
  const FCID = getFCID();
144
116
  const logger = sdkLogger.withFields({ FCID });
@@ -157,34 +129,18 @@ export async function init() {
157
129
  }
158
130
  });
159
131
  });
160
- triggerEvent = {};
161
- let originalTriggerEvent = {};
132
+ let triggerEvent = {};
162
133
  if (triggerEventStr) {
163
134
  try {
164
- originalTriggerEvent = JSON.parse(triggerEventStr);
165
- triggerEvent.type = originalTriggerEvent.type;
166
- sdkLogger.info(`Got trigger event of type ${triggerEvent.type}`);
167
- if (triggerEvent.type === "keyPressEvent") {
168
- triggerEvent.data = { keyValue: originalTriggerEvent.keyValue };
169
- } else if (triggerEvent.type === "alarmFiredEvent") {
170
- triggerEvent.data = { alarmName: originalTriggerEvent.alarmName, payload: originalTriggerEvent.payload };
171
- } else if (triggerEvent.type === "userAlertEvent") {
172
- triggerEvent.data = { eventCode: originalTriggerEvent.eventCode, message: originalTriggerEvent.message };
173
- } else if (triggerEvent.type === "externalEvent") {
174
- triggerEvent.data = { eventName: originalTriggerEvent.eventName, payload: originalTriggerEvent.payload };
175
- } else if (triggerEvent.type === "videoPlaybackEvent") {
176
- triggerEvent.data = { eventCode: originalTriggerEvent.eventCode, errorCode: originalTriggerEvent.errorCode };
177
- } else if (triggerEvent.type === "getLicense") {
178
- sdkLogger.info("The license request is available on the license-request event which is triggered after uiReady is called");
179
- }
135
+ triggerEvent = JSON.parse(triggerEventStr);
180
136
  } catch (e) {
181
137
  sdkLogger.error(`failed to parse trigger event string ${triggerEventStr}: ${e.message}`);
182
138
  }
183
139
  }
184
140
 
185
141
  // Initialize lifecycle first to make sure the state is updated.
186
- await lifecycle._init(sessionInfoObj?.settings?.["ui-streamer"]);
187
- await remotePlayer._init(sessionInfoObj?.settings?.["ui-streamer"], originalTriggerEvent);
142
+ await lifecycle._init(sessionInfoObj?.settings?.["ui-streamer"], triggerEvent);
143
+ await remotePlayer._init(sessionInfoObj?.settings?.["ui-streamer"], triggerEvent);
188
144
 
189
145
  const devSequence = sessionInfoObj?.settings?.["ui-streamer"]?.devSequence;
190
146
  if (devSequence) {
@@ -203,18 +159,20 @@ export async function init() {
203
159
  };
204
160
  }
205
161
 
206
- /** Returns the reason the ui has been loaded
207
- * @returns {ConnectReason} */
162
+ /**
163
+ * @deprecated use lifecycle.connectReason instead
164
+ * Returns the reason the ui has been loaded
165
+ **/
208
166
  export function getConnectReason() {
209
- return connectReason;
167
+ return lifecycle.connectReason;
210
168
  }
211
169
 
212
- /** Returns the event that triggered the reloading of the ui after ui has been released
213
- * @returns {Object} trigger event
214
- * @property {string} type - the type of the trigger event (e.g. keyPressEvent, videoPlaybackEvent)
215
- * @property {object} data - data of the event, dependent on its type (e.g. keyPressEvent has data of keyValue) */
170
+ /**
171
+ * @deprecated use lifecycle.triggerEvent instead
172
+ * Returns the event that triggered the reloading of the ui after ui has been released
173
+ **/
216
174
  export function getTriggerEvent() {
217
- return triggerEvent;
175
+ return lifecycle.triggerEvent;
218
176
  }
219
177
 
220
178
  // Returns the platform information, including version, pod, pod ip and session info.
package/src/lifecycle.js CHANGED
@@ -21,8 +21,30 @@ const DEFAULT_AUTO_BACKGROUND_ENABLED = false;
21
21
  * Lifecycle is a singleton class that manages the application lifecycle states.<br>
22
22
  * @fires onstatechange
23
23
  * @fires userinactivity
24
+ * @fires userdisconnected
24
25
  */
25
26
  class Lifecycle extends EventTarget {
27
+
28
+ /**
29
+ * @typedef {Object} ConnectReason - The reason the ui app has been loaded
30
+ * @property {string} UNKNOWN
31
+ * @property {string} INITIAL_CONNECTION - Indicates that ui app has been loaded for the first time
32
+ * @property {string} APPLICATION_RELOAD - Indicates that ui app has been reloaded (e.g. after HOME keypress)
33
+ * @property {string} UI_RELEASE - Indicates that ui app has been reloaded after ui release
34
+ * @property {string} UI_TERMINATION - Indicates that ui app has been reloaded due to ui termination
35
+ * @property {string} WEBRTC_ERROR - Indicates that ui app has been reloaded due to webrtc error
36
+ * @property {string} UI_WATCHDOG - Indicates that ui app has been reloaded due to ui watchdog not receiving ui frames
37
+ */
38
+ ConnectReason = Object.freeze({
39
+ UNKNOWN: "unknown",
40
+ INITIAL_CONNECTION: "initial_connection",
41
+ APPLICATION_RELOAD: "reload_app",
42
+ UI_RELEASE: "ui_release",
43
+ UI_TERMINATION: "ui_termination",
44
+ WEBRTC_ERROR: "webrtc_error",
45
+ UI_WATCHDOG: "ui_watchdog"
46
+ });
47
+
26
48
  /**
27
49
  * @typedef {Object} UiState - The ui lifecycle state
28
50
  * @property {string} UNKNOWN - state is unknown at this time
@@ -60,6 +82,17 @@ class Lifecycle extends EventTarget {
60
82
  * });
61
83
  * @alpha API has not yet been released
62
84
  */
85
+
86
+ /**
87
+ * @event Lifecycle#userdisconnected
88
+ * @description Fired when the user session ends .
89
+ * This event is useful for cleaning up application state or saving data before the application closes.
90
+ * @example
91
+ * lifecycle.addEventListener("userdisconnected", () => {
92
+ * console.log("User session ended, cleaning up application state");
93
+ * // Perform any necessary cleanup here
94
+ * });
95
+ */
63
96
  constructor() {
64
97
  super();
65
98
 
@@ -106,6 +139,11 @@ class Lifecycle extends EventTarget {
106
139
  this.dispatchEvent(event);
107
140
  });
108
141
 
142
+ typeof document !== "undefined" && document.addEventListener("hs/endOfSession", () => {
143
+ const event = new Event("userdisconnected");
144
+ this.dispatchEvent(event);
145
+ });
146
+
109
147
  typeof document !== "undefined" && document.addEventListener("keydown", () => {
110
148
  if (this._autoBackground) {
111
149
  if (this.state === this.UiState.BACKGROUND ||
@@ -135,8 +173,10 @@ class Lifecycle extends EventTarget {
135
173
  * @param {number} [uiStreamerSettings.remotePlayerConfirmationTimeout=5000] - Timeout in milliseconds for remote player operations
136
174
  * @param {number} [uiStreamerSettings.remotePlayerApiVersion=1] - Remote player API version
137
175
  */
138
- async _init(uiStreamerSettings) {
176
+ async _init(uiStreamerSettings, triggerEvent) {
139
177
  if (window.cefQuery) {
178
+
179
+ // Get UI state
140
180
  this._state = await new Promise((resolve) => {
141
181
  window.cefQuery({
142
182
  request: "uiState",
@@ -151,6 +191,57 @@ class Lifecycle extends EventTarget {
151
191
  }
152
192
  });
153
193
  });
194
+
195
+ // Get connection reason
196
+ this._connectReason = await new Promise((resolve) => {
197
+ const FCID = getFCID();
198
+ const logger = sdkLogger.withFields({ FCID });
199
+ const message = { type: "connectReason", fcid: FCID };
200
+ const request = { target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message) };
201
+ window.cefQuery({
202
+ request: JSON.stringify(request),
203
+ persistent: false,
204
+ onSuccess: (response) => {
205
+ logger.log(`connectReason request successfully returned '${response}'`);
206
+ resolve(response);
207
+ },
208
+ onFailure: (code, msg) => {
209
+ logger.error(`connectReason request failed: ${code} ${msg}`);
210
+ resolve(this.ConnectReason.UNKNOWN);
211
+ }
212
+ });
213
+ });
214
+
215
+ // Set internal trigger event and fcid.
216
+ // Currently, the known types (and details per type) are:
217
+ // keyPressEvent (keyValue)
218
+ // alarmFiredEvent (alarmName, payload)
219
+ // userAlertEvent (eventCode, message)
220
+ // externalEvent (eventName, payload)
221
+ // videoPlaybackEvent (eventCode, errorCode)
222
+ // getLicense
223
+ this._triggerEvent = {};
224
+ if (triggerEvent.type) {
225
+ this._triggerEvent.type = triggerEvent.type;
226
+ sdkLogger.info(`Got trigger event of type ${this._triggerEvent.type}`);
227
+ if (triggerEvent.type === "keyPressEvent") {
228
+ this._triggerEvent.data = { keyValue: triggerEvent.keyValue };
229
+ } else if (triggerEvent.type === "alarmFiredEvent") {
230
+ this._triggerEvent.data = { alarmName: triggerEvent.alarmName, payload: triggerEvent.payload };
231
+ } else if (triggerEvent.type === "userAlertEvent") {
232
+ this._triggerEvent.data = { eventCode: triggerEvent.eventCode, message: triggerEvent.message };
233
+ } else if (triggerEvent.type === "externalEvent") {
234
+ this._triggerEvent.data = { eventName: triggerEvent.eventName, payload: triggerEvent.payload };
235
+ } else if (triggerEvent.type === "videoPlaybackEvent") {
236
+ this._triggerEvent.data = { eventCode: triggerEvent.eventCode, errorCode: triggerEvent.errorCode };
237
+ } else if (triggerEvent.type === "getLicense") {
238
+ sdkLogger.info("The license request is available on the license-request event which is triggered after uiReady is called");
239
+ }
240
+ }
241
+ if (triggerEvent.fcid) {
242
+ this._triggerEventFcid = triggerEvent.fcid;
243
+ }
244
+
154
245
  this._isInitialized = true;
155
246
  this._remotePlayerConfirmationTimeout = uiStreamerSettings?.remotePlayerConfirmationTimeout ?? DEFAULT_REMOTE_PLAYER_CONFIRMATION_TIMEOUT;
156
247
  this._remotePlayerApiVersion = uiStreamerSettings?.remotePlayerApiVersion || 1;
@@ -219,6 +310,29 @@ class Lifecycle extends EventTarget {
219
310
  return this._state;
220
311
  }
221
312
 
313
+ /**
314
+ * Getter for returning the application connection reason
315
+ * @returns {ConnectReason} the application connection reason
316
+ */
317
+ get connectReason() {
318
+ if (!this._isInitialized) {
319
+ this._connectReason = this.ConnectReason.UNKNOWN;
320
+ }
321
+ return this._connectReason;
322
+ }
323
+
324
+ /**
325
+ * Getter for returning the event that triggered the reloading of the ui after ui has been released
326
+ * @returns {Object} trigger event
327
+ * @property {string} type - the type of the trigger event (e.g. keyPressEvent, videoPlaybackEvent)
328
+ * @property {object} data - data of the event, dependent on its type (e.g. keyPressEvent has data of keyValue) */
329
+ get triggerEvent() {
330
+ if (!this._isInitialized) {
331
+ this._triggerEvent = {};
332
+ }
333
+ return this._triggerEvent;
334
+ }
335
+
222
336
  /**
223
337
  * Controls the autoBackground feature<br>
224
338
  * When enabled, the application will automatically move to the background state after a configurable
@@ -432,6 +546,8 @@ class Lifecycle extends EventTarget {
432
546
  remotePlayer._syncRemotePlayerWithLocalPlayer();
433
547
  }
434
548
 
549
+ alarmManager._moveToBackgroundCalled();
550
+
435
551
  if (!remotePlayer._isPlaying) {
436
552
  return this._moveToUiStandby();
437
553
  }
@@ -199,8 +199,7 @@ class RemotePlayer extends EventTarget {
199
199
  sdkLogger.info("Got hs/playbackInfoEvent");
200
200
  // When attached, the sdk controls the synchronization between the local and remote player.
201
201
  if (this._videoElement) {
202
- // When audio sync is configured, the local player is already in sync with the remote player.
203
- if (!isAudioSyncConfigured() && (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
202
+ if ((lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
204
203
  this._syncLocalPlayerWithRemotePlayer();
205
204
  }
206
205
  } else {
@@ -622,6 +621,7 @@ class RemotePlayer extends EventTarget {
622
621
 
623
622
  _syncLocalPlayerWithRemotePlayer() {
624
623
  if (this._videoElement) {
624
+ if (isAudioSyncConfigured()) this._isSeekingByPlatform = true;
625
625
  this._videoElement.currentTime = this.currentTime;
626
626
  }
627
627
  }
package/src/utils.js CHANGED
@@ -39,7 +39,7 @@ class SdkLogger {
39
39
  console.error(this.formatLogString(data));
40
40
  }
41
41
  metrics(metricObj) {
42
- console.info(`[hs-sdk][metrics] ${JSON.stringify(metricObj)}`);
42
+ console.info(`[hs-sdk] [metrics] ${JSON.stringify(metricObj)}`);
43
43
  }
44
44
  withFields(logFields) {
45
45
  return new SdkLogger({...this.logFields, ...logFields});