senza-sdk 4.2.65-d2761c0.0 → 4.3.1-ca3d96f.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.
Files changed (27) hide show
  1. package/dist/bundle.js +1 -1
  2. package/package.json +17 -8
  3. package/src/api.js +258 -327
  4. package/src/{alarmManager.js → implementation/alarmManager.js} +15 -52
  5. package/src/implementation/api.js +363 -0
  6. package/src/{deviceManager.js → implementation/deviceManager.js} +6 -78
  7. package/src/{lifecycle.js → implementation/lifecycle.js} +37 -225
  8. package/src/implementation/messageManager.js +55 -0
  9. package/src/{platformManager.js → implementation/platformManager.js} +5 -23
  10. package/src/{remotePlayer.js → implementation/remotePlayer.js} +35 -237
  11. package/src/{senzaShakaPlayer.js → implementation/senzaShakaPlayer.js} +92 -125
  12. package/src/{utils.js → implementation/utils.js} +15 -6
  13. package/src/interface/alarmManager.js +76 -0
  14. package/src/interface/api.js +8 -0
  15. package/src/{devSequence.js → interface/devSequence.js} +35 -0
  16. package/src/interface/deviceManager.js +142 -0
  17. package/src/interface/lifecycle.js +283 -0
  18. package/src/interface/messageManager.js +53 -0
  19. package/src/interface/platformManager.js +41 -0
  20. package/src/interface/remotePlayer.js +470 -0
  21. package/src/interface/senzaShakaPlayer.js +168 -0
  22. package/src/interface/utils.js +45 -0
  23. package/src/messageManager.js +0 -88
  24. /package/src/{SessionInfo.js → implementation/SessionInfo.js} +0 -0
  25. /package/src/{devHelper.js → implementation/devHelper.js} +0 -0
  26. /package/src/{eventListenersManager.js → implementation/eventListenersManager.js} +0 -0
  27. /package/src/{subtitlesUtils.js → implementation/subtitlesUtils.js} +0 -0
@@ -1,3 +1,5 @@
1
+ // eslint-disable-next-line no-unused-vars
2
+ import { RemotePlayer as RemotePlayerInterface, RemotePlayerError as RemotePlayerErrorInterface, Config } from "../interface/remotePlayer";
1
3
  import {
2
4
  getFCID,
3
5
  isAudioSyncConfigured,
@@ -27,11 +29,7 @@ const cloneDeep = (element) => {
27
29
  return JSON.parse(JSON.stringify(element));
28
30
  };
29
31
 
30
- // TODO: check that the link below to the error list is working on dashreadme
31
- /** Error object to be thrown on remotePlayer api failures.
32
- * [See error list]{@link RemotePlayer#error}
33
- */
34
- export class RemotePlayerError extends Error {
32
+ export class RemotePlayerError extends RemotePlayerErrorInterface {
35
33
  constructor(code, message) {
36
34
  super(message);
37
35
  this.code = code;
@@ -63,26 +61,7 @@ function setPlaybackInfo(playbackInfo) {
63
61
  }
64
62
  }
65
63
 
66
-
67
- /**
68
- * @typedef {Object} Config
69
- * @property {string} preferredAudioLanguage
70
- * @property {string} preferredSubtitlesLanguage
71
- * @property {boolean} autoPlay - (Not implemented yet) upon loading start playing automatically
72
- */
73
-
74
- /**
75
- * RemotePlayer a singleton class to communicate with remote player
76
- * @fires timeupdate
77
- * @fires tracksupdate
78
- * @fires ended
79
- * @fires error
80
- * @fires onloadmodechange
81
- * @fires seeking (Not implemented yet)
82
- * @fires seeked (Not implemented yet)
83
- * @fires loadedmetadata (Not implemented yet)
84
- */
85
- class RemotePlayer extends EventTarget {
64
+ class RemotePlayer extends RemotePlayerInterface {
86
65
  constructor() {
87
66
  super();
88
67
  /**
@@ -91,7 +70,8 @@ class RemotePlayer extends EventTarget {
91
70
  */
92
71
  this._config = {
93
72
  preferredAudioLanguage: "",
94
- preferredSubtitlesLanguage: ""
73
+ preferredSubtitlesLanguage: "",
74
+ minSuggestedPresentationDelay: 0
95
75
  };
96
76
  /**
97
77
  * @type {string}
@@ -161,49 +141,11 @@ class RemotePlayer extends EventTarget {
161
141
  */
162
142
  this._isPlaying = false;
163
143
 
164
- /**
165
- * @event RemotePlayer#canplay
166
- * @description canplay event will be dispatched when the remote player can start play the event
167
- * @example
168
- * remotePlayer.addEventListener("canplay", () => {
169
- * console.info("remotePlayer canplay");
170
- * remotePlayer.play();
171
- * });
172
- * */
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
-
183
- /**
184
- * @event RemotePlayer#loadedmetadata
185
- * @description loadedmetadata event will be dispatched when the remote player metadata is loaded
186
- * and the audio/video tracks are available
187
- * @example
188
- * remotePlayer.addEventListener("loadedmetadata", () => {
189
- * console.info("remotePlayer loadedmetadata", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
190
- * });
191
- * */
192
144
  typeof document !== "undefined" && document.addEventListener("hs/remotePlayerEvent", (e) => {
193
145
  sdkLogger.info("Got hs/remotePlayerEvent event with detail", JSON.stringify(e?.detail));
194
146
  this.dispatchEvent(new Event(e?.detail?.eventName));
195
147
  });
196
148
 
197
- /**
198
- *
199
- * @event RemotePlayer#timeupdate
200
- * @example
201
- * remotePlayer.addEventListener("timeupdate", () => {
202
- * console.info("remotePlayer timeupdate", remotePlayer.currentTime);
203
- * localPlayer.getMediaElement().currentTime = remotePlayer.currentTime || 0;
204
- * });
205
- *
206
- */
207
149
  typeof document !== "undefined" && document.addEventListener("hs/playbackInfoEvent", () => {
208
150
  sdkLogger.info("Got hs/playbackInfoEvent");
209
151
  // When attached, the sdk controls the synchronization between the local and remote player.
@@ -217,15 +159,6 @@ class RemotePlayer extends EventTarget {
217
159
  }
218
160
  });
219
161
 
220
- /**
221
- *
222
- * @event RemotePlayer#tracksupdate
223
- * @example
224
- * remotePlayer.addEventListener("tracksupdate", () => {
225
- * console.info("remotePlayer tracksupdate", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
226
- * });
227
- *
228
- */
229
162
  typeof document !== "undefined" && document.addEventListener("hs/playback", (e) => {
230
163
  sdkLogger.info("Got hs/playback event with detail", JSON.stringify(e?.detail));
231
164
  this._availabilityStartTime = e?.detail?.availabilityStartTime;
@@ -233,13 +166,6 @@ class RemotePlayer extends EventTarget {
233
166
  this.dispatchEvent(new Event("tracksupdate"));
234
167
  });
235
168
 
236
- /**
237
- * @event RemotePlayer#ended
238
- * @example
239
- * remotePlayer.addEventListener("ended", () => {
240
- * console.info("remotePlayer ended");
241
- * });
242
- */
243
169
  typeof document !== "undefined" && document.addEventListener("hs/EOS", () => {
244
170
  sdkLogger.info("Got hs/EOS event");
245
171
  this.dispatchEvent(new Event("ended"));
@@ -272,72 +198,7 @@ class RemotePlayer extends EventTarget {
272
198
  sdkLogger.info(`Adding ${event.detail} seconds, previousTime=${previousCurrentTime} currentTime=${this._videoElement.currentTime}`);
273
199
  });
274
200
 
275
- /**
276
- *
277
- * @event RemotePlayer#error
278
- * @type {object}
279
- * @property {int} detail.errorCode
280
- * @property {string} detail.message
281
- *
282
- * @see Possible error codes:
283
- *
284
- * | Code | Domain | Description |
285
- * | :-------- | :---------------- | :-----------------------------------------------------------------------------------------------|
286
- * | 23 | Player | load() failed due to remote player initialization error |
287
- * | 98 | Player | load() failed due to remote player failure to send message to the client |
288
- * | 99 | Player | load() failed due to remote player reporting invalid message |
289
- * | 1000 | Encrypted content | Failed to create or initialise the CDM |
290
- * | 1001 | Encrypted content | Failed to create a CDM session |
291
- * | 1002 | Encrypted content | CDM failed to generate a license request |
292
- * | 1003 | Encrypted content | The CDM rejected the license server response |
293
- * | 1004 | Encrypted content | The CDM rejected the license server certificate |
294
- * | 1005 | Encrypted content | All keys in the license have expired |
295
- * | 1006 | Encrypted content | Output device is incompatible with the license requirements (HDCP) |
296
- * | 1007 | Encrypted content | The device has been revoked |
297
- * | 1008 | Encrypted content | The device secrets aren't available |
298
- * | 1009 | Encrypted content | Keys are loaded but the KID requested by playback isn't found. The app has likely issued a license for the wrong content or there is a mismatch between the KIDs in the license and the data plane |
299
- * | 1010 | Encrypted content | The CDM failed to provision, therefore it is not possible to play encrypted content |
300
- * | 1100 | Encrypted content | The CDM session has already received a license response. The app has likely issued 2, or more, license responses for the same request. The subsequent licenses will be ignored so this error is informational only |
301
- * | 1101 | Encrypted content | The license has been rejected since an error was received by the CDM. The app has likely sent a non-200 code to `WriteLicenseResponse` |
302
- * | 1102 | Encrypted content | A license response wasn't received from the app within a pre-defined timeout |
303
- * | 1103 | Encrypted content | The CDM session associated with this license response is in an invalid state. This is an internal Senza platform error |
304
- * | 1104 | Encrypted content | The CDM failed to send a license request to the app. This is an internal Senza platform error |
305
- * | 1999 | Encrypted content | An unknown encrypted content error |
306
- * | 2000 | Player | Content makes reference to no or unsupported key system |
307
- * | 3000 | Player | Unexpected problem with playback, only used if no more specific code in 3xxx range applies |
308
- * | 3001 | Player | Problem accessing content manifest, only used if no more specific code in 8xxx range applies |
309
- * | 3002 | Player | Unexpectedly stopped playback |
310
- * | 3100 | Player | Problem parsing MP4 content |
311
- * | 3200 | Player | Problem with decoder |
312
- * | 3300 | Player | DRM keys unavailable, player waited for keys but none arrived |
313
- * | 3400 | Player | Problem accessing segments, only used if no more specific code in 34xx range applies |
314
- * | 3401 | Player | Problem accessing segments, connection issue or timeout |
315
- * | 3402 | Player | Problem accessing segments, server returned HTTP error code |
316
- * | 3403 | Player | Problem accessing segments, server authentication issue |
317
- * | 3404 | Player | Problem accessing segments, server returned not found |
318
- * | 3900-3999 | Player | Internal player error |
319
- * | 6000 | Player | The remote player api call has reached the configurable timeout with no response from the remote player |
320
- * | 6001 | Player | play() was called while the remote player is not loaded |
321
- * | 6002 | Player | load() was called while the application was in state 'background' or 'inTransitionToBackground' |
322
- * | 6500 | Player | remotePlayer api was called before initializing remotePlayer |
323
- * | 6501 | Player | load() was called while previous load/unload was still in progress |
324
- * | 6502 | Player | unload() was called while previous unload/load was still in progress |
325
- * | 8001 | Player | Error pulling manifest. bad parameters |
326
- * | 8002 | Player | Error pulling manifest. filters returned no data |
327
- * | 8003 | Player | Error pulling manifest. fetch error |
328
- * | 8004 | Player | Error pulling manifest. parse error |
329
- * | 8005 | Player | Error pulling manifest. stale manifest detected |
330
- * | 8006 | Player | Error updating manifest. internal cache error |
331
- * | 8007 | Player | Error updating manifest. internal error during backoff |
332
- * | 8008 | Player | Error pulling manifest. sidx parsing error |
333
- * | 8009 | Player | Error pulling manifest. internal error |
334
- *
335
- * @example
336
- * remotePlayer.addEventListener("error", (event) => {
337
- * console.error("received remotePlayer error:", event.detail.errorCode, event.detail.message);
338
- * });
339
- *
340
- */
201
+
341
202
  typeof document !== "undefined" && document.addEventListener("hs/ERR", (event) => {
342
203
  sdkLogger.info("Got hs/ERR event");
343
204
  delete event?.detail?.type; // type is always videoPlaybackEvent, so no need to pass it
@@ -345,58 +206,6 @@ class RemotePlayer extends EventTarget {
345
206
  this.dispatchEvent(new CustomEvent("error", event));
346
207
  });
347
208
 
348
- /**
349
- *
350
- * @event RemotePlayer#license-request
351
- * @description Fired whenever the platform requires a license to play encrypted content.
352
- * The Web App is responsible for passing the (opaque) license request blob to the license server and passing the (opaque) license server response to the CDM by calling the `writeLicenseResponse` method on the event.
353
- * @type {LicenseRequestEvent}
354
- * @property {object} detail - Object containing ievent data
355
- * @property {string} detail.licenseRequest - Base64 coded opaque license request. The app is responsible for decoding the request before sending to the license server. Note that after decoding, the request may still be in Base64 form and this form should be sent to the license server without further decoding
356
- * @property {writeLicenseResponse} writeLicenseResponse - Write the license server response to the platform
357
- * @example
358
- * Whilst the payload structure and access controls are specific to each license server implementation, the Widevine UAT license server requires no authentication and minimal payload formatting and therefore serves as a useful case study that may be adapted.
359
- *
360
- * remotePlayer.addEventListener("license-request", async (event) => {
361
- * console.log("Got license-request event");
362
- * const requestBuffer = event?.detail?.licenseRequest;
363
- * const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
364
- * console.log("License Request in base64:", requestBufferStr);
365
- * const decodedLicenseRequest = window.atob(requestBufferStr); // from base 64
366
- * const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
367
- * // call Google API
368
- * const res = await getLicenseFromServer(licenseRequestBytes.buffer);
369
- * console.log("Writing response to platform ", res.code, res.responseBody);
370
- * event.writeLicenseResponse(res.code, res.responseBody);
371
- * });
372
-
373
- * async function getLicenseFromServer(licenseRequest) {
374
- * console.log("Requesting License from Widevine server");
375
- * const response = await fetch("https://proxy.uat.widevine.com/proxy", {
376
- * "method": "POST",
377
- * "body": licenseRequest,
378
- * "headers" : {
379
- * "Content-Type": "application/octet-stream"
380
- * }
381
- * });
382
- * const code = response.status;
383
- * if (code !== 200) {
384
- * console.error("failed to to get response from widevine:", code);
385
- * const responseBody = await response.text();
386
- * console.error(responseBody);
387
- * return {code, responseBody};
388
- * }
389
- * const responseBody = await response.arrayBuffer();
390
- * console.info("Got response: ");
391
- * return {code, responseBody};
392
- * }
393
- **/
394
- /**
395
- * @function writeLicenseResponse
396
- * @param {number} statusCode - License server HTTP response code, e.g. 200, 401, etc. Must be 200 to indicate a successful license exchange.
397
- * @param {string} response - License server response as opaque binary data in an ArrayBuffer.
398
- *
399
- * */
400
209
  typeof document !== "undefined" && document.addEventListener("hs/getLicense", (event) => {
401
210
  sdkLogger.info("Got hs/getLicense event");
402
211
  const getLicenseEventData = event?.detail;
@@ -416,20 +225,6 @@ class RemotePlayer extends EventTarget {
416
225
  });
417
226
  }
418
227
 
419
- /**
420
- * @typedef {Object} LoadMode
421
- * @property {string} NOT_LOADED
422
- * @property {string} LOADING
423
- * @property {string} LOADED
424
- * @property {string} UNLOADING
425
- */
426
- LoadMode = Object.freeze({
427
- NOT_LOADED: "notLoaded",
428
- LOADING: "loading",
429
- LOADED: "loaded",
430
- UNLOADING: "unloading"
431
- });
432
-
433
228
  /** @private Initialize the remote player
434
229
  * @param {Object} uiStreamerSettings ui-streamer portion of the settings taken from session info
435
230
  * */
@@ -524,10 +319,10 @@ class RemotePlayer extends EventTarget {
524
319
 
525
320
  /** setting values for properties in the player configuration using an object.
526
321
  * If the config does not support a property this is a no-op.
527
- * @param {Object} props the object with all the different properties to change
322
+ * @param {Config} props the object with all the different properties to change.
528
323
  * @example
529
324
  * remotePlayer.configure({ preferredAudioLanguage: 'en-US' })
530
- *
325
+ * remotePlayer.configure({ minSuggestedPresentationDelay: 6 })
531
326
  * */
532
327
  configure(props) {
533
328
  Object.entries(props).forEach(([key, value]) => {
@@ -720,7 +515,7 @@ class RemotePlayer extends EventTarget {
720
515
  };
721
516
  let waitForResponse = false;
722
517
  if (this._remotePlayerApiVersion >= 2) {
723
- message.switchMode = this._isAudioSyncEnabled() && lifecycle.state !== lifecycle.UiState.BACKGROUND ? SwitchMode.SEAMLESS : SwitchMode.NON_SEAMLESS;
518
+ message.switchMode = this._isAudioSyncEnabled() ? SwitchMode.SEAMLESS : SwitchMode.NON_SEAMLESS;
724
519
  message.streamType = streamType;
725
520
  waitForResponse = true;
726
521
 
@@ -896,6 +691,9 @@ class RemotePlayer extends EventTarget {
896
691
  this._updateSeekListeners(video);
897
692
  }
898
693
  this._videoElement = video;
694
+
695
+ // Emit a custom event to notify about the attachment of the video element
696
+ this.dispatchEvent(new Event("videoelementattached"));
899
697
  }
900
698
  }
901
699
 
@@ -911,7 +709,7 @@ class RemotePlayer extends EventTarget {
911
709
  /** Tell the remote player to load the given URL.
912
710
  * @param {string} url url to load
913
711
  * @param {number} [position] start position in seconds (if not provided, start from beginning (VOD) or current time (LTV))
914
- * @returns {Promise}
712
+ * @returns {Promise}
915
713
  * @throws {RemotePlayerError} error object contains code & msg
916
714
  *
917
715
  * */
@@ -976,6 +774,11 @@ class RemotePlayer extends EventTarget {
976
774
  message.action = "load";
977
775
  message.audioLanguage = audioLanguage;
978
776
  message.subtitlesLanguage = subtitlesLanguage;
777
+ if (this.getConfiguration().minSuggestedPresentationDelay > 0) {
778
+ message.cloudPlayerParams = {
779
+ "mspd": this.getConfiguration().minSuggestedPresentationDelay
780
+ };
781
+ }
979
782
  } else {
980
783
  message.type = "setPlayableUri";
981
784
  }
@@ -1133,13 +936,8 @@ class RemotePlayer extends EventTarget {
1133
936
 
1134
937
  // If seeking in progress, wait for seek to complete before playing
1135
938
  if (this._isSeekingByApplication) {
1136
- if (lifecycle.state === lifecycle.UiState.FOREGROUND) {
1137
- sdkLogger.info("application requesting play during seek. setting targetSeekPlayingState to PLAYING_UI");
1138
- this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
1139
- } else {
1140
- sdkLogger.info("application requesting play during seek. setting targetSeekPlayingState to PLAYING_ABR");
1141
- this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
1142
- }
939
+ sdkLogger.info("application requesting play during seek");
940
+ this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
1143
941
  return Promise.resolve(true);
1144
942
  }
1145
943
  /*
@@ -1661,7 +1459,9 @@ class RemotePlayer extends EventTarget {
1661
1459
  }
1662
1460
  }
1663
1461
 
1664
- if (this._remotePlayerApiVersion >= 2 && !this._isSeekingByPlatform && !this._isSeekingByApplication) {
1462
+ // Only allow seeking in foreground. Still ignore the initialized local player seeking event above
1463
+ if (this._remotePlayerApiVersion >= 2 && !this._isSeekingByPlatform && !this._isSeekingByApplication &&
1464
+ (lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND)) {
1665
1465
  this._atomicSeek();
1666
1466
  } else {
1667
1467
  sdkLogger.info(`Seeking: skipping seeking event to currentTime: ${playbackPosition}, internalSeek: ${this._isSeekingByPlatform}, localPlayerSeek: ${this._isSeekingByApplication}, state: ${lifecycle.state}`);
@@ -1680,16 +1480,16 @@ class RemotePlayer extends EventTarget {
1680
1480
  * */
1681
1481
  async _atomicSeek() {
1682
1482
  sdkLogger.info("Seeking: local video element seeking start while isPlaying: ", this._isPlaying);
1683
- if (this._isPlaying) {
1684
- if (!lifecycle._inTransitionToForeground && (lifecycle._inTransitionToBackground || lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
1685
- sdkLogger.info("seek in background", this._isPlaying);
1686
- this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
1687
- } else {
1688
- this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
1689
- }
1690
- } else {
1691
- this._targetSeekPlayingState = TargetPlayingState.PAUSED;
1692
- }
1483
+
1484
+ // Initialize the target playing state unless changed during the seek process
1485
+ // In the future, we should allow for seeking in background. Currently, there's no
1486
+ // way to know when the web application will call moveToForeground (i.e Before/After seek)
1487
+ // Therefore, for now, we will assume the target is either paused or playing in ui unless
1488
+ // specifically receiving a moveToBackground during the process.
1489
+ // if (this._isPlaying && (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
1490
+ // this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
1491
+ // }
1492
+ this._targetSeekPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1693
1493
 
1694
1494
  // The platform could be currently syncing audio/video using playback rate. Reset when performing seek.
1695
1495
  if (this._videoElement) {
@@ -1756,7 +1556,7 @@ class RemotePlayer extends EventTarget {
1756
1556
 
1757
1557
  // If in TargetPlayingState.PAUSE, no need to resume.
1758
1558
  // Resume without awaiting to avoid blocking the seek process anymore
1759
- // In case where we aborted (new load or unload called), we don't want to resume playback.
1559
+ // In case where we aborted, we don't want to resume playback.
1760
1560
  if (!this._abortSeeking) {
1761
1561
  if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1762
1562
  if (!this._isAudioSyncEnabled()) {
@@ -1765,8 +1565,6 @@ class RemotePlayer extends EventTarget {
1765
1565
  // resume audio play only if _isAudioSyncEnabled
1766
1566
  this._play(StreamType.AUDIO);
1767
1567
  } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1768
- // When moving back to background, we need to put the remote player back into play mode
1769
- this._changePlayMode(true);
1770
1568
  lifecycle._moveToBackground();
1771
1569
  }
1772
1570
  }