senza-sdk 4.2.60 → 4.2.62

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.2.60",
3
+ "version": "4.2.62",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -14,7 +14,8 @@
14
14
  "prepublish": "npm run build",
15
15
  "eslint": "eslint --max-warnings 0 src test",
16
16
  "build": "npx webpack --config webpack.config.js",
17
- "test": "jest --coverage"
17
+ "test": "jest --coverage --verbose",
18
+ "testall" : "jest --coverage --verbose && npm run eslint --fix"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@babel/cli": "^7.13.16",
@@ -1,6 +1,6 @@
1
1
  import { getFCID, sdkLogger, getRestResponse } from "./utils";
2
- import {isRunningE2E} from "./api";
3
- import {sessionInfo} from "./SessionInfo";
2
+ import { isRunningE2E } from "./api";
3
+ import { sessionInfo } from "./SessionInfo";
4
4
 
5
5
  const wifiInfo = {};
6
6
  let wifi_ap_data;
@@ -290,7 +290,7 @@ class DeviceManager extends EventTarget {
290
290
  // This api is part of epic HSDEV-4185
291
291
  async getWifiInfo() {
292
292
  await Promise.all([getWifiApData(), getWifiStatus()]);
293
- return {...wifi_ap_data, ...wifi_status};
293
+ return { ...wifi_ap_data, ...wifi_status };
294
294
  }
295
295
  }
296
296
 
@@ -171,6 +171,15 @@ class RemotePlayer extends EventTarget {
171
171
  * });
172
172
  * */
173
173
 
174
+ /**
175
+ * @event RemotePlayer#playing
176
+ * @description playing event will be dispatched when the remote player started playback of the first audio frame. This event can be used to synchronize the local player with the remote player.
177
+ * @example
178
+ * remotePlayer.addEventListener("playing", () => {
179
+ * console.info("remotePlayer playing");
180
+ * });
181
+ * */
182
+
174
183
  /**
175
184
  * @event RemotePlayer#loadedmetadata
176
185
  * @description loadedmetadata event will be dispatched when the remote player metadata is loaded
@@ -55,35 +55,92 @@ const senzaShaka = { ...shaka };
55
55
  */
56
56
 
57
57
  export class SenzaShakaPlayer extends shaka.Player {
58
+ /** @private {SenzaShakaPlayer|null} Previous instance of the player */
58
59
  static _prevInstance = null;
60
+
59
61
  /**
60
62
  * @private
61
63
  * @type {Object.<string, string>}
62
- * @description Map between audio track language and id
64
+ * @description Map of audio track languages to their IDs
63
65
  */
64
66
  _audioTracksMap = {};
65
67
 
66
68
  /**
67
69
  * @private
68
70
  * @type {Object.<string, string>}
69
- * @description Map between text track language and id
71
+ * @description Map of text track languages to their IDs
70
72
  */
71
73
  _textTracksMap = {};
72
74
 
75
+ /**
76
+ * @private
77
+ * @type {number}
78
+ * @description Timeout in milliseconds to wait for playing event
79
+ * @default 3000
80
+ */
81
+ _playingTimeout = 3000;
82
+
83
+ /**
84
+ * @private
85
+ * @type {number|null}
86
+ * @description Timestamp when play was called
87
+ */
88
+ _playStartTime = null;
89
+
90
+ /**
91
+ * @private
92
+ * @type {boolean}
93
+ * @description Whether to stop remote player on error
94
+ * @default false
95
+ */
96
+ _shouldStopRemotePlayerOnError = false;
97
+
98
+ /**
99
+ * @private
100
+ * @type {Function|null}
101
+ * @description Original play function from video element
102
+ */
103
+ _originalPlay = null;
104
+
105
+ /**
106
+ * @private
107
+ * @type {Function|null}
108
+ * @description Resolve function for play promise
109
+ */
110
+ _playPromiseResolve = null;
111
+
112
+ /**
113
+ * @private
114
+ * @type {Function|null}
115
+ * @description Reject function for play promise
116
+ */
117
+ _playPromiseReject = null;
118
+
119
+ /**
120
+ * @private
121
+ * @type {number|null}
122
+ * @description Timer ID for play timeout
123
+ */
124
+ _playTimeoutId = null;
125
+
73
126
  /**
74
127
  * @private
75
128
  * @type {Object.<string, Function>}
76
- * @description Object containing event listeners for the video element.
129
+ * @description Event listeners for video element
77
130
  */
78
131
  _videoEventListeners = {
79
132
  "play": () => {
80
- this.remotePlayer.play()
81
- .catch(error => {
82
- sdkLogger.error("Failed to play remote player:", error);
83
- this.handleSenzaError(error.code, error.message || "Unknown play error");
84
- });
133
+ // keep old play behavior, in case playing timeout is defined as 0
134
+ if (this._playingTimeout === 0) {
135
+ this.remotePlayer.play()
136
+ .catch(error => {
137
+ sdkLogger.error("Failed to play remote player:", error);
138
+ this.handleSenzaError(error.code, error.message || "Unknown play error");
139
+ });
140
+ }
85
141
  },
86
- "pause": () => {
142
+ "pause" : () => {
143
+ this._resetPlayPromise();
87
144
  this.remotePlayer.pause()
88
145
  .catch(error => {
89
146
  sdkLogger.error("Failed to pause remote player:", error);
@@ -97,7 +154,7 @@ export class SenzaShakaPlayer extends shaka.Player {
97
154
  /**
98
155
  * @private
99
156
  * @type {Object.<string, Function>}
100
- * @description Object containing event listeners for the remote player.
157
+ * @description Event listeners for remote player
101
158
  */
102
159
  _remotePlayerEventListeners = {
103
160
  "ended": () => {
@@ -109,7 +166,6 @@ export class SenzaShakaPlayer extends shaka.Player {
109
166
  },
110
167
  "error": (event) => {
111
168
  sdkLogger.log("remotePlayer error:", event.detail.errorCode, event.detail.message);
112
- // we need to move to foreground here for the auto background mode to work
113
169
  lifecycle.moveToForeground();
114
170
  this.handleSenzaError(event.detail.errorCode, event.detail.message);
115
171
  },
@@ -166,17 +222,85 @@ export class SenzaShakaPlayer extends shaka.Player {
166
222
  this._textTracksMap[lang] = track.id;
167
223
  }
168
224
  }
225
+ },
226
+ "playing": async () => {
227
+ sdkLogger.info("remotePlayer playing event received");
228
+ // If playing timeout was not expored, and the feature is set, handle the playing event
229
+ if (this._playingTimeout > 0 && this._playTimeoutId !== null) {
230
+ this._handlePlayingEvent();
231
+ }
169
232
  }
170
233
  };
171
234
 
235
+ /**
236
+ * Clears the play timeout and resets the timer ID
237
+ * @private
238
+ * @description Cancels any pending play timeout and nullifies the timeout ID
239
+ */
240
+ _clearPlayTimeout = () => {
241
+ if (this._playTimeoutId) {
242
+ sdkLogger.info("Clearing play timeout");
243
+ clearTimeout(this._playTimeoutId);
244
+ this._playTimeoutId = null;
245
+ }
246
+ };
247
+ /**
248
+ * @private
249
+ * @description Handles the playing event from the remote player.
250
+ */
251
+ _handlePlayingEvent = async () => {
252
+
253
+ this._clearPlayTimeout();
172
254
 
255
+ if (this.videoElement && this._originalPlay && this._playPromiseResolve) {
256
+ try {
257
+ const elapsedTime = Date.now() - this._playStartTime;
258
+ sdkLogger.info(`Time for playback start ${elapsedTime}ms`);
259
+ await this._originalPlay.call(this.videoElement);
260
+ sdkLogger.info("Video element play resolved successfully");
261
+ this._playPromiseResolve();
262
+ } catch (error) {
263
+ this._handlePlayPromiseError(error);
264
+ return;
265
+ } finally {
266
+ this._playPromiseResolve = null;
267
+ this._playPromiseReject = null;
268
+ }
269
+ }
270
+ };
173
271
  /**
174
- * Flag indicating whether the remote player should be stopped when an error occurs
175
272
  * @private
176
- * @type {boolean}
177
- * @default false
273
+ * @description Resets the play promise state, clearing any existing resolve/reject functions and timeouts.
178
274
  */
179
- _shouldStopRemotePlayerOnError = false;
275
+ _resetPlayPromise = () => {
276
+ if (this._playPromiseResolve) {
277
+ sdkLogger.info("Resolving play promise");
278
+ this._playPromiseResolve();
279
+ }
280
+ this._playPromiseResolve = null;
281
+ this._playPromiseReject = null;
282
+ this._clearPlayTimeout();
283
+ };
284
+
285
+
286
+ /**
287
+ * Handles errors for play promises and remote player events.
288
+ * @private
289
+ * @param {Error} error - The error object.
290
+ */
291
+ _handlePlayPromiseError(error) {
292
+
293
+ sdkLogger.error("Error while waiting for playing event:", error);
294
+ if (this._playPromiseReject) {
295
+ this._playPromiseReject(error);
296
+ this._playPromiseResolve = null;
297
+ this._playPromiseReject = null;
298
+ }
299
+ if (this._playTimeoutId) {
300
+ clearTimeout(this._playTimeoutId);
301
+ this._playTimeoutId = null;
302
+ }
303
+ }
180
304
 
181
305
  /**
182
306
  * Creates an instance of SenzaShakaPlayer, which is a subclass of shaka.Player.
@@ -201,15 +325,62 @@ export class SenzaShakaPlayer extends shaka.Player {
201
325
 
202
326
  this.remotePlayer = remotePlayer;
203
327
  this._addRemotePlayerEventListeners();
328
+ SenzaShakaPlayer._prevInstance = this;
329
+ const playTimeout = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"]?.playingEventTimeout;
330
+ this._playingTimeout = (playTimeout >= 0) ? playTimeout*1000 : this._playingTimeout;
331
+
204
332
  // if video element is provided, add the listeres here. In this case ,there is no need to call attach.
205
333
  if (videoElement) {
206
- this.videoElement = videoElement;
207
- this._attachVideoElementToRemotePlayer();
334
+ this._attach(videoElement);
208
335
  sdkLogger.warn("SenzaShakaPlayer constructor Adding videoElement in the constructor is going to be deprecated in the future. Please use attach method instead.");
209
336
  }
210
- SenzaShakaPlayer._prevInstance = this;
211
337
 
338
+ }
339
+
340
+ _attach(videoElement) {
341
+ this.videoElement = videoElement;
342
+
343
+ // Store original play and replace with our implementation.
344
+ if (this._playingTimeout > 0) {
345
+ this._originalPlay = this.videoElement.play;
346
+ this.videoElement.play = async () => {
347
+ if (this._playPromiseResolve || this._playPromiseReject) {
348
+ sdkLogger.warn("play() was called while a play promise is already pending. Ignoring play call.");
349
+ return;
350
+ }
351
+
352
+ // Clear any existing timeout
353
+ if (this._playTimeoutId) {
354
+ clearTimeout(this._playTimeoutId);
355
+ }
356
+ // Store the timestamp of the play call
357
+ this._playStartTime = Date.now();
358
+
359
+ const returnPromise = new Promise((resolve, reject) => {
360
+ this._playPromiseResolve = resolve;
361
+ this._playPromiseReject = reject;
362
+
363
+
364
+ });
365
+ // Set timeout to reject if playing event doesn't arrive
366
+ this._playTimeoutId = setTimeout(() => {
367
+ sdkLogger.error("Playing event timeout reached");
368
+ // when timeout reached start the local playback anyway
369
+ this._handlePlayingEvent();
370
+ }, this._playingTimeout);
371
+ await this.remotePlayer.play()
372
+ .catch(error => {
373
+ sdkLogger.error("Failed to play remote player:", error);
374
+ this.handleSenzaError(error.code, error.message || "Unknown play error");
375
+
376
+ });
377
+
378
+ // Create a new promise that will resolve when the real play succeeds
379
+ return returnPromise;
380
+ };
381
+ };
212
382
 
383
+ this._attachVideoElementToRemotePlayer();
213
384
  }
214
385
 
215
386
  /**
@@ -220,8 +391,7 @@ export class SenzaShakaPlayer extends shaka.Player {
220
391
  */
221
392
  async attach(videoElement, initializeMediaSource = true) {
222
393
  await super.attach(videoElement, initializeMediaSource);
223
- this.videoElement = videoElement;
224
- this._attachVideoElementToRemotePlayer();
394
+ this._attach(videoElement);
225
395
  }
226
396
 
227
397
  /**
@@ -235,6 +405,17 @@ export class SenzaShakaPlayer extends shaka.Player {
235
405
  * @export
236
406
  */
237
407
  async detach(keepAdManager = false) {
408
+ // Clear any pending timeout
409
+ this._resetPlayPromise();
410
+
411
+ if (this.videoElement && this._originalPlay) {
412
+ // Clear any pending play promise
413
+ this._playPromiseResolve = null;
414
+ this._playPromiseReject = null;
415
+ // Restore original play function before detaching
416
+ this.videoElement.play = this._originalPlay;
417
+ this._originalPlay = null;
418
+ }
238
419
 
239
420
  await super.detach(keepAdManager);
240
421
  this._audioTracksMap = {};
@@ -265,6 +446,7 @@ export class SenzaShakaPlayer extends shaka.Player {
265
446
  // Call the remote player's unload method
266
447
  try {
267
448
  await lifecycle.moveToForeground();
449
+ this._clearPlayTimeout();
268
450
  await remotePlayer.unload();
269
451
  } catch (error) {
270
452
  sdkLogger.error("Failed to unload remote player:", error);
@@ -391,7 +573,8 @@ export class SenzaShakaPlayer extends shaka.Player {
391
573
  sdkLogger.error("Error while trying to stop video element playback:", stopError);
392
574
  }
393
575
  }
394
-
576
+ // Handle error while waiting for play event
577
+ this._handlePlayPromiseError(error);
395
578
  this.dispatchEvent(new shaka.util.FakeEvent("error", errorMap));
396
579
  }
397
580
 
@@ -487,6 +670,17 @@ export class SenzaShakaPlayer extends shaka.Player {
487
670
  return super.destroy();
488
671
  }
489
672
 
673
+ /**
674
+ * A temporary override for older versions of Shaka.
675
+ * Senza doesn't support out-of-band subtitles
676
+ */
677
+ addTextTrack(uri, language, kind, mimeType, codec, label, forced = false) {
678
+ sdkLogger.warn("addTextTrack is deprecated, please use addTextTrackAsync");
679
+ super.addTextTrackAsync(uri, language, kind, mimeType, codec, label, forced).then(subs => {
680
+ sdkLogger.warn("addTextTrackAsync Done" + subs);
681
+ });
682
+ }
683
+
490
684
  /**
491
685
  * Override the configure method to add custom configuration handling
492
686
  * Supports the following additional configuration options: