senza-sdk 4.5.5 → 4.5.7

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.5",
3
+ "version": "4.5.7",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -66,8 +66,13 @@ class BackgroundRenderControl {
66
66
 
67
67
  _init() {
68
68
  if (this._initialized) return;
69
- this._enabled = sessionInfo?.sessionInfoObj.settings?.["ui-streamer"]?.throttleRenderingInBackground?.enabled ?? false;
70
- sdkLogger.log(`Initializing BackgroundRenderControl: enabled=${this._enabled}`);
69
+ const sessionInfoObj = sessionInfo?.sessionInfoObj;
70
+ const explicitEnabled = sessionInfoObj?.settings?.["ui-streamer"]?.throttleRenderingInBackground?.enabled;
71
+ const profile = sessionInfoObj?.profile ?? "";
72
+ const isRemoteGpuProfile = typeof profile === "string" && profile.startsWith("remote-gpu");
73
+ // An explicit setting always wins; otherwise auto-enable for remote-gpu profiles.
74
+ this._enabled = explicitEnabled ?? isRemoteGpuProfile;
75
+ sdkLogger.log(`Initializing BackgroundRenderControl: enabled=${this._enabled} (profile=${profile}, explicit=${explicitEnabled})`);
71
76
  if (!this._enabled) return;
72
77
  this._lastState = lifecycle.state;
73
78
  lifecycle.addEventListener("onstatechange", this._listener);
@@ -3,7 +3,6 @@ import { RemotePlayer as RemotePlayerInterface, RemotePlayerError as RemotePlaye
3
3
  import {
4
4
  getFCID,
5
5
  generatePlaybackId,
6
- getRestResponse,
7
6
  isAudioSyncConfigured,
8
7
  clearTimer,
9
8
  sdkLogger,
@@ -154,6 +153,21 @@ class RemotePlayer extends RemotePlayerInterface {
154
153
  * @private
155
154
  */
156
155
  this._playbackId = "";
156
+
157
+ /**
158
+ * Anchor position in seconds used by getPlaybackPosition().
159
+ * @type {number}
160
+ * @private
161
+ */
162
+ this._playbackPositionAnchor = getPlaybackInfo()?.playbackPosition || 0;
163
+
164
+ /**
165
+ * Timestamp in msec from which elapsed play time is accumulated.
166
+ * Undefined means playback clock is currently paused.
167
+ * @type {number | undefined}
168
+ * @private
169
+ */
170
+ this._playbackClockStartTimestampMs = undefined;
157
171
  }
158
172
 
159
173
  /** @private Initialize the remote player
@@ -199,6 +213,12 @@ class RemotePlayer extends RemotePlayerInterface {
199
213
  this._autoTune = false;
200
214
 
201
215
  this._isPlaying = window?.sessionStorage?.getItem("senzaSdk_isPlaying") === "true";
216
+ if (this._isPlaying) {
217
+ this._playbackPositionAnchor = getPlaybackInfo()?.playbackPosition || 0;
218
+ this._startPlaybackClock();
219
+ } else {
220
+ this._stopPlaybackClock();
221
+ }
202
222
 
203
223
  sdkLogger.info(`RemotePlayer initialized with load mode: ${this._loadMode}, senzaSdk_isPlaying: ${this._isPlaying}`);
204
224
  this._loadedUrl = playerState?.playbackUrl || "";
@@ -267,6 +287,16 @@ class RemotePlayer extends RemotePlayerInterface {
267
287
  }
268
288
  });
269
289
 
290
+ typeof document !== "undefined" && document.addEventListener("hs/playbackPosition", (e) => {
291
+ if (this._hasPlaybackIdMismatch(e?.detail, "hs/playbackPosition")) return;
292
+ // while seeking we do not want to update the playback position anchor, as it will cause the playback position to jump back to the previous position
293
+ if (!this._isSeekingByApplication) {
294
+ const detail = e?.detail;
295
+ const playbackPosition = typeof detail === "number" ? detail : detail?.data?.UiExpectedPts;
296
+ this._setPlaybackPositionAnchor(playbackPosition);
297
+ }
298
+ });
299
+
270
300
  typeof document !== "undefined" && document.addEventListener("hs/playback", (e) => {
271
301
  if (this._hasPlaybackIdMismatch(e?.detail, "hs/playback")) return;
272
302
  sdkLogger.info("Got hs/playback event with detail", JSON.stringify(e?.detail));
@@ -422,17 +452,74 @@ class RemotePlayer extends RemotePlayerInterface {
422
452
 
423
453
  /** @private change the internal load mode and store the new mode in session storage */
424
454
  _changePlayMode(newPlayMode) {
425
- if (this._isPlaying !== newPlayMode) {
455
+ const didPlayModeChange = this._isPlaying !== newPlayMode;
456
+ if (didPlayModeChange) {
426
457
  // Create event with play mode detail
427
458
  const event = new CustomEvent("playModeChange", {
428
459
  detail: { isPlaying: newPlayMode }
429
460
  });
430
461
  this.dispatchEvent(event);
462
+ if (newPlayMode) {
463
+ this._startPlaybackClock();
464
+ }
431
465
  }
432
466
  this._isPlaying = newPlayMode;
467
+
468
+ if (!newPlayMode) {
469
+ this._stopPlaybackClock();
470
+ }
433
471
  window?.sessionStorage?.setItem("senzaSdk_isPlaying", String(newPlayMode));
434
472
  }
435
473
 
474
+ /**
475
+ * @private
476
+ * @param {number} playbackPosition
477
+ */
478
+ _setPlaybackPositionAnchor(playbackPosition) {
479
+ if (typeof playbackPosition !== "number" || !Number.isFinite(playbackPosition)) {
480
+ return;
481
+ }
482
+
483
+ this._playbackPositionAnchor = playbackPosition;
484
+ if (this._playbackClockStartTimestampMs !== undefined) {
485
+ this._playbackClockStartTimestampMs = Date.now();
486
+ }
487
+ }
488
+
489
+ /**
490
+ * @private
491
+ */
492
+ _startPlaybackClock() {
493
+ if (this._playbackClockStartTimestampMs === undefined) {
494
+ this._playbackClockStartTimestampMs = Date.now();
495
+ }
496
+ }
497
+
498
+ /**
499
+ * @private
500
+ */
501
+ _stopPlaybackClock() {
502
+ if (this._playbackClockStartTimestampMs === undefined) {
503
+ return;
504
+ }
505
+
506
+ const elapsedSec = (Date.now() - this._playbackClockStartTimestampMs) / 1000;
507
+ this._playbackPositionAnchor += Math.max(0, elapsedSec);
508
+ this._playbackClockStartTimestampMs = undefined;
509
+ }
510
+
511
+ /**
512
+ * @private
513
+ * @returns {number}
514
+ */
515
+ _getSyncedPlaybackPosition() {
516
+ if (this._playbackClockStartTimestampMs === undefined) {
517
+ return this._playbackPositionAnchor;
518
+ }
519
+ const elapsedSec = (Date.now() - this._playbackClockStartTimestampMs) / 1000;
520
+ return this._playbackPositionAnchor + Math.max(0, elapsedSec);
521
+ }
522
+
436
523
 
437
524
  /** @private manage getting the local player current video frame media time and sending to the client
438
525
  * */
@@ -498,12 +585,15 @@ class RemotePlayer extends RemotePlayerInterface {
498
585
  this._availabilityStartTime = undefined;
499
586
  this._isSeekingByPlatform = false;
500
587
  this._isSeekingByApplication = false;
588
+ this._stopPlaybackClock();
589
+ this._playbackPositionAnchor = 0;
501
590
  }
502
591
 
503
592
  _seek(playbackPosition, waitForResponse = true) {
504
593
  if (!this._isInitialized) {
505
594
  throw new RemotePlayerError(6500, "Cannot call seek() if remote player is not initialized");
506
595
  }
596
+ this._setPlaybackPositionAnchor(playbackPosition);
507
597
  setPlaybackInfo({ playbackPosition });
508
598
  if (window.cefQuery) {
509
599
  if (this._loadMode !== this.LoadMode.LOADED) {
@@ -632,6 +722,9 @@ class RemotePlayer extends RemotePlayerInterface {
632
722
  }
633
723
 
634
724
  _pause() {
725
+ // Stop the playback clock whenever we pause, including during internal seek operations
726
+ this._stopPlaybackClock();
727
+
635
728
  if (window.cefQuery) {
636
729
  const isForegroundState = lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND;
637
730
  if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled() && isForegroundState) {
@@ -826,12 +919,12 @@ class RemotePlayer extends RemotePlayerInterface {
826
919
  this._changeLoadMode(this.LoadMode.LOADING);
827
920
  this._changePlayMode(false);
828
921
  return new Promise((resolve, reject) => {
922
+ const playbackPosition = position || 0;
829
923
  if (reset) {
830
924
  // first, zero playback position
831
925
  setPlaybackInfo({ playbackPosition: 0 });
832
926
  }
833
927
  const FCID = getFCID();
834
- const playbackPosition = position ?? 0;
835
928
  const logger = sdkLogger.withFields({ FCID, loadUrl: url, playbackPosition });
836
929
  const audioLanguage = audioTrackId || this._selectedAudioTrack || this._config.preferredAudioLanguage || "";
837
930
  const subtitlesLanguage = textTrackId || this._selectedSubtitlesTrack || this._config.preferredSubtitlesLanguage || "";
@@ -876,8 +969,9 @@ class RemotePlayer extends RemotePlayerInterface {
876
969
  onSuccess: () => {
877
970
  const duration = Date.now() - timeBeforeSendingRequest;
878
971
  logger.withFields({ duration }).log(`${message.type} completed successfully after ${duration} ms`);
879
- if (position > 0) {
880
- setPlaybackInfo({ playbackPosition: position });
972
+ if (playbackPosition > 0) {
973
+ this._setPlaybackPositionAnchor(playbackPosition);
974
+ setPlaybackInfo({ playbackPosition });
881
975
  }
882
976
  timerId = clearTimer(timerId);
883
977
  this._changeLoadMode(this.LoadMode.LOADED);
@@ -1138,7 +1232,7 @@ class RemotePlayer extends RemotePlayerInterface {
1138
1232
  * @returns {Promise<number|undefined>} current playback position
1139
1233
  * @throws {RemotePlayerError} error object contains code & msg
1140
1234
  */
1141
- async getPlaybackPosition() {
1235
+ getPlaybackPosition() {
1142
1236
  if (!this._isInitialized) {
1143
1237
  throw new RemotePlayerError(6500, "Cannot call getPlaybackPosition() if remote player is not initialized");
1144
1238
  }
@@ -1146,15 +1240,7 @@ class RemotePlayer extends RemotePlayerInterface {
1146
1240
  throw new RemotePlayerError(6001, "Cannot call getPlaybackPosition() if player is not loaded");
1147
1241
  }
1148
1242
 
1149
- try {
1150
- const response = await getRestResponse("playback-position");
1151
- const { currentVideoPosition } = JSON.parse(response || "{}");
1152
- return currentVideoPosition;
1153
- } catch (error) {
1154
- const message = error instanceof Error ? error.message : String(error);
1155
- sdkLogger.error(`remotePlayer getPlaybackPosition failed: ${message}`);
1156
- throw new RemotePlayerError(6504, `getPlaybackPosition() failed: ${message}`);
1157
- }
1243
+ return this._getSyncedPlaybackPosition();
1158
1244
  }
1159
1245
 
1160
1246
  /**
@@ -1572,6 +1658,7 @@ class RemotePlayer extends RemotePlayerInterface {
1572
1658
  * Getter/Setter for currentTime
1573
1659
  */
1574
1660
  get currentTime() {
1661
+ // Todo - this can be replaced with the getPlaybackPosition, but it might cause a regression so we can leave it for now.
1575
1662
  return getPlaybackInfo()?.playbackPosition;
1576
1663
  }
1577
1664
  set currentTime(playbackPosition) {
@@ -1674,7 +1761,7 @@ class RemotePlayer extends RemotePlayerInterface {
1674
1761
 
1675
1762
  if (!this._isSeekingByPlatform) {
1676
1763
  this._pendingSeekPosition = playbackPosition;
1677
-
1764
+ this._setPlaybackPositionAnchor(playbackPosition);
1678
1765
  // If the local player is just initializing after loading the manifest, ignore the seeking event.
1679
1766
  if (this._localPlayerLoadCurrentTime === 0 && this._videoElement.currentTime > ONE_DAY_SECONDS) {
1680
1767
  sdkLogger.info(`Seeking ignored for video currentTime init: currentTime: ${this._videoElement.currentTime}`);
@@ -357,11 +357,11 @@ class RemotePlayer extends EventTarget {
357
357
  return this.LoadMode.NOT_LOADED;
358
358
  }
359
359
 
360
- /** Get the playback position directly from the connector. Note that this function can load the system. It is recommended to use it when accurate time is needed and currentTime can not be used.
361
- * @returns {Promise<number|undefined>} current playback position
360
+ /** Get the current playback position.
361
+ * @returns {number|undefined} current playback position
362
362
  * @throws {RemotePlayerError} error object contains code & msg
363
363
  */
364
- async getPlaybackPosition() {
364
+ getPlaybackPosition() {
365
365
  return 0;
366
366
  }
367
367
 
@@ -1 +1 @@
1
- export const version = "4.5.5";
1
+ export const version = "4.5.7";