senza-sdk 4.5.8 → 4.6.0

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.
@@ -1,7 +1,7 @@
1
1
  import { SenzaShakaPlayer as SenzaShakaInterface, shaka } from "../interface/senzaShakaPlayer";
2
2
  import * as shakaDebug from "shaka-player/dist/shaka-player.compiled.debug.js";
3
3
  import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
4
- import { ONE_DAY_SECONDS } from "./remotePlayer.js";
4
+ import { ONE_DAY_SECONDS, VOD_INIT_SEEK_EPSILON_SEC } from "./remotePlayer.js";
5
5
  import { sdkLogger, SwitchMode } from "./utils";
6
6
  import moment from "moment";
7
7
  import set from "lodash.set";
@@ -156,12 +156,19 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
156
156
  _seekCounterAfterLoad = 0;
157
157
 
158
158
  /**
159
- * When true, the next `seeked` is Shaka jumping to live-edge time after load; skip background pause logic.
159
+ * When true, the next `seeked` is Shaka jumping to live-edge / VOD startTime after load; skip background pause logic.
160
160
  * @private
161
161
  * @type {boolean}
162
162
  */
163
163
  _ignoreNextSeekedForInit = false;
164
164
 
165
+ /**
166
+ * Start position (seconds) from the most recent load(). Used to ignore Shaka's VOD init seeking/seeked.
167
+ * @private
168
+ * @type {number}
169
+ */
170
+ _loadStartPosition = 0;
171
+
165
172
  /**
166
173
  * Whether the player should call lifecycle.moveToForeground on pause, remote ended/error, unload, and destroy.
167
174
  * Only when auto-background is enabled (including streamer overrides) and the UI moved to background via the
@@ -185,8 +192,16 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
185
192
  this._ignoreNextSeekedForInit = false;
186
193
  },
187
194
  "seeking": () => {
188
- // If the first seeking after load started from time 0, and the current time is a UTC time (live asset), ignore the next seeked event to avoid background pause logic.
189
- if (this._seekCounterAfterLoad < 1 && this._loadStartedAtZero && this.videoElement.currentTime > ONE_DAY_SECONDS) {
195
+ // Ignore the first seeking after load when it is Shaka's initialization jump:
196
+ // - Live: currentTime is a UTC wall-clock time (> one day in seconds)
197
+ // - VOD: currentTime is within epsilon of this._loadStartPosition
198
+ const isFirstSeekAfterLoadAtZero = this._seekCounterAfterLoad < 1 && this._loadStartedAtZero;
199
+ const isLiveInitSeek = isFirstSeekAfterLoadAtZero && this.videoElement.currentTime > ONE_DAY_SECONDS;
200
+ const isVodInitSeek = isFirstSeekAfterLoadAtZero
201
+ && this._loadStartPosition > 0
202
+ && Math.abs(this.videoElement.currentTime - this._loadStartPosition) <= VOD_INIT_SEEK_EPSILON_SEC;
203
+ if (isLiveInitSeek || isVodInitSeek) {
204
+ sdkLogger.info("senzaShakaPlayer seeking: ignoring next seeked for init");
190
205
  this._ignoreNextSeekedForInit = true;
191
206
  } else {
192
207
  // Another seeking before seeked means this is no longer the load initialization jump.
@@ -820,6 +835,9 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
820
835
  startTime = -minInitialLiveOffset;
821
836
  }
822
837
 
838
+ // Used to ignore Shaka's init seeking/seeked when currentTime matches load start.
839
+ this._loadStartPosition = startTime ?? 0;
840
+
823
841
  // This callback will be activated when the manifest is loaded. It will trigger load of the remote player.
824
842
  // This will ensure that the remote player is loaded only after the manifest is loaded by local player.
825
843
  const responseFilterCallback = async (type, response) => {
@@ -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({
@@ -10,7 +10,6 @@ import { noop } from "./utils.js";
10
10
  /**
11
11
  * Overlay is a singleton class that manages displaying UI frames as image overlays. The image can be displayed over background video playback.
12
12
  * It can be used in any lifecycle state.
13
- * @private
14
13
  * @fires error
15
14
  */
16
15
  class Overlay extends EventTarget {
@@ -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.6.0";