senza-sdk 4.2.65-a3f8c5a.0 → 4.3.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.
- package/dist/bundle.js +1 -1
- package/dist/bundle.js.LICENSE.txt +0 -4
- package/dist/implementation.bundle.js +2 -0
- package/dist/implementation.bundle.js.LICENSE.txt +57 -0
- package/package.json +16 -8
- package/src/api.js +258 -327
- package/src/{alarmManager.js → implementation/alarmManager.js} +15 -52
- package/src/implementation/api.js +363 -0
- package/src/{deviceManager.js → implementation/deviceManager.js} +6 -78
- package/src/{lifecycle.js → implementation/lifecycle.js} +14 -192
- package/src/implementation/messageManager.js +55 -0
- package/src/{platformManager.js → implementation/platformManager.js} +5 -23
- package/src/{remotePlayer.js → implementation/remotePlayer.js} +5 -276
- package/src/{senzaShakaPlayer.js → implementation/senzaShakaPlayer.js} +8 -120
- package/src/{utils.js → implementation/utils.js} +15 -6
- package/src/interface/alarmManager.js +76 -0
- package/src/interface/api.js +8 -0
- package/src/interface/deviceManager.js +143 -0
- package/src/interface/lifecycle.js +284 -0
- package/src/interface/messageManager.js +54 -0
- package/src/interface/platformManager.js +42 -0
- package/src/interface/remotePlayer.js +469 -0
- package/src/interface/senzaShakaPlayer.js +168 -0
- package/src/interface/utils.js +45 -0
- package/src/messageManager.js +0 -88
- /package/src/{SessionInfo.js → implementation/SessionInfo.js} +0 -0
- /package/src/{devHelper.js → implementation/devHelper.js} +0 -0
- /package/src/{eventListenersManager.js → implementation/eventListenersManager.js} +0 -0
- /package/src/{subtitlesUtils.js → implementation/subtitlesUtils.js} +0 -0
- /package/src/{devSequence.js → interface/devSequence.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
|
-
|
|
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,27 +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 {number} minSuggestedPresentationDelay - minimal delay allowed for live playback in seconds
|
|
72
|
-
* @property {boolean} autoPlay - (Not implemented yet) upon loading start playing automatically
|
|
73
|
-
*/
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* RemotePlayer a singleton class to communicate with remote player
|
|
77
|
-
* @fires timeupdate
|
|
78
|
-
* @fires tracksupdate
|
|
79
|
-
* @fires ended
|
|
80
|
-
* @fires error
|
|
81
|
-
* @fires onloadmodechange
|
|
82
|
-
* @fires playing
|
|
83
|
-
* @fires seeking (Not implemented yet)
|
|
84
|
-
* @fires seeked (Not implemented yet)
|
|
85
|
-
*/
|
|
86
|
-
class RemotePlayer extends EventTarget {
|
|
64
|
+
class RemotePlayer extends RemotePlayerInterface {
|
|
87
65
|
constructor() {
|
|
88
66
|
super();
|
|
89
67
|
/**
|
|
@@ -163,56 +141,11 @@ class RemotePlayer extends EventTarget {
|
|
|
163
141
|
*/
|
|
164
142
|
this._isPlaying = false;
|
|
165
143
|
|
|
166
|
-
/**
|
|
167
|
-
* @type {string}
|
|
168
|
-
* @description the last set blackout time as epoch seconds.
|
|
169
|
-
* @private
|
|
170
|
-
*/
|
|
171
|
-
this._blackoutTime = undefined;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* @event RemotePlayer#canplay
|
|
175
|
-
* @description canplay event will be dispatched when the remote player can start play the event
|
|
176
|
-
* @example
|
|
177
|
-
* remotePlayer.addEventListener("canplay", () => {
|
|
178
|
-
* console.info("remotePlayer canplay");
|
|
179
|
-
* remotePlayer.play();
|
|
180
|
-
* });
|
|
181
|
-
* */
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @event RemotePlayer#playing
|
|
185
|
-
* @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.
|
|
186
|
-
* @example
|
|
187
|
-
* remotePlayer.addEventListener("playing", () => {
|
|
188
|
-
* console.info("remotePlayer playing");
|
|
189
|
-
* });
|
|
190
|
-
* */
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* @event RemotePlayer#loadedmetadata
|
|
194
|
-
* @description loadedmetadata event will be dispatched when the remote player metadata is loaded
|
|
195
|
-
* and the audio/video tracks are available
|
|
196
|
-
* @example
|
|
197
|
-
* remotePlayer.addEventListener("loadedmetadata", () => {
|
|
198
|
-
* console.info("remotePlayer loadedmetadata", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
|
|
199
|
-
* });
|
|
200
|
-
* */
|
|
201
144
|
typeof document !== "undefined" && document.addEventListener("hs/remotePlayerEvent", (e) => {
|
|
202
145
|
sdkLogger.info("Got hs/remotePlayerEvent event with detail", JSON.stringify(e?.detail));
|
|
203
146
|
this.dispatchEvent(new Event(e?.detail?.eventName));
|
|
204
147
|
});
|
|
205
148
|
|
|
206
|
-
/**
|
|
207
|
-
*
|
|
208
|
-
* @event RemotePlayer#timeupdate
|
|
209
|
-
* @example
|
|
210
|
-
* remotePlayer.addEventListener("timeupdate", () => {
|
|
211
|
-
* console.info("remotePlayer timeupdate", remotePlayer.currentTime);
|
|
212
|
-
* localPlayer.getMediaElement().currentTime = remotePlayer.currentTime || 0;
|
|
213
|
-
* });
|
|
214
|
-
*
|
|
215
|
-
*/
|
|
216
149
|
typeof document !== "undefined" && document.addEventListener("hs/playbackInfoEvent", () => {
|
|
217
150
|
sdkLogger.info("Got hs/playbackInfoEvent");
|
|
218
151
|
// When attached, the sdk controls the synchronization between the local and remote player.
|
|
@@ -226,15 +159,6 @@ class RemotePlayer extends EventTarget {
|
|
|
226
159
|
}
|
|
227
160
|
});
|
|
228
161
|
|
|
229
|
-
/**
|
|
230
|
-
*
|
|
231
|
-
* @event RemotePlayer#tracksupdate
|
|
232
|
-
* @example
|
|
233
|
-
* remotePlayer.addEventListener("tracksupdate", () => {
|
|
234
|
-
* console.info("remotePlayer tracksupdate", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
|
|
235
|
-
* });
|
|
236
|
-
*
|
|
237
|
-
*/
|
|
238
162
|
typeof document !== "undefined" && document.addEventListener("hs/playback", (e) => {
|
|
239
163
|
sdkLogger.info("Got hs/playback event with detail", JSON.stringify(e?.detail));
|
|
240
164
|
this._availabilityStartTime = e?.detail?.availabilityStartTime;
|
|
@@ -242,13 +166,6 @@ class RemotePlayer extends EventTarget {
|
|
|
242
166
|
this.dispatchEvent(new Event("tracksupdate"));
|
|
243
167
|
});
|
|
244
168
|
|
|
245
|
-
/**
|
|
246
|
-
* @event RemotePlayer#ended
|
|
247
|
-
* @example
|
|
248
|
-
* remotePlayer.addEventListener("ended", () => {
|
|
249
|
-
* console.info("remotePlayer ended");
|
|
250
|
-
* });
|
|
251
|
-
*/
|
|
252
169
|
typeof document !== "undefined" && document.addEventListener("hs/EOS", () => {
|
|
253
170
|
sdkLogger.info("Got hs/EOS event");
|
|
254
171
|
this.dispatchEvent(new Event("ended"));
|
|
@@ -281,72 +198,7 @@ class RemotePlayer extends EventTarget {
|
|
|
281
198
|
sdkLogger.info(`Adding ${event.detail} seconds, previousTime=${previousCurrentTime} currentTime=${this._videoElement.currentTime}`);
|
|
282
199
|
});
|
|
283
200
|
|
|
284
|
-
|
|
285
|
-
*
|
|
286
|
-
* @event RemotePlayer#error
|
|
287
|
-
* @type {object}
|
|
288
|
-
* @property {int} detail.errorCode
|
|
289
|
-
* @property {string} detail.message
|
|
290
|
-
*
|
|
291
|
-
* @see Possible error codes:
|
|
292
|
-
*
|
|
293
|
-
* | Code | Domain | Description |
|
|
294
|
-
* | :-------- | :---------------- | :-----------------------------------------------------------------------------------------------|
|
|
295
|
-
* | 23 | Player | load() failed due to remote player initialization error |
|
|
296
|
-
* | 98 | Player | load() failed due to remote player failure to send message to the client |
|
|
297
|
-
* | 99 | Player | load() failed due to remote player reporting invalid message |
|
|
298
|
-
* | 1000 | Encrypted content | Failed to create or initialise the CDM |
|
|
299
|
-
* | 1001 | Encrypted content | Failed to create a CDM session |
|
|
300
|
-
* | 1002 | Encrypted content | CDM failed to generate a license request |
|
|
301
|
-
* | 1003 | Encrypted content | The CDM rejected the license server response |
|
|
302
|
-
* | 1004 | Encrypted content | The CDM rejected the license server certificate |
|
|
303
|
-
* | 1005 | Encrypted content | All keys in the license have expired |
|
|
304
|
-
* | 1006 | Encrypted content | Output device is incompatible with the license requirements (HDCP) |
|
|
305
|
-
* | 1007 | Encrypted content | The device has been revoked |
|
|
306
|
-
* | 1008 | Encrypted content | The device secrets aren't available |
|
|
307
|
-
* | 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 |
|
|
308
|
-
* | 1010 | Encrypted content | The CDM failed to provision, therefore it is not possible to play encrypted content |
|
|
309
|
-
* | 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 |
|
|
310
|
-
* | 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` |
|
|
311
|
-
* | 1102 | Encrypted content | A license response wasn't received from the app within a pre-defined timeout |
|
|
312
|
-
* | 1103 | Encrypted content | The CDM session associated with this license response is in an invalid state. This is an internal Senza platform error |
|
|
313
|
-
* | 1104 | Encrypted content | The CDM failed to send a license request to the app. This is an internal Senza platform error |
|
|
314
|
-
* | 1999 | Encrypted content | An unknown encrypted content error |
|
|
315
|
-
* | 2000 | Player | Content makes reference to no or unsupported key system |
|
|
316
|
-
* | 3000 | Player | Unexpected problem with playback, only used if no more specific code in 3xxx range applies |
|
|
317
|
-
* | 3001 | Player | Problem accessing content manifest, only used if no more specific code in 8xxx range applies |
|
|
318
|
-
* | 3002 | Player | Unexpectedly stopped playback |
|
|
319
|
-
* | 3100 | Player | Problem parsing MP4 content |
|
|
320
|
-
* | 3200 | Player | Problem with decoder |
|
|
321
|
-
* | 3300 | Player | DRM keys unavailable, player waited for keys but none arrived |
|
|
322
|
-
* | 3400 | Player | Problem accessing segments, only used if no more specific code in 34xx range applies |
|
|
323
|
-
* | 3401 | Player | Problem accessing segments, connection issue or timeout |
|
|
324
|
-
* | 3402 | Player | Problem accessing segments, server returned HTTP error code |
|
|
325
|
-
* | 3403 | Player | Problem accessing segments, server authentication issue |
|
|
326
|
-
* | 3404 | Player | Problem accessing segments, server returned not found |
|
|
327
|
-
* | 3900-3999 | Player | Internal player error |
|
|
328
|
-
* | 6000 | Player | The remote player api call has reached the configurable timeout with no response from the remote player |
|
|
329
|
-
* | 6001 | Player | play() was called while the remote player is not loaded |
|
|
330
|
-
* | 6002 | Player | load() was called while the application was in state 'background' or 'inTransitionToBackground' |
|
|
331
|
-
* | 6500 | Player | remotePlayer api was called before initializing remotePlayer |
|
|
332
|
-
* | 6501 | Player | load() was called while previous load/unload was still in progress |
|
|
333
|
-
* | 6502 | Player | unload() was called while previous unload/load was still in progress |
|
|
334
|
-
* | 8001 | Player | Error pulling manifest. bad parameters |
|
|
335
|
-
* | 8002 | Player | Error pulling manifest. filters returned no data |
|
|
336
|
-
* | 8003 | Player | Error pulling manifest. fetch error |
|
|
337
|
-
* | 8004 | Player | Error pulling manifest. parse error |
|
|
338
|
-
* | 8005 | Player | Error pulling manifest. stale manifest detected |
|
|
339
|
-
* | 8006 | Player | Error updating manifest. internal cache error |
|
|
340
|
-
* | 8007 | Player | Error updating manifest. internal error during backoff |
|
|
341
|
-
* | 8008 | Player | Error pulling manifest. sidx parsing error |
|
|
342
|
-
* | 8009 | Player | Error pulling manifest. internal error |
|
|
343
|
-
*
|
|
344
|
-
* @example
|
|
345
|
-
* remotePlayer.addEventListener("error", (event) => {
|
|
346
|
-
* console.error("received remotePlayer error:", event.detail.errorCode, event.detail.message);
|
|
347
|
-
* });
|
|
348
|
-
*
|
|
349
|
-
*/
|
|
201
|
+
|
|
350
202
|
typeof document !== "undefined" && document.addEventListener("hs/ERR", (event) => {
|
|
351
203
|
sdkLogger.info("Got hs/ERR event");
|
|
352
204
|
delete event?.detail?.type; // type is always videoPlaybackEvent, so no need to pass it
|
|
@@ -354,58 +206,6 @@ class RemotePlayer extends EventTarget {
|
|
|
354
206
|
this.dispatchEvent(new CustomEvent("error", event));
|
|
355
207
|
});
|
|
356
208
|
|
|
357
|
-
/**
|
|
358
|
-
*
|
|
359
|
-
* @event RemotePlayer#license-request
|
|
360
|
-
* @description Fired whenever the platform requires a license to play encrypted content.
|
|
361
|
-
* 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.
|
|
362
|
-
* @type {LicenseRequestEvent}
|
|
363
|
-
* @property {object} detail - Object containing ievent data
|
|
364
|
-
* @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
|
|
365
|
-
* @property {writeLicenseResponse} writeLicenseResponse - Write the license server response to the platform
|
|
366
|
-
* @example
|
|
367
|
-
* 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.
|
|
368
|
-
*
|
|
369
|
-
* remotePlayer.addEventListener("license-request", async (event) => {
|
|
370
|
-
* console.log("Got license-request event");
|
|
371
|
-
* const requestBuffer = event?.detail?.licenseRequest;
|
|
372
|
-
* const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
|
|
373
|
-
* console.log("License Request in base64:", requestBufferStr);
|
|
374
|
-
* const decodedLicenseRequest = window.atob(requestBufferStr); // from base 64
|
|
375
|
-
* const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
|
|
376
|
-
* // call Google API
|
|
377
|
-
* const res = await getLicenseFromServer(licenseRequestBytes.buffer);
|
|
378
|
-
* console.log("Writing response to platform ", res.code, res.responseBody);
|
|
379
|
-
* event.writeLicenseResponse(res.code, res.responseBody);
|
|
380
|
-
* });
|
|
381
|
-
|
|
382
|
-
* async function getLicenseFromServer(licenseRequest) {
|
|
383
|
-
* console.log("Requesting License from Widevine server");
|
|
384
|
-
* const response = await fetch("https://proxy.uat.widevine.com/proxy", {
|
|
385
|
-
* "method": "POST",
|
|
386
|
-
* "body": licenseRequest,
|
|
387
|
-
* "headers" : {
|
|
388
|
-
* "Content-Type": "application/octet-stream"
|
|
389
|
-
* }
|
|
390
|
-
* });
|
|
391
|
-
* const code = response.status;
|
|
392
|
-
* if (code !== 200) {
|
|
393
|
-
* console.error("failed to to get response from widevine:", code);
|
|
394
|
-
* const responseBody = await response.text();
|
|
395
|
-
* console.error(responseBody);
|
|
396
|
-
* return {code, responseBody};
|
|
397
|
-
* }
|
|
398
|
-
* const responseBody = await response.arrayBuffer();
|
|
399
|
-
* console.info("Got response: ");
|
|
400
|
-
* return {code, responseBody};
|
|
401
|
-
* }
|
|
402
|
-
**/
|
|
403
|
-
/**
|
|
404
|
-
* @function writeLicenseResponse
|
|
405
|
-
* @param {number} statusCode - License server HTTP response code, e.g. 200, 401, etc. Must be 200 to indicate a successful license exchange.
|
|
406
|
-
* @param {string} response - License server response as opaque binary data in an ArrayBuffer.
|
|
407
|
-
*
|
|
408
|
-
* */
|
|
409
209
|
typeof document !== "undefined" && document.addEventListener("hs/getLicense", (event) => {
|
|
410
210
|
sdkLogger.info("Got hs/getLicense event");
|
|
411
211
|
const getLicenseEventData = event?.detail;
|
|
@@ -425,20 +225,6 @@ class RemotePlayer extends EventTarget {
|
|
|
425
225
|
});
|
|
426
226
|
}
|
|
427
227
|
|
|
428
|
-
/**
|
|
429
|
-
* @typedef {Object} LoadMode
|
|
430
|
-
* @property {string} NOT_LOADED
|
|
431
|
-
* @property {string} LOADING
|
|
432
|
-
* @property {string} LOADED
|
|
433
|
-
* @property {string} UNLOADING
|
|
434
|
-
*/
|
|
435
|
-
LoadMode = Object.freeze({
|
|
436
|
-
NOT_LOADED: "notLoaded",
|
|
437
|
-
LOADING: "loading",
|
|
438
|
-
LOADED: "loaded",
|
|
439
|
-
UNLOADING: "unloading"
|
|
440
|
-
});
|
|
441
|
-
|
|
442
228
|
/** @private Initialize the remote player
|
|
443
229
|
* @param {Object} uiStreamerSettings ui-streamer portion of the settings taken from session info
|
|
444
230
|
* */
|
|
@@ -958,7 +744,6 @@ class RemotePlayer extends EventTarget {
|
|
|
958
744
|
this._abortSetAudioLanguage = true;
|
|
959
745
|
this._abortSetSubtitleLanguage = true;
|
|
960
746
|
this._abortSeeking = true;
|
|
961
|
-
this._blackoutTime = undefined;
|
|
962
747
|
if (reset) {
|
|
963
748
|
this._reset();
|
|
964
749
|
}
|
|
@@ -1570,62 +1355,6 @@ class RemotePlayer extends EventTarget {
|
|
|
1570
1355
|
}
|
|
1571
1356
|
}
|
|
1572
1357
|
|
|
1573
|
-
setBlackoutTime(blackoutTime) {
|
|
1574
|
-
|
|
1575
|
-
if (!this._isInitialized) {
|
|
1576
|
-
throw new RemotePlayerError(6500, "Cannot call setBlackoutTime() if remote player is not initialized");
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
if (this._loadMode !== this.LoadMode.LOADED) {
|
|
1580
|
-
throw new RemotePlayerError(6001, "Cannot call setBlackoutTime() if player is not loaded");
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
if (window.cefQuery) {
|
|
1584
|
-
const FCID = getFCID();
|
|
1585
|
-
const logger = sdkLogger.withFields({ FCID });
|
|
1586
|
-
logger.log("remotePlayer setBlackoutTime: sending screenBlackout action");
|
|
1587
|
-
const message = {
|
|
1588
|
-
type: "remotePlayer.screenBlackout",
|
|
1589
|
-
class: "remotePlayer",
|
|
1590
|
-
action: "screenBlackout",
|
|
1591
|
-
fcid: FCID,
|
|
1592
|
-
blackoutTime
|
|
1593
|
-
};
|
|
1594
|
-
const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
|
|
1595
|
-
return new Promise((resolve, reject) => {
|
|
1596
|
-
let timerId = 0;
|
|
1597
|
-
const timeBeforeSendingRequest = Date.now();
|
|
1598
|
-
const queryId = window.cefQuery({
|
|
1599
|
-
request: JSON.stringify(request),
|
|
1600
|
-
persistent: false,
|
|
1601
|
-
onSuccess: () => {
|
|
1602
|
-
this._blackoutTime = blackoutTime;
|
|
1603
|
-
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1604
|
-
logger.withFields({ duration }).log(`setBlackoutTime completed successfully after ${duration} ms`);
|
|
1605
|
-
timerId = clearTimer(timerId);
|
|
1606
|
-
resolve();
|
|
1607
|
-
},
|
|
1608
|
-
onFailure: (code, msg) => {
|
|
1609
|
-
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1610
|
-
logger.withFields({ duration }).log(`setBlackoutTime failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
1611
|
-
timerId = clearTimer(timerId);
|
|
1612
|
-
reject(new RemotePlayerError(code, msg));
|
|
1613
|
-
}
|
|
1614
|
-
});
|
|
1615
|
-
|
|
1616
|
-
logger.log(`window.cefQuery for setBlackoutTime returned query id ${queryId}`);
|
|
1617
|
-
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
1618
|
-
timerId = setTimeout(() => {
|
|
1619
|
-
logger.log(`setBlackoutTime reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
1620
|
-
window.cefQueryCancel(queryId);
|
|
1621
|
-
reject(new RemotePlayerError(6000, `setBlackoutTime reached timeout of ${timeout} ms`));
|
|
1622
|
-
}, timeout, queryId);
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
sdkLogger.error("remotePlayer setBlackoutTime: window.cefQuery is undefined");
|
|
1626
|
-
return Promise.resolve(undefined);
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
1358
|
/**
|
|
1630
1359
|
* Getter/Setter for currentTime
|
|
1631
1360
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { SenzaShakaPlayer as SenzaShakaInterface, shaka } from "../interface/senzaShakaPlayer";
|
|
2
|
+
|
|
2
3
|
import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
|
|
3
4
|
import { sdkLogger, iso6393to1 } from "./utils";
|
|
4
5
|
import moment from "moment";
|
|
@@ -32,31 +33,7 @@ class SenzaError extends shaka.util.Error {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
// if we don't Copy the shaka module, the Player class will be replaced for all the other modules that import shaka
|
|
37
|
-
const senzaShaka = { ...shaka };
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* SenzaShakaPlayer subclass of Shaka that handles both local and remote playback.
|
|
42
|
-
*
|
|
43
|
-
* @class SenzaShakaPlayer
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* import { SenzaShakaPlayer } from "./senzaShakaPlayer.js";
|
|
47
|
-
*
|
|
48
|
-
* try {
|
|
49
|
-
* const videoElement = document.getElementById("video");
|
|
50
|
-
* const player = new SenzaShakaPlayer(videoElement);
|
|
51
|
-
* await player.load("http://playable.url/file.mpd");
|
|
52
|
-
* await videoElement.play(); // will start the playback
|
|
53
|
-
*
|
|
54
|
-
* } catch (err) {
|
|
55
|
-
* console.error("SenzaShakaPlayer failed with error", err);
|
|
56
|
-
* }
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
export class SenzaShakaPlayer extends shaka.Player {
|
|
36
|
+
export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
60
37
|
/** @private {SenzaShakaPlayer|null} Previous instance of the player */
|
|
61
38
|
static _prevInstance = null;
|
|
62
39
|
|
|
@@ -141,7 +118,7 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
141
118
|
});
|
|
142
119
|
}
|
|
143
120
|
},
|
|
144
|
-
"pause"
|
|
121
|
+
"pause": () => {
|
|
145
122
|
this._resetPlayPromise();
|
|
146
123
|
this.remotePlayer.pause()
|
|
147
124
|
.catch(error => {
|
|
@@ -187,7 +164,7 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
187
164
|
originatesFromRemotePlayer: true
|
|
188
165
|
};
|
|
189
166
|
|
|
190
|
-
const response = await this.getNetworkingEngine().request(
|
|
167
|
+
const response = await this.getNetworkingEngine().request(shaka.net.NetworkingEngine.RequestType.LICENSE, request).promise;
|
|
191
168
|
|
|
192
169
|
let responseBody = response.data;
|
|
193
170
|
if (response.status < 200 || response.status >= 300) {
|
|
@@ -346,14 +323,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
346
323
|
return undefined;
|
|
347
324
|
}
|
|
348
325
|
|
|
349
|
-
/**
|
|
350
|
-
* Creates an instance of SenzaShakaPlayer, which is a subclass of shaka.Player.
|
|
351
|
-
*
|
|
352
|
-
* @param {HTMLVideoElement} videoElement - The video element to be used for local playback. This parameter is optional. If not provided, the video element can be attached later using the attach method.
|
|
353
|
-
* @param {HTMLElement=} videoContainer - The videoContainer to construct UITextDisplayer
|
|
354
|
-
* @param {function(shaka.Player)=} dependencyInjector Optional callback
|
|
355
|
-
* which is called to inject mocks into the Player. Used for testing.
|
|
356
|
-
*/
|
|
357
326
|
constructor(videoElement, videoContainer, dependencyInjector) {
|
|
358
327
|
super(videoElement, videoContainer, dependencyInjector);
|
|
359
328
|
|
|
@@ -371,7 +340,7 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
371
340
|
this._addRemotePlayerEventListeners();
|
|
372
341
|
SenzaShakaPlayer._prevInstance = this;
|
|
373
342
|
const playTimeout = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"]?.playingEventTimeout;
|
|
374
|
-
this._playingTimeout = (playTimeout >= 0) ? playTimeout*1000 : this._playingTimeout;
|
|
343
|
+
this._playingTimeout = (playTimeout >= 0) ? playTimeout * 1000 : this._playingTimeout;
|
|
375
344
|
|
|
376
345
|
// Initialize minSuggestedPresentationDelay from UI settings or use default
|
|
377
346
|
const uiSettings = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"];
|
|
@@ -451,27 +420,11 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
451
420
|
this._attachVideoElementToRemotePlayer();
|
|
452
421
|
}
|
|
453
422
|
|
|
454
|
-
/**
|
|
455
|
-
* Overrides the attach method of shaka.Player to attach the video element.
|
|
456
|
-
*
|
|
457
|
-
* @param {HTMLVideoElement} videoElement - The video element to be used for local playback.
|
|
458
|
-
* @param {boolean} [initializeMediaSource=true] - Whether to initialize the media source.
|
|
459
|
-
*/
|
|
460
423
|
async attach(videoElement, initializeMediaSource = true) {
|
|
461
424
|
await super.attach(videoElement, initializeMediaSource);
|
|
462
425
|
this._attach(videoElement);
|
|
463
426
|
}
|
|
464
427
|
|
|
465
|
-
/**
|
|
466
|
-
* Detach the player from the current media element. Leaves the player in a
|
|
467
|
-
* state where it cannot play media, until it has been attached to something
|
|
468
|
-
* else.
|
|
469
|
-
*
|
|
470
|
-
* @param {boolean=} keepAdManager
|
|
471
|
-
*
|
|
472
|
-
* @return {!Promise}
|
|
473
|
-
* @export
|
|
474
|
-
*/
|
|
475
428
|
async detach(keepAdManager = false) {
|
|
476
429
|
// Clear any pending timeout
|
|
477
430
|
this._resetPlayPromise();
|
|
@@ -502,14 +455,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
502
455
|
this.videoElement = null;
|
|
503
456
|
}
|
|
504
457
|
|
|
505
|
-
/**
|
|
506
|
-
* Unloads the currently playing stream, if any.
|
|
507
|
-
*
|
|
508
|
-
* @param {boolean=} initializeMediaSource
|
|
509
|
-
* @param {boolean=} keepAdManager
|
|
510
|
-
* @return {!Promise}
|
|
511
|
-
* @export
|
|
512
|
-
*/
|
|
513
458
|
async unload(initializeMediaSource = true, keepAdManager = false) {
|
|
514
459
|
// Call the remote player's unload method
|
|
515
460
|
try {
|
|
@@ -524,27 +469,14 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
524
469
|
await super.unload(initializeMediaSource, keepAdManager);
|
|
525
470
|
}
|
|
526
471
|
|
|
527
|
-
/**
|
|
528
|
-
* Overrides the getTextTracks method to use the remote player's text tracks.
|
|
529
|
-
* @returns {Array} An array of text tracks.
|
|
530
|
-
*/
|
|
531
472
|
getTextLanguages() {
|
|
532
473
|
return Object.keys(this._textTracksMap);
|
|
533
474
|
}
|
|
534
475
|
|
|
535
|
-
/**
|
|
536
|
-
* Overrides the getAudioTracks method to use the remote player's audio tracks.
|
|
537
|
-
* @returns {Array} An array of audio tracks.
|
|
538
|
-
*/
|
|
539
476
|
getAudioLanguages() {
|
|
540
477
|
return Object.keys(this._audioTracksMap);
|
|
541
478
|
}
|
|
542
479
|
|
|
543
|
-
/**
|
|
544
|
-
* Overrides the selectAudioLanguage method to use the remote player's audio track selection.
|
|
545
|
-
* @param {string} language - The language to select.
|
|
546
|
-
* @param {string=} role - The role of the track to select. (e.g. 'main', 'caption', or 'commentary')
|
|
547
|
-
*/
|
|
548
480
|
selectAudioLanguage(language, role) {
|
|
549
481
|
sdkLogger.log("Selecting audio language:", language, "with role: ", role);
|
|
550
482
|
if (this._audioTracksMap[language]) {
|
|
@@ -555,11 +487,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
555
487
|
super.selectAudioLanguage(language, role);
|
|
556
488
|
}
|
|
557
489
|
|
|
558
|
-
/**
|
|
559
|
-
* Overrides the selectTextLanguage method to use the remote player's text track selection.
|
|
560
|
-
* @param {string} language - The language to select.
|
|
561
|
-
* @param {string=} role - The role of the track to select.
|
|
562
|
-
*/
|
|
563
490
|
selectTextLanguage(language, role) {
|
|
564
491
|
sdkLogger.log("Selecting text language:", language, "with role:", role);
|
|
565
492
|
if (this._textTracksMap[language]) {
|
|
@@ -570,20 +497,12 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
570
497
|
super.selectTextLanguage(language, role);
|
|
571
498
|
}
|
|
572
499
|
|
|
573
|
-
/**
|
|
574
|
-
* Overrides the setTextTrackVisibility method to use the remote player's text track visibility settings.
|
|
575
|
-
* @param {boolean} visible - Whether the text tracks should be visible.
|
|
576
|
-
*/
|
|
577
500
|
setTextTrackVisibility(visible) {
|
|
578
501
|
sdkLogger.log("Setting text track visibility to:", visible);
|
|
579
502
|
remotePlayer.setTextTrackVisibility(visible);
|
|
580
503
|
super.setTextTrackVisibility(visible);
|
|
581
504
|
}
|
|
582
505
|
|
|
583
|
-
/**
|
|
584
|
-
* Helper function that makes it easier to check if lifecycle.state is
|
|
585
|
-
* either background or inTransitionToBackground.
|
|
586
|
-
*/
|
|
587
506
|
get isInRemotePlayback() {
|
|
588
507
|
return lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND;
|
|
589
508
|
}
|
|
@@ -649,12 +568,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
649
568
|
this.dispatchEvent(new shaka.util.FakeEvent("error", errorMap));
|
|
650
569
|
}
|
|
651
570
|
|
|
652
|
-
/**
|
|
653
|
-
* Loads a media URL into both local and remote players.
|
|
654
|
-
*
|
|
655
|
-
* @param {string} url - The URL of the media to load.
|
|
656
|
-
* @returns {Promise<void>}
|
|
657
|
-
*/
|
|
658
571
|
async load(url, startTime, mimeType) {
|
|
659
572
|
|
|
660
573
|
// Create a promise that will resolve when _remotePlayerLoad is called
|
|
@@ -741,13 +654,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
741
654
|
|
|
742
655
|
}
|
|
743
656
|
|
|
744
|
-
/***
|
|
745
|
-
*
|
|
746
|
-
* Configure the Player instance.
|
|
747
|
-
* @param {Object} config the configuration object
|
|
748
|
-
* @returns {Boolean}
|
|
749
|
-
*/
|
|
750
|
-
|
|
751
657
|
async destroy() {
|
|
752
658
|
await lifecycle.moveToForeground();
|
|
753
659
|
SenzaShakaPlayer._prevInstance = null;
|
|
@@ -755,10 +661,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
755
661
|
return super.destroy();
|
|
756
662
|
}
|
|
757
663
|
|
|
758
|
-
/**
|
|
759
|
-
* A temporary override for older versions of Shaka.
|
|
760
|
-
* Senza doesn't support out-of-band subtitles
|
|
761
|
-
*/
|
|
762
664
|
addTextTrack(uri, language, kind, mimeType, codec, label, forced = false) {
|
|
763
665
|
sdkLogger.warn("addTextTrack is deprecated, please use addTextTrackAsync");
|
|
764
666
|
super.addTextTrackAsync(uri, language, kind, mimeType, codec, label, forced).then(subs => {
|
|
@@ -766,20 +668,6 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
766
668
|
});
|
|
767
669
|
}
|
|
768
670
|
|
|
769
|
-
/**
|
|
770
|
-
* Override the configure method to add custom configuration handling
|
|
771
|
-
* Supports the following additional configuration options:
|
|
772
|
-
* - shouldStopOnRemotePlayerError: boolean - If true, local player will be stopped on remote player error
|
|
773
|
-
*
|
|
774
|
-
* @override
|
|
775
|
-
* @param {Object} config - Configuration object to be merged with existing config
|
|
776
|
-
* @param {boolean} [config.shouldStopOnRemotePlayerError=true] - Whether to stop local player on remote player error
|
|
777
|
-
* @example
|
|
778
|
-
* player.configure({
|
|
779
|
-
* shouldStopOnRemotePlayerError: false, // Don't stop local playback on remote player error
|
|
780
|
-
* // ... other shaka configurations
|
|
781
|
-
* });
|
|
782
|
-
*/
|
|
783
671
|
configure(config) {
|
|
784
672
|
sdkLogger.log("configure player with: ", JSON.stringify(config));
|
|
785
673
|
|
|
@@ -889,5 +777,5 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
889
777
|
|
|
890
778
|
}
|
|
891
779
|
|
|
892
|
-
|
|
893
|
-
export {
|
|
780
|
+
shaka.Player = SenzaShakaPlayer;
|
|
781
|
+
export { shaka };
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { getPlatformInfo } from "./api";
|
|
2
|
-
import pack from "../package.json";
|
|
3
2
|
import { sessionInfo } from "./SessionInfo";
|
|
4
|
-
const { version } = pack;
|
|
5
3
|
|
|
6
4
|
const REST_RESPONSE_TIMEOUT_SECONDS = 5;
|
|
7
5
|
|
|
6
|
+
export function getVersion() {
|
|
7
|
+
return typeof IMPLEMENTATION_VERSION !== "undefined"
|
|
8
|
+
// eslint-disable-next-line no-undef
|
|
9
|
+
? IMPLEMENTATION_VERSION
|
|
10
|
+
: "unknown";
|
|
11
|
+
};
|
|
12
|
+
|
|
8
13
|
export function getFCID() {
|
|
9
14
|
return Math.round(Math.random() * 100000) + "-" + getPlatformInfo().sessionInfo?.connectionId;
|
|
10
15
|
}
|
|
@@ -42,7 +47,7 @@ class SdkLogger {
|
|
|
42
47
|
console.info(`[hs-sdk] [metrics] ${JSON.stringify(metricObj)}`);
|
|
43
48
|
}
|
|
44
49
|
withFields(logFields) {
|
|
45
|
-
return new SdkLogger({...this.logFields, ...logFields});
|
|
50
|
+
return new SdkLogger({ ...this.logFields, ...logFields });
|
|
46
51
|
}
|
|
47
52
|
formatLogString(data) {
|
|
48
53
|
let logString = "[hs-sdk] " + data.join(" ");
|
|
@@ -51,9 +56,13 @@ class SdkLogger {
|
|
|
51
56
|
}
|
|
52
57
|
return logString;
|
|
53
58
|
}
|
|
59
|
+
addfields(fields) {
|
|
60
|
+
this.logFields = { ...this.logFields, ...fields };
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
export const sdkLogger = new SdkLogger({sdkVersion:
|
|
65
|
+
export const sdkLogger = new SdkLogger({ sdkVersion: getVersion(), url: window?.location?.href ?? "" });
|
|
57
66
|
|
|
58
67
|
export async function getRestResponse(messageName) {
|
|
59
68
|
|
|
@@ -64,14 +73,14 @@ export async function getRestResponse(messageName) {
|
|
|
64
73
|
|
|
65
74
|
return new Promise((resolve, reject) => {
|
|
66
75
|
const FCID = getFCID();
|
|
67
|
-
const logger = sdkLogger.withFields({FCID});
|
|
76
|
+
const logger = sdkLogger.withFields({ FCID });
|
|
68
77
|
const message = {
|
|
69
78
|
type: "restRequest",
|
|
70
79
|
name: messageName,
|
|
71
80
|
method: "GET",
|
|
72
81
|
fcid: FCID
|
|
73
82
|
};
|
|
74
|
-
const request = {target: "TC", waitForResponse: true, message: JSON.stringify(message)};
|
|
83
|
+
const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
|
|
75
84
|
let timeoutHandler = 0;
|
|
76
85
|
const queryId = window.cefQuery({
|
|
77
86
|
request: JSON.stringify(request),
|