senza-sdk 4.5.8 → 4.5.9
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 +3 -1
- package/src/implementation/lifecycle.js +105 -0
- package/src/implementation/remotePlayer.js +157 -1
- package/src/implementation/utils.js +28 -1
- package/src/interface/remotePlayer.js +44 -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.5.
|
|
3
|
+
"version": "4.5.9",
|
|
4
4
|
"main": "./src/api.js",
|
|
5
5
|
"description": "API for Senza application",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"build:interface": "npx webpack --config interface.webpack.config.js",
|
|
19
19
|
"prebuild:implementation": "npm run version:output",
|
|
20
20
|
"build:implementation": "npx webpack --config implementation.webpack.config.js",
|
|
21
|
+
"build:dual-decode-sample": "npm run build && mkdir -p samples/dualDecode/sdk && cp dist/bundle.js dist/implementation.bundle.js samples/dualDecode/sdk/ && npm run build:dual-decode-sample:app",
|
|
22
|
+
"build:dual-decode-sample:app": "npx webpack --config samples/dualDecode/webpack.config.js --context samples/dualDecode",
|
|
21
23
|
"test": "jest --maxWorkers=20% --coverage --verbose",
|
|
22
24
|
"testall": "npm run test && npm run eslint --fix",
|
|
23
25
|
"version:output": "echo 'export const version = \"'$npm_package_version'\";' > src/interface/version.js"
|
|
@@ -27,6 +27,12 @@ const DEFAULT_AUTO_SUSPEND_IDLE_DELAY = 60;
|
|
|
27
27
|
// Session storage key for tracking timer-triggered background state
|
|
28
28
|
const BACKGROUND_TIMER_KEY = "senzaSDK_backgroundTriggeredByTimer";
|
|
29
29
|
|
|
30
|
+
/** @enum {string} */
|
|
31
|
+
const UiTransition = Object.freeze({
|
|
32
|
+
START_UI: "startUi",
|
|
33
|
+
STOP_UI: "stopUi"
|
|
34
|
+
});
|
|
35
|
+
|
|
30
36
|
class Lifecycle extends LifecycleInterface {
|
|
31
37
|
constructor() {
|
|
32
38
|
super();
|
|
@@ -552,6 +558,58 @@ class Lifecycle extends LifecycleInterface {
|
|
|
552
558
|
return Promise.resolve(true);
|
|
553
559
|
}
|
|
554
560
|
|
|
561
|
+
/**
|
|
562
|
+
* @private Sends a simple message to the client and resolves/rejects based on the response.
|
|
563
|
+
* Used by the dual decode startUi/stopUi transitions.
|
|
564
|
+
* @param {UiTransition} type the message type to send
|
|
565
|
+
* @returns {Promise<boolean>}
|
|
566
|
+
*/
|
|
567
|
+
_sendUiTransition(type) {
|
|
568
|
+
if (window.cefQuery) {
|
|
569
|
+
return new Promise((resolve, reject) => {
|
|
570
|
+
const FCID = getFCID();
|
|
571
|
+
const logger = sdkLogger.withFields({ FCID });
|
|
572
|
+
const message = { type, fcid: FCID };
|
|
573
|
+
// Mirror V2 remotePlayer lifecycle: ui-streamer resumes/pauses locally via internalAction
|
|
574
|
+
// while TC receives startUi/stopUi on the datachannel.
|
|
575
|
+
const internalAction = type === UiTransition.START_UI ? "uiActive" : "uiExit";
|
|
576
|
+
// TC confirms stopUi with isResponse; startUi is fire-and-forget until TC adds a response.
|
|
577
|
+
const waitForResponse = type !== UiTransition.START_UI;
|
|
578
|
+
const request = { target: "TC", waitForResponse, internalAction, message: JSON.stringify(message) };
|
|
579
|
+
window.cefQuery({
|
|
580
|
+
request: JSON.stringify(request),
|
|
581
|
+
persistent: false,
|
|
582
|
+
onSuccess: () => {
|
|
583
|
+
logger.log(`${type} successfully sent`);
|
|
584
|
+
resolve(true);
|
|
585
|
+
},
|
|
586
|
+
onFailure: (code, msg) => {
|
|
587
|
+
logger.error(`${type} failed: ${code} ${msg}`);
|
|
588
|
+
reject(new SenzaError(code, msg));
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
sdkLogger.warn(`lifecycle ${type}: window.cefQuery is undefined`);
|
|
594
|
+
return Promise.resolve(true);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* @private Starts the UI streaming.
|
|
599
|
+
* @returns {Promise<boolean>}
|
|
600
|
+
*/
|
|
601
|
+
_startUi() {
|
|
602
|
+
return this._sendUiTransition(UiTransition.START_UI);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* @private Stops the UI streaming.
|
|
607
|
+
* @returns {Promise<boolean>}
|
|
608
|
+
*/
|
|
609
|
+
_stopUi() {
|
|
610
|
+
return this._sendUiTransition(UiTransition.STOP_UI);
|
|
611
|
+
}
|
|
612
|
+
|
|
555
613
|
get state() {
|
|
556
614
|
if (!this._isInitialized) {
|
|
557
615
|
this._state = this.UiState.UNKNOWN;
|
|
@@ -725,6 +783,11 @@ class Lifecycle extends LifecycleInterface {
|
|
|
725
783
|
this._isBackgroundTriggeredByTimer = false;
|
|
726
784
|
|
|
727
785
|
if (window.cefQuery) {
|
|
786
|
+
if (remotePlayer._isDualDecodeActive()) {
|
|
787
|
+
// Cancel a pending dual-decode background transition that is waiting for the playing event.
|
|
788
|
+
this._inTransitionToBackground = false;
|
|
789
|
+
}
|
|
790
|
+
|
|
728
791
|
const inTransition = this._isInTransition();
|
|
729
792
|
if (inTransition || this._isForegroundOrTransitioning()) {
|
|
730
793
|
sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${inTransition}`);
|
|
@@ -746,11 +809,23 @@ class Lifecycle extends LifecycleInterface {
|
|
|
746
809
|
this._inTransitionToForeground = true;
|
|
747
810
|
bus.dispatchEvent(new Event(Events.LifecycleForeground));
|
|
748
811
|
const FCID = getFCID();
|
|
812
|
+
|
|
749
813
|
if (this._remotePlayerApiVersion >= 2) {
|
|
750
814
|
// Only update to playing UI if we started seeking in ABR. But, if we are seeking while already paused, keep the target seek state as is.
|
|
751
815
|
if (remotePlayer._isSeekingByApplication && remotePlayer._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
|
|
752
816
|
remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
753
817
|
}
|
|
818
|
+
|
|
819
|
+
if (remotePlayer._isDualDecodeActive()) {
|
|
820
|
+
// In dual decode mode, returning to the UI only requires (re)showing the UI on top of the ABR video.
|
|
821
|
+
// We must not stop the remote player playback, so skip the v2 uiActive/stop and legacy uiActiveRequest paths.
|
|
822
|
+
try {
|
|
823
|
+
return await this._startUi();
|
|
824
|
+
} finally {
|
|
825
|
+
this._inTransitionToForeground = false;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
754
829
|
return new Promise((resolve, reject) => {
|
|
755
830
|
const FCID = getFCID();
|
|
756
831
|
const logger = sdkLogger.withFields({ FCID });
|
|
@@ -817,6 +892,31 @@ class Lifecycle extends LifecycleInterface {
|
|
|
817
892
|
return Promise.resolve(false);
|
|
818
893
|
}
|
|
819
894
|
|
|
895
|
+
/**
|
|
896
|
+
* @private Handles the dual decode background transition: hide the UI via stopUi.
|
|
897
|
+
* Does not send any play/uiExit message. If the remote "playing" event has not arrived yet,
|
|
898
|
+
* stopUi is still called immediately and a warning is logged.
|
|
899
|
+
*
|
|
900
|
+
* @returns {Promise<boolean>} stopUi result
|
|
901
|
+
*/
|
|
902
|
+
_moveToBackgroundDualDecode() {
|
|
903
|
+
if (remotePlayer._isPlayingEventPending) {
|
|
904
|
+
sdkLogger.warn("dual decode moveToBackground: moving to background before the playing event arrived; stopping the UI now");
|
|
905
|
+
} else {
|
|
906
|
+
sdkLogger.info("dual decode moveToBackground: playing event already arrived, stopping the UI");
|
|
907
|
+
}
|
|
908
|
+
this._inTransitionToBackground = true;
|
|
909
|
+
return this._stopUi()
|
|
910
|
+
.then((result) => {
|
|
911
|
+
this._inTransitionToBackground = false;
|
|
912
|
+
return result;
|
|
913
|
+
})
|
|
914
|
+
.catch((error) => {
|
|
915
|
+
this._inTransitionToBackground = false;
|
|
916
|
+
throw error;
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
|
|
820
920
|
_moveToBackground(switchMode = SwitchMode.SEAMLESS) {
|
|
821
921
|
/*const inTransition = this._isInTransition();
|
|
822
922
|
if (inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
@@ -846,6 +946,11 @@ class Lifecycle extends LifecycleInterface {
|
|
|
846
946
|
return this._moveToUiStandby();
|
|
847
947
|
}
|
|
848
948
|
remotePlayer._changePlayMode(true);
|
|
949
|
+
|
|
950
|
+
if (remotePlayer._isDualDecodeActive()) {
|
|
951
|
+
return this._moveToBackgroundDualDecode();
|
|
952
|
+
}
|
|
953
|
+
|
|
849
954
|
this._inTransitionToBackground = true;
|
|
850
955
|
return new Promise((resolve, reject) => {
|
|
851
956
|
const FCID = getFCID();
|
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
getFCID,
|
|
5
5
|
generatePlaybackId,
|
|
6
6
|
isAudioSyncConfigured,
|
|
7
|
+
isDualDecodeEnabled,
|
|
8
|
+
getDualDecodeAbrSurfaceSize,
|
|
7
9
|
clearTimer,
|
|
8
10
|
sdkLogger,
|
|
9
11
|
StreamType,
|
|
10
12
|
SwitchMode,
|
|
13
|
+
VideoLayer,
|
|
11
14
|
delay,
|
|
12
15
|
SeekState,
|
|
13
16
|
TargetPlayingState,
|
|
@@ -68,6 +71,8 @@ function setPlaybackInfo(playbackInfo) {
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
class RemotePlayer extends RemotePlayerInterface {
|
|
74
|
+
VideoLayer = VideoLayer;
|
|
75
|
+
|
|
71
76
|
constructor() {
|
|
72
77
|
super();
|
|
73
78
|
/**
|
|
@@ -154,6 +159,13 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
154
159
|
*/
|
|
155
160
|
this._playbackId = "";
|
|
156
161
|
|
|
162
|
+
/**
|
|
163
|
+
* @type {boolean}
|
|
164
|
+
* @description True while a dual-decode play() is awaiting the remote "playing" event.
|
|
165
|
+
* @private
|
|
166
|
+
*/
|
|
167
|
+
this._isPlayingEventPending = false;
|
|
168
|
+
|
|
157
169
|
/**
|
|
158
170
|
* Anchor position in seconds used by getPlaybackPosition().
|
|
159
171
|
* @type {number}
|
|
@@ -270,6 +282,9 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
270
282
|
typeof document !== "undefined" && document.addEventListener("hs/remotePlayerEvent", (e) => {
|
|
271
283
|
if (this._hasPlaybackIdMismatch(e?.detail, "hs/remotePlayerEvent")) return;
|
|
272
284
|
sdkLogger.info("Got hs/remotePlayerEvent event with detail", JSON.stringify(e?.detail));
|
|
285
|
+
if (e?.detail?.eventName === "playing") {
|
|
286
|
+
this._isPlayingEventPending = false;
|
|
287
|
+
}
|
|
273
288
|
this.dispatchEvent(new Event(e?.detail?.eventName));
|
|
274
289
|
});
|
|
275
290
|
|
|
@@ -585,6 +600,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
585
600
|
this._availabilityStartTime = undefined;
|
|
586
601
|
this._isSeekingByPlatform = false;
|
|
587
602
|
this._isSeekingByApplication = false;
|
|
603
|
+
this._isPlayingEventPending = false;
|
|
588
604
|
this._stopPlaybackClock();
|
|
589
605
|
this._playbackPositionAnchor = 0;
|
|
590
606
|
}
|
|
@@ -685,6 +701,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
685
701
|
} else if (!this.textTrackVisibility) {
|
|
686
702
|
message.subtitlesLanguage = "";
|
|
687
703
|
}
|
|
704
|
+
if (switchMode === SwitchMode.DUAL_DECODE) {
|
|
705
|
+
message.switchMode = SwitchMode.DUAL_DECODE;
|
|
706
|
+
message.streamType = streamType;
|
|
707
|
+
waitForResponse = true;
|
|
708
|
+
this._isPlayingEventPending = true;
|
|
709
|
+
}
|
|
688
710
|
logger.log(`remotePlayer play: sending play action remotePlayer._isPlaying: ${this._isPlaying} audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${this.textTrackVisibility}`);
|
|
689
711
|
const request = { target: "TC", waitForResponse: waitForResponse, message: JSON.stringify(message) };
|
|
690
712
|
return new Promise((resolve, reject) => {
|
|
@@ -703,6 +725,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
703
725
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
704
726
|
logger.withFields({ duration }).log(`play failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
705
727
|
timerId = clearTimer(timerId);
|
|
728
|
+
this._isPlayingEventPending = false;
|
|
706
729
|
reject(new RemotePlayerError(code, msg));
|
|
707
730
|
}
|
|
708
731
|
});
|
|
@@ -727,7 +750,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
727
750
|
|
|
728
751
|
if (window.cefQuery) {
|
|
729
752
|
const isForegroundState = lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND;
|
|
730
|
-
if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled() && isForegroundState) {
|
|
753
|
+
if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled() && !this._isDualDecodeActive() && isForegroundState) {
|
|
731
754
|
sdkLogger.info("remotePlayer pause: application in foreground, remote player is not playing.");
|
|
732
755
|
return Promise.resolve();
|
|
733
756
|
}
|
|
@@ -793,6 +816,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
793
816
|
fcid: FCID,
|
|
794
817
|
playbackId: this._playbackId
|
|
795
818
|
};
|
|
819
|
+
if (this._isDualDecodeActive()) {
|
|
820
|
+
message.switchMode = SwitchMode.DUAL_DECODE;
|
|
821
|
+
if (streamType & StreamType.VIDEO) {
|
|
822
|
+
this._isPlayingEventPending = false;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
796
825
|
const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
|
|
797
826
|
return new Promise((resolve, reject) => {
|
|
798
827
|
let timerId = 0;
|
|
@@ -1026,6 +1055,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1026
1055
|
this._abortSetAudioLanguage = true;
|
|
1027
1056
|
this._abortSetSubtitleLanguage = true;
|
|
1028
1057
|
this._abortSeeking = true;
|
|
1058
|
+
this._isPlayingEventPending = false;
|
|
1029
1059
|
const previousLoadMode = this._loadMode;
|
|
1030
1060
|
this._changeLoadMode(this.LoadMode.UNLOADING);
|
|
1031
1061
|
this._changePlayMode(false);
|
|
@@ -1113,6 +1143,17 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1113
1143
|
this._changePlayMode(true);
|
|
1114
1144
|
this._autoTune = autoTune;
|
|
1115
1145
|
|
|
1146
|
+
if (this._isDualDecodeActive()) {
|
|
1147
|
+
// In dual decode mode, ABR video is rendered over the UI video, so we always play audio + video
|
|
1148
|
+
// (and subtitles when visible) using the DUAL_DECODE switch mode, bypassing the foreground/audio-sync
|
|
1149
|
+
// early returns below. Start the waitForPlaying timer so callers (e.g. moveToBackground)
|
|
1150
|
+
// can wait for the remote player to actually start playing.
|
|
1151
|
+
const dualDecodeStreamType = this.textTrackVisibility
|
|
1152
|
+
? (StreamType.AUDIO | StreamType.VIDEO | StreamType.SUBTITLE)
|
|
1153
|
+
: (StreamType.AUDIO | StreamType.VIDEO);
|
|
1154
|
+
return this._play(dualDecodeStreamType, SwitchMode.DUAL_DECODE);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1116
1157
|
if (this._isSetAudioInProgress) {
|
|
1117
1158
|
sdkLogger.info("application requesting play during setAudioLanguage");
|
|
1118
1159
|
this._targetSetAudioPlayingState = TargetPlayingState.PLAYING_UI;
|
|
@@ -1198,6 +1239,18 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1198
1239
|
return this._pause();
|
|
1199
1240
|
}
|
|
1200
1241
|
|
|
1242
|
+
/** Stop playback of all streams (audio, video and subtitles).
|
|
1243
|
+
* @returns {Promise}
|
|
1244
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
1245
|
+
*
|
|
1246
|
+
* */
|
|
1247
|
+
stop() {
|
|
1248
|
+
if (!this._isInitialized) {
|
|
1249
|
+
throw new RemotePlayerError(6500, "Cannot call stop() if remote player is not initialized");
|
|
1250
|
+
}
|
|
1251
|
+
return this._stop(StreamType.AUDIO | StreamType.VIDEO | StreamType.SUBTITLE);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1201
1254
|
/** Get the currently loaded uri, or empty string is player is not loaded.
|
|
1202
1255
|
* @returns {string} loaded uri
|
|
1203
1256
|
* @throws {RemotePlayerError} error object contains code & msg
|
|
@@ -1654,6 +1707,109 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1654
1707
|
return this._setScreenBlackout(0);
|
|
1655
1708
|
}
|
|
1656
1709
|
|
|
1710
|
+
/**
|
|
1711
|
+
* @private
|
|
1712
|
+
* Whether dual decode is active for this player (remote player API v2+ and session capability/settings enabled).
|
|
1713
|
+
* @returns {boolean}
|
|
1714
|
+
*/
|
|
1715
|
+
_isDualDecodeActive() {
|
|
1716
|
+
return this._remotePlayerApiVersion >= 2 && isDualDecodeEnabled();
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/** Set the position and size of the remote player (ABR) video on screen, or make it fullscreen.
|
|
1720
|
+
* Only available when dual decode is enabled.
|
|
1721
|
+
* The given rectangle is expressed in the UI coordinate space (window.innerWidth/window.innerHeight)
|
|
1722
|
+
* and is validated and scaled to the client's ABR surface size before being sent to the client.
|
|
1723
|
+
* @param {Object} rect
|
|
1724
|
+
* @param {number} [rect.x] horizontal position in px (UI coordinate space)
|
|
1725
|
+
* @param {number} [rect.y] vertical position in px (UI coordinate space)
|
|
1726
|
+
* @param {number} [rect.width] width in px (UI coordinate space)
|
|
1727
|
+
* @param {number} [rect.height] height in px (UI coordinate space)
|
|
1728
|
+
* @param {boolean} [rect.fullScreen=false] when true, the video is rendered fullscreen and the rect is ignored
|
|
1729
|
+
* @param {VideoLayer} [rect.layer=VideoLayer.FRONT] which layer the ABR video should be rendered on
|
|
1730
|
+
* @returns {Promise}
|
|
1731
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
1732
|
+
*/
|
|
1733
|
+
setVideoRect({ x, y, width, height, fullScreen = false, layer } = {}) {
|
|
1734
|
+
if (!this._isDualDecodeActive()) {
|
|
1735
|
+
throw new RemotePlayerError(6506, "Cannot call setVideoRect() when dual decode is not enabled");
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
layer = layer ?? VideoLayer.FRONT;
|
|
1739
|
+
|
|
1740
|
+
const FCID = getFCID();
|
|
1741
|
+
const message = {
|
|
1742
|
+
type: "remotePlayer.setVideoRect",
|
|
1743
|
+
class: "remotePlayer",
|
|
1744
|
+
action: "setVideoRect",
|
|
1745
|
+
fcid: FCID,
|
|
1746
|
+
playbackId: this._playbackId,
|
|
1747
|
+
layer
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1750
|
+
if (fullScreen) {
|
|
1751
|
+
message.fullScreen = true;
|
|
1752
|
+
} else {
|
|
1753
|
+
const winW = window.innerWidth;
|
|
1754
|
+
const winH = window.innerHeight;
|
|
1755
|
+
const isValid = [x, y, width, height].every((value) => typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
1756
|
+
&& x + width <= winW && y + height <= winH;
|
|
1757
|
+
if (!isValid) {
|
|
1758
|
+
throw new RemotePlayerError(6505, `setVideoRect() received invalid rectangle {x:${x}, y:${y}, width:${width}, height:${height}} for window {width:${winW}, height:${winH}}`);
|
|
1759
|
+
}
|
|
1760
|
+
const { width: abrW, height: abrH } = getDualDecodeAbrSurfaceSize();
|
|
1761
|
+
message.x = Math.round(x * abrW / winW);
|
|
1762
|
+
message.y = Math.round(y * abrH / winH);
|
|
1763
|
+
message.width = Math.round(width * abrW / winW);
|
|
1764
|
+
message.height = Math.round(height * abrH / winH);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
if (window.cefQuery) {
|
|
1768
|
+
const logger = sdkLogger.withFields({ FCID });
|
|
1769
|
+
logger.log(`remotePlayer setVideoRect: sending setVideoRect action fullScreen=${!!fullScreen} layer=${layer}`);
|
|
1770
|
+
const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
|
|
1771
|
+
return new Promise((resolve, reject) => {
|
|
1772
|
+
let timerId = 0;
|
|
1773
|
+
const timeBeforeSendingRequest = Date.now();
|
|
1774
|
+
const queryId = window.cefQuery({
|
|
1775
|
+
request: JSON.stringify(request),
|
|
1776
|
+
persistent: false,
|
|
1777
|
+
onSuccess: () => {
|
|
1778
|
+
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1779
|
+
logger.withFields({ duration }).log(`setVideoRect completed successfully after ${duration} ms`);
|
|
1780
|
+
timerId = clearTimer(timerId);
|
|
1781
|
+
resolve();
|
|
1782
|
+
},
|
|
1783
|
+
onFailure: (code, msg) => {
|
|
1784
|
+
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1785
|
+
logger.withFields({ duration }).log(`setVideoRect failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
1786
|
+
timerId = clearTimer(timerId);
|
|
1787
|
+
reject(new RemotePlayerError(code, msg));
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
logger.log(`window.cefQuery for setVideoRect returned query id ${queryId}`);
|
|
1791
|
+
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
1792
|
+
timerId = setTimeout(() => {
|
|
1793
|
+
logger.log(`setVideoRect reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
1794
|
+
window.cefQueryCancel(queryId);
|
|
1795
|
+
reject(new RemotePlayerError(6000, `setVideoRect reached timeout of ${timeout} ms`));
|
|
1796
|
+
}, timeout, queryId);
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
sdkLogger.error("remotePlayer setVideoRect: window.cefQuery is undefined");
|
|
1800
|
+
return Promise.resolve(undefined);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
/** Make the remote player (ABR) video fullscreen on the given layer.
|
|
1804
|
+
* Only available when dual decode is enabled.
|
|
1805
|
+
* @param {VideoLayer} [layer=VideoLayer.FRONT] which layer the ABR video should be rendered on
|
|
1806
|
+
* @returns {Promise}
|
|
1807
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
1808
|
+
*/
|
|
1809
|
+
setVideoFullscreen(layer) {
|
|
1810
|
+
return this.setVideoRect({ fullScreen: true, layer: layer ?? VideoLayer.FRONT });
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1657
1813
|
/**
|
|
1658
1814
|
* Getter/Setter for currentTime
|
|
1659
1815
|
*/
|
|
@@ -140,6 +140,26 @@ export function isAudioSyncConfigured() {
|
|
|
140
140
|
return clientUiAudioSettings?.enabled && clientUiAudioSettings?.from_cdn && clientUiAudioSettings?.ui_sync_enabled;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/** Dual decode is enabled only when both the client capability and the common settings report it as enabled.
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
*/
|
|
146
|
+
export function isDualDecodeEnabled() {
|
|
147
|
+
const sessionInfoObj = sessionInfo.sessionInfoObj;
|
|
148
|
+
return !!(sessionInfoObj?.capabilities?.dualDecode?.enabled && sessionInfoObj?.settings?.common?.dualDecode?.enabled);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Get the ABR surface size that dual decode video should be scaled to before being sent to the client.
|
|
152
|
+
* The values are read from the client capabilities and coerced to numbers (the platform may report them as strings).
|
|
153
|
+
* @returns {{width: number, height: number}}
|
|
154
|
+
*/
|
|
155
|
+
export function getDualDecodeAbrSurfaceSize() {
|
|
156
|
+
const abrSurfaceSize = sessionInfo.sessionInfoObj?.capabilities?.dualDecode?.abrSurfaceSize;
|
|
157
|
+
return {
|
|
158
|
+
width: Number(abrSurfaceSize?.width ?? 1280),
|
|
159
|
+
height: Number(abrSurfaceSize?.height ?? 720)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
143
163
|
export function isSubtitlesTranslationAllowed() {
|
|
144
164
|
return sessionInfo.sessionInfoObj?.settings?.["ui-streamer"]?.allowSubtitlesTranslation === true;
|
|
145
165
|
}
|
|
@@ -181,7 +201,14 @@ export const StreamType = Object.freeze({
|
|
|
181
201
|
|
|
182
202
|
export const SwitchMode = Object.freeze({
|
|
183
203
|
NON_SEAMLESS: 0,
|
|
184
|
-
SEAMLESS: 1
|
|
204
|
+
SEAMLESS: 1,
|
|
205
|
+
DUAL_DECODE: 2
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/** Layer on which dual-decode ABR video is composited (remotePlayer.setVideoRect). */
|
|
209
|
+
export const VideoLayer = Object.freeze({
|
|
210
|
+
FRONT: 0,
|
|
211
|
+
BACK: 1
|
|
185
212
|
});
|
|
186
213
|
|
|
187
214
|
export const SeekState = Object.freeze({
|
|
@@ -132,6 +132,8 @@ export class RemotePlayerError extends Error {
|
|
|
132
132
|
* | 6502 | Player | unload() was called while previous unload/load was still in progress |
|
|
133
133
|
* | 6503 | Player | The remote player api call has invalid parameters |
|
|
134
134
|
* | 6504 | Player | The remote player api internal error |
|
|
135
|
+
* | 6505 | Player | setVideoRect() was called with an invalid rectangle (out of the window bounds or negative) |
|
|
136
|
+
* | 6506 | Player | setVideoRect() or setVideoFullscreen() was called when dual decode is not enabled |
|
|
135
137
|
* | 8001 | Player | Error pulling manifest. bad parameters |
|
|
136
138
|
* | 8002 | Player | Error pulling manifest. filters returned no data |
|
|
137
139
|
* | 8003 | Player | Error pulling manifest. fetch error |
|
|
@@ -246,6 +248,19 @@ class RemotePlayer extends EventTarget {
|
|
|
246
248
|
SPOKEN_SUBTITLES: "9"
|
|
247
249
|
};
|
|
248
250
|
|
|
251
|
+
/**
|
|
252
|
+
* @typedef {Object} VideoLayer
|
|
253
|
+
* @property {number} FRONT - The ABR video is rendered in front of the UI video.
|
|
254
|
+
* @property {number} BACK - The ABR video is rendered behind the UI video.
|
|
255
|
+
*/
|
|
256
|
+
/** The layer on which the remote player (ABR) video is rendered when dual decode is enabled.
|
|
257
|
+
* @type {VideoLayer}
|
|
258
|
+
*/
|
|
259
|
+
VideoLayer = Object.freeze({
|
|
260
|
+
FRONT: 0,
|
|
261
|
+
BACK: 1
|
|
262
|
+
});
|
|
263
|
+
|
|
249
264
|
/** get the player configuration.
|
|
250
265
|
* @returns {Config}
|
|
251
266
|
* */
|
|
@@ -325,7 +340,7 @@ class RemotePlayer extends EventTarget {
|
|
|
325
340
|
return noop("RemotePlayer.pause");
|
|
326
341
|
}
|
|
327
342
|
|
|
328
|
-
/** Stop playback
|
|
343
|
+
/** Stop playback of all streams (audio, video and subtitles).
|
|
329
344
|
* @returns {Promise}
|
|
330
345
|
* @throws {RemotePlayerError} error object contains code & msg
|
|
331
346
|
* */
|
|
@@ -333,6 +348,34 @@ class RemotePlayer extends EventTarget {
|
|
|
333
348
|
return noop("RemotePlayer.stop");
|
|
334
349
|
}
|
|
335
350
|
|
|
351
|
+
/** Set the position and size of the remote player (ABR) video on screen, or make it fullscreen.
|
|
352
|
+
* Only available when dual decode is enabled, otherwise throws.
|
|
353
|
+
* The given rectangle is expressed in the UI coordinate space (window.innerWidth/window.innerHeight)
|
|
354
|
+
* and is validated and scaled to the client's ABR surface size before being sent to the client.
|
|
355
|
+
* @param {Object} rect
|
|
356
|
+
* @param {number} [rect.x] horizontal position in px (UI coordinate space)
|
|
357
|
+
* @param {number} [rect.y] vertical position in px (UI coordinate space)
|
|
358
|
+
* @param {number} [rect.width] width in px (UI coordinate space)
|
|
359
|
+
* @param {number} [rect.height] height in px (UI coordinate space)
|
|
360
|
+
* @param {boolean} [rect.fullScreen=false] when true, the video is rendered fullscreen and the rect is ignored
|
|
361
|
+
* @param {VideoLayer} [rect.layer=VideoLayer.FRONT] which layer the ABR video should be rendered on
|
|
362
|
+
* @returns {Promise}
|
|
363
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
364
|
+
*/
|
|
365
|
+
setVideoRect(rect) {
|
|
366
|
+
return noop("RemotePlayer.setVideoRect", rect);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Make the remote player (ABR) video fullscreen on the given layer.
|
|
370
|
+
* Only available when dual decode is enabled, otherwise throws.
|
|
371
|
+
* @param {VideoLayer} [layer=VideoLayer.FRONT] which layer the ABR video should be rendered on
|
|
372
|
+
* @returns {Promise}
|
|
373
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
374
|
+
*/
|
|
375
|
+
setVideoFullscreen(layer = this.VideoLayer.FRONT) {
|
|
376
|
+
return noop("RemotePlayer.setVideoFullscreen", layer);
|
|
377
|
+
}
|
|
378
|
+
|
|
336
379
|
/** Get the currently loaded uri, or empty string is player is not loaded.
|
|
337
380
|
* @returns {string} loaded uri
|
|
338
381
|
* @throws {RemotePlayerError} error object contains code & msg
|
package/src/interface/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "4.5.
|
|
1
|
+
export const version = "4.5.9";
|