senza-sdk 4.4.4 → 4.4.5
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 +42 -17
- package/src/interface/lifecycle.js +18 -1
- package/src/interface/version.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senza-sdk",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.5",
|
|
4
4
|
"main": "./src/api.js",
|
|
5
5
|
"description": "API for Senza application",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,17 +23,18 @@
|
|
|
23
23
|
"version:output": "echo 'export const version = \"'$npm_package_version'\";' > src/interface/version.js"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@babel/cli": "^7.
|
|
27
|
-
"@babel/core": "7.
|
|
28
|
-
"@babel/plugin-transform-modules-commonjs": "^7.
|
|
29
|
-
"@babel/preset-env": "^7.
|
|
26
|
+
"@babel/cli": "^7.22.0",
|
|
27
|
+
"@babel/core": "^7.22.0",
|
|
28
|
+
"@babel/plugin-transform-modules-commonjs": "^7.22.0",
|
|
29
|
+
"@babel/preset-env": "^7.22.0",
|
|
30
30
|
"@eslint/eslintrc": "^3.3.0",
|
|
31
31
|
"@eslint/js": "^9.22.0",
|
|
32
|
-
"babel-jest": "^
|
|
32
|
+
"babel-jest": "^30.2.0",
|
|
33
33
|
"eslint": "^9.22.0",
|
|
34
34
|
"eslint-plugin-jest": "^28.11.0",
|
|
35
35
|
"globals": "^16.0.0",
|
|
36
|
-
"jest": "^
|
|
36
|
+
"jest": "^30.2.0",
|
|
37
|
+
"jest-environment-jsdom" : "^30.2.0",
|
|
37
38
|
"jsdoc-to-markdown": "^7.1.1",
|
|
38
39
|
"webpack": "^5.72.1",
|
|
39
40
|
"webpack-cli": "^5.1.4"
|
|
@@ -6,6 +6,7 @@ import { remotePlayer } from "./remotePlayer.js";
|
|
|
6
6
|
import { deviceManager } from "./deviceManager.js";
|
|
7
7
|
import { displayManager } from "./displayManager.js";
|
|
8
8
|
import { messageManager } from "./messageManager.js";
|
|
9
|
+
import { backgroundRenderControl } from "./backgroundRenderControl.js";
|
|
9
10
|
|
|
10
11
|
export { remotePlayer };
|
|
11
12
|
export { lifecycle };
|
|
@@ -141,6 +142,7 @@ export async function init(interfaceApiVersion, showSequenceFunc, initSequenceFu
|
|
|
141
142
|
await remotePlayer._init(sessionInfoObj, triggerEvent);
|
|
142
143
|
alarmManager._init();
|
|
143
144
|
messageManager._init();
|
|
145
|
+
backgroundRenderControl._init();
|
|
144
146
|
await displayManager._init();
|
|
145
147
|
sdkLogger.log("All submodules initialized");
|
|
146
148
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { lifecycle } from "./lifecycle.js";
|
|
2
|
+
import { sdkLogger } from "./utils.js";
|
|
3
|
+
import { sessionInfo } from "./SessionInfo";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal background render control.
|
|
7
|
+
* Listens to lifecycle state changes and disables animation / video rendering when in background mode.
|
|
8
|
+
* Specifically it pauses CSS animations and hides video elements to reduce CPU/GPU load
|
|
9
|
+
*
|
|
10
|
+
* For remote-gpu deployments this reduces the CPU use in background mode from 6 cores to 0.4 cores.
|
|
11
|
+
*
|
|
12
|
+
* Extra CPU can be saved by also overriding requestAnimationFrame and slowing down time.
|
|
13
|
+
* This has the effect of ensuring the segments are downloaded at a slower pace
|
|
14
|
+
* - or not at all if you actually pause video, but these may have more complicated state and audio sync issues.
|
|
15
|
+
* Either way if you slow the decoding speed then we save more CPU by not doing as much SW video decoding,
|
|
16
|
+
* but the bulk is saved by the hidden video elements.
|
|
17
|
+
* See comments on https://jira01.engit.synamedia.com/browse/HSDEV-16827 for more about a PoC / spike that did this.
|
|
18
|
+
*
|
|
19
|
+
* As it stands, the current implementation that gets us 0.4 cores in background mode for remote-gpu is a good compromise of complexity vs benefit.
|
|
20
|
+
* It also roughly matches GPU mode background CPU use (maybe slightly better).
|
|
21
|
+
* GPU mode with this feature enabled (from my testing) saves about 0.15 cores ~0.45 -> ~0.30 cores
|
|
22
|
+
*
|
|
23
|
+
* This module uses the following settings:
|
|
24
|
+
* {
|
|
25
|
+
* "ui-streamer": {
|
|
26
|
+
* "throttleRenderingInBackground": {
|
|
27
|
+
* "enabled": true, // enable or disable the feature
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
class BackgroundRenderControl {
|
|
33
|
+
|
|
34
|
+
/** initialised
|
|
35
|
+
* @type {boolean}
|
|
36
|
+
* @private
|
|
37
|
+
* Whether the instance has been initialized.
|
|
38
|
+
*/
|
|
39
|
+
_initialized = false;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @type {boolean}
|
|
43
|
+
* @private
|
|
44
|
+
* Whether render control is enabled.
|
|
45
|
+
*/
|
|
46
|
+
_enabled = false;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
* @private
|
|
51
|
+
* Whether rendering is currently active.
|
|
52
|
+
*/
|
|
53
|
+
_active = true;
|
|
54
|
+
|
|
55
|
+
/** @type {string|null}
|
|
56
|
+
* @private
|
|
57
|
+
* Stores the last known lifecycle state.
|
|
58
|
+
*/
|
|
59
|
+
_lastState = null;
|
|
60
|
+
|
|
61
|
+
/** @type {Function}
|
|
62
|
+
* @private
|
|
63
|
+
* Event listener for lifecycle state changes.
|
|
64
|
+
*/
|
|
65
|
+
_listener = (e) => this._handleStateChange(e.state);
|
|
66
|
+
|
|
67
|
+
_init() {
|
|
68
|
+
if (this._initialized) return;
|
|
69
|
+
this._enabled = sessionInfo?.sessionInfoObj.settings?.["ui-streamer"]?.throttleRenderingInBackground?.enabled ?? false;
|
|
70
|
+
sdkLogger.log(`Initializing BackgroundRenderControl: enabled=${this._enabled}`);
|
|
71
|
+
if (!this._enabled) return;
|
|
72
|
+
this._lastState = lifecycle.state;
|
|
73
|
+
lifecycle.addEventListener("onstatechange", this._listener);
|
|
74
|
+
this._initialized = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_handleStateChange(state) {
|
|
78
|
+
if (!this._enabled) return;
|
|
79
|
+
if (state === this._lastState) return;
|
|
80
|
+
this._lastState = state;
|
|
81
|
+
try {
|
|
82
|
+
if (state === lifecycle.UiState.FOREGROUND) this._onForeground();
|
|
83
|
+
else if (state === lifecycle.UiState.BACKGROUND) this._onBackground();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
sdkLogger.error("BackgroundRenderControl state handling error", err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_onForeground() {
|
|
90
|
+
if (this._active) return;
|
|
91
|
+
sdkLogger.log("BackgroundRenderControl resume rendering");
|
|
92
|
+
this._active = true;
|
|
93
|
+
const style = document.getElementById("hs-sdk-bg-render-styles");
|
|
94
|
+
if (style) style.remove();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_onBackground() {
|
|
98
|
+
if (!this._active) return;
|
|
99
|
+
sdkLogger.log("BackgroundRenderControl throttle rendering");
|
|
100
|
+
this._active = false;
|
|
101
|
+
if (!document.getElementById("hs-sdk-bg-render-styles")) {
|
|
102
|
+
const style = document.createElement("style");
|
|
103
|
+
style.id = "hs-sdk-bg-render-styles";
|
|
104
|
+
style.textContent = `
|
|
105
|
+
* { animation-play-state: paused !important; transition: none !important; scroll-behavior: auto !important; }
|
|
106
|
+
canvas, video, img { visibility: hidden !important; }
|
|
107
|
+
`;
|
|
108
|
+
document.head.appendChild(style);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const backgroundRenderControl = new BackgroundRenderControl();
|
|
@@ -62,6 +62,12 @@ class Lifecycle extends LifecycleInterface {
|
|
|
62
62
|
this._autoBackgroundOnUIDelay = DEFAULT_AUTO_BACKGROUND_UI_DELAY;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// List of event types that should use the _eventManager
|
|
66
|
+
static _waitForListenersEvents = [
|
|
67
|
+
"userdisconnected","beforestatechange"
|
|
68
|
+
// Add more event types here if needed in the future
|
|
69
|
+
];
|
|
70
|
+
|
|
65
71
|
/**
|
|
66
72
|
* @private Initialize the lifecycle
|
|
67
73
|
* @param {Object} uiStreamerSettings - UI-streamer portion of the settings taken from session info
|
|
@@ -177,7 +183,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
177
183
|
const event = new Event("onstatechange");
|
|
178
184
|
event.state = e.detail;
|
|
179
185
|
this._state = event.state;
|
|
180
|
-
if (this._isAutoBackgroundEnabled() && this.
|
|
186
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
181
187
|
this._startCountdown();
|
|
182
188
|
}
|
|
183
189
|
this.dispatchEvent(event);
|
|
@@ -213,7 +219,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
213
219
|
|
|
214
220
|
// Add playModeChange listener
|
|
215
221
|
remotePlayer.addEventListener("playModeChange", (event) => {
|
|
216
|
-
if (this._isAutoBackgroundEnabled() && this.
|
|
222
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
217
223
|
sdkLogger.log("Resetting auto background timer due to play mode change", event.detail.isPlaying);
|
|
218
224
|
this._startCountdown(event.detail.isPlaying);
|
|
219
225
|
}
|
|
@@ -221,6 +227,14 @@ class Lifecycle extends LifecycleInterface {
|
|
|
221
227
|
sdkLogger.log("[Lifecycle] Added event listeners for system events");
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
/**
|
|
231
|
+
* @private Checks if the UI is in foreground or transitioning to foreground.
|
|
232
|
+
* @returns {boolean}
|
|
233
|
+
*/
|
|
234
|
+
_isForegroundOrTransitioning() {
|
|
235
|
+
return this._state === this.UiState.FOREGROUND || this._state === this.UiState.IN_TRANSITION_TO_FOREGROUND;
|
|
236
|
+
}
|
|
237
|
+
|
|
224
238
|
/**
|
|
225
239
|
* @private Checks if auto background is enabled including overrides.
|
|
226
240
|
* @returns {boolean}
|
|
@@ -284,7 +298,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
284
298
|
}
|
|
285
299
|
|
|
286
300
|
// Start or stop countdown based on new configuration
|
|
287
|
-
if (this._isAutoBackgroundEnabled()) {
|
|
301
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
288
302
|
this._startCountdown();
|
|
289
303
|
} else {
|
|
290
304
|
this._stopCountdown();
|
|
@@ -364,7 +378,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
364
378
|
|
|
365
379
|
set autoBackground(enabled) {
|
|
366
380
|
this._autoBackground = enabled;
|
|
367
|
-
if (this._isAutoBackgroundEnabled()) {
|
|
381
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning()) {
|
|
368
382
|
this._startCountdown();
|
|
369
383
|
} else {
|
|
370
384
|
this._stopCountdown();
|
|
@@ -377,7 +391,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
377
391
|
|
|
378
392
|
set autoBackgroundDelay(delay) {
|
|
379
393
|
this._autoBackgroundOnVideoDelay = delay;
|
|
380
|
-
if (this._isAutoBackgroundEnabled() && remotePlayer._isPlaying) {
|
|
394
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning() && remotePlayer._isPlaying) {
|
|
381
395
|
this._startCountdown();
|
|
382
396
|
}
|
|
383
397
|
}
|
|
@@ -388,7 +402,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
388
402
|
|
|
389
403
|
set autoBackgroundOnUIDelay(delay) {
|
|
390
404
|
this._autoBackgroundOnUIDelay = delay;
|
|
391
|
-
if (this._isAutoBackgroundEnabled() && !remotePlayer._isPlaying) {
|
|
405
|
+
if (this._isAutoBackgroundEnabled() && this._isForegroundOrTransitioning() && !remotePlayer._isPlaying) {
|
|
392
406
|
this._startCountdown();
|
|
393
407
|
}
|
|
394
408
|
}
|
|
@@ -408,9 +422,24 @@ class Lifecycle extends LifecycleInterface {
|
|
|
408
422
|
// Only start countdown if delay is positive
|
|
409
423
|
if (timeoutDelay > 0) {
|
|
410
424
|
sdkLogger.debug("Starting countdown timeout", timeoutDelay, isPlaying ? "(video)" : "(UI)");
|
|
411
|
-
this._countdown = setTimeout(() => {
|
|
425
|
+
this._countdown = setTimeout(async () => {
|
|
412
426
|
sdkLogger.debug("Countdown timeout reached, moving to background");
|
|
413
|
-
|
|
427
|
+
|
|
428
|
+
// Create the event with cancelable: true
|
|
429
|
+
const event = new Event("beforestatechange", { cancelable: true });
|
|
430
|
+
event.state = this.UiState.BACKGROUND;
|
|
431
|
+
// Use the event manager to dispatch the event and wait for all listeners
|
|
432
|
+
await this._eventManager.dispatch("beforestatechange", event);
|
|
433
|
+
// Check if any listener called preventDefault()
|
|
434
|
+
if (event.defaultPrevented) {
|
|
435
|
+
sdkLogger.info("moveToBackground was prevented by a listener");
|
|
436
|
+
// Restart the countdown since the move was cancelled
|
|
437
|
+
if (this._isForegroundOrTransitioning()) {
|
|
438
|
+
this._startCountdown();
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
this.moveToBackground();
|
|
442
|
+
}
|
|
414
443
|
}, timeoutDelay * 1000);
|
|
415
444
|
} else {
|
|
416
445
|
sdkLogger.debug("Countdown not started - delay is negative or zero");
|
|
@@ -424,8 +453,8 @@ class Lifecycle extends LifecycleInterface {
|
|
|
424
453
|
if (this._countdown) {
|
|
425
454
|
sdkLogger.debug("Stopping countdown timer");
|
|
426
455
|
clearTimeout(this._countdown);
|
|
456
|
+
this._countdown = null;
|
|
427
457
|
}
|
|
428
|
-
this._countdown = null;
|
|
429
458
|
}
|
|
430
459
|
|
|
431
460
|
/**
|
|
@@ -459,7 +488,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
459
488
|
moveToForeground() {
|
|
460
489
|
if (window.cefQuery) {
|
|
461
490
|
const inTransition = this._isInTransition();
|
|
462
|
-
if (inTransition || this.
|
|
491
|
+
if (inTransition || this._isForegroundOrTransitioning()) {
|
|
463
492
|
sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${inTransition}`);
|
|
464
493
|
return Promise.resolve(false);
|
|
465
494
|
}
|
|
@@ -771,23 +800,19 @@ class Lifecycle extends LifecycleInterface {
|
|
|
771
800
|
return Promise.reject("disconnect is not supported if NOT running e2e");
|
|
772
801
|
}
|
|
773
802
|
|
|
803
|
+
|
|
774
804
|
addEventListener(type, listener, options) {
|
|
775
|
-
if (type
|
|
776
|
-
// Use the event manager for userdisconnected events
|
|
805
|
+
if (Lifecycle._waitForListenersEvents.includes(type)) {
|
|
777
806
|
this._eventManager.addEventListener(type, listener);
|
|
778
807
|
} else {
|
|
779
|
-
// For all other event types, use the parent class implementation
|
|
780
808
|
super.addEventListener(type, listener, options);
|
|
781
809
|
}
|
|
782
810
|
}
|
|
783
811
|
|
|
784
|
-
|
|
785
812
|
removeEventListener(type, listener, options) {
|
|
786
|
-
if (type
|
|
787
|
-
// Use the event manager for userdisconnected events
|
|
813
|
+
if (Lifecycle._waitForListenersEvents.includes(type)) {
|
|
788
814
|
this._eventManager.removeEventListener(type, listener);
|
|
789
815
|
} else {
|
|
790
|
-
// For all other event types, use the parent class implementation
|
|
791
816
|
super.removeEventListener(type, listener, options);
|
|
792
817
|
}
|
|
793
818
|
}
|
|
@@ -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>
|
|
@@ -265,7 +282,7 @@ class Lifecycle extends EventTarget {
|
|
|
265
282
|
/**
|
|
266
283
|
* Add event listener for lifecycle events
|
|
267
284
|
* @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.
|
|
285
|
+
* @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
286
|
* @param {Object} options - Event listener options
|
|
270
287
|
*/
|
|
271
288
|
addEventListener(type, listener, options) {
|
package/src/interface/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "4.4.
|
|
1
|
+
export const version = "4.4.5";
|