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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.5.8",
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
@@ -1 +1 @@
1
- export const version = "4.5.8";
1
+ export const version = "4.5.9";