senza-sdk 4.4.6 → 4.4.8

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.6",
3
+ "version": "4.4.8",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "eslint-plugin-jest": "^28.11.0",
35
35
  "globals": "^16.0.0",
36
36
  "jest": "^30.2.0",
37
- "jest-environment-jsdom" : "^30.2.0",
37
+ "jest-environment-jsdom": "^30.2.0",
38
38
  "jsdoc-to-markdown": "^7.1.1",
39
39
  "webpack": "^5.72.1",
40
40
  "webpack-cli": "^5.1.4"
@@ -20,6 +20,7 @@ let authToken;
20
20
 
21
21
  const API_VERSION = "1.0";
22
22
  let interfaceVersion;
23
+ let uiReadyHasBeenCalled = false;
23
24
 
24
25
  /** @namespace auth
25
26
  *@example
@@ -262,6 +263,7 @@ export function isRunningE2E() {
262
263
 
263
264
  /** Call this API once after application startup, when the ui is ready to accept keys/events */
264
265
  export function uiReady() {
266
+ uiReadyHasBeenCalled = true;
265
267
  if (window.cefQuery) {
266
268
  window.cefQuery({
267
269
  request: "uiReady",
@@ -278,6 +280,13 @@ export function uiReady() {
278
280
  }
279
281
  }
280
282
 
283
+ /**
284
+ * @private
285
+ */
286
+ export function _isUiReady() {
287
+ return uiReadyHasBeenCalled;
288
+ }
289
+
281
290
  import "./devHelper.js";
282
291
 
283
292
  /**
@@ -1,4 +1,4 @@
1
- import { DeviceManager as DeviceManagerInterface } from "../interface/deviceManager";
1
+ import { DeviceManager as DeviceManagerInterface} from "../interface/deviceManager";
2
2
  import { getFCID, sdkLogger, getRestResponse } from "./utils";
3
3
  import { sessionInfo } from "./SessionInfo";
4
4
 
@@ -9,6 +9,186 @@ let wifi_status_last_update = 0;
9
9
  const WIFI_STATUS_CACHE_SECONDS = 5;
10
10
  const FACTORY_RESET_TIMEOUT_SECONDS = 5;
11
11
 
12
+ const byteSize = str => new Blob([str]).size;
13
+
14
+ /**
15
+ * MessageChannel is not extended from the interface as its objects are only instantiated internally.
16
+ * The implementation here needs to match the interface definition which exists purely for documentation.
17
+ */
18
+ class MessageChannel extends EventTarget {
19
+ constructor(deviceManager, channelType) {
20
+ super();
21
+ switch (channelType) {
22
+ case deviceManager.DeviceMessageChannelType.SZE_HOST_APP:
23
+ this._type = deviceManager.DeviceMessageChannelType.SZE_HOST_APP;
24
+ this._description = "Message channel for bidirectional communication with the Senza Embedded device's host application";
25
+ this._capabilities = {
26
+ send: true,
27
+ receive: true,
28
+ dataTypes: ["string"],
29
+ maxMessageSize: 60 * 1024 // 60KB - to account for SCTP default max message size of 64Kb minus the wrapping object overhead
30
+ };
31
+ break;
32
+ case deviceManager.DeviceMessageChannelType.USB_SERIAL:
33
+ this._type = deviceManager.DeviceMessageChannelType.USB_SERIAL;
34
+ this._description = "Message channel for sending messages to the device's USB serial interface";
35
+ this._capabilities = {
36
+ send: true,
37
+ receive: false,
38
+ dataTypes: ["string"],
39
+ maxMessageSize: 60 * 1024 // 60KB
40
+ };
41
+ break;
42
+ default:
43
+ throw new Error("Invalid channel type");
44
+ }
45
+
46
+ this._open = true;
47
+ this._manager = deviceManager;
48
+ }
49
+
50
+ /**
51
+ * Calls EventTarget's addEventListener after checking if channel is open
52
+ */
53
+ addEventListener(type, callback) {
54
+ if (!this._open) throw new Error("Channel is closed");
55
+ EventTarget.prototype.addEventListener.call(this, type, callback);
56
+ }
57
+
58
+ /**
59
+ * Calls EventTarget's removeEventListener after checking if channel is open
60
+ */
61
+ removeEventListener(type, callback) {
62
+ if (!this._open) throw new Error("Channel is closed");
63
+ EventTarget.prototype.removeEventListener.call(this, type, callback);
64
+ }
65
+
66
+ /**
67
+ * Marks channel as closed and not usable anymore. Also removes it from the DeviceManager's createdChannels map.
68
+ */
69
+ dispose() {
70
+ if (!this._open) throw new Error("Channel is closed");
71
+ this._open = false;
72
+ if (this._manager && typeof this._manager._removeChannel === "function") {
73
+ this._manager._removeChannel(this._type);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get the type of this message channel.
79
+ * @returns {DeviceMessageChannelType} The channel type
80
+ */
81
+ get type() {
82
+ if (!this._open) throw new Error("Channel is closed");
83
+ return this._type;
84
+ }
85
+
86
+ /**
87
+ * Get the description of this message channel.
88
+ * @returns {string} The channel description
89
+ */
90
+ get description() {
91
+ if (!this._open) throw new Error("Channel is closed");
92
+ return this._description;
93
+ }
94
+
95
+ /**
96
+ * Get the capabilities of this message channel.
97
+ * @returns {Object} The capabilities object containing send/receive flags, supported data types, etc.
98
+ */
99
+ get capabilities() {
100
+ if (!this._open) throw new Error("Channel is closed");
101
+ return this._capabilities;
102
+ }
103
+
104
+ /**
105
+ * @private
106
+ * Performs checks to see if a message can be sent through this channel.
107
+ * @param {*} message The message to check
108
+ */
109
+ _checkMessageBeforeSend(message) {
110
+ if (!this._open) throw new Error("Channel is closed");
111
+ if (typeof window === "undefined" || typeof window.cefQuery !== "function") {
112
+ throw new Error("cefQuery is not available");
113
+ }
114
+ if (typeof message !== "string" && !(message instanceof String)) {
115
+ throw new Error("message must be a string");
116
+ }
117
+ // Enforce maxMessageSize limit
118
+ const maxSize = this.capabilities.maxMessageSize;
119
+ const messageSize = byteSize(message);
120
+ if (messageSize > maxSize) {
121
+ throw new Error(`Message size (${messageSize} bytes) exceeds maximum allowed size (${maxSize} bytes)`);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Senza Embedded Host App Message Channel
128
+ */
129
+ class SzeHostAppMessageChannel extends MessageChannel {
130
+ _docListener = (e) => {
131
+ const logger = sdkLogger.withFields({ "FCID": e?.detail?.fcid });
132
+ logger.log("Got hs/deviceMsgSzeHostApp", JSON.stringify(e?.detail));
133
+
134
+ const event = new Event("message");
135
+ event.message = e?.detail?.message || "";
136
+ this.dispatchEvent(event);
137
+ };
138
+
139
+ constructor(deviceManager) {
140
+ super(deviceManager, deviceManager.DeviceMessageChannelType.SZE_HOST_APP);
141
+ // Listen for device host app to web app messages and forward to EventTarget listeners
142
+ if (typeof document !== "undefined") {
143
+ document.addEventListener("hs/deviceMsgSzeHostApp", this._docListener);
144
+ }
145
+ }
146
+
147
+ async sendMessage(message) {
148
+ this._checkMessageBeforeSend(message);
149
+ const tc_message = { type: "deviceMsgSzeHostApp", message: message };
150
+ const request = {
151
+ target: "TC",
152
+ waitForResponse: false,
153
+ message: JSON.stringify(tc_message)
154
+ };
155
+ return new Promise((resolve, reject) => {
156
+ window.cefQuery({
157
+ request: JSON.stringify(request),
158
+ persistent: false,
159
+ onSuccess: () => {
160
+ resolve();
161
+ },
162
+ onFailure: (code, msg) => {
163
+ reject(new Error(`Request failed: ${code} ${msg}`));
164
+ }
165
+ });
166
+ });
167
+ }
168
+
169
+ dispose() {
170
+ if (typeof document !== "undefined") {
171
+ document.removeEventListener("hs/deviceMsgSzeHostApp", this._docListener);
172
+ }
173
+ super.dispose();
174
+ }
175
+ }
176
+
177
+ /**
178
+ * USB Serial Message Channel implementation
179
+ */
180
+ class UsbSerialMessageChannel extends MessageChannel {
181
+ constructor(deviceManager) {
182
+ super(deviceManager, deviceManager.DeviceMessageChannelType.USB_SERIAL);
183
+ }
184
+
185
+ async sendMessage(message) {
186
+ this._checkMessageBeforeSend(message);
187
+ // Use the existing sendDataToDevice functionality
188
+ return getDeviceManagerInstance().sendDataToDevice(message);
189
+ }
190
+ }
191
+
12
192
  async function getWifiApData() {
13
193
  // Wi-Fi access point data is static, so it needs to be retrieved only once
14
194
  if (!wifi_ap_data) {
@@ -38,6 +218,13 @@ class DeviceManager extends DeviceManagerInterface {
38
218
 
39
219
  constructor() {
40
220
  super();
221
+ this.availableMessageChannels = [
222
+ // Future enhancement: add available channels based on cfg or some device info
223
+ this.DeviceMessageChannelType.SZE_HOST_APP,
224
+ this.DeviceMessageChannelType.USB_SERIAL
225
+ ];
226
+ // Track created channels in a map to enforce one-per-type limit, i.e. only one SZE_HOST_APP channel at a time
227
+ this.createdChannels = new Map();
41
228
  }
42
229
 
43
230
  get deviceInfo() {
@@ -202,10 +389,67 @@ class DeviceManager extends DeviceManagerInterface {
202
389
  await Promise.all([getWifiApData(), getWifiStatus()]);
203
390
  return { ...wifi_ap_data, ...wifi_status };
204
391
  }
392
+
393
+ /**
394
+ * Returns a list of available message channels for the device.
395
+ * @returns {DeviceMessageChannelType[]} An array of DeviceMessageChannelType values representing the available message channels.
396
+ */
397
+ getAvailableMessageChannels() {
398
+ return this.availableMessageChannels || [];
399
+ }
400
+
401
+ /**
402
+ * Create a message channel of the specified type.
403
+ * Only one channel of each type can be created at a time.
404
+ * @param {DeviceMessageChannelType} type The type of message channel to create.
405
+ * @returns {MessageChannel} A MessageChannel object representing the created message channel.
406
+ * @throws {Error} If the specified type is not supported or already exists.
407
+ */
408
+ createMessageChannel(type) {
409
+ const channelType = this.availableMessageChannels.find(c => c === type);
410
+ const availableTypes = this.availableMessageChannels.join(", ");
411
+ if (!channelType) {
412
+ throw new Error(`Unsupported channel type: ${type}. Available types: ${availableTypes}`);
413
+ }
414
+ // Check if a channel of this type already exists
415
+ if (this.createdChannels.has(type)) {
416
+ throw new Error(`A message channel of type '${type}' already exists. Only one channel per type is allowed.`);
417
+ }
418
+ // Create channel implementation based on type
419
+ let channel;
420
+ if (type === this.DeviceMessageChannelType.SZE_HOST_APP) {
421
+ channel = new SzeHostAppMessageChannel(this);
422
+ } else if (type === this.DeviceMessageChannelType.USB_SERIAL) {
423
+ channel = new UsbSerialMessageChannel(this);
424
+ }
425
+ // else channel type is unsupported, but is already checked above
426
+
427
+ this.createdChannels.set(type, channel);
428
+ return channel;
429
+ }
430
+
431
+ /**
432
+ * Remove a message channel of the specified type.
433
+ * @private
434
+ * @param {DeviceMessageChannelType} type The type of message channel to remove.
435
+ */
436
+ _removeChannel(type) {
437
+ if (!this.createdChannels || !this.createdChannels.has(type)) {
438
+ throw new Error(`No channel of type '${type}' exists.`);
439
+ }
440
+ this.createdChannels.delete(type);
441
+ }
205
442
  }
206
443
 
207
- /**
444
+ // Create the singleton instance
445
+ const deviceManagerInstance = new DeviceManager();
446
+
447
+ // Function to get the singleton instance (for forward reference)
448
+ function getDeviceManagerInstance() {
449
+ return deviceManagerInstance;
450
+ }
208
451
 
452
+ /**
209
453
  * @module
210
454
  * @example
211
455
  * import { deviceManager } from "senza-sdk";
@@ -214,6 +458,13 @@ class DeviceManager extends DeviceManagerInterface {
214
458
  * await deviceManager.clearWifi();
215
459
  * deviceManager.reboot();
216
460
  *
461
+ * // Message channels
462
+ * const channels = deviceManager.getAvailableMessageChannels();
463
+ * const channel = deviceManager.createMessageChannel(deviceManager.DeviceMessageChannelType.SZE_HOST_APP);
464
+ * channel.addEventListener('message', (event) => {
465
+ * console.log(`Received: ${event.message}`);
466
+ * });
467
+ *
217
468
  * @return {DeviceManager} pointer to the DeviceManager singleton
218
469
  */
219
- export const deviceManager = new DeviceManager();
470
+ export const deviceManager = deviceManagerInstance;
@@ -1,5 +1,5 @@
1
1
  import { Lifecycle as LifecycleInterface } from "../interface/lifecycle.js";
2
- import { getPlatformInfo } from "./api.js";
2
+ import { getPlatformInfo, _isUiReady } from "./api.js";
3
3
  import { alarmManager } from "./alarmManager.js";
4
4
  import {
5
5
  getFCID,
@@ -108,6 +108,8 @@ class Lifecycle extends LifecycleInterface {
108
108
  */
109
109
  this._isSuspendTriggeredByTimer = false;
110
110
 
111
+ this._isInTransitionToSuspended = false;
112
+
111
113
  }
112
114
 
113
115
  /**
@@ -705,7 +707,6 @@ class Lifecycle extends LifecycleInterface {
705
707
  return this._inTransitionToForeground || this._inTransitionToBackground || this._inTransitionToStandby;
706
708
  }
707
709
 
708
-
709
710
  getState() {
710
711
  if (window.cefQuery) {
711
712
  return new Promise((resolve, reject) => {
@@ -727,6 +728,13 @@ class Lifecycle extends LifecycleInterface {
727
728
  }
728
729
 
729
730
  moveToForeground() {
731
+ if (!_isUiReady()) {
732
+ sdkLogger.warn("lifecycle moveToForeground: UI is not ready yet. Make sure to call senza.uiReady() before calling lifecycle.moveToForeground()");
733
+ }
734
+ if (this._isInTransitionToSuspended) {
735
+ sdkLogger.error("lifecycle moveToForeground: Currently in transition to suspend, cannot move to foreground");
736
+ return Promise.resolve(false);
737
+ }
730
738
  // Reset activity timestamp when moving to foreground
731
739
  this._lastActivityTimestamp = Date.now();
732
740
 
@@ -911,7 +919,10 @@ class Lifecycle extends LifecycleInterface {
911
919
  }
912
920
 
913
921
  moveToBackground(isTriggeredByTimer = false) {
914
-
922
+ if (this._isInTransitionToSuspended) {
923
+ sdkLogger.error("lifecycle moveToBackground: Currently in transition to suspend, cannot move to background");
924
+ return Promise.resolve(false);
925
+ }
915
926
  // If the background transition is triggered by the auto background timer, set the flag
916
927
  this._isBackgroundTriggeredByTimer = isTriggeredByTimer;
917
928
 
@@ -1073,6 +1084,7 @@ class Lifecycle extends LifecycleInterface {
1073
1084
  return Promise.reject(errorMsg);
1074
1085
  }
1075
1086
 
1087
+ this._isInTransitionToSuspended = true;
1076
1088
  // Report metrics for time between background and suspend
1077
1089
  const duration = (Date.now() - this._backgroundTimestamp) / 1000;
1078
1090
 
@@ -1109,6 +1121,7 @@ class Lifecycle extends LifecycleInterface {
1109
1121
  resolve(true);
1110
1122
  },
1111
1123
  onFailure: (code, msg) => {
1124
+ this._isInTransitionToSuspended = false;
1112
1125
  logger.error(`moveToSuspended failed: ${code} ${msg}`);
1113
1126
  reject(new SenzaError(code, msg));
1114
1127
  }
@@ -15,7 +15,7 @@ import {
15
15
  isAppVersionAboveOrEqual
16
16
  } from "./utils";
17
17
  import { lifecycle } from "./lifecycle";
18
- import { writeLicenseResponse } from "./api";
18
+ import { writeLicenseResponse, _isUiReady } from "./api";
19
19
  import { mergeAutoTranslationLanguages } from "./subtitlesUtils";
20
20
 
21
21
  // This is the delay we wait for to detect if the web application is seeking multiple times
@@ -736,6 +736,9 @@ class RemotePlayer extends RemotePlayerInterface {
736
736
  *
737
737
  * */
738
738
  async load(url, position) {
739
+ if (!_isUiReady()) {
740
+ sdkLogger.warn("remotePlayer load: UI is not ready yet. Make sure to call senza.uiReady() before calling remotePlayer.load()");
741
+ }
739
742
  return this._load(url, position);
740
743
  }
741
744
 
@@ -942,6 +945,9 @@ class RemotePlayer extends RemotePlayerInterface {
942
945
  * @throws {RemotePlayerError} error object contains code & msg
943
946
  */
944
947
  play(autoTune = false) {
948
+ if (!_isUiReady()) {
949
+ sdkLogger.warn("remotePlayer play: UI is not ready yet. Make sure to call senza.uiReady() before calling remotePlayer.play()");
950
+ }
945
951
  if (!this._isInitialized) {
946
952
  throw new RemotePlayerError(6500, "Cannot call play() if remote player is not initialized");
947
953
  }
@@ -4,6 +4,15 @@ import { sdkLogger, noop } from "./utils.js";
4
4
  * DeviceManager is a singleton class that manages the device.<br>
5
5
  */
6
6
  export class DeviceManager extends EventTarget {
7
+ /**
8
+ * @typedef {Object} DeviceMessageChannelType Defines the types of message channels available for communication with the device.
9
+ * @property {string} SZE_HOST_APP Message channel for bidirectional communication with the Senza Embedded device host application.
10
+ * @property {string} USB_SERIAL Message channel for sending messages to the device's USB serial interface.
11
+ */
12
+ DeviceMessageChannelType = Object.freeze({
13
+ SZE_HOST_APP: "SZE_HOST_APP",
14
+ USB_SERIAL: "USB_SERIAL"
15
+ });
7
16
 
8
17
  /**
9
18
  * @property {object} DeviceInfo
@@ -97,8 +106,117 @@ export class DeviceManager extends EventTarget {
97
106
  async getWifiInfo() {
98
107
  return Promise.resolve({});
99
108
  }
109
+
110
+ /**
111
+ * Returns a list of available message channels for the device.
112
+ * @returns {DeviceMessageChannelType[]} An array of DeviceMessageChannelType objects representing the available message channels.
113
+ * @example
114
+ * import { deviceManager } from "senza-sdk";
115
+ * const channelType = deviceManager.getAvailableMessageChannels();
116
+ * channels.forEach(channel => {
117
+ * console.log(`Channel Type: ${channel.type}, Description: ${channel.description}`);
118
+ * });
119
+ */
120
+ getAvailableMessageChannels() {
121
+ return noop("DeviceManager.getAvailableMessageChannels");
122
+ }
123
+
124
+ /**
125
+ * Create a message channel of the specified type.
126
+ * Only one channel of each type can be created at a time.
127
+ * @param {DeviceMessageChannelType} type The type of message channel to create.
128
+ * @returns {MessageChannel} A MessageChannel object representing the created message channel.
129
+ * @throws {Error} If the specified type is not supported or already exists.
130
+ * @example
131
+ * import { deviceManager } from "senza-sdk";
132
+ * const channel = deviceManager.createMessageChannel(deviceManager.DeviceMessageChannelType.SZE_HOST_APP);
133
+ * channel.sendMessage("Hello Senza Embedded host application");
134
+ */
135
+ createMessageChannel(type) {
136
+ return noop("DeviceManager.createMessageChannel", type);
137
+ }
100
138
  }
101
139
 
140
+ /**
141
+ * MessageChannel represents a communication channel to the device.
142
+ * Objects of this type are created via DeviceManager.createMessageChannel().
143
+ * Available channel types depend on the device and are queried at runtime via DeviceManager.getAvailableMessageChannels().
144
+ * <pre>
145
+ * type: DeviceMessageChannelType.SZE_HOST_APP
146
+ * description: Message channel for bidirectional communication ...
147
+ * capabilities:
148
+ * send: true
149
+ * receive: true
150
+ * dataTypes: [string] // string messages are supported using the sendMessage() method and 'message' event
151
+ * maxMessageSize: 61440
152
+ * </pre>
153
+ */
154
+ // eslint-disable-next-line no-unused-vars
155
+ class MessageChannel extends EventTarget {
156
+ /**
157
+ * Send a string message to the device via this message channel.
158
+ * @param {string} message The message to send.
159
+ * @returns {Promise<void>} Promise that resolves when message is sent
160
+ * @example
161
+ * const channel = deviceManager.createMessageChannel(DeviceMessageChannelType.SZE_HOST_APP);
162
+ * await channel.sendMessage("Hello Senza Embedded host application");
163
+ */
164
+ sendMessage() {
165
+ return noop("MessageChannel.sendMessage");
166
+ }
167
+
168
+ /**
169
+ * Get the type of this message channel.
170
+ * @returns {DeviceMessageChannelType} The channel type
171
+ */
172
+ get type() {
173
+ return noop("MessageChannel.get() type");
174
+ }
175
+
176
+ /**
177
+ * Get the description of this message channel.
178
+ * @returns {string} The channel description
179
+ */
180
+ get description() {
181
+ return noop("MessageChannel.get() description");
182
+ }
183
+
184
+ /**
185
+ * Get the capabilities of this message channel.
186
+ * @returns {Object} The capabilities object containing send/receive flags, supported data types, max message size.
187
+ */
188
+ get capabilities() {
189
+ return noop("MessageChannel.get() capabilities");
190
+ }
191
+
192
+ /**
193
+ * Listen for events on this channel.
194
+ * @param {string} type Event type. 'message' is currently the only supported event type.
195
+ * @param {Function} listener The event listener function
196
+ *
197
+ * Events fired:
198
+ * - 'message': Fired when a message is received from the device. The event object has a 'message' property containing the message string.
199
+ *
200
+ * @example
201
+ * // Listen for messages
202
+ * channel.addEventListener('message', (event) => {
203
+ * console.log(`Received message: ${event.message}`);
204
+ * });
205
+ *
206
+ * // remove listener
207
+ * channel.removeEventListener('message', messageHandler);
208
+ */
209
+
210
+ /** dispose of this message channel.
211
+ * After calling dispose(), the channel is no longer usable.
212
+ * You may create a new channel of the same type if needed via the DeviceManager.
213
+ * @example
214
+ * channel.dispose();
215
+ */
216
+ dispose() {
217
+ return noop("MessageChannel.dispose");
218
+ }
219
+ }
102
220
 
103
221
  /**
104
222
  * @module
@@ -110,6 +228,14 @@ export class DeviceManager extends EventTarget {
110
228
  * await deviceManager.clearWifi();
111
229
  * deviceManager.reboot();
112
230
  *
231
+ * // Message channel example
232
+ * const channels = deviceManager.getAvailableMessageChannels();
233
+ * const channel = deviceManager.createMessageChannel(deviceManager.DeviceMessageChannelType.SZE_HOST_APP);
234
+ * channel.addEventListener('message', (event) => {
235
+ * console.log(`Received: ${event.message}`);
236
+ * });
237
+ * await channel.sendMessage("Hello Senza Embedded host application");
238
+ *
113
239
  * @return {DeviceManager} pointer to the DeviceManager singleton
114
240
  */
115
241
  "needed for the module doc comment to be recognized";
@@ -16,9 +16,9 @@ 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 state (e.g., from autoBackground feature).<br>
19
+ * Currently only fired when transitioning to BACKGROUND and SUSPENDED state (e.g., from autoBackground feature).<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.
21
+ * Can be used to prevent automatic transitions to background state when using autoBackground feature, or for suspneded from autoSuspended feature .
22
22
  * @property {UiState} state - Indicates the target state the lifecycle is trying to transition to.
23
23
  * @property {boolean} cancelable - true, indicating the event can be cancelled using preventDefault()
24
24
  * @example
@@ -87,13 +87,15 @@ class Lifecycle extends EventTarget {
87
87
  * @property {string} IN_TRANSITION_TO_FOREGROUND - ui is about to be displayed
88
88
  * @property {string} BACKGROUND - remote player is playing (full screen playback is displayed)
89
89
  * @property {string} IN_TRANSITION_TO_BACKGROUND - remote player is about to be playing
90
+ * @property {string} SUSPENDED - application is suspended. This state is used for the beforestatechange event only.
90
91
  */
91
92
  UiState = Object.freeze({
92
93
  UNKNOWN: "unknown",
93
94
  FOREGROUND: "foreground",
94
95
  IN_TRANSITION_TO_FOREGROUND: "inTransitionToForeground",
95
96
  BACKGROUND: "background",
96
- IN_TRANSITION_TO_BACKGROUND: "inTransitionToBackground"
97
+ IN_TRANSITION_TO_BACKGROUND: "inTransitionToBackground",
98
+ SUSPENDED: "suspended"
97
99
  });
98
100
 
99
101
  /**
@@ -1 +1 @@
1
- export const version = "4.4.6";
1
+ export const version = "4.4.8";