senza-sdk 4.4.4 → 4.4.6
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/dist/bundle.js +1 -1
- package/dist/implementation.bundle.js +1 -1
- package/package.json +8 -7
- package/src/implementation/api.js +2 -0
- package/src/implementation/backgroundRenderControl.js +113 -0
- package/src/implementation/lifecycle.js +367 -23
- package/src/interface/lifecycle.js +48 -5
- package/src/interface/version.js +1 -1
|
@@ -17,9 +17,15 @@ 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 =
|
|
21
|
-
const DEFAULT_AUTO_BACKGROUND_UI_DELAY =
|
|
22
|
-
const DEFAULT_AUTO_BACKGROUND_ENABLED =
|
|
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;
|
|
23
|
+
// Default values for autoSuspend settings
|
|
24
|
+
const DEFAULT_AUTO_SUSPEND_ENABLED = false;
|
|
25
|
+
const DEFAULT_AUTO_SUSPEND_PLAYING_DELAY = 60;
|
|
26
|
+
const DEFAULT_AUTO_SUSPEND_IDLE_DELAY = 60;
|
|
27
|
+
// Session storage key for tracking timer-triggered background state
|
|
28
|
+
const BACKGROUND_TIMER_KEY = "senzaSDK_backgroundTriggeredByTimer";
|
|
23
29
|
|
|
24
30
|
class Lifecycle extends LifecycleInterface {
|
|
25
31
|
constructor() {
|
|
@@ -60,8 +66,89 @@ class Lifecycle extends LifecycleInterface {
|
|
|
60
66
|
* @private
|
|
61
67
|
*/
|
|
62
68
|
this._autoBackgroundOnUIDelay = DEFAULT_AUTO_BACKGROUND_UI_DELAY;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Timestamp of the last user activity
|
|
72
|
+
* @type {number}
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
this._lastActivityTimestamp = Date.now();
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Auto suspend configuration
|
|
79
|
+
* @type {Object}
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
this._autoSuspend = {
|
|
83
|
+
enabled: DEFAULT_AUTO_SUSPEND_ENABLED,
|
|
84
|
+
timeout: {
|
|
85
|
+
playing: DEFAULT_AUTO_SUSPEND_PLAYING_DELAY,
|
|
86
|
+
idle: DEFAULT_AUTO_SUSPEND_IDLE_DELAY
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Timer for auto suspend functionality
|
|
92
|
+
* @type {number|null}
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
this._suspendCountdown = null;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Timestamp when the application entered background state
|
|
99
|
+
* @type {number}
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
this._backgroundTimestamp = Date.now();
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Flag to track if suspend was triggered by timer
|
|
106
|
+
* @type {boolean}
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
this._isSuspendTriggeredByTimer = false;
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @private Gets the background triggered by timer flag from sessionStorage
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
get _isBackgroundTriggeredByTimer() {
|
|
118
|
+
try {
|
|
119
|
+
const value = typeof window !== "undefined" && window.sessionStorage ?
|
|
120
|
+
window.sessionStorage.getItem(BACKGROUND_TIMER_KEY) : null;
|
|
121
|
+
return value === "true";
|
|
122
|
+
} catch (error) {
|
|
123
|
+
sdkLogger.warn("Failed to read _isBackgroundTriggeredByTimer from sessionStorage:", error);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
63
126
|
}
|
|
64
127
|
|
|
128
|
+
/**
|
|
129
|
+
* @private Sets the background triggered by timer flag in sessionStorage
|
|
130
|
+
* @param {boolean} value
|
|
131
|
+
*/
|
|
132
|
+
set _isBackgroundTriggeredByTimer(value) {
|
|
133
|
+
try {
|
|
134
|
+
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
135
|
+
if (value) {
|
|
136
|
+
window.sessionStorage.setItem(BACKGROUND_TIMER_KEY, "true");
|
|
137
|
+
} else {
|
|
138
|
+
window.sessionStorage.removeItem(BACKGROUND_TIMER_KEY);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
sdkLogger.warn("Failed to write _backgroundTriggeredByTimer to sessionStorage:", error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// List of event types that should use the _eventManager
|
|
147
|
+
static _waitForListenersEvents = [
|
|
148
|
+
"userdisconnected","beforestatechange"
|
|
149
|
+
// Add more event types here if needed in the future
|
|
150
|
+
];
|
|
151
|
+
|
|
65
152
|
/**
|
|
66
153
|
* @private Initialize the lifecycle
|
|
67
154
|
* @param {Object} uiStreamerSettings - UI-streamer portion of the settings taken from session info
|
|
@@ -70,6 +157,11 @@ class Lifecycle extends LifecycleInterface {
|
|
|
70
157
|
* @param {Object} [uiStreamerSettings.autoBackground.timeout] - Timeout settings
|
|
71
158
|
* @param {number|false} [uiStreamerSettings.autoBackground.timeout.playing=30] - Timeout in seconds when video is playing, false to disable
|
|
72
159
|
* @param {number|false} [uiStreamerSettings.autoBackground.timeout.idle=false] - Timeout in seconds when in UI mode, false to disable
|
|
160
|
+
* @param {Object} [uiStreamerSettings.autoSuspend] - Auto suspend mode configuration
|
|
161
|
+
* @param {boolean} [uiStreamerSettings.autoSuspend.enabled=false] - Enable/disable auto suspend
|
|
162
|
+
* @param {Object} [uiStreamerSettings.autoSuspend.timeout] - Timeout settings
|
|
163
|
+
* @param {number} [uiStreamerSettings.autoSuspend.timeout.playing=60] - Timeout in seconds when video is playing
|
|
164
|
+
* @param {number} [uiStreamerSettings.autoSuspend.timeout.idle=60] - Timeout in seconds when in idle mode
|
|
73
165
|
* @param {number} [uiStreamerSettings.remotePlayerConfirmationTimeout=5000] - Timeout in milliseconds for remote player operations
|
|
74
166
|
* @param {number} [uiStreamerSettings.remotePlayerApiVersion=1] - Remote player API version
|
|
75
167
|
*/
|
|
@@ -165,6 +257,13 @@ class Lifecycle extends LifecycleInterface {
|
|
|
165
257
|
autoBackground: uiStreamerSettings.autoBackground
|
|
166
258
|
});
|
|
167
259
|
}
|
|
260
|
+
|
|
261
|
+
// Apply autoSuspend settings if provided
|
|
262
|
+
if (uiStreamerSettings?.autoSuspend) {
|
|
263
|
+
this.configure({
|
|
264
|
+
autoSuspend: uiStreamerSettings.autoSuspend
|
|
265
|
+
});
|
|
266
|
+
}
|
|
168
267
|
}
|
|
169
268
|
}
|
|
170
269
|
|
|
@@ -177,9 +276,20 @@ class Lifecycle extends LifecycleInterface {
|
|
|
177
276
|
const event = new Event("onstatechange");
|
|
178
277
|
event.state = e.detail;
|
|
179
278
|
this._state = event.state;
|
|
180
|
-
if (this._isAutoBackgroundEnabled() && this.
|
|
279
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
181
280
|
this._startCountdown();
|
|
182
281
|
}
|
|
282
|
+
// Track timestamp when entering background state
|
|
283
|
+
if (this._state === this.UiState.BACKGROUND) {
|
|
284
|
+
this._backgroundTimestamp = Date.now();
|
|
285
|
+
}
|
|
286
|
+
// Start suspend countdown when entering background state
|
|
287
|
+
if (this._isAutoSuspendEnabled() && this._state === this.UiState.BACKGROUND) {
|
|
288
|
+
this._startSuspendCountdown();
|
|
289
|
+
} else {
|
|
290
|
+
// Stop suspend countdown when leaving background state
|
|
291
|
+
this._stopSuspendCountdown();
|
|
292
|
+
}
|
|
183
293
|
this.dispatchEvent(event);
|
|
184
294
|
});
|
|
185
295
|
|
|
@@ -201,9 +311,14 @@ class Lifecycle extends LifecycleInterface {
|
|
|
201
311
|
});
|
|
202
312
|
|
|
203
313
|
typeof document !== "undefined" && document.addEventListener("keydown", () => {
|
|
314
|
+
// Track the timestamp of the last user activity
|
|
315
|
+
this._lastActivityTimestamp = Date.now();
|
|
316
|
+
|
|
204
317
|
if (this._isAutoBackgroundEnabled()) {
|
|
205
|
-
|
|
206
|
-
|
|
318
|
+
|
|
319
|
+
if (this._isBackgroundTriggeredByTimer && (this.state === this.UiState.BACKGROUND ||
|
|
320
|
+
this.state === this.UiState.IN_TRANSITION_TO_BACKGROUND)) {
|
|
321
|
+
sdkLogger.log("Moving to foreground due to user interaction after auto background");
|
|
207
322
|
this.moveToForeground();
|
|
208
323
|
} else {
|
|
209
324
|
this._startCountdown();
|
|
@@ -213,14 +328,27 @@ class Lifecycle extends LifecycleInterface {
|
|
|
213
328
|
|
|
214
329
|
// Add playModeChange listener
|
|
215
330
|
remotePlayer.addEventListener("playModeChange", (event) => {
|
|
216
|
-
if (this._isAutoBackgroundEnabled() && this.
|
|
331
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
217
332
|
sdkLogger.log("Resetting auto background timer due to play mode change", event.detail.isPlaying);
|
|
218
333
|
this._startCountdown(event.detail.isPlaying);
|
|
219
334
|
}
|
|
335
|
+
// Also restart suspend countdown if in background state
|
|
336
|
+
if (this._isAutoSuspendEnabled() && this._state === this.UiState.BACKGROUND) {
|
|
337
|
+
sdkLogger.log("Resetting auto suspend timer due to play mode change", event.detail.isPlaying);
|
|
338
|
+
this._startSuspendCountdown(event.detail.isPlaying);
|
|
339
|
+
}
|
|
220
340
|
});
|
|
221
341
|
sdkLogger.log("[Lifecycle] Added event listeners for system events");
|
|
222
342
|
}
|
|
223
343
|
|
|
344
|
+
/**
|
|
345
|
+
* @private Checks if the UI is in foreground or transitioning to foreground.
|
|
346
|
+
* @returns {boolean}
|
|
347
|
+
*/
|
|
348
|
+
_isForegroundOrTransitioning() {
|
|
349
|
+
return this._state === this.UiState.FOREGROUND || this._state === this.UiState.IN_TRANSITION_TO_FOREGROUND;
|
|
350
|
+
}
|
|
351
|
+
|
|
224
352
|
/**
|
|
225
353
|
* @private Checks if auto background is enabled including overrides.
|
|
226
354
|
* @returns {boolean}
|
|
@@ -253,6 +381,30 @@ class Lifecycle extends LifecycleInterface {
|
|
|
253
381
|
return this._autoBackgroundOnUIDelay;
|
|
254
382
|
}
|
|
255
383
|
|
|
384
|
+
/**
|
|
385
|
+
* @private Checks if auto suspend is enabled.
|
|
386
|
+
* @returns {boolean}
|
|
387
|
+
*/
|
|
388
|
+
_isAutoSuspendEnabled() {
|
|
389
|
+
return this._autoSuspend.enabled;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @private Gets the auto suspend playing delay.
|
|
394
|
+
* @returns {number}
|
|
395
|
+
*/
|
|
396
|
+
_getAutoSuspendOnPlayingDelay() {
|
|
397
|
+
return this._autoSuspend.timeout.playing;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* @private Gets the auto suspend idle delay.
|
|
402
|
+
* @returns {number}
|
|
403
|
+
*/
|
|
404
|
+
_getAutoSuspendOnIdleDelay() {
|
|
405
|
+
return this._autoSuspend.timeout.idle;
|
|
406
|
+
}
|
|
407
|
+
|
|
256
408
|
configure(config) {
|
|
257
409
|
if (config?.autoBackground) {
|
|
258
410
|
const { enabled, timeout } = config.autoBackground;
|
|
@@ -284,12 +436,45 @@ class Lifecycle extends LifecycleInterface {
|
|
|
284
436
|
}
|
|
285
437
|
|
|
286
438
|
// Start or stop countdown based on new configuration
|
|
287
|
-
if (this._isAutoBackgroundEnabled()) {
|
|
439
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
288
440
|
this._startCountdown();
|
|
289
441
|
} else {
|
|
290
442
|
this._stopCountdown();
|
|
291
443
|
}
|
|
292
444
|
}
|
|
445
|
+
|
|
446
|
+
if (config?.autoSuspend) {
|
|
447
|
+
const { enabled, timeout } = config.autoSuspend;
|
|
448
|
+
|
|
449
|
+
// Update enabled state
|
|
450
|
+
if (typeof enabled === "boolean") {
|
|
451
|
+
this._autoSuspend.enabled = enabled;
|
|
452
|
+
}
|
|
453
|
+
// Update timeouts
|
|
454
|
+
if (timeout) {
|
|
455
|
+
if (timeout.playing !== undefined) {
|
|
456
|
+
if (typeof timeout.playing === "number") {
|
|
457
|
+
this._autoSuspend.timeout.playing = timeout.playing;
|
|
458
|
+
} else {
|
|
459
|
+
sdkLogger.warn("Invalid autoSuspend.timeout.playing value, expected number");
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (timeout.idle !== undefined) {
|
|
463
|
+
if (typeof timeout.idle === "number") {
|
|
464
|
+
this._autoSuspend.timeout.idle = timeout.idle;
|
|
465
|
+
} else {
|
|
466
|
+
sdkLogger.warn("Invalid autoSuspend.timeout.idle value, expected number");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Start or stop suspend countdown based on new configuration
|
|
472
|
+
if (this._isAutoSuspendEnabled() && this._state === this.UiState.BACKGROUND) {
|
|
473
|
+
this._startSuspendCountdown();
|
|
474
|
+
} else {
|
|
475
|
+
this._stopSuspendCountdown();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
293
478
|
}
|
|
294
479
|
|
|
295
480
|
getConfiguration() {
|
|
@@ -300,6 +485,13 @@ class Lifecycle extends LifecycleInterface {
|
|
|
300
485
|
playing: this._autoBackgroundOnVideoDelay <= 0 ? false : this._autoBackgroundOnVideoDelay,
|
|
301
486
|
idle: this._autoBackgroundOnUIDelay <= 0 ? false : this._autoBackgroundOnUIDelay
|
|
302
487
|
}
|
|
488
|
+
},
|
|
489
|
+
autoSuspend: {
|
|
490
|
+
enabled: this._autoSuspend.enabled,
|
|
491
|
+
timeout: {
|
|
492
|
+
playing: this._autoSuspend.timeout.playing,
|
|
493
|
+
idle: this._autoSuspend.timeout.idle
|
|
494
|
+
}
|
|
303
495
|
}
|
|
304
496
|
};
|
|
305
497
|
}
|
|
@@ -364,7 +556,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
364
556
|
|
|
365
557
|
set autoBackground(enabled) {
|
|
366
558
|
this._autoBackground = enabled;
|
|
367
|
-
if (this._isAutoBackgroundEnabled()) {
|
|
559
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
368
560
|
this._startCountdown();
|
|
369
561
|
} else {
|
|
370
562
|
this._stopCountdown();
|
|
@@ -377,7 +569,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
377
569
|
|
|
378
570
|
set autoBackgroundDelay(delay) {
|
|
379
571
|
this._autoBackgroundOnVideoDelay = delay;
|
|
380
|
-
if (this._isAutoBackgroundEnabled() && remotePlayer._isPlaying) {
|
|
572
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning() && remotePlayer._isPlaying) {
|
|
381
573
|
this._startCountdown();
|
|
382
574
|
}
|
|
383
575
|
}
|
|
@@ -388,7 +580,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
388
580
|
|
|
389
581
|
set autoBackgroundOnUIDelay(delay) {
|
|
390
582
|
this._autoBackgroundOnUIDelay = delay;
|
|
391
|
-
if (this._isAutoBackgroundEnabled() && !remotePlayer._isPlaying) {
|
|
583
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning() && !remotePlayer._isPlaying) {
|
|
392
584
|
this._startCountdown();
|
|
393
585
|
}
|
|
394
586
|
}
|
|
@@ -408,9 +600,25 @@ class Lifecycle extends LifecycleInterface {
|
|
|
408
600
|
// Only start countdown if delay is positive
|
|
409
601
|
if (timeoutDelay > 0) {
|
|
410
602
|
sdkLogger.debug("Starting countdown timeout", timeoutDelay, isPlaying ? "(video)" : "(UI)");
|
|
411
|
-
this._countdown = setTimeout(() => {
|
|
603
|
+
this._countdown = setTimeout(async () => {
|
|
412
604
|
sdkLogger.debug("Countdown timeout reached, moving to background");
|
|
413
|
-
|
|
605
|
+
|
|
606
|
+
// Create the event with cancelable: true
|
|
607
|
+
const event = new Event("beforestatechange", { cancelable: true });
|
|
608
|
+
event.state = this.UiState.BACKGROUND;
|
|
609
|
+
// Use the event manager to dispatch the event and wait for all listeners
|
|
610
|
+
await this._eventManager.dispatch("beforestatechange", event);
|
|
611
|
+
// Check if any listener called preventDefault()
|
|
612
|
+
if (event.defaultPrevented) {
|
|
613
|
+
sdkLogger.info("moveToBackground was prevented by a listener");
|
|
614
|
+
// Restart the countdown since the move was cancelled
|
|
615
|
+
if (this._isForegroundOrTransitioning()) {
|
|
616
|
+
this._startCountdown();
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
this.moveToBackground(true);
|
|
620
|
+
}
|
|
621
|
+
|
|
414
622
|
}, timeoutDelay * 1000);
|
|
415
623
|
} else {
|
|
416
624
|
sdkLogger.debug("Countdown not started - delay is negative or zero");
|
|
@@ -424,8 +632,70 @@ class Lifecycle extends LifecycleInterface {
|
|
|
424
632
|
if (this._countdown) {
|
|
425
633
|
sdkLogger.debug("Stopping countdown timer");
|
|
426
634
|
clearTimeout(this._countdown);
|
|
635
|
+
this._countdown = null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* @private Start the suspend countdown timer when in background state
|
|
641
|
+
*/
|
|
642
|
+
_startSuspendCountdown(isPlaying = remotePlayer._isPlaying) {
|
|
643
|
+
this._stopSuspendCountdown();
|
|
644
|
+
|
|
645
|
+
// Only start if we're in background state and auto suspend is enabled
|
|
646
|
+
if (this._state !== this.UiState.BACKGROUND || !this._isAutoSuspendEnabled()) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const timeoutDelay = isPlaying ? this._getAutoSuspendOnPlayingDelay() : this._getAutoSuspendOnIdleDelay();
|
|
651
|
+
|
|
652
|
+
// Only start countdown if delay is positive
|
|
653
|
+
if (timeoutDelay > 0) {
|
|
654
|
+
sdkLogger.debug("Starting suspend countdown timeout", timeoutDelay, isPlaying ? "(playing)" : "(idle)");
|
|
655
|
+
this._suspendCountdown = setTimeout(async () => {
|
|
656
|
+
sdkLogger.debug("Suspend countdown timeout reached, moving to suspended");
|
|
657
|
+
if (this._state === this.UiState.BACKGROUND) {
|
|
658
|
+
// Create the event with cancelable: true
|
|
659
|
+
const event = new Event("beforestatechange", { cancelable: true });
|
|
660
|
+
event.state = this.UiState.SUSPENDED;
|
|
661
|
+
// Use the event manager to dispatch the event and wait for all listeners
|
|
662
|
+
await this._eventManager.dispatch("beforestatechange", event);
|
|
663
|
+
// Check if any listener called preventDefault()
|
|
664
|
+
if (event.defaultPrevented) {
|
|
665
|
+
sdkLogger.info("moveToSuspended was prevented by a listener");
|
|
666
|
+
// Restart the suspend countdown since the move was cancelled
|
|
667
|
+
this._startSuspendCountdown();
|
|
668
|
+
} else {
|
|
669
|
+
try {
|
|
670
|
+
// Set flag to indicate suspend was triggered by timer
|
|
671
|
+
this._isSuspendTriggeredByTimer = true;
|
|
672
|
+
await this.moveToSuspended();
|
|
673
|
+
} catch (error) {
|
|
674
|
+
sdkLogger.error("Failed to move to suspended:", error);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
} else {
|
|
679
|
+
sdkLogger.warn("suspend timer was called while not in background");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
}, timeoutDelay * 1000);
|
|
683
|
+
} else {
|
|
684
|
+
sdkLogger.debug("Suspend countdown not started - delay is zero or negative");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* @private Stop the suspend countdown timer
|
|
690
|
+
*/
|
|
691
|
+
_stopSuspendCountdown() {
|
|
692
|
+
if (this._suspendCountdown) {
|
|
693
|
+
sdkLogger.debug("Stopping suspend countdown timer");
|
|
694
|
+
clearTimeout(this._suspendCountdown);
|
|
695
|
+
this._suspendCountdown = null;
|
|
696
|
+
// Reset the timer flag when stopping countdown
|
|
697
|
+
this._isSuspendTriggeredByTimer = false;
|
|
427
698
|
}
|
|
428
|
-
this._countdown = null;
|
|
429
699
|
}
|
|
430
700
|
|
|
431
701
|
/**
|
|
@@ -457,9 +727,15 @@ class Lifecycle extends LifecycleInterface {
|
|
|
457
727
|
}
|
|
458
728
|
|
|
459
729
|
moveToForeground() {
|
|
730
|
+
// Reset activity timestamp when moving to foreground
|
|
731
|
+
this._lastActivityTimestamp = Date.now();
|
|
732
|
+
|
|
733
|
+
// Clear the timer-triggered background flag when moving to foreground
|
|
734
|
+
this._isBackgroundTriggeredByTimer = false;
|
|
735
|
+
|
|
460
736
|
if (window.cefQuery) {
|
|
461
737
|
const inTransition = this._isInTransition();
|
|
462
|
-
if (inTransition || this.
|
|
738
|
+
if (inTransition || this._isForegroundOrTransitioning()) {
|
|
463
739
|
sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${inTransition}`);
|
|
464
740
|
return Promise.resolve(false);
|
|
465
741
|
}
|
|
@@ -537,6 +813,16 @@ class Lifecycle extends LifecycleInterface {
|
|
|
537
813
|
}
|
|
538
814
|
|
|
539
815
|
_moveToBackground() {
|
|
816
|
+
// Report metrics for time since last user activity when moving to background
|
|
817
|
+
|
|
818
|
+
const duration = (Date.now() - this._lastActivityTimestamp)/1000;
|
|
819
|
+
|
|
820
|
+
sdkLogger.metrics({
|
|
821
|
+
type: "backgroundTransitions",
|
|
822
|
+
isAutoBackground: this._isBackgroundTriggeredByTimer,
|
|
823
|
+
isPlaying: remotePlayer._isPlaying,
|
|
824
|
+
duration
|
|
825
|
+
});
|
|
540
826
|
if (window.cefQuery) {
|
|
541
827
|
// If audio sync is disabled, we only need to sync before remote player starts playing
|
|
542
828
|
if (!isAudioSyncConfigured()) {
|
|
@@ -600,6 +886,8 @@ class Lifecycle extends LifecycleInterface {
|
|
|
600
886
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
601
887
|
logger.withFields({ duration }).log(`play failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
602
888
|
this._inTransitionToBackground = false;
|
|
889
|
+
// Clear the timer flag even if the transition fails
|
|
890
|
+
this._isBackgroundTriggeredByTimer = false;
|
|
603
891
|
timerId = clearTimer(timerId);
|
|
604
892
|
reject(new SenzaError(code, msg));
|
|
605
893
|
}
|
|
@@ -610,6 +898,8 @@ class Lifecycle extends LifecycleInterface {
|
|
|
610
898
|
timerId = setTimeout(() => {
|
|
611
899
|
logger.log(`play reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
612
900
|
this._inTransitionToBackground = false;
|
|
901
|
+
// Clear the timer flag even if the transition times out
|
|
902
|
+
this._isBackgroundTriggeredByTimer = false;
|
|
613
903
|
window.cefQueryCancel(queryId);
|
|
614
904
|
reject(new SenzaError(6000, `play reached timeout of ${timeout} ms`));
|
|
615
905
|
}, timeout, queryId);
|
|
@@ -620,7 +910,11 @@ class Lifecycle extends LifecycleInterface {
|
|
|
620
910
|
return Promise.resolve(false);
|
|
621
911
|
}
|
|
622
912
|
|
|
623
|
-
moveToBackground() {
|
|
913
|
+
moveToBackground(isTriggeredByTimer = false) {
|
|
914
|
+
|
|
915
|
+
// If the background transition is triggered by the auto background timer, set the flag
|
|
916
|
+
this._isBackgroundTriggeredByTimer = isTriggeredByTimer;
|
|
917
|
+
|
|
624
918
|
if (window.cefQuery) {
|
|
625
919
|
const inTransition = this._isInTransition();
|
|
626
920
|
if (inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
@@ -771,23 +1065,73 @@ class Lifecycle extends LifecycleInterface {
|
|
|
771
1065
|
return Promise.reject("disconnect is not supported if NOT running e2e");
|
|
772
1066
|
}
|
|
773
1067
|
|
|
1068
|
+
moveToSuspended() {
|
|
1069
|
+
// Check if the current state is BACKGROUND
|
|
1070
|
+
if (this._state !== this.UiState.BACKGROUND) {
|
|
1071
|
+
const errorMsg = `moveToSuspended can only be called from BACKGROUND state. Current state: ${this._state}`;
|
|
1072
|
+
sdkLogger.error(errorMsg);
|
|
1073
|
+
return Promise.reject(errorMsg);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Report metrics for time between background and suspend
|
|
1077
|
+
const duration = (Date.now() - this._backgroundTimestamp) / 1000;
|
|
1078
|
+
|
|
1079
|
+
sdkLogger.metrics({
|
|
1080
|
+
type: "suspendTransitions",
|
|
1081
|
+
isAutoSuspend: this._isSuspendTriggeredByTimer,
|
|
1082
|
+
isPlaying: remotePlayer._isPlaying,
|
|
1083
|
+
duration
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
// Reset the timer flag after reporting metrics
|
|
1087
|
+
const reason = this._isSuspendTriggeredByTimer ? "timer" : "appInitiated";
|
|
1088
|
+
this._isSuspendTriggeredByTimer = false;
|
|
1089
|
+
|
|
1090
|
+
if (window.cefQuery) {
|
|
1091
|
+
return new Promise((resolve, reject) => {
|
|
1092
|
+
const FCID = getFCID();
|
|
1093
|
+
const logger = sdkLogger.withFields({ FCID });
|
|
1094
|
+
const message = {
|
|
1095
|
+
type: "suspend",
|
|
1096
|
+
reason,
|
|
1097
|
+
fcid: FCID
|
|
1098
|
+
};
|
|
1099
|
+
const request = { target: "TC",
|
|
1100
|
+
waitForResponse: false,
|
|
1101
|
+
message: JSON.stringify(message) };
|
|
1102
|
+
|
|
1103
|
+
logger.log("moveToSuspended: sending suspend message");
|
|
1104
|
+
window.cefQuery({
|
|
1105
|
+
request: JSON.stringify(request),
|
|
1106
|
+
persistent: false,
|
|
1107
|
+
onSuccess: () => {
|
|
1108
|
+
logger.log("moveToSuspended request successfully sent");
|
|
1109
|
+
resolve(true);
|
|
1110
|
+
},
|
|
1111
|
+
onFailure: (code, msg) => {
|
|
1112
|
+
logger.error(`moveToSuspended failed: ${code} ${msg}`);
|
|
1113
|
+
reject(new SenzaError(code, msg));
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
sdkLogger.warn("moveToSuspended is not supported if NOT running e2e");
|
|
1119
|
+
return Promise.reject("moveToSuspended is not supported if NOT running e2e");
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
|
|
774
1123
|
addEventListener(type, listener, options) {
|
|
775
|
-
if (type
|
|
776
|
-
// Use the event manager for userdisconnected events
|
|
1124
|
+
if (Lifecycle._waitForListenersEvents.includes(type)) {
|
|
777
1125
|
this._eventManager.addEventListener(type, listener);
|
|
778
1126
|
} else {
|
|
779
|
-
// For all other event types, use the parent class implementation
|
|
780
1127
|
super.addEventListener(type, listener, options);
|
|
781
1128
|
}
|
|
782
1129
|
}
|
|
783
1130
|
|
|
784
|
-
|
|
785
1131
|
removeEventListener(type, listener, options) {
|
|
786
|
-
if (type
|
|
787
|
-
// Use the event manager for userdisconnected events
|
|
1132
|
+
if (Lifecycle._waitForListenersEvents.includes(type)) {
|
|
788
1133
|
this._eventManager.removeEventListener(type, listener);
|
|
789
1134
|
} else {
|
|
790
|
-
// For all other event types, use the parent class implementation
|
|
791
1135
|
super.removeEventListener(type, listener, options);
|
|
792
1136
|
}
|
|
793
1137
|
}
|
|
@@ -13,6 +13,23 @@ import {
|
|
|
13
13
|
* });
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @event Lifecycle#beforestatechange
|
|
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>
|
|
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.
|
|
22
|
+
* @property {UiState} state - Indicates the target state the lifecycle is trying to transition to.
|
|
23
|
+
* @property {boolean} cancelable - true, indicating the event can be cancelled using preventDefault()
|
|
24
|
+
* @example
|
|
25
|
+
* lifecycle.addEventListener("beforestatechange", (e) => {
|
|
26
|
+
* if (e.state === lifecycle.UiState.BACKGROUND && userIsInteracting) {
|
|
27
|
+
* // Prevent transition to background
|
|
28
|
+
* e.preventDefault();
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
*/
|
|
32
|
+
|
|
16
33
|
/**
|
|
17
34
|
* @event Lifecycle#userinactivity
|
|
18
35
|
* @description Fired after the ui has been inactive (i.e. no key presses) for a configurable number of seconds.<br>
|
|
@@ -83,10 +100,15 @@ class Lifecycle extends EventTarget {
|
|
|
83
100
|
* Configure lifecycle settings
|
|
84
101
|
* @param {Object} config - Configuration object
|
|
85
102
|
* @param {Object} [config.autoBackground] - Auto background settings
|
|
86
|
-
* @param {boolean} [config.autoBackground.enabled] - Enable/disable auto background
|
|
103
|
+
* @param {boolean} [config.autoBackground.enabled=true] - Enable/disable auto background
|
|
87
104
|
* @param {Object} [config.autoBackground.timeout] - Timeout settings
|
|
88
105
|
* @param {number|false} [config.autoBackground.timeout.playing=30] - Timeout in seconds when video is playing, false to disable
|
|
89
|
-
* @param {number|false} [config.autoBackground.timeout.idle=
|
|
106
|
+
* @param {number|false} [config.autoBackground.timeout.idle=30] - Timeout in seconds when in UI mode, false to disable
|
|
107
|
+
* @param {Object} [config.autoSuspend] - Auto suspend settings
|
|
108
|
+
* @param {boolean} [config.autoSuspend.enabled=false] - Enable/disable auto suspend
|
|
109
|
+
* @param {Object} [config.autoSuspend.timeout] - Timeout settings
|
|
110
|
+
* @param {number} [config.autoSuspend.timeout.playing=60] - Timeout in seconds when video is playing before moving to suspended state
|
|
111
|
+
* @param {number} [config.autoSuspend.timeout.idle=60] - Timeout in seconds when in background mode before moving to suspended state
|
|
90
112
|
*/
|
|
91
113
|
configure(config) {
|
|
92
114
|
noop("lifecycle.configure", config);
|
|
@@ -97,9 +119,12 @@ class Lifecycle extends EventTarget {
|
|
|
97
119
|
* @returns {Object} The current configuration object
|
|
98
120
|
* @example
|
|
99
121
|
* const config = lifecycle.getConfiguration();
|
|
100
|
-
* console.log(config.autoBackground.enabled); // true
|
|
122
|
+
* console.log(config.autoBackground.enabled); // true
|
|
101
123
|
* console.log(config.autoBackground.timeout.playing); // 30
|
|
102
|
-
* console.log(config.autoBackground.timeout.idle); //
|
|
124
|
+
* console.log(config.autoBackground.timeout.idle); // 30
|
|
125
|
+
* console.log(config.autoSuspend.enabled); // false
|
|
126
|
+
* console.log(config.autoSuspend.timeout.playing); // 60
|
|
127
|
+
* console.log(config.autoSuspend.timeout.idle); // 60
|
|
103
128
|
*/
|
|
104
129
|
getConfiguration() {
|
|
105
130
|
return {
|
|
@@ -109,6 +134,13 @@ class Lifecycle extends EventTarget {
|
|
|
109
134
|
playing: 0,
|
|
110
135
|
idle: 0
|
|
111
136
|
}
|
|
137
|
+
},
|
|
138
|
+
autoSuspend: {
|
|
139
|
+
enabled: false,
|
|
140
|
+
timeout: {
|
|
141
|
+
playing: 0,
|
|
142
|
+
idle: 0
|
|
143
|
+
}
|
|
112
144
|
}
|
|
113
145
|
};
|
|
114
146
|
}
|
|
@@ -262,10 +294,21 @@ class Lifecycle extends EventTarget {
|
|
|
262
294
|
return noop("lifecycle.disconnect");
|
|
263
295
|
}
|
|
264
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Use this api to move the application to suspended state.
|
|
299
|
+
* This method can only be called when the application is in BACKGROUND state.
|
|
300
|
+
* @return {Promise} Promise which is resolved when the suspend command has been successfully sent.
|
|
301
|
+
* Failure to process the suspend command will result in the promise being rejected.
|
|
302
|
+
* @alpha API has not yet been released
|
|
303
|
+
*/
|
|
304
|
+
moveToSuspended() {
|
|
305
|
+
return noop("lifecycle.moveToSuspended");
|
|
306
|
+
}
|
|
307
|
+
|
|
265
308
|
/**
|
|
266
309
|
* Add event listener for lifecycle events
|
|
267
310
|
* @param {string} type - The event type to listen for
|
|
268
|
-
* @param {Function} listener - The callback function. Listeners for 'userdisconnected' events should return a promise to ensure the event is processed before the application exits.
|
|
311
|
+
* @param {Function} listener - The callback function. Listeners for 'userdisconnected' and 'beforestatechange' events should return a promise to ensure the event is processed before the application exits.
|
|
269
312
|
* @param {Object} options - Event listener options
|
|
270
313
|
*/
|
|
271
314
|
addEventListener(type, listener, options) {
|
package/src/interface/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "4.4.
|
|
1
|
+
export const version = "4.4.6";
|