senza-sdk 4.5.0 → 4.5.1
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 +1 -1
- package/src/implementation/lifecycle.js +1 -0
- package/src/implementation/overlay.js +70 -12
- package/src/implementation/remotePlayer.js +1 -1
- package/src/implementation/senzaShakaPlayer.js +46 -8
- package/src/interface/overlay.js +11 -0
- package/src/interface/version.js +1 -1
package/package.json
CHANGED
|
@@ -757,6 +757,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
757
757
|
class: "remotePlayer",
|
|
758
758
|
action: "stop",
|
|
759
759
|
streamType: remotePlayer._isAudioSyncEnabled() ? StreamType.VIDEO | StreamType.SUBTITLE : StreamType.AUDIO | StreamType.VIDEO | StreamType.SUBTITLE,
|
|
760
|
+
switchMode: remotePlayer._isAudioSyncEnabled() ? SwitchMode.SEAMLESS : SwitchMode.NON_SEAMLESS,
|
|
760
761
|
fcid: FCID
|
|
761
762
|
};
|
|
762
763
|
let timerId = 0;
|
|
@@ -7,15 +7,49 @@ class Overlay extends OverlayInterface {
|
|
|
7
7
|
|
|
8
8
|
this._element = null;
|
|
9
9
|
this._configuration = {
|
|
10
|
-
useTransparency: true
|
|
10
|
+
useTransparency: true,
|
|
11
|
+
autoRefreshSeconds: 2
|
|
11
12
|
};
|
|
13
|
+
this._retryGeneration = 0;
|
|
14
|
+
this._retryTimerId = null;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
_isTransparencyEnabled() {
|
|
15
18
|
return this._configuration.useTransparency !== false;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
_clearRetryTimers() {
|
|
22
|
+
this._retryGeneration++;
|
|
23
|
+
clearTimeout(this._retryTimerId);
|
|
24
|
+
this._retryTimerId = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_scheduleRetries(maxMs) {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
let delay = 100;
|
|
30
|
+
const generation = ++this._retryGeneration;
|
|
31
|
+
|
|
32
|
+
const scheduleNext = () => {
|
|
33
|
+
if (this._retryGeneration !== generation) return;
|
|
34
|
+
const elapsed = Date.now() - startTime;
|
|
35
|
+
if (elapsed + delay > maxMs) return;
|
|
36
|
+
|
|
37
|
+
this._retryTimerId = setTimeout(async () => {
|
|
38
|
+
if (this._retryGeneration !== generation) return;
|
|
39
|
+
try {
|
|
40
|
+
await this._renderFrame(true);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
sdkLogger.log(`Overlay: conditional refresh error: ${err}`);
|
|
43
|
+
}
|
|
44
|
+
delay *= 2;
|
|
45
|
+
scheduleNext();
|
|
46
|
+
}, delay);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
scheduleNext();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_sendFrame(x, y, width, height, conditional = false) {
|
|
19
53
|
if (window.cefQuery) {
|
|
20
54
|
return new Promise((resolve, reject) => {
|
|
21
55
|
const FCID = getFCID();
|
|
@@ -26,7 +60,8 @@ class Overlay extends OverlayInterface {
|
|
|
26
60
|
width,
|
|
27
61
|
height,
|
|
28
62
|
useTransparency: this._isTransparencyEnabled(),
|
|
29
|
-
fcid: FCID
|
|
63
|
+
fcid: FCID,
|
|
64
|
+
conditional
|
|
30
65
|
};
|
|
31
66
|
const request = { target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message) };
|
|
32
67
|
window.cefQuery({
|
|
@@ -44,18 +79,25 @@ class Overlay extends OverlayInterface {
|
|
|
44
79
|
return Promise.reject("Overlay is not supported if NOT running e2e");
|
|
45
80
|
}
|
|
46
81
|
|
|
47
|
-
_renderFrame() {
|
|
82
|
+
_renderFrame(conditional = false) {
|
|
48
83
|
if (!this._element) {
|
|
49
84
|
return Promise.reject("No element configured");
|
|
50
85
|
}
|
|
51
86
|
const rect = this._element.getBoundingClientRect();
|
|
52
87
|
const frame = this._normalizeFrame(rect);
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
if (frame.width === 0 || frame.height === 0) {
|
|
90
|
+
sdkLogger.warn(`Overlay: skipping frame with zero size (width=${frame.width}, height=${frame.height})`);
|
|
91
|
+
return Promise.resolve(false);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
sdkLogger.log(`Overlay: rendering frame x=${frame.x} y=${frame.y} width=${frame.width} height=${frame.height} (conditional=${conditional})`);
|
|
95
|
+
return this._sendFrame(frame.x, frame.y, frame.width, frame.height, conditional).then(() => {
|
|
96
|
+
if (!conditional) {
|
|
97
|
+
const refreshEvent = new Event("refresh");
|
|
98
|
+
refreshEvent.frame = frame;
|
|
99
|
+
this.dispatchEvent(refreshEvent);
|
|
100
|
+
}
|
|
59
101
|
return true;
|
|
60
102
|
});
|
|
61
103
|
}
|
|
@@ -65,8 +107,8 @@ class Overlay extends OverlayInterface {
|
|
|
65
107
|
|
|
66
108
|
const x = Math.round(toFiniteNumber(rect.x));
|
|
67
109
|
const y = Math.round(toFiniteNumber(rect.y));
|
|
68
|
-
const width = Math.
|
|
69
|
-
const height =
|
|
110
|
+
const width = Math.round(toFiniteNumber(rect.width));
|
|
111
|
+
const height =Math.round(toFiniteNumber(rect.height));
|
|
70
112
|
|
|
71
113
|
return { x, y, width, height };
|
|
72
114
|
}
|
|
@@ -78,6 +120,11 @@ class Overlay extends OverlayInterface {
|
|
|
78
120
|
normalizedConfiguration.useTransparency = normalizedConfiguration.useTransparency !== false;
|
|
79
121
|
}
|
|
80
122
|
|
|
123
|
+
if (Object.prototype.hasOwnProperty.call(normalizedConfiguration, "autoRefreshSeconds")) {
|
|
124
|
+
const v = normalizedConfiguration.autoRefreshSeconds;
|
|
125
|
+
normalizedConfiguration.autoRefreshSeconds = (typeof v === "number" && v >= 0) ? v : this._configuration.autoRefreshSeconds;
|
|
126
|
+
}
|
|
127
|
+
|
|
81
128
|
this._configuration = { ...this._configuration, ...normalizedConfiguration };
|
|
82
129
|
}
|
|
83
130
|
|
|
@@ -111,6 +158,7 @@ class Overlay extends OverlayInterface {
|
|
|
111
158
|
return Promise.reject(errorMsg);
|
|
112
159
|
}
|
|
113
160
|
|
|
161
|
+
this._clearRetryTimers();
|
|
114
162
|
this._element = null;
|
|
115
163
|
|
|
116
164
|
return Promise.resolve(true);
|
|
@@ -122,11 +170,20 @@ class Overlay extends OverlayInterface {
|
|
|
122
170
|
sdkLogger.error(errorMsg);
|
|
123
171
|
return Promise.reject(errorMsg);
|
|
124
172
|
}
|
|
125
|
-
|
|
173
|
+
this._clearRetryTimers();
|
|
174
|
+
const result = await this._renderFrame();
|
|
175
|
+
if (result === true) {
|
|
176
|
+
const maxMs = (this._configuration.autoRefreshSeconds ?? 0) * 1000;
|
|
177
|
+
if (maxMs > 0) {
|
|
178
|
+
this._scheduleRetries(maxMs);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
126
182
|
}
|
|
127
183
|
|
|
128
184
|
async removeAllElements() {
|
|
129
185
|
sdkLogger.log("Overlay: removing all elements");
|
|
186
|
+
this._clearRetryTimers();
|
|
130
187
|
this._element = null;
|
|
131
188
|
}
|
|
132
189
|
|
|
@@ -162,6 +219,7 @@ class Overlay extends OverlayInterface {
|
|
|
162
219
|
* @returns {Promise<boolean>} Resolves to true if successful, rejects with error if failed.
|
|
163
220
|
*/
|
|
164
221
|
hideOverlay() {
|
|
222
|
+
this._clearRetryTimers();
|
|
165
223
|
return this._sendHideOverlay();
|
|
166
224
|
}
|
|
167
225
|
|
|
@@ -1021,7 +1021,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1021
1021
|
}
|
|
1022
1022
|
// if load in background is supported, play called while state is in background should include all streams
|
|
1023
1023
|
const backgroundStreamType = this.textTrackVisibility ? (StreamType.AUDIO | StreamType.VIDEO | StreamType.SUBTITLE) : (StreamType.AUDIO | StreamType.VIDEO);
|
|
1024
|
-
const streamType = backgroundLoadSupported ? backgroundStreamType : StreamType.AUDIO;
|
|
1024
|
+
const streamType = (backgroundLoadSupported && !isForegroundState) ? backgroundStreamType : StreamType.AUDIO;
|
|
1025
1025
|
|
|
1026
1026
|
return this._play(streamType, switchMode);
|
|
1027
1027
|
}
|
|
@@ -504,6 +504,18 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
504
504
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
_hasActiveTrackForLanguageAndRole(tracks, language, role) {
|
|
508
|
+
const activeTrack = tracks.find(track => track.active);
|
|
509
|
+
if (!activeTrack) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if ((!role || activeTrack.roles?.includes(role)) && activeTrack.language === language) {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
507
519
|
|
|
508
520
|
_attach(videoElement) {
|
|
509
521
|
this.videoElement = videoElement;
|
|
@@ -525,7 +537,10 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
525
537
|
this.handleSenzaError(error.code, error.message || "Unknown play error");
|
|
526
538
|
throw error;
|
|
527
539
|
});
|
|
528
|
-
|
|
540
|
+
this._originalPlay.call(this.videoElement).catch(error => {
|
|
541
|
+
this.handleSenzaError(error.code, error.message || "Unknown play error");
|
|
542
|
+
});
|
|
543
|
+
return;
|
|
529
544
|
}
|
|
530
545
|
|
|
531
546
|
if (this.remotePlayer._isPlaying) {
|
|
@@ -620,7 +635,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
620
635
|
return [...new Set(tracks.map(item => item.lang))];
|
|
621
636
|
}
|
|
622
637
|
|
|
623
|
-
|
|
624
638
|
selectVariantTrack(track, clearBuffer = false, safeMargin = 0) {
|
|
625
639
|
const audioLang = track.language;
|
|
626
640
|
const audioRole = Array.isArray(track.audioRoles) && track.audioRoles.length > 0
|
|
@@ -632,8 +646,11 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
632
646
|
const apCode = this._getAccessibilityCodeFromPurpose(track.accessibilityPurpose);
|
|
633
647
|
remotePlayer.selectAudioLanguage(audioLang, audioRole, apCode);
|
|
634
648
|
|
|
635
|
-
|
|
636
|
-
|
|
649
|
+
if (track && !track.active) {
|
|
650
|
+
super.selectVariantTrack(track, clearBuffer, safeMargin);
|
|
651
|
+
} else {
|
|
652
|
+
sdkLogger.log("selectVariantTrack() skipping local Shaka selection because track is already active");
|
|
653
|
+
}
|
|
637
654
|
}
|
|
638
655
|
|
|
639
656
|
selectAudioTrack(audioTrack, safeMargin = 0) {
|
|
@@ -646,13 +663,24 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
646
663
|
const apCode = this._getAccessibilityCodeFromPurpose(audioTrack.accessibilityPurpose);
|
|
647
664
|
remotePlayer.selectAudioLanguage(audioLang, role, apCode);
|
|
648
665
|
|
|
649
|
-
|
|
666
|
+
if (audioTrack && !audioTrack.active) {
|
|
667
|
+
super.selectAudioTrack(audioTrack, safeMargin);
|
|
668
|
+
} else {
|
|
669
|
+
sdkLogger.log("selectAudioTrack() skipping local Shaka selection because track is already active");
|
|
670
|
+
}
|
|
650
671
|
}
|
|
651
672
|
|
|
652
673
|
selectAudioLanguage(language, role) {
|
|
653
674
|
sdkLogger.log("selectAudioLanguage() Selecting audio language:", language, "with role: ", role);
|
|
654
675
|
remotePlayer.selectAudioLanguage(language, role);
|
|
655
|
-
|
|
676
|
+
|
|
677
|
+
const tracks = this.getAudioTracks();
|
|
678
|
+
sdkLogger.log("selectAudioLanguage: audio tracks", JSON.stringify(tracks), "language", language, "role", role);
|
|
679
|
+
if (!this._hasActiveTrackForLanguageAndRole(tracks, language, role)) {
|
|
680
|
+
super.selectAudioLanguage(language, role);
|
|
681
|
+
} else {
|
|
682
|
+
sdkLogger.log("selectAudioLanguage() skipping local Shaka selection because language is already active");
|
|
683
|
+
}
|
|
656
684
|
}
|
|
657
685
|
|
|
658
686
|
selectTextTrack(textTrack) {
|
|
@@ -665,12 +693,22 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
665
693
|
const apCode = this._getAccessibilityCodeFromPurpose(textTrack.accessibilityPurpose);
|
|
666
694
|
remotePlayer.selectTextLanguage(textLang, role, apCode);
|
|
667
695
|
|
|
668
|
-
|
|
696
|
+
if (textTrack && !textTrack.active) {
|
|
697
|
+
super.selectTextTrack(textTrack);
|
|
698
|
+
} else {
|
|
699
|
+
sdkLogger.log("selectTextTrack() skipping local Shaka selection because track is already active");
|
|
700
|
+
}
|
|
669
701
|
}
|
|
670
702
|
selectTextLanguage(language, role) {
|
|
671
703
|
sdkLogger.log("selectTextLanguage() Selecting text language:", language, "with role: ", role);
|
|
672
704
|
remotePlayer.selectTextLanguage(language, role);
|
|
673
|
-
|
|
705
|
+
const tracks = this.getTextTracks();
|
|
706
|
+
sdkLogger.log("selectTextLanguage: text tracks", JSON.stringify(tracks));
|
|
707
|
+
if (!this._hasActiveTrackForLanguageAndRole(tracks, language, role)) {
|
|
708
|
+
super.selectTextLanguage(language, role);
|
|
709
|
+
} else {
|
|
710
|
+
sdkLogger.log("selectTextLanguage() skipping local Shaka selection because language is already active");
|
|
711
|
+
}
|
|
674
712
|
}
|
|
675
713
|
|
|
676
714
|
setTextTrackVisibility(visible) {
|
package/src/interface/overlay.js
CHANGED
|
@@ -46,6 +46,14 @@ class Overlay extends EventTarget {
|
|
|
46
46
|
* This is useful when the element's content or position has changed.
|
|
47
47
|
* The overlay must have an element registered before calling refresh.
|
|
48
48
|
*
|
|
49
|
+
* After the initial capture, conditional retries are automatically scheduled using
|
|
50
|
+
* exponential backoff (starting at 100 ms, doubling each step) until the window
|
|
51
|
+
* configured by `configure({ autoRefreshSeconds })` expires (default: 2 seconds).
|
|
52
|
+
* Each retry is skipped by the UI-Streamer unless the browser has repainted since
|
|
53
|
+
* the last non-conditional capture, eliminating redundant JPEG encodes.
|
|
54
|
+
* Pending retries are cancelled by a subsequent call to refresh(), hideOverlay(),
|
|
55
|
+
* or removeElement().
|
|
56
|
+
*
|
|
49
57
|
* @returns {Promise<boolean>} Resolves to true if successful, rejects with error if failed.
|
|
50
58
|
*/
|
|
51
59
|
refresh() {
|
|
@@ -77,6 +85,9 @@ class Overlay extends EventTarget {
|
|
|
77
85
|
* @param {Object} configuration - The new configuration to apply.
|
|
78
86
|
* @param {boolean} [configuration.useTransparency=true] - Controls whether the overlay should be rendered with transparency.
|
|
79
87
|
* When set to `true`, the value is forwarded to UI-Streamer in `displayOverlay` requests.
|
|
88
|
+
* @param {number} [configuration.autoRefreshSeconds=2] - Maximum window in seconds for automatic
|
|
89
|
+
* conditional retries after each refresh() call. Retries are spaced with exponential backoff
|
|
90
|
+
* starting at 100 ms. Set to 0 to disable automatic retries.
|
|
80
91
|
*/
|
|
81
92
|
configure(configuration) {
|
|
82
93
|
noop(configuration);
|
package/src/interface/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "4.5.
|
|
1
|
+
export const version = "4.5.1";
|