@eluvio/elv-player-js 1.0.140 → 2.0.1

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 (102) hide show
  1. package/README.md +35 -6
  2. package/dist/.vite/manifest.json +67 -0
  3. package/dist/Analytics-HWXR7tWt.mjs +2028 -0
  4. package/dist/Analytics-IUVysdzU.js +29 -0
  5. package/dist/dash.all.min-1QS9Xbir.js +25 -0
  6. package/dist/dash.all.min-9V1xYBRv.mjs +19428 -0
  7. package/dist/elv-player-js.cjs.js +1 -0
  8. package/dist/elv-player-js.css +1 -0
  9. package/dist/elv-player-js.es.js +5 -0
  10. package/dist/hls-1eCRapWm.mjs +15461 -0
  11. package/dist/hls-6O5SV1FQ.js +26 -0
  12. package/dist/index-6cMQneJf.mjs +2273 -0
  13. package/dist/index-C8mwW09z.js +23 -0
  14. package/dist/index-J4QpmTkA.js +367 -0
  15. package/dist/index-hvQzQ6UX.mjs +67432 -0
  16. package/lib/index.js +7 -0
  17. package/{src → lib/player}/Analytics.js +9 -8
  18. package/lib/player/Controls.js +913 -0
  19. package/{src → lib/player}/FairPlay.js +2 -0
  20. package/lib/player/Player.js +881 -0
  21. package/lib/player/PlayerParameters.js +173 -0
  22. package/lib/static/icons/Icons.js +29 -0
  23. package/lib/static/icons/svgs/backward-circle.svg +5 -0
  24. package/lib/static/icons/svgs/backward.svg +4 -0
  25. package/lib/static/icons/svgs/captions-off.svg +7 -0
  26. package/lib/static/icons/svgs/captions.svg +6 -0
  27. package/lib/static/icons/svgs/check.svg +1 -0
  28. package/lib/static/icons/svgs/chevron-left.svg +1 -0
  29. package/lib/static/icons/svgs/chevron-right.svg +1 -0
  30. package/lib/static/icons/svgs/forward-circle.svg +5 -0
  31. package/lib/static/icons/svgs/forward.svg +4 -0
  32. package/{src/static/icons/media/Full Screen icon.svg → lib/static/icons/svgs/full-screen.svg} +1 -1
  33. package/lib/static/icons/svgs/large-play-circle.svg +4 -0
  34. package/lib/static/icons/svgs/list.svg +1 -0
  35. package/{src/static/icons → lib/static/icons/svgs}/minimize.svg +1 -1
  36. package/{src/static/icons/media/Pause icon.svg → lib/static/icons/svgs/pause-circle.svg} +3 -3
  37. package/lib/static/icons/svgs/pause.svg +1 -0
  38. package/{src/static/icons/media/Play icon.svg → lib/static/icons/svgs/play-circle.svg} +1 -1
  39. package/lib/static/icons/svgs/play.svg +1 -0
  40. package/lib/static/icons/svgs/rotate-cw.svg +1 -0
  41. package/lib/static/icons/svgs/settings.svg +11 -0
  42. package/{src/static/icons/media/skip back icon.svg → lib/static/icons/svgs/skip-backward.svg} +2 -3
  43. package/{src/static/icons/media/Skip forward icon.svg → lib/static/icons/svgs/skip-forward.svg} +2 -3
  44. package/{src/static/icons/media/Volume icon.svg → lib/static/icons/svgs/volume-high.svg} +3 -3
  45. package/lib/static/icons/svgs/volume-low.svg +10 -0
  46. package/{src/static/icons/media/low volume icon.svg → lib/static/icons/svgs/volume-medium.svg} +2 -2
  47. package/{src/static/icons/media/no volume icon.svg → lib/static/icons/svgs/volume-off.svg} +3 -3
  48. package/lib/static/stylesheets/common.module.scss +486 -0
  49. package/lib/static/stylesheets/controls-tv.module.scss +488 -0
  50. package/lib/static/stylesheets/controls-web.module.scss +422 -0
  51. package/lib/static/stylesheets/player-profile-form.module.scss +141 -0
  52. package/lib/static/stylesheets/player.module.scss +92 -0
  53. package/lib/static/stylesheets/reset.module.scss +79 -0
  54. package/lib/static/stylesheets/ticket-form.module.scss +123 -0
  55. package/lib/ui/BuildIcons.cjs +44 -0
  56. package/lib/ui/Common.js +210 -0
  57. package/lib/ui/Components.jsx +342 -0
  58. package/lib/ui/Observers.js +449 -0
  59. package/lib/ui/PlayerProfileForm.jsx +106 -0
  60. package/lib/ui/PlayerUI.jsx +317 -0
  61. package/lib/ui/TVControls.jsx +337 -0
  62. package/lib/ui/TicketForm.jsx +147 -0
  63. package/lib/ui/WebControls.jsx +290 -0
  64. package/package.json +35 -47
  65. package/dist/index.js +0 -2
  66. package/dist/index.js.LICENSE.txt +0 -80
  67. package/src/BuildIcons.js +0 -27
  68. package/src/PlayerControls.js +0 -1478
  69. package/src/index.js +0 -1417
  70. package/src/static/icons/Icons.js +0 -15
  71. package/src/static/icons/Settings icon.svg +0 -4
  72. package/src/static/icons/chat icon collapse.svg +0 -1
  73. package/src/static/icons/chat icon.svg +0 -11
  74. package/src/static/icons/chat send.svg +0 -1
  75. package/src/static/icons/full screen.svg +0 -1
  76. package/src/static/icons/media/LargePlayIcon.svg +0 -4
  77. package/src/static/icons/media/Settings icon.svg +0 -4
  78. package/src/static/icons/media/Skip backward icon.svg +0 -4
  79. package/src/static/icons/media/list.svg +0 -1
  80. package/src/static/icons/media/loop icon.svg +0 -12
  81. package/src/static/icons/media/shuffle icon.svg +0 -13
  82. package/src/static/icons/muted.svg +0 -11
  83. package/src/static/icons/pause.svg +0 -1
  84. package/src/static/icons/play circle.svg +0 -1
  85. package/src/static/icons/play.svg +0 -1
  86. package/src/static/icons/settings.svg +0 -1
  87. package/src/static/icons/slider circle.svg +0 -1
  88. package/src/static/icons/unmuted.svg +0 -10
  89. package/src/static/images/ELUV.IO logo embed player.png +0 -0
  90. package/src/static/images/ELUV.IO logo embed player.svg +0 -1
  91. package/src/static/images/ELUV.IO white 20 px V2.png +0 -0
  92. package/src/static/images/ELUVIO white.svg +0 -26
  93. package/src/static/images/Logo.png +0 -0
  94. package/src/static/stylesheets/player.scss +0 -1065
  95. package/webpack.config.js +0 -152
  96. /package/{src/static/icons → lib/static/icons/svgs}/arrow-left.svg +0 -0
  97. /package/{src/static/icons/live icon.svg → lib/static/icons/svgs/live.svg} +0 -0
  98. /package/{src/static/icons → lib/static/icons/svgs}/multiview.svg +0 -0
  99. /package/{src/static/icons/media → lib/static/icons/svgs}/next.svg +0 -0
  100. /package/{src/static/icons/media → lib/static/icons/svgs}/previous.svg +0 -0
  101. /package/{src/static/icons → lib/static/icons/svgs}/x.svg +0 -0
  102. /package/{dist/5897e28fa3e8ac0a2fae.png → lib/static/images/Logo.png} +0 -0
@@ -0,0 +1,881 @@
1
+ import EluvioPlayerParameters from "./PlayerParameters.js";
2
+ import {InitializeFairPlayStream} from "./FairPlay.js";
3
+
4
+ import {Utils} from "@eluvio/elv-client-js";
5
+ import PlayerControls from "./Controls.js";
6
+ import {MergeDefaultParameters} from "../ui/Common";
7
+
8
+ const PlayerProfiles = {
9
+ default: {
10
+ label: "Default",
11
+ hlsSettings: Utils.HLSJSSettings({profile: "default"}),
12
+ },
13
+ low_latency: {
14
+ label: "Low Latency Live",
15
+ hlsSettings: Utils.HLSJSSettings({profile: "ll"})
16
+ },
17
+ ultra_low_latency: {
18
+ label: "Ultra Low Latency Live",
19
+ hlsSettings: Utils.HLSJSSettings({profile: "ull"})
20
+ },
21
+ custom: {
22
+ label: "Custom",
23
+ hlsSettings: {}
24
+ }
25
+ };
26
+
27
+ export class EluvioPlayer {
28
+ // Register a listener for the specified video element event
29
+ __RegisterVideoEventListener(event, callback) {
30
+ this.video.addEventListener(event, callback);
31
+
32
+ this.__listenerDisposers.push(() => this.video.removeEventListener(event, callback));
33
+ }
34
+
35
+ // Register a listener that will be called any time the video settings have changed
36
+ __RegisterSettingsListener(listener) {
37
+ this.__settingsListeners.push(listener);
38
+
39
+ return () => this.__settingsListeners = this.__settingsListeners.filter(l => l !== listener);
40
+ }
41
+
42
+ constructor({target, video, parameters, SetErrorMessage}) {
43
+ this.loading = true;
44
+ this.target = target;
45
+ this.video = video;
46
+ this.SetErrorMessage = SetErrorMessage;
47
+ this.controls = new PlayerControls({player: this});
48
+ this.__settingsListeners = [];
49
+ this.__listenerDisposers = [];
50
+ this.__showPlayerProfileForm = false;
51
+ this.playbackStarted = false;
52
+ this.reloads = 0;
53
+ this.canPlay = false;
54
+
55
+ try {
56
+ // If custom HLS parameters are specified, set profile to custom
57
+ if(
58
+ parameters.playerOptions.hlsjsOptions &&
59
+ Object.keys(parameters.playerOptions.hlsjsOptions).length > 0
60
+ ) {
61
+ this.customHLSOptions = parameters.playerOptions.hlsjsOptions;
62
+ parameters.playerOptions.playerProfile = EluvioPlayerParameters.playerProfile.CUSTOM;
63
+ }
64
+ } catch (error) {
65
+ this.Log(error, true);
66
+ }
67
+
68
+ this.__Initialize(parameters);
69
+ }
70
+
71
+ async __Client() {
72
+ if(this.clientPromise) {
73
+ await this.clientPromise;
74
+ }
75
+
76
+ if(!this.clientOptions.client) {
77
+ this.clientPromise = (async () => {
78
+ const {ElvClient} = await import("@eluvio/elv-client-js");
79
+ this.clientOptions.client = await ElvClient.FromConfigurationUrl({
80
+ configUrl: this.clientOptions.network
81
+ });
82
+
83
+ this.clientOptions.client.SetStaticToken({
84
+ token:
85
+ this.clientOptions.staticToken ||
86
+ this.clientOptions.client.utils.B64(JSON.stringify({qspace_id: await this.clientOptions.client.ContentSpaceId()}))
87
+ });
88
+
89
+ return this.clientOptions.client;
90
+ })();
91
+
92
+ await this.clientPromise;
93
+ }
94
+
95
+ return this.clientOptions.client;
96
+ }
97
+
98
+ async __PlayoutOptions() {
99
+ const client = await this.__Client();
100
+ const playoutParameters = this.sourceOptions.playoutParameters || {};
101
+
102
+ if(this.collectionInfo) {
103
+ const activeMedia = this.collectionInfo.content[this.collectionInfo.mediaIndex];
104
+ playoutParameters.objectId = client.utils.DecodeVersionHash(activeMedia.mediaHash).objectId;
105
+ playoutParameters.versionHash = activeMedia.mediaHash;
106
+ this.sourceOptions.playoutOptions = undefined;
107
+ }
108
+
109
+ let offeringId, offeringURI, options = {};
110
+ if(playoutParameters.clipStart || playoutParameters.clipEnd) {
111
+ options.clip_start = parseFloat(playoutParameters.clipStart || 0);
112
+
113
+ if(playoutParameters.clipEnd) {
114
+ options.clip_end = parseFloat(playoutParameters.clipEnd);
115
+ }
116
+ }
117
+
118
+ options.ignore_trimming = playoutParameters.ignoreTrimming;
119
+ options.resolve = playoutParameters.resolve;
120
+
121
+ if(playoutParameters.offering || playoutParameters.directLink || (playoutParameters.offerings || []).length > 0) {
122
+ let availableOfferings = (await client.AvailableOfferings({
123
+ objectId: playoutParameters.objectId,
124
+ versionHash: playoutParameters.versionHash,
125
+ writeToken: playoutParameters.writeToken,
126
+ linkPath: playoutParameters.linkPath,
127
+ directLink: playoutParameters.directLink,
128
+ resolveIncludeSource: true,
129
+ authorizationToken: playoutParameters.authorizationToken
130
+ })) || {};
131
+
132
+ offeringId = Object.keys(availableOfferings)[0];
133
+ if(playoutParameters.offering) {
134
+ offeringId = availableOfferings[playoutParameters.offering] ? playoutParameters.offering : undefined;
135
+ } else if((playoutParameters.offerings || []).length > 0) {
136
+ offeringId = playoutParameters.offerings.find(offeringId => availableOfferings[offeringId]);
137
+ }
138
+
139
+ if(!offeringId) {
140
+ throw new Error(`Unable to find offering from '${playoutParameters.offering || playoutParameters.offerings}'`);
141
+ }
142
+
143
+ offeringURI = availableOfferings[offeringId].uri;
144
+ }
145
+
146
+ if(playoutParameters.directLink) {
147
+ if(!this.sourceOptions.playoutOptions) {
148
+ this.sourceOptions.playoutOptions = await client.PlayoutOptions({
149
+ offeringURI,
150
+ options
151
+ });
152
+ }
153
+ } else {
154
+ if(!this.sourceOptions.playoutOptions) {
155
+ this.sourceOptions.playoutOptions = await client.PlayoutOptions({
156
+ ...playoutParameters,
157
+ offering: offeringId,
158
+ options
159
+ });
160
+ }
161
+ }
162
+
163
+ let availableDRMs = (await client.AvailableDRMs()).filter(drm => (this.sourceOptions.drms || []).includes(drm));
164
+ let availableProtocols = this.sourceOptions.protocols;
165
+
166
+ let protocol, drm;
167
+ while(!(protocol && drm)) {
168
+ protocol = availableProtocols.find(protocol => this.sourceOptions.playoutOptions[protocol]);
169
+ drm = this.sourceOptions.drms.find(drm => availableDRMs.includes(drm) && this.sourceOptions.playoutOptions[protocol].playoutMethods[drm]);
170
+
171
+ if(!drm) {
172
+ availableProtocols = availableProtocols.filter(p => p !== protocol);
173
+
174
+ if(availableProtocols.length === 0) {
175
+ throw Error("No valid protocol / DRM combination available");
176
+ }
177
+ }
178
+ }
179
+
180
+ const { playoutUrl, drms } = this.sourceOptions.playoutOptions[protocol].playoutMethods[drm];
181
+
182
+ return {
183
+ protocol,
184
+ drm,
185
+ playoutUrl,
186
+ drms,
187
+ availableDRMs,
188
+ offeringURI,
189
+ offering: offeringId,
190
+ sessionId: this.sourceOptions.playoutOptions.sessionId,
191
+ multiviewOptions: {
192
+ enabled: this.sourceOptions.playoutOptions.multiview,
193
+ AvailableViews: this.sourceOptions.playoutOptions.AvailableViews,
194
+ SwitchView: this.sourceOptions.playoutOptions.SwitchView
195
+ }
196
+ };
197
+ }
198
+
199
+ __CollectionPlay({mediaIndex, mediaId, autoplay}) {
200
+ if(mediaId) {
201
+ mediaIndex = this.collectionInfo.content.find(media => media.id === mediaId);
202
+ }
203
+
204
+ this.collectionInfo.mediaIndex = mediaIndex;
205
+
206
+ this.__SettingsUpdate();
207
+
208
+ this.__Initialize(
209
+ this.originalParameters,
210
+ !this.video ? null :
211
+ {
212
+ muted: this.video.muted,
213
+ volume: this.video.volume,
214
+ playing: typeof autoplay !== "undefined" ? autoplay : !this.video.paused
215
+ }
216
+ );
217
+ }
218
+
219
+ async __LoadCollection() {
220
+ if(this.collectionInfo) { return; }
221
+
222
+ let {mediaCatalogObjectId, mediaCatalogVersionHash, collectionId} = (this.sourceOptions && this.sourceOptions.mediaCollectionOptions) || {};
223
+
224
+ if(!collectionId) { return; }
225
+
226
+ if(!mediaCatalogObjectId && !mediaCatalogVersionHash) {
227
+ throw { displayMessage: "Invalid collection options: Media catalog not specified" };
228
+ }
229
+
230
+ const client = await this.__Client();
231
+
232
+ try {
233
+ const authorizationToken = this.sourceOptions.playoutParameters.authorizationToken;
234
+
235
+ mediaCatalogVersionHash = mediaCatalogVersionHash || await client.LatestVersionHash({objectId: mediaCatalogObjectId});
236
+ const collections = (await client.ContentObjectMetadata({
237
+ versionHash: mediaCatalogVersionHash,
238
+ metadataSubtree: "public/asset_metadata/info/collections",
239
+ authorizationToken,
240
+ produceLinkUrls: true
241
+ })) || [];
242
+
243
+ const collectionInfo = collections.find(collection => collection.id === collectionId);
244
+
245
+ if(!collectionInfo) {
246
+ throw { displayMessage: `No collection with ID ${collectionId} found for media catalog ${mediaCatalogObjectId || mediaCatalogVersionHash}` };
247
+ }
248
+
249
+ collectionInfo.content = collectionInfo.content
250
+ .filter(content => content.media)
251
+ .map((content, index) => ({
252
+ ...content,
253
+ active: index === 0,
254
+ mediaId: content.id,
255
+ mediaIndex: index,
256
+ mediaHash: content.media && content.media["/"] && content.media["/"].split("/").find(segment => segment.startsWith("hq__"))
257
+ }));
258
+
259
+ this.collectionInfo = {
260
+ ...collectionInfo,
261
+ isPlaylist: collectionInfo.type === "playlist",
262
+ mediaIndex: 0,
263
+ mediaLength: collectionInfo.content.length
264
+ };
265
+ } catch (error) {
266
+ this.Log("Failed to load collection:");
267
+ throw error;
268
+ }
269
+ }
270
+
271
+ async __Initialize(parameters, restartParameters) {
272
+ if(this.__destroyed) { return; }
273
+
274
+ this.__Reset();
275
+
276
+ this.loading = true;
277
+ this.initTime = Date.now();
278
+
279
+ this.__SettingsUpdate();
280
+
281
+ if(parameters) {
282
+ this.originalParameters = MergeDefaultParameters(parameters);
283
+
284
+ this.clientOptions = parameters.clientOptions;
285
+ this.sourceOptions = parameters.sourceOptions;
286
+ this.playerOptions = parameters.playerOptions;
287
+ }
288
+
289
+ this.isLive = parameters.sourceOptions.contentInfo.type === EluvioPlayerParameters.type.LIVE;
290
+
291
+ this.errors = 0;
292
+
293
+ // Start client loading
294
+ this.__Client();
295
+
296
+ try {
297
+ if(restartParameters) {
298
+ this.video.volume = restartParameters.volume;
299
+ this.video.muted = restartParameters.muted;
300
+
301
+ if(restartParameters.playing) {
302
+ this.playerOptions.autoplay = EluvioPlayerParameters.autoplay.ON;
303
+ }
304
+
305
+ if(restartParameters.currentTime) {
306
+ this.__RegisterVideoEventListener(
307
+ "loadedmetadata",
308
+ () => this.video.currentTime = restartParameters.currentTime
309
+ );
310
+ }
311
+ }
312
+
313
+ this.__RegisterVideoEventListener("play", () => {
314
+ this.reloads = 0;
315
+ this.playbackStarted = true;
316
+ this.__SettingsUpdate();
317
+ });
318
+
319
+ const CheckIsLive = () => {
320
+ if(this.canPlay && !isFinite(this.video.duration)) {
321
+ this.isLive = true;
322
+ } else if(this.video.duration && this.videoDuration > 0 && Math.abs(this.video.duration - this.videoDuration) > 1) {
323
+ this.isLive = true;
324
+ }
325
+ };
326
+
327
+ this.__RegisterVideoEventListener("canplay", () => {
328
+ if(this.initTime && !this.initTimeLogged) {
329
+ this.Log(`Player initialization: ${((Date.now() - this.initTime) / 1000).toFixed(2)} seconds`);
330
+ this.initTimeLogged = true;
331
+ this.canPlay = true;
332
+
333
+ CheckIsLive();
334
+
335
+ if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON) {
336
+ this.controls.Play();
337
+ }
338
+ }
339
+ });
340
+
341
+ // Detect live video
342
+ this.__RegisterVideoEventListener("durationchange", () => {
343
+ CheckIsLive();
344
+
345
+ this.videoDuration = this.video.duration;
346
+ });
347
+
348
+ // Load collection info, if present
349
+ await this.__LoadCollection();
350
+
351
+ if(this.collectionInfo && this.collectionInfo.isPlaylist && this.collectionInfo.mediaIndex < this.collectionInfo.mediaLength - 1) {
352
+ this.__RegisterVideoEventListener("ended", () => this.controls && this.controls.CollectionPlayNext({autoplay: true}));
353
+ }
354
+
355
+ let { protocol, drm, playoutUrl, drms, multiviewOptions } = await this.__PlayoutOptions();
356
+
357
+ //multiviewOptions.target = this.target;
358
+
359
+ playoutUrl = new URL(playoutUrl);
360
+ const authorizationToken =
361
+ this.sourceOptions.playoutParameters.authorizationToken ||
362
+ playoutUrl.searchParams.get("authorization");
363
+
364
+ if(this.__destroyed) { return; }
365
+
366
+ if(protocol === "hls") {
367
+ await this.__InitializeHLS({playoutUrl, authorizationToken, drm, drms, multiviewOptions});
368
+ } else {
369
+ await this.__InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions});
370
+ }
371
+
372
+ if(this.playerOptions.collectVideoAnalytics) {
373
+ import("./Analytics.js")
374
+ .then(({InitializeMuxMonitoring}) => InitializeMuxMonitoring({
375
+ appName: this.playerOptions.appName || "elv-player-js",
376
+ elvPlayer: this,
377
+ playoutUrl,
378
+ authorizationToken,
379
+ disableCookies: this.playerOptions.collectVideoAnalytics === EluvioPlayerParameters.collectVideoAnalytics.DISABLE_COOKIES
380
+ }));
381
+ }
382
+
383
+ if(this.playerOptions.playerCallback) {
384
+ this.playerOptions.playerCallback({
385
+ player: this,
386
+ videoElement: this.video,
387
+ hlsPlayer: this.hlsPlayer,
388
+ dashPlayer: this.dashPlayer
389
+ });
390
+ }
391
+
392
+ /* TODO: Account watermark
393
+ if(this.controls && this.playerOptions.accountWatermark) {
394
+ // Watermark
395
+ this.controls.InitializeAccountWatermark(
396
+ (await this.__Client()).CurrentAccountAddress()
397
+ );
398
+ }
399
+
400
+ */
401
+
402
+ if(this.__destroyed) {
403
+ // If Destroy was called during the initialization process, ensure that the player is properly destroyed
404
+ this.__DestroyPlayer();
405
+ }
406
+ } catch (error) {
407
+ // If playout failed due to a permission issue, check the content to see if there is a message to display
408
+ let permissionErrorMessage;
409
+ if(error && [401, 403].includes(error.status) || [401, 403].includes(error.code)) {
410
+ try {
411
+ const client = await this.__Client();
412
+
413
+ const targetHash =
414
+ this.sourceOptions.playoutParameters.linkPath ?
415
+ await client.LinkTarget({...this.sourceOptions.playoutParameters}) :
416
+ this.sourceOptions.playoutParameters.versionHash ||
417
+ await client.LatestVersionHash({objectId: this.sourceOptions.playoutParameters.objectId});
418
+
419
+ permissionErrorMessage = await client.ContentObjectMetadata({
420
+ versionHash: targetHash,
421
+ metadataSubtree: "public/asset_metadata/permission_message",
422
+ authorizationToken: this.sourceOptions.playoutParameters.authorizationToken
423
+ });
424
+
425
+ if(permissionErrorMessage) {
426
+ error.permission_message = permissionErrorMessage;
427
+ this.SetErrorMessage(permissionErrorMessage);
428
+
429
+ if(typeof error === "object") {
430
+ error.permission_message = permissionErrorMessage;
431
+ } else {
432
+ this.Log(permissionErrorMessage, true);
433
+ }
434
+ } else {
435
+ this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
436
+ }
437
+ // eslint-disable-next-line no-empty
438
+ } catch (error) {
439
+ this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
440
+ }
441
+ } else if(error.status === 500) {
442
+ this.__HardReload(error, 10000);
443
+ } else {
444
+ this.SetErrorMessage(error.displayMessage || "Something went wrong");
445
+ }
446
+
447
+ if(this.playerOptions.errorCallback) {
448
+ this.playerOptions.errorCallback(error, this);
449
+ }
450
+ } finally {
451
+ this.loading = false;
452
+ this.__SettingsUpdate();
453
+ }
454
+ }
455
+
456
+ async __InitializeHLS({playoutUrl, authorizationToken, drm, multiviewOptions}) {
457
+ this.HLS = (await import("hls.js")).default;
458
+
459
+ if(["fairplay", "sample-aes"].includes(drm) || !this.HLS.isSupported()) {
460
+ // HLS JS NOT SUPPORTED - Handle native player
461
+ this.nativeHLS = true;
462
+
463
+ if(drm === "fairplay") {
464
+ InitializeFairPlayStream({playoutOptions: this.sourceOptions.playoutOptions, video: this.video});
465
+ } else {
466
+ this.video.src = playoutUrl.toString();
467
+ }
468
+
469
+ if(multiviewOptions.enabled) {
470
+ const Switch = multiviewOptions.SwitchView;
471
+
472
+ multiviewOptions.SwitchView = async (view) => {
473
+ await Switch(view);
474
+ };
475
+
476
+ /* TODO: Init multiview
477
+ if(this.controls) {
478
+ this.controls.InitializeMultiViewControls(multiviewOptions);
479
+ }
480
+
481
+ */
482
+ }
483
+ } else {
484
+ // HLS JS
485
+ playoutUrl.searchParams.delete("authorization");
486
+
487
+ const profileSettings = (PlayerProfiles[this.playerOptions.playerProfile] || {}).hlsSettings || {};
488
+ const customProfileSettings = this.playerOptions.playerProfile === EluvioPlayerParameters.playerProfile.CUSTOM ? this.customHLSOptions : {};
489
+
490
+ this.hlsOptions = {
491
+ capLevelToPlayerSize: this.playerOptions.capLevelToPlayerSize,
492
+ ...profileSettings,
493
+ ...customProfileSettings
494
+ };
495
+
496
+ const hlsPlayer = new this.HLS({
497
+ xhrSetup: xhr => {
498
+ xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
499
+
500
+ if((this.playerOptions.hlsjsOptions || {}).xhrSetup) {
501
+ this.playerOptions.hlsjsOptions.xhrSetup(xhr);
502
+ }
503
+
504
+ return xhr;
505
+ },
506
+ ...this.hlsOptions
507
+ });
508
+
509
+ // Limit playback to maximum bitrate, if specified
510
+ if(this.playerOptions.maxBitrate) {
511
+ hlsPlayer.on(this.HLS.Events.MANIFEST_PARSED, (_, {levels, firstLevel}) => {
512
+ let levelsToRemove = levels
513
+ .map((level, i) => level.bitrate > this.playerOptions.maxBitrate ? i : undefined)
514
+ .filter(i => typeof i !== "undefined")
515
+ // Note: Remove levels from highest to lowest index
516
+ .reverse();
517
+
518
+ if(levelsToRemove.length === levels.length) {
519
+ this.Log(`Warning: Max bitrate '${this.playerOptions.maxBitrate}bps' is less than all available levels for this content.`);
520
+ // Keep first level
521
+ levelsToRemove = levelsToRemove.filter(i => i > 0);
522
+ }
523
+
524
+ this.Log("Removing the following levels due to maxBitrate setting:");
525
+ this.Log(levelsToRemove.map(i => [levels[i].width, "x", levels[i].height, ` (${(levels[i].bitrate / 1000 / 1000).toFixed(1)}Mbps)`].join("")).join(", "));
526
+
527
+ if(levelsToRemove.find(i => firstLevel === i)) {
528
+ // Player will start on level that is being removed - switch to highest level that will not be removed
529
+ hlsPlayer.startLevel = levels.map((_, i) => i).filter(i => !levelsToRemove.includes(i)).reverse()[0];
530
+ }
531
+
532
+ levelsToRemove.map(i => hlsPlayer.removeLevel(i));
533
+ });
534
+ }
535
+
536
+ hlsPlayer.loadSource(playoutUrl.toString());
537
+ hlsPlayer.attachMedia(this.video);
538
+
539
+ if(this.controls && multiviewOptions.enabled) {
540
+ const Switch = multiviewOptions.SwitchView;
541
+
542
+ multiviewOptions.SwitchView = async (view) => {
543
+ await Switch(view);
544
+ hlsPlayer.nextLevel = hlsPlayer.currentLevel;
545
+ };
546
+
547
+ this.controls.InitializeMultiViewControls(multiviewOptions);
548
+ }
549
+
550
+ // Keep track of relevant settings updates so the UI can react
551
+ [
552
+ this.HLS.Events.SUBTITLE_TRACKS_UPDATED,
553
+ this.HLS.Events.SUBTITLE_TRACK_SWITCH,
554
+ this.HLS.Events.LEVEL_UPDATED,
555
+ this.HLS.Events.LEVEL_SWITCHED,
556
+ this.HLS.Events.AUDIO_TRACKS_UPDATED,
557
+ this.HLS.Events.AUDIO_TRACK_SWITCHED,
558
+ this.HLS.Events.MANIFEST_LOADED
559
+ ]
560
+ .map(event => hlsPlayer.on(event, () => this.__SettingsUpdate()));
561
+
562
+ // TODO: Refactor this somewhere else
563
+ this.SetPlayerProfile = async ({profile, customHLSOptions={}}) => {
564
+ this.videoDuration = undefined;
565
+ this.playerOptions.playerProfile = profile;
566
+ this.customHLSOptions = customHLSOptions;
567
+
568
+ const playing = !this.video.paused;
569
+ const currentTime = this.video.currentTime;
570
+
571
+ this.hlsPlayer.destroy();
572
+ await this.__InitializeHLS({
573
+ playoutUrl,
574
+ authorizationToken,
575
+ drm,
576
+ multiviewOptions
577
+ });
578
+
579
+ playing ? this.video.play() : this.video.pause();
580
+
581
+ if(!this.isLive) {
582
+ this.video.currentTime = currentTime;
583
+ }
584
+ };
585
+
586
+ // Error handling
587
+ hlsPlayer.on(this.HLS.Events.FRAG_LOADED, () =>
588
+ this.errors = 0
589
+ );
590
+
591
+ hlsPlayer.on(this.HLS.Events.ERROR, async (event, error) => {
592
+ this.errors += 1;
593
+
594
+ this.Log(`Encountered ${error.details}`, true);
595
+ this.Log(error, true);
596
+
597
+ if(error.response && error.response.code === 403) {
598
+ // Not allowed to access
599
+ this.SetErrorMessage("Insufficient permissions");
600
+ } else if(this.errors < 5) {
601
+ if(error.fatal) {
602
+ if(error.data && error.data.type === this.HLS.ErrorTypes.MEDIA_ERROR) {
603
+ this.Log("Attempting to recover using hlsPlayer.recoverMediaError");
604
+ hlsPlayer.recoverMediaError();
605
+ } else {
606
+ this.__HardReload(error);
607
+ }
608
+ }
609
+ } else {
610
+ this.__HardReload(error);
611
+ }
612
+ });
613
+
614
+ this.hlsPlayer = hlsPlayer;
615
+ this.player = hlsPlayer;
616
+ }
617
+ }
618
+
619
+ async __InitializeDash({playoutUrl, authorizationToken, drm, drms}) {
620
+ this.Dash = (await import("dashjs")).default;
621
+ const dashPlayer = this.Dash.MediaPlayer().create();
622
+
623
+ const customDashOptions = this.playerOptions.dashjsOptions || {};
624
+ dashPlayer.updateSettings({
625
+ ...customDashOptions,
626
+ "streaming": {
627
+ "buffer": {
628
+ "fastSwitchEnabled": true,
629
+ "flushBufferAtTrackSwitch": true,
630
+ ...((customDashOptions.streaming || {}).buffer || {})
631
+ },
632
+ "text": {
633
+ "defaultEnabled": false,
634
+ ...((customDashOptions.streaming || {}).text || {})
635
+ },
636
+ ...(customDashOptions.streaming || {})
637
+ }
638
+ });
639
+
640
+ if(this.playerOptions.capLevelToPlayerSize) {
641
+ dashPlayer.updateSettings({
642
+ "streaming": {
643
+ "abr": {
644
+ "limitBitrateByPortal": true
645
+ }
646
+ }
647
+ });
648
+ }
649
+
650
+ if(this.playerOptions.maxBitrate) {
651
+ dashPlayer.updateSettings({
652
+ "streaming": {
653
+ "abr": {
654
+ "maxBitrate": { "video": this.playerOptions.maxBitrate / 1000 }
655
+ }
656
+ }
657
+ });
658
+ }
659
+
660
+ playoutUrl.searchParams.delete("authorization");
661
+ dashPlayer.extend("RequestModifier", function () {
662
+ return {
663
+ modifyRequestHeader: xhr => {
664
+ xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
665
+
666
+ return xhr;
667
+ },
668
+ modifyRequestURL: url => url
669
+ };
670
+ });
671
+
672
+ // Widevine
673
+ if(drm === EluvioPlayerParameters.drms.WIDEVINE) {
674
+ const widevineUrl = drms.widevine.licenseServers[0];
675
+
676
+ dashPlayer.setProtectionData({
677
+ "com.widevine.alpha": {
678
+ "serverURL": widevineUrl
679
+ }
680
+ });
681
+ }
682
+
683
+ dashPlayer.initialize(
684
+ this.video,
685
+ playoutUrl.toString(),
686
+ this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON
687
+ );
688
+
689
+ /*
690
+ if(this.controls && multiviewOptions.enabled) {
691
+ this.controls.InitializeMultiViewControls(multiviewOptions);
692
+ }
693
+
694
+ */
695
+
696
+ // Keep track of relevant settings updates so the UI can react
697
+ [
698
+ this.Dash.MediaPlayer.events.TRACK_CHANGE_RENDERED,
699
+ this.Dash.MediaPlayer.events.QUALITY_CHANGE_RENDERED,
700
+ this.Dash.MediaPlayer.events.REPRESENTATION_SWITCH,
701
+ this.Dash.MediaPlayer.events.TEXT_TRACKS_ADDED,
702
+ this.Dash.MediaPlayer.events.TEXT_TRACK_ADDED,
703
+ this.Dash.MediaPlayer.events.MANIFEST_LOADED,
704
+ this.Dash.MediaPlayer.events.CAN_PLAY
705
+ ]
706
+ .map(event => dashPlayer.on(event, () => this.__SettingsUpdate()));
707
+
708
+ this.player = dashPlayer;
709
+ this.dashPlayer = dashPlayer;
710
+ }
711
+
712
+ async __Play() {
713
+ try {
714
+ await this.video.play();
715
+ return true;
716
+ } catch(error) {
717
+ this.Log("Unable to autoplay", true);
718
+ this.Log(error, true);
719
+ this.playbackStarted = false;
720
+
721
+ if(this.playerOptions.muted === EluvioPlayerParameters.muted.OFF_IF_POSSIBLE && this.video.paused && !this.video.muted) {
722
+ await new Promise(resolve => setTimeout(resolve, 250));
723
+ this.Log("Attempting to autoplay muted");
724
+ this.video.muted = true;
725
+
726
+ try {
727
+ await this.video.play();
728
+ return true;
729
+ } catch (error) {
730
+ this.playbackStarted = false;
731
+ return false;
732
+ }
733
+ } else {
734
+ this.playbackStarted = false;
735
+ return false;
736
+ }
737
+ }
738
+ }
739
+
740
+ // Indicate to controls that the settings have updated
741
+ __SettingsUpdate() {
742
+ this.__settingsListeners.forEach(listener => {
743
+ try {
744
+ listener();
745
+ } catch (error) {
746
+ this.Log("Failed to call settings listener", true);
747
+ this.Log(error, true);
748
+ }
749
+ });
750
+ }
751
+
752
+ __SetControlsVisibility(visible) {
753
+ if(this.controls.visible === visible) { return; }
754
+
755
+ this.controls.visible = visible;
756
+ this.__SettingsUpdate();
757
+ }
758
+
759
+ __DestroyPlayer() {
760
+ this.__destroyed = true;
761
+ this.__Reset();
762
+ }
763
+
764
+ __Reset() {
765
+ if(!this.player) { return; }
766
+
767
+ this.Log("Destroying player");
768
+
769
+ if(this.video) {
770
+ this.video.pause();
771
+ }
772
+
773
+ if(this.hlsPlayer) {
774
+ this.hlsPlayer.destroy();
775
+ } else if(this.dashPlayer) {
776
+ this.dashPlayer.destroy();
777
+ }
778
+
779
+ this.__listenerDisposers.forEach(Disposer => {
780
+ try {
781
+ Disposer();
782
+ } catch (error) {
783
+ this.Log("Failed to dispose of video event listener", true);
784
+ this.Log(error);
785
+ }
786
+ });
787
+
788
+ this.__listenerDisposers = [];
789
+ this.__showPlayerProfileForm = false;
790
+
791
+ if(this.video.mux) {
792
+ try {
793
+ this.video.mux.destroy();
794
+ } catch(error) {
795
+ this.Log("Error destroying mux monitoring:");
796
+ this.Log(error);
797
+ }
798
+ }
799
+
800
+ this.nativeHLS = false;
801
+ this.hlsPlayer = undefined;
802
+ this.dashPlayer = undefined;
803
+ this.player = undefined;
804
+ this.initTimeLogged = false;
805
+ this.canPlay = false;
806
+ }
807
+
808
+ async __HardReload(error, delay=6000) {
809
+ if(this.reloading) { return; }
810
+
811
+ this.reloading = true;
812
+ this.reloads += 1;
813
+
814
+ if(this.reloads > 2) {
815
+ this.SetErrorMessage(error.displayMessage || "Unable to play content");
816
+ return;
817
+ }
818
+
819
+ try {
820
+ if(error && this.playerOptions.restartCallback) {
821
+ try {
822
+ const abort = await this.playerOptions.restartCallback(error);
823
+
824
+ if(abort && typeof abort === "boolean") {
825
+ this.Destroy();
826
+ return;
827
+ }
828
+ } catch (error) {
829
+ this.Log("Restart callback failed:");
830
+ this.Log(error);
831
+ }
832
+ }
833
+
834
+ this.SetErrorMessage(error.displayMessage || "Something went wrong, reloading player...");
835
+ await new Promise(resolve => setTimeout(resolve, delay));
836
+
837
+ if(this.__destroyed) { return; }
838
+
839
+ this.Log("Reloading stream");
840
+
841
+ // Recall config to get new nodes
842
+ const client = await this.__Client();
843
+ if(client) {
844
+ await client.ResetRegion();
845
+ }
846
+
847
+ this.restarted = true;
848
+ this.SetErrorMessage(undefined);
849
+ this.__Initialize(
850
+ this.originalParameters,
851
+ !this.video ? null :
852
+ {
853
+ muted: this.video.muted,
854
+ volume: this.video.volume,
855
+ currentTime: this.video.currentTime,
856
+ playing: !this.video.paused
857
+ }
858
+ );
859
+ } finally {
860
+ this.reloading = false;
861
+ }
862
+ }
863
+
864
+ Log(message, error=false) {
865
+ if(error) {
866
+ // eslint-disable-next-line no-console
867
+ console.error("ELUVIO PLAYER:", message);
868
+ } else {
869
+ if(this.playerOptions.debugLogging) {
870
+ // eslint-disable-next-line no-console
871
+ console.warn("ELUVIO PLAYER:", message);
872
+ }
873
+ }
874
+ }
875
+ }
876
+
877
+ EluvioPlayer.EluvioPlayerParameters = EluvioPlayerParameters;
878
+ EluvioPlayer.EluvioPlayer = EluvioPlayer;
879
+
880
+ export default EluvioPlayer;
881
+