@eluvio/elv-player-js 1.0.139 → 2.0.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 (98) hide show
  1. package/README.md +35 -6
  2. package/dist/.vite/manifest.json +17 -17
  3. package/dist/{Analytics-cQC_NR8f.mjs → Analytics-MzZmvYgy.mjs} +1 -1
  4. package/dist/{Analytics-z6nAtuJx.js → Analytics-jM8HcyUa.js} +1 -1
  5. package/dist/{dash.all.min-Uvqi9PBX.js → dash.all.min-16Sl6Y0h.js} +1 -1
  6. package/dist/{dash.all.min-JeIXEd1s.mjs → dash.all.min-2ST8aEXP.mjs} +1 -1
  7. package/dist/elv-player-js.cjs.js +1 -1
  8. package/dist/elv-player-js.css +1 -1
  9. package/dist/elv-player-js.es.js +1 -1
  10. package/dist/{index-RKrb2ZFL.js → index-BThzGsbn.js} +1 -1
  11. package/dist/index-Cw8L2-NE.js +367 -0
  12. package/dist/{index-FpQhGSc8.mjs → index-herSXPMN.mjs} +1 -1
  13. package/dist/{index-FmpRD8ov.mjs → index-mO9GR6Op.mjs} +25278 -24415
  14. package/lib/index.js +7 -0
  15. package/{src → lib/player}/Analytics.js +9 -8
  16. package/lib/player/Controls.js +912 -0
  17. package/{src → lib/player}/FairPlay.js +2 -0
  18. package/lib/player/Player.js +881 -0
  19. package/lib/player/PlayerParameters.js +173 -0
  20. package/lib/static/icons/Icons.js +29 -0
  21. package/lib/static/icons/svgs/backward-circle.svg +5 -0
  22. package/lib/static/icons/svgs/backward.svg +4 -0
  23. package/lib/static/icons/svgs/captions-off.svg +7 -0
  24. package/lib/static/icons/svgs/captions.svg +6 -0
  25. package/lib/static/icons/svgs/check.svg +1 -0
  26. package/lib/static/icons/svgs/chevron-left.svg +1 -0
  27. package/lib/static/icons/svgs/chevron-right.svg +1 -0
  28. package/lib/static/icons/svgs/forward-circle.svg +5 -0
  29. package/lib/static/icons/svgs/forward.svg +4 -0
  30. package/{src/static/icons/media/Full Screen icon.svg → lib/static/icons/svgs/full-screen.svg} +1 -1
  31. package/lib/static/icons/svgs/large-play-circle.svg +4 -0
  32. package/lib/static/icons/svgs/list.svg +1 -0
  33. package/{src/static/icons → lib/static/icons/svgs}/minimize.svg +1 -1
  34. package/{src/static/icons/media/Pause icon.svg → lib/static/icons/svgs/pause-circle.svg} +3 -3
  35. package/lib/static/icons/svgs/pause.svg +1 -0
  36. package/{src/static/icons/media/Play icon.svg → lib/static/icons/svgs/play-circle.svg} +1 -1
  37. package/lib/static/icons/svgs/play.svg +1 -0
  38. package/lib/static/icons/svgs/rotate-cw.svg +1 -0
  39. package/lib/static/icons/svgs/settings.svg +11 -0
  40. package/{src/static/icons/media/skip back icon.svg → lib/static/icons/svgs/skip-backward.svg} +2 -3
  41. package/{src/static/icons/media/Skip forward icon.svg → lib/static/icons/svgs/skip-forward.svg} +2 -3
  42. package/{src/static/icons/media/Volume icon.svg → lib/static/icons/svgs/volume-high.svg} +3 -3
  43. package/lib/static/icons/svgs/volume-low.svg +10 -0
  44. package/{src/static/icons/media/low volume icon.svg → lib/static/icons/svgs/volume-medium.svg} +2 -2
  45. package/{src/static/icons/media/no volume icon.svg → lib/static/icons/svgs/volume-off.svg} +3 -3
  46. package/lib/static/stylesheets/common.module.scss +486 -0
  47. package/lib/static/stylesheets/controls-tv.module.scss +488 -0
  48. package/lib/static/stylesheets/controls-web.module.scss +422 -0
  49. package/lib/static/stylesheets/player-profile-form.module.scss +141 -0
  50. package/lib/static/stylesheets/player.module.scss +92 -0
  51. package/lib/static/stylesheets/reset.module.scss +79 -0
  52. package/lib/static/stylesheets/ticket-form.module.scss +123 -0
  53. package/lib/ui/BuildIcons.cjs +44 -0
  54. package/lib/ui/Common.js +210 -0
  55. package/lib/ui/Components.jsx +342 -0
  56. package/lib/ui/Observers.js +449 -0
  57. package/lib/ui/PlayerProfileForm.jsx +106 -0
  58. package/lib/ui/PlayerUI.jsx +316 -0
  59. package/lib/ui/TVControls.jsx +337 -0
  60. package/lib/ui/TicketForm.jsx +147 -0
  61. package/lib/ui/WebControls.jsx +290 -0
  62. package/package.json +35 -47
  63. package/dist/index-88AgCVwU.js +0 -367
  64. package/src/BuildIcons.js +0 -27
  65. package/src/PlayerControls.js +0 -1478
  66. package/src/index.js +0 -1416
  67. package/src/static/icons/Icons.js +0 -15
  68. package/src/static/icons/Settings icon.svg +0 -4
  69. package/src/static/icons/chat icon collapse.svg +0 -1
  70. package/src/static/icons/chat icon.svg +0 -11
  71. package/src/static/icons/chat send.svg +0 -1
  72. package/src/static/icons/full screen.svg +0 -1
  73. package/src/static/icons/media/LargePlayIcon.svg +0 -4
  74. package/src/static/icons/media/Settings icon.svg +0 -4
  75. package/src/static/icons/media/Skip backward icon.svg +0 -4
  76. package/src/static/icons/media/list.svg +0 -1
  77. package/src/static/icons/media/loop icon.svg +0 -12
  78. package/src/static/icons/media/shuffle icon.svg +0 -13
  79. package/src/static/icons/muted.svg +0 -11
  80. package/src/static/icons/pause.svg +0 -1
  81. package/src/static/icons/play circle.svg +0 -1
  82. package/src/static/icons/play.svg +0 -1
  83. package/src/static/icons/settings.svg +0 -1
  84. package/src/static/icons/slider circle.svg +0 -1
  85. package/src/static/icons/unmuted.svg +0 -10
  86. package/src/static/images/ELUV.IO logo embed player.png +0 -0
  87. package/src/static/images/ELUV.IO logo embed player.svg +0 -1
  88. package/src/static/images/ELUVIO white.svg +0 -26
  89. package/src/static/images/Logo.png +0 -0
  90. package/src/static/stylesheets/player.scss +0 -1065
  91. package/webpack.config.js +0 -152
  92. /package/{src/static/icons → lib/static/icons/svgs}/arrow-left.svg +0 -0
  93. /package/{src/static/icons/live icon.svg → lib/static/icons/svgs/live.svg} +0 -0
  94. /package/{src/static/icons → lib/static/icons/svgs}/multiview.svg +0 -0
  95. /package/{src/static/icons/media → lib/static/icons/svgs}/next.svg +0 -0
  96. /package/{src/static/icons/media → lib/static/icons/svgs}/previous.svg +0 -0
  97. /package/{src/static/icons → lib/static/icons/svgs}/x.svg +0 -0
  98. /package/{src/static/images/ELUV.IO white 20 px V2.png → lib/static/images/Logo.png} +0 -0
package/src/index.js DELETED
@@ -1,1416 +0,0 @@
1
- import "./static/stylesheets/player.scss";
2
-
3
- import "focus-visible";
4
-
5
- import MergeWith from "lodash/mergeWith";
6
- import Clone from "lodash/cloneDeep";
7
-
8
- import ResizeObserver from "resize-observer-polyfill";
9
-
10
- import {InitializeFairPlayStream} from "./FairPlay";
11
- import PlayerControls, {CreateElement, InitializeTicketPrompt, PlayPause} from "./PlayerControls";
12
-
13
- import {Utils} from "@eluvio/elv-client-js";
14
-
15
- export const EluvioPlayerParameters = {
16
- networks: {
17
- MAIN: "https://main.net955305.contentfabric.io/config",
18
- DEMO: "https://demov3.net955210.contentfabric.io/config",
19
- TEST: "https://test.net955203.contentfabric.io/config",
20
- TESTV4: "https://test.net955205.contentfabric.io/config"
21
- },
22
- playerProfile: {
23
- DEFAULT: "default",
24
- LOW_LATENCY: "low_latency",
25
- ULTRA_LOW_LATENCY: "ultra_low_latency",
26
- CUSTOM: "custom"
27
- },
28
- drms: {
29
- FAIRPLAY: "fairplay",
30
- SAMPLE_AES: "sample-aes",
31
- AES128: "aes-128",
32
- WIDEVINE: "widevine",
33
- CLEAR: "clear"
34
- },
35
- protocols: {
36
- HLS: "hls",
37
- DASH: "dash"
38
- },
39
- autoplay: {
40
- OFF: false,
41
- WHEN_VISIBLE: "when visible",
42
- ON: true
43
- },
44
- controls: {
45
- OFF: false,
46
- OFF_WITH_VOLUME_TOGGLE: "off_with_volume_toggle",
47
- AUTO_HIDE: "autohide",
48
- ON: true,
49
- DEFAULT: "default"
50
- },
51
- title: {
52
- ON: true,
53
- OFF: false
54
- },
55
- loop: {
56
- OFF: false,
57
- ON: true
58
- },
59
- muted: {
60
- OFF: false,
61
- WHEN_NOT_VISIBLE: "when_not_visible",
62
- OFF_IF_POSSIBLE: "off_if_possible",
63
- ON: true
64
- },
65
- watermark: {
66
- OFF: false,
67
- ON: true
68
- },
69
- accountWatermark: {
70
- OFF: false,
71
- ON: true
72
- },
73
- capLevelToPlayerSize: {
74
- OFF: false,
75
- ON: true
76
- },
77
- collectVideoAnalytics: {
78
- OFF: false,
79
- ON: true,
80
- DISABLE_COOKIES: "disable_cookies"
81
- }
82
- };
83
-
84
- const DefaultParameters = {
85
- clientOptions: {
86
- network: EluvioPlayerParameters.networks.MAIN,
87
- client: undefined,
88
- staticToken: undefined,
89
- tenantId: undefined,
90
- ntpId: undefined,
91
- promptTicket: false,
92
- ticketCode: undefined,
93
- ticketSubject: undefined
94
- },
95
- sourceOptions: {
96
- protocols: [
97
- EluvioPlayerParameters.protocols.HLS,
98
- EluvioPlayerParameters.protocols.DASH
99
- ],
100
- drms: [
101
- EluvioPlayerParameters.drms.FAIRPLAY,
102
- EluvioPlayerParameters.drms.SAMPLE_AES,
103
- EluvioPlayerParameters.drms.AES128,
104
- EluvioPlayerParameters.drms.WIDEVINE,
105
- EluvioPlayerParameters.drms.CLEAR
106
- ],
107
- contentOptions: {
108
- title: undefined,
109
- description: undefined
110
- },
111
- mediaCollectionOptions: {
112
- mediaCatalogObjectId: undefined,
113
- mediaCatalogVersionHash: undefined,
114
- collectionId: undefined
115
- },
116
- playoutOptions: undefined,
117
- playoutParameters: {
118
- objectId: undefined,
119
- versionHash: undefined,
120
- writeToken: undefined,
121
- linkPath: undefined,
122
- signedLink: false,
123
- handler: "playout",
124
- offering: "default",
125
- playoutType: undefined,
126
- context: undefined,
127
- hlsjsProfile: true,
128
- authorizationToken: undefined,
129
- clipStart: undefined,
130
- clipEnd: undefined
131
- }
132
- },
133
- playerOptions: {
134
- appName: undefined,
135
- controls: EluvioPlayerParameters.controls.AUTO_HIDE,
136
- autoplay: EluvioPlayerParameters.autoplay.OFF,
137
- muted: EluvioPlayerParameters.muted.OFF,
138
- loop: EluvioPlayerParameters.loop.OFF,
139
- watermark: EluvioPlayerParameters.watermark.ON,
140
- capLevelToPlayerSize: EluvioPlayerParameters.capLevelToPlayerSize.OFF,
141
- title: EluvioPlayerParameters.title.ON,
142
- posterUrl: undefined,
143
- className: undefined,
144
- controlsClassName: undefined,
145
- playerProfile: EluvioPlayerParameters.playerProfile.DEFAULT,
146
- hlsjsOptions: undefined,
147
- dashjsOptions: undefined,
148
- debugLogging: false,
149
- collectVideoAnalytics: true,
150
- maxBitrate: undefined,
151
- // eslint-disable-next-line no-unused-vars
152
- playerCallback: ({player, videoElement, hlsPlayer, dashPlayer, posterUrl}) => {},
153
- // eslint-disable-next-line no-unused-vars
154
- errorCallback: (error, player) => {
155
- // eslint-disable-next-line no-console
156
- console.error("ELUVIO PLAYER: Error");
157
- // eslint-disable-next-line no-console
158
- console.error(error);
159
- },
160
- // eslint-disable-next-line no-unused-vars
161
- restartCallback: async (error) => {}
162
- }
163
- };
164
-
165
- const PlayerProfiles = {
166
- default: {
167
- label: "Default",
168
- hlsSettings: Utils.HLSJSSettings({profile: "default"}),
169
- },
170
- low_latency: {
171
- label: "Low Latency Live",
172
- hlsSettings: Utils.HLSJSSettings({profile: "ll"})
173
- },
174
- ultra_low_latency: {
175
- label: "Ultra Low Latency Live",
176
- hlsSettings: Utils.HLSJSSettings({profile: "ull"})
177
- },
178
- custom: {
179
- label: "Custom",
180
- hlsSettings: {}
181
- }
182
- };
183
-
184
- export class EluvioPlayer {
185
- constructor(target, parameters) {
186
- this.reloads = [];
187
-
188
- try {
189
- if(
190
- parameters.playerOptions.hlsjsOptions &&
191
- Object.keys(parameters.playerOptions.hlsjsOptions).length > 0
192
- ) {
193
- this.customHLSOptions = parameters.playerOptions.hlsjsOptions;
194
- parameters.playerOptions.playerProfile = EluvioPlayerParameters.playerProfile.CUSTOM;
195
- }
196
- } catch (error) {
197
- this.Log(error, true);
198
- }
199
-
200
- this.DetectRemoval = this.DetectRemoval.bind(this);
201
-
202
- this.target = target;
203
- this.originalParameters = parameters;
204
-
205
- this.Initialize(target, parameters);
206
-
207
- window.EluvioPlayer = this;
208
- }
209
-
210
- Log(message, error=false) {
211
- if(error) {
212
- // eslint-disable-next-line no-console
213
- console.error("ELUVIO PLAYER:", message);
214
- } else {
215
- if(this.playerOptions.debugLogging) {
216
- // eslint-disable-next-line no-console
217
- console.warn("ELUVIO PLAYER:", message);
218
- }
219
- }
220
- }
221
-
222
- Destroy() {
223
- this.__destroyed = true;
224
-
225
- if(this.mutationObserver) {
226
- this.mutationObserver.disconnect();
227
- }
228
-
229
- if(this.resizeObserver) {
230
- this.resizeObserver.unobserve(this.target);
231
- }
232
-
233
- this.__DestroyPlayer();
234
- this.target.innerHTML = "";
235
- }
236
-
237
- SetErrorMessage(message) {
238
- let errorMessage = this.target.querySelector(".eluvio-player__error-message");
239
-
240
- if(!errorMessage) {
241
- errorMessage = CreateElement({
242
- parent: this.target,
243
- classes: ["eluvio-player__error-message"]
244
- });
245
- }
246
-
247
- errorMessage.innerHTML = "";
248
-
249
- CreateElement({
250
- parent: errorMessage,
251
- classes: ["eluvio-player__error-message__text"]
252
- }).innerHTML = message;
253
-
254
- this.target.classList.add("eluvio-player--error");
255
- }
256
-
257
- RegisterVisibilityCallback() {
258
- if(
259
- this.playerOptions.autoplay !== EluvioPlayerParameters.autoplay.WHEN_VISIBLE &&
260
- this.playerOptions.muted !== EluvioPlayerParameters.muted.WHEN_NOT_VISIBLE
261
- ) {
262
- // Nothing to watch
263
- return;
264
- }
265
-
266
- let lastPlayPauseAction, lastMuteAction;
267
- const Callback = async ([bodyElement]) => {
268
- // Play / pause when entering / leaving viewport
269
- if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.WHEN_VISIBLE) {
270
- if(lastPlayPauseAction !== "play" && bodyElement.isIntersecting && this.video.paused) {
271
- PlayPause(this.video, true);
272
- lastPlayPauseAction = "play";
273
- } else if(lastPlayPauseAction !== "pause" && !bodyElement.isIntersecting && !this.video.paused) {
274
- PlayPause(this.video, false);
275
- lastPlayPauseAction = "pause";
276
- }
277
- }
278
-
279
- // Mute / unmute when entering / leaving viewport
280
- if(this.playerOptions.muted === EluvioPlayerParameters.muted.WHEN_NOT_VISIBLE) {
281
- if(lastMuteAction !== "unmute" && bodyElement.isIntersecting && this.video.muted) {
282
- this.video.muted = false;
283
- lastMuteAction = "unmute";
284
- } else if(lastMuteAction !== "mute" && !bodyElement.isIntersecting && !this.video.muted) {
285
- this.video.muted = true;
286
- lastMuteAction = "mute";
287
- }
288
- }
289
- };
290
-
291
- new window.IntersectionObserver(Callback, { threshold: 0.1 }).observe(this.video);
292
- }
293
-
294
- async Client() {
295
- if(this.clientPromise) {
296
- await this.clientPromise;
297
- }
298
-
299
- // Always initialize new client if ticket is used
300
- if(!this.clientOptions.client) {
301
- this.clientPromise = new Promise(async resolve => {
302
- const {ElvClient} = await import("@eluvio/elv-client-js");
303
- this.clientOptions.client = await ElvClient.FromConfigurationUrl({
304
- configUrl: this.clientOptions.network
305
- });
306
-
307
- this.clientOptions.client.SetStaticToken({
308
- token:
309
- this.clientOptions.staticToken ||
310
- this.clientOptions.client.utils.B64(JSON.stringify({qspace_id: await this.clientOptions.client.ContentSpaceId()}))
311
- });
312
-
313
- resolve(this.clientOptions.client);
314
- });
315
-
316
- await this.clientPromise;
317
- }
318
-
319
- return this.clientOptions.client;
320
- }
321
-
322
- async RedeemCode(code) {
323
- if(!this.clientOptions.tenantId || !this.clientOptions.ntpId) {
324
- throw { displayMessage: "Tenant ID and NTP ID must be provided if ticket code is specified." };
325
- }
326
-
327
- code = code || this.clientOptions.ticketCode;
328
- let subject = this.clientOptions.ticketSubject;
329
- if(code.includes(":")) {
330
- subject = code.split(":")[0];
331
- code = code.split(":")[1];
332
- }
333
-
334
- await (await this.Client()).RedeemCode({
335
- tenantId: this.clientOptions.tenantId,
336
- ntpId: this.clientOptions.ntpId,
337
- code,
338
- email: subject
339
- });
340
-
341
- this.ticketInitialized = true;
342
- this.clientOptions.ticketCode = code;
343
- this.originalParameters.clientOptions.ticketCode = code;
344
- }
345
-
346
- async PosterUrl() {
347
- if(typeof this.playerOptions.posterUrl !== "undefined") {
348
- return this.playerOptions.posterUrl;
349
- }
350
-
351
- const client = await this.Client();
352
-
353
- try {
354
- const targetHash =
355
- this.sourceOptions.playoutParameters.linkPath ?
356
- await client.LinkTarget({...this.sourceOptions.playoutParameters}) :
357
- this.sourceOptions.playoutParameters.versionHash ||
358
- await client.LatestVersionHash({objectId: this.sourceOptions.playoutParameters.objectId});
359
-
360
- if(targetHash) {
361
- const imageMetadata = await client.ContentObjectMetadata({
362
- versionHash: targetHash,
363
- metadataSubtree: "public",
364
- authorizationToken: this.sourceOptions.playoutParameters.authorizationToken,
365
- select: [
366
- "display_image",
367
- "asset_metadata/nft/image"
368
- ]
369
- });
370
-
371
- if(imageMetadata && imageMetadata.asset_metadata && imageMetadata.asset_metadata.nft && imageMetadata.asset_metadata.nft.image) {
372
- return imageMetadata.asset_metadata.nft.image;
373
- } else if(imageMetadata && imageMetadata.display_image) {
374
- return await client.ContentObjectImageUrl({versionHash: targetHash});
375
- }
376
- }
377
- // eslint-disable-next-line no-empty
378
- } catch (error) {}
379
- }
380
-
381
- async PlayoutOptions() {
382
- const client = await this.Client();
383
-
384
- if(this.collectionInfo) {
385
- const activeMedia = this.ActiveCollectionMedia();
386
- this.sourceOptions.playoutParameters.versionHash = activeMedia.mediaHash;
387
- }
388
-
389
- let offeringURI, options = {};
390
- if(this.sourceOptions.playoutParameters.clipStart || this.sourceOptions.playoutParameters.clipEnd) {
391
- options.clip_start = parseFloat(this.sourceOptions.playoutParameters.clipStart || 0);
392
-
393
- if(this.sourceOptions.playoutParameters.clipEnd) {
394
- options.clip_end = parseFloat(this.sourceOptions.playoutParameters.clipEnd);
395
- }
396
- }
397
-
398
- options.ignore_trimming = this.sourceOptions.playoutParameters.ignoreTrimming;
399
- options.resolve = this.sourceOptions.playoutParameters.resolve;
400
-
401
- if(this.sourceOptions.playoutParameters.directLink) {
402
- const availableOfferings = await client.AvailableOfferings({
403
- objectId: this.sourceOptions.playoutParameters.objectId,
404
- versionHash: this.sourceOptions.playoutParameters.versionHash,
405
- writeToken: this.sourceOptions.playoutParameters.writeToken,
406
- linkPath: this.sourceOptions.playoutParameters.linkPath,
407
- directLink: true,
408
- resolveIncludeSource: true,
409
- authorizationToken: this.sourceOptions.playoutParameters.authorizationToken
410
- });
411
-
412
- const offeringId = Object.keys(availableOfferings || {})[0];
413
-
414
- if(!offeringId) { return; }
415
-
416
- offeringURI = availableOfferings[offeringId].uri;
417
-
418
- if(!this.sourceOptions.playoutOptions) {
419
- this.sourceOptions.playoutOptions = await client.PlayoutOptions({
420
- offeringURI,
421
- options
422
- });
423
- }
424
- } else {
425
- if(!this.sourceOptions.playoutOptions) {
426
- this.sourceOptions.playoutOptions = await client.PlayoutOptions({
427
- ...this.sourceOptions.playoutParameters,
428
- options
429
- });
430
- }
431
- }
432
-
433
- let availableDRMs = (await client.AvailableDRMs()).filter(drm => (this.sourceOptions.drms || []).includes(drm));
434
- let availableProtocols = this.sourceOptions.protocols;
435
-
436
- let protocol, drm;
437
- while(!(protocol && drm)) {
438
- protocol = availableProtocols.find(protocol => this.sourceOptions.playoutOptions[protocol]);
439
- drm = this.sourceOptions.drms.find(drm => availableDRMs.includes(drm) && this.sourceOptions.playoutOptions[protocol].playoutMethods[drm]);
440
-
441
- if(!drm) {
442
- availableProtocols = availableProtocols.filter(p => p !== protocol);
443
-
444
- if(availableProtocols.length === 0) {
445
- throw Error("No valid protocol / DRM combination available");
446
- }
447
- }
448
- }
449
-
450
- const { playoutUrl, drms } = this.sourceOptions.playoutOptions[protocol].playoutMethods[drm];
451
-
452
- return {
453
- protocol,
454
- drm,
455
- playoutUrl,
456
- drms,
457
- availableDRMs,
458
- offeringURI,
459
- sessionId: this.sourceOptions.playoutOptions.sessionId,
460
- multiviewOptions: {
461
- enabled: this.sourceOptions.playoutOptions.multiview,
462
- AvailableViews: this.sourceOptions.playoutOptions.AvailableViews,
463
- SwitchView: this.sourceOptions.playoutOptions.SwitchView
464
- }
465
- };
466
- }
467
-
468
- __DestroyPlayer() {
469
- if(!this.player) { return; }
470
-
471
- this.Log("Destroying player");
472
-
473
- if(this.video) {
474
- PlayPause(this.video, false);
475
- }
476
-
477
- if(this.hlsPlayer) {
478
- this.hlsPlayer.destroy();
479
- } else if(this.dashPlayer) {
480
- this.dashPlayer.destroy();
481
- }
482
-
483
- this.hlsPlayer = undefined;
484
- this.dashPlayer = undefined;
485
- this.player = undefined;
486
- }
487
-
488
- DetectRemoval() {
489
- this.mutationTimeout = undefined;
490
- if(!Array.from(document.querySelectorAll(".eluvio-player__video")).find(video => video === this.video)) {
491
- this.Destroy();
492
- }
493
- }
494
-
495
- async HardReload(error, delay=6000) {
496
- if(this.reloading) { return; }
497
-
498
- this.reloading = true;
499
-
500
- /*
501
- if(this.reloads.filter(reload => Date.now() - reload < 60 * 1000).length > 3) {
502
- this.Log("Too many reloads, destroying player", true);
503
- this.Destroy();
504
- return;
505
- }
506
-
507
- this.reloads.push(Date.now());
508
-
509
- */
510
- try {
511
- if(error && this.playerOptions.restartCallback) {
512
- try {
513
- const abort = await this.playerOptions.restartCallback(error);
514
-
515
- if(abort && typeof abort === "boolean") {
516
- this.Destroy();
517
- return;
518
- }
519
- } catch (error) {
520
- this.Log("Restart callback failed:");
521
- this.Log(error);
522
- }
523
- }
524
-
525
- this.SetErrorMessage(error.displayMessage || "Something went wrong, reloading player...");
526
- await new Promise(resolve => setTimeout(resolve, delay));
527
-
528
- if(this.__destroyed) { return; }
529
-
530
- this.Log("Reloading stream");
531
-
532
- // Recall config to get new nodes
533
- const client = await this.Client();
534
- if(client) {
535
- await client.ResetRegion();
536
- }
537
-
538
- this.restarted = true;
539
- this.Initialize(
540
- this.target,
541
- this.originalParameters,
542
- !this.video ? null :
543
- {
544
- muted: this.video.muted,
545
- volume: this.video.volume,
546
- currentTime: this.video.currentTime,
547
- playing: !this.video.paused
548
- }
549
- );
550
- } finally {
551
- this.reloading = false;
552
- }
553
- }
554
-
555
- ActiveCollectionMedia() {
556
- if(!this.collectionInfo || !this.collectionInfo.content) { return; }
557
-
558
- return this.collectionInfo.content[this.collectionInfo.mediaIndex];
559
- }
560
-
561
- CollectionPlay({mediaIndex, mediaId}) {
562
- if(mediaId) {
563
- mediaIndex = this.collectionInfo.content.find(media => media.id === mediaId);
564
- }
565
-
566
- this.collectionInfo.mediaIndex = mediaIndex;
567
- this.Initialize(
568
- this.target,
569
- this.originalParameters,
570
- !this.video ? null :
571
- {
572
- muted: this.video.muted,
573
- volume: this.video.volume,
574
- playing: !this.video.paused
575
- }
576
- );
577
- }
578
-
579
- CollectionPlayNext() {
580
- const nextIndex = Math.min(this.collectionInfo.mediaIndex + 1, this.collectionInfo.mediaLength - 1);
581
-
582
- if(nextIndex === this.collectionInfo.mediaIndex) { return; }
583
-
584
- this.CollectionPlay({mediaIndex: nextIndex});
585
- }
586
-
587
- CollectionPlayPrevious() {
588
- const previousIndex = Math.max(0, this.collectionInfo.mediaIndex - 1);
589
-
590
- if(previousIndex === this.collectionInfo.mediaIndex) { return; }
591
-
592
- this.CollectionPlay({mediaIndex: previousIndex});
593
- }
594
-
595
- async LoadCollection() {
596
- if(this.collectionInfo) { return; }
597
-
598
- let {mediaCatalogObjectId, mediaCatalogVersionHash, collectionId} = (this.sourceOptions?.mediaCollectionOptions || {});
599
-
600
- if(!collectionId) { return; }
601
-
602
- if(!mediaCatalogObjectId && !mediaCatalogVersionHash) {
603
- throw { displayMessage: "Invalid collection options: Media catalog not specified" };
604
- }
605
-
606
- const client = await this.Client();
607
-
608
- try {
609
- const authorizationToken = this.sourceOptions.playoutParameters.authorizationToken;
610
-
611
- mediaCatalogVersionHash = mediaCatalogVersionHash || await client.LatestVersionHash({objectId: mediaCatalogObjectId});
612
- const collections = (await client.ContentObjectMetadata({
613
- versionHash: mediaCatalogVersionHash,
614
- metadataSubtree: "public/asset_metadata/info/collections",
615
- authorizationToken
616
- })) || [];
617
-
618
- const collectionInfo = collections.find(collection => collection.id === collectionId);
619
-
620
- if(!collectionInfo) {
621
- throw { displayMessage: `No collection with ID ${collectionId} found for media catalog ${mediaCatalogObjectId || mediaCatalogVersionHash}` };
622
- }
623
-
624
- collectionInfo.content = collectionInfo.content
625
- .filter(content => content.media)
626
- .map(content => ({
627
- ...content,
628
- mediaHash: content.media?.["/"]?.split("/").find(segment => segment.startsWith("hq__"))
629
- }));
630
-
631
- this.collectionInfo = {
632
- ...collectionInfo,
633
- isPlaylist: collectionInfo.type === "playlist",
634
- mediaIndex: 0,
635
- mediaLength: collectionInfo.content.length
636
- };
637
- } catch (error) {
638
- this.Log("Failed to load collection:");
639
- throw error;
640
- }
641
- }
642
-
643
- async Initialize(target, parameters, restartParameters) {
644
- if(this.__destroyed) { return; }
645
-
646
- this.__DestroyPlayer();
647
-
648
- this.initTime = Date.now();
649
-
650
- this.target = target;
651
-
652
- // Clear target
653
- this.target.innerHTML = "";
654
-
655
- if(parameters) {
656
- this.originalParameters = MergeWith({}, parameters);
657
-
658
- parameters = MergeWith(
659
- Clone(DefaultParameters),
660
- parameters
661
- );
662
-
663
- this.clientOptions = parameters.clientOptions;
664
- this.sourceOptions = parameters.sourceOptions;
665
- this.playerOptions = parameters.playerOptions;
666
-
667
- // If ticket redemption required, ensure new client is used unless specified
668
- if(
669
- this.clientOptions.promptTicket &&
670
- !this.ticketInitialized &&
671
- !this.clientOptions.allowClientTicketRedemption
672
- ) {
673
- this.clientOptions.client = undefined;
674
- }
675
- }
676
-
677
- this.errors = 0;
678
-
679
- this.target.classList.add("eluvio-player");
680
-
681
- // Start client loading
682
- this.Client();
683
-
684
- // Handle ticket authorization
685
- if(this.clientOptions.promptTicket && !this.ticketInitialized) {
686
- if(!this.clientOptions.tenantId || !this.clientOptions.ntpId) {
687
- throw { displayMessage: "Tenant ID and NTP ID must be provided if ticket code is needed." };
688
- }
689
-
690
- InitializeTicketPrompt(
691
- this.target,
692
- this.clientOptions.ticketCode,
693
- async code => {
694
- await this.RedeemCode(code);
695
-
696
- this.Initialize(target, parameters);
697
- }
698
- );
699
-
700
- return;
701
- }
702
-
703
- try {
704
- this.target.classList.add("eluvio-player");
705
-
706
- // Load collection info, if present
707
- await this.LoadCollection();
708
-
709
- if(this.restarted) {
710
- // Prevent big play button from flashing on restart
711
- this.target.classList.add("eluvio-player-restarted");
712
- }
713
-
714
- if(this.playerOptions.controls === EluvioPlayerParameters.controls.AUTO_HIDE) {
715
- this.target.classList.add("eluvio-player-autohide");
716
- }
717
-
718
- if(this.playerOptions.className) {
719
- this.target.classList.add(this.playerOptions.className);
720
- }
721
-
722
- this.video = CreateElement({
723
- parent: this.target,
724
- type: "video",
725
- options: {
726
- muted: [EluvioPlayerParameters.muted.ON, EluvioPlayerParameters.muted.WHEN_NOT_VISIBLE].includes(this.playerOptions.muted),
727
- controls: this.playerOptions.controls === EluvioPlayerParameters.controls.DEFAULT,
728
- loop: this.playerOptions.loop === EluvioPlayerParameters.loop.ON
729
- },
730
- classes: ["eluvio-player__video"]
731
- });
732
-
733
- this.video.setAttribute("playsinline", "playsinline");
734
-
735
- this.controls = new PlayerControls({
736
- player: this,
737
- target: this.target,
738
- video: this.video,
739
- playerOptions: this.playerOptions,
740
- className: this.playerOptions.controlsClassName
741
- });
742
-
743
- if(this.playerOptions.title !== false && this.playerOptions.controls !== EluvioPlayerParameters.controls.DEFAULT) {
744
- if(this.ActiveCollectionMedia()) {
745
- const {title, description} = this.ActiveCollectionMedia();
746
-
747
- this.controls.InitializeContentTitle({title, description});
748
- } else if(this.sourceOptions.contentOptions.title) {
749
- this.controls.InitializeContentTitle({
750
- title: this.sourceOptions.contentOptions.title,
751
- description: this.sourceOptions.contentOptions.description
752
- });
753
- }
754
- }
755
-
756
- if(restartParameters) {
757
- this.video.addEventListener("loadedmetadata", async () => {
758
- this.video.volume = restartParameters.volume;
759
- this.video.muted = restartParameters.muted;
760
-
761
- if(restartParameters.currentTime) {
762
- this.video.currentTime = restartParameters.currentTime;
763
- }
764
-
765
- if(restartParameters.playing) {
766
- PlayPause(this.video, true);
767
- }
768
- });
769
- }
770
-
771
- // Detect live video
772
- this.video.addEventListener("durationchange", () => {
773
- if(this.video.duration && this.videoDuration > 0 && this.video.duration !== this.videoDuration) {
774
- this.isLive = true;
775
- }
776
-
777
- this.videoDuration = this.video.duration;
778
- });
779
-
780
- // Detect removal of video to ensure player is properly destroyed
781
- this.mutationObserver = new MutationObserver(() => {
782
- if(this.mutationTimeout) { return; }
783
-
784
- this.mutationTimeout = setTimeout(this.DetectRemoval, 2000);
785
- });
786
- this.mutationObserver.observe(document.body, {childList: true, subtree: true});
787
-
788
- this.resizeObserver = new ResizeObserver(entries => {
789
- if(this.__destroyed) {
790
- return;
791
- }
792
-
793
- const dimensions = entries[0].contentRect;
794
-
795
- if(this.controls) {
796
- this.controls.HandleResize(dimensions);
797
- }
798
-
799
- const sizes = ["xl", "l", "m", "s"];
800
- sizes.forEach(size => this.target.classList.remove(`eluvio-player-${size}`));
801
-
802
- // Use actual player size instead of media queries
803
- if(dimensions.width > 1400) {
804
- this.target.classList.add("eluvio-player-xl");
805
- } else if(dimensions.width > 750) {
806
- this.target.classList.add("eluvio-player-l");
807
- } else if(dimensions.width > 500) {
808
- this.target.classList.add("eluvio-player-m");
809
- } else {
810
- this.target.classList.add("eluvio-player-s");
811
- }
812
-
813
- if(dimensions.width > dimensions.height) {
814
- this.target.classList.add("eluvio-player-landscape");
815
- this.target.classList.remove("eluvio-player-portrait");
816
- } else {
817
- this.target.classList.add("eluvio-player-portrait");
818
- this.target.classList.remove("eluvio-player-landscape");
819
- }
820
- });
821
-
822
- this.resizeObserver.observe(this.target);
823
-
824
- if(this.collectionInfo && this.collectionInfo.isPlaylist && this.collectionInfo.mediaIndex < this.collectionInfo.mediaLength - 1) {
825
- this.video.addEventListener("ended", () => this.CollectionPlayNext());
826
- }
827
-
828
- let { protocol, drm, playoutUrl, drms, multiviewOptions } = await this.PlayoutOptions();
829
-
830
- this.PosterUrl().then(posterUrl => this.controls.SetPosterUrl(posterUrl));
831
-
832
- multiviewOptions.target = this.target;
833
-
834
- playoutUrl = new URL(playoutUrl);
835
- const authorizationToken =
836
- this.sourceOptions.playoutParameters.authorizationToken ||
837
- playoutUrl.searchParams.get("authorization");
838
-
839
- if(protocol === "hls") {
840
- await this.InitializeHLS({playoutUrl, authorizationToken, drm, drms, multiviewOptions});
841
- } else {
842
- await this.InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions});
843
- }
844
-
845
- if(this.playerOptions.collectVideoAnalytics) {
846
- import("./Analytics")
847
- .then(({InitializeMuxMonitoring}) => InitializeMuxMonitoring({
848
- appName: this.playerOptions.appName || "elv-player-js",
849
- elvPlayer: this,
850
- playoutUrl,
851
- authorizationToken,
852
- disableCookies: this.playerOptions.collectVideoAnalytics === EluvioPlayerParameters.collectVideoAnalytics.DISABLE_COOKIES
853
- }));
854
- }
855
-
856
- if(this.playerOptions.playerCallback) {
857
- this.playerOptions.playerCallback({
858
- player: this,
859
- videoElement: this.video,
860
- hlsPlayer: this.hlsPlayer,
861
- dashPlayer: this.dashPlayer,
862
- posterUrl: this.posterUrl
863
- });
864
- }
865
-
866
- if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON) {
867
- PlayPause(this.video, true);
868
-
869
- setTimeout(async () => {
870
- if(this.playerOptions.muted === EluvioPlayerParameters.muted.OFF_IF_POSSIBLE && this.video.paused && !this.video.muted) {
871
- this.video.muted = true;
872
- PlayPause(this.video, true);
873
- }
874
- }, 250);
875
- }
876
-
877
- this.RegisterVisibilityCallback();
878
-
879
- if(this.controls && this.playerOptions.accountWatermark) {
880
- // Watermark
881
- this.controls.InitializeAccountWatermark(
882
- (await this.Client()).CurrentAccountAddress()
883
- );
884
- }
885
-
886
- if(this.__destroyed) {
887
- // If Destroy was called during the initialization process, ensure that the player is properly destroyed
888
- this.Destroy();
889
- }
890
- } catch (error) {
891
- // If playout failed due to a permission issue, check the content to see if there is a message to display
892
- let permissionErrorMessage;
893
- if(error && [401, 403].includes(error.status) || [401, 403].includes(error.code)) {
894
- try {
895
- const client = await this.Client();
896
-
897
- const targetHash =
898
- this.sourceOptions.playoutParameters.linkPath ?
899
- await client.LinkTarget({...this.sourceOptions.playoutParameters}) :
900
- this.sourceOptions.playoutParameters.versionHash ||
901
- await client.LatestVersionHash({objectId: this.sourceOptions.playoutParameters.objectId});
902
-
903
- permissionErrorMessage = await client.ContentObjectMetadata({
904
- versionHash: targetHash,
905
- metadataSubtree: "public/asset_metadata/permission_message",
906
- authorizationToken: this.sourceOptions.playoutParameters.authorizationToken
907
- });
908
-
909
- if(permissionErrorMessage) {
910
- error.permission_message = permissionErrorMessage;
911
- this.SetErrorMessage(permissionErrorMessage);
912
-
913
- if(typeof error === "object") {
914
- error.permission_message = permissionErrorMessage;
915
- } else {
916
- this.Log(permissionErrorMessage, true);
917
- }
918
- } else {
919
- this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
920
- }
921
- // eslint-disable-next-line no-empty
922
- } catch (error) {
923
- this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
924
- }
925
- } else if(error.status === 500) {
926
- this.HardReload(error, 10000);
927
- } else {
928
- this.SetErrorMessage(error.displayMessage || "Something went wrong");
929
- }
930
-
931
- if(this.playerOptions.errorCallback) {
932
- this.playerOptions.errorCallback(error, this);
933
- }
934
- }
935
- }
936
-
937
- async InitializeHLS({playoutUrl, authorizationToken, drm, multiviewOptions}) {
938
- this.HLS = (await import("hls.js")).default;
939
-
940
- if(["fairplay", "sample-aes"].includes(drm) || !this.HLS.isSupported()) {
941
- // HLS JS NOT SUPPORTED - Handle native player
942
-
943
- if(drm === "fairplay") {
944
- InitializeFairPlayStream({playoutOptions: this.sourceOptions.playoutOptions, video: this.video});
945
- } else {
946
- this.video.src = playoutUrl.toString();
947
- }
948
-
949
- if(multiviewOptions.enabled) {
950
- const Switch = multiviewOptions.SwitchView;
951
-
952
- multiviewOptions.SwitchView = async (view) => {
953
- await Switch(view);
954
- };
955
-
956
- if(this.controls) {
957
- this.controls.InitializeMultiViewControls(multiviewOptions);
958
- }
959
- }
960
-
961
- const UpdateAudioTracks = () => {
962
- if(!this.video.audioTracks || this.video.audioTracks.length <= 1) { return; }
963
-
964
- this.controls.SetAudioTrackControls({
965
- GetAudioTracks: () => {
966
- const tracks = Array.from(this.video.audioTracks).map(track => ({
967
- index: track.id,
968
- label: track.label || track.language,
969
- active: track.enabled,
970
- activeLabel: `Audio: ${track.label || track.language}`
971
- }));
972
-
973
- return {label: "Audio Track", options: tracks};
974
- },
975
- SetAudioTrack: index => {
976
- Array.from(this.video.audioTracks).forEach(track =>
977
- track.enabled = index.toString() === track.id
978
- );
979
- }
980
- });
981
- };
982
-
983
- // Set up audio and subtitle tracks
984
- if(this.controls) {
985
- if(this.video.textTracks) {
986
- this.video.textTracks.addEventListener("addtrack", this.UpdateTextTracks());
987
- this.video.textTracks.addEventListener("removetrack", this.UpdateTextTracks());
988
- }
989
-
990
- if(this.video.audioTracks) {
991
- this.video.audioTracks.addEventListener("addtrack", UpdateAudioTracks);
992
- this.video.audioTracks.addEventListener("removetrack", UpdateAudioTracks);
993
- }
994
- }
995
- } else {
996
- // HLS JS
997
- playoutUrl.searchParams.delete("authorization");
998
-
999
- const profileSettings = (PlayerProfiles[this.playerOptions.playerProfile] || {}).hlsSettings || {};
1000
- const customProfileSettings = this.playerOptions.playerProfile === EluvioPlayerParameters.playerProfile.CUSTOM ? this.customHLSOptions : {};
1001
-
1002
- this.hlsOptions = {
1003
- capLevelToPlayerSize: this.playerOptions.capLevelToPlayerSize,
1004
- ...profileSettings,
1005
- ...customProfileSettings
1006
- };
1007
-
1008
- const hlsPlayer = new this.HLS({
1009
- xhrSetup: xhr => {
1010
- xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
1011
-
1012
- if((this.playerOptions.hlsjsOptions || {}).xhrSetup) {
1013
- this.playerOptions.hlsjsOptions.xhrSetup(xhr);
1014
- }
1015
-
1016
- return xhr;
1017
- },
1018
- ...this.hlsOptions
1019
- });
1020
-
1021
- // Limit playback to maximum bitrate, if specified
1022
- if(this.playerOptions.maxBitrate) {
1023
- hlsPlayer.on(this.HLS.Events.MANIFEST_PARSED, (_, {levels, firstLevel}) => {
1024
- let levelsToRemove = levels
1025
- .map((level, i) => level.bitrate > this.playerOptions.maxBitrate ? i : undefined)
1026
- .filter(i => typeof i !== "undefined")
1027
- // Note: Remove levels from highest to lowest index
1028
- .reverse();
1029
-
1030
- if(levelsToRemove.length === levels.length) {
1031
- this.Log(`Warning: Max bitrate '${this.playerOptions.maxBitrate}bps' is less than all available levels for this content.`);
1032
- // Keep first level
1033
- levelsToRemove = levelsToRemove.filter(i => i > 0);
1034
- }
1035
-
1036
- this.Log("Removing the following levels due to maxBitrate setting:");
1037
- this.Log(levelsToRemove.map(i => [levels[i].width, "x", levels[i].height, ` (${(levels[i].bitrate / 1000 / 1000).toFixed(1)}Mbps)`].join("")).join(", "));
1038
-
1039
- if(levelsToRemove.find(i => firstLevel === i)) {
1040
- // Player will start on level that is being removed - switch to highest level that will not be removed
1041
- hlsPlayer.startLevel = levels.map((_, i) => i).filter(i => !levelsToRemove.includes(i)).reverse()[0];
1042
- }
1043
-
1044
- levelsToRemove.map(i => hlsPlayer.removeLevel(i));
1045
- });
1046
- }
1047
-
1048
- hlsPlayer.loadSource(playoutUrl.toString());
1049
- hlsPlayer.attachMedia(this.video);
1050
-
1051
- if(this.controls && multiviewOptions.enabled) {
1052
- const Switch = multiviewOptions.SwitchView;
1053
-
1054
- multiviewOptions.SwitchView = async (view) => {
1055
- await Switch(view);
1056
- hlsPlayer.nextLevel = hlsPlayer.currentLevel;
1057
- };
1058
-
1059
- this.controls.InitializeMultiViewControls(multiviewOptions);
1060
- }
1061
-
1062
- if(this.controls) {
1063
- const UpdateQualityOptions = () => {
1064
- try {
1065
- this.controls.SetQualityControls({
1066
- GetLevels: () => {
1067
- let levels = hlsPlayer.levels
1068
- .map((level, index) => ({
1069
- index,
1070
- active: hlsPlayer.currentLevel === index,
1071
- resolution: level.attrs.RESOLUTION,
1072
- bitrate: level.bitrate,
1073
- audioTrack: !level.videoCodec,
1074
- label:
1075
- level.audioTrack ?
1076
- `${level.bitrate / 1000}kbps` :
1077
- `${level.attrs.RESOLUTION} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
1078
- activeLabel:
1079
- level.audioTrack ?
1080
- `Quality: ${level.bitrate / 1000}kbps` :
1081
- `Quality: ${level.attrs.RESOLUTION}`
1082
- }))
1083
- .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
1084
-
1085
- levels.unshift({index: -1, label: "Auto"});
1086
-
1087
- return {label: "Quality", options: levels};
1088
- },
1089
- SetLevel: levelIndex => {
1090
- hlsPlayer.nextLevel = levelIndex;
1091
- hlsPlayer.streamController.immediateLevelSwitch();
1092
- }
1093
- });
1094
- } catch (error) {
1095
- // eslint-disable-next-line no-console
1096
- console.error("ELUVIO PLAYER:", error);
1097
- }
1098
- };
1099
-
1100
- hlsPlayer.on(this.HLS.Events.SUBTITLE_TRACKS_UPDATED, () => this.UpdateTextTracks());
1101
- hlsPlayer.on(this.HLS.Events.LEVEL_LOADED, () => UpdateQualityOptions());
1102
- hlsPlayer.on(this.HLS.Events.LEVEL_SWITCHED, () => UpdateQualityOptions());
1103
- hlsPlayer.on(this.HLS.Events.SUBTITLE_TRACK_SWITCH, () => this.UpdateTextTracks());
1104
- hlsPlayer.on(this.HLS.Events.AUDIO_TRACKS_UPDATED, () => {
1105
- this.controls.SetAudioTrackControls({
1106
- GetAudioTracks: () => {
1107
- const tracks = hlsPlayer.audioTracks.map(track => ({
1108
- index: track.id,
1109
- label: track.name,
1110
- active: track.id === hlsPlayer.audioTrack,
1111
- activeLabel: `Audio: ${track.name}`
1112
- }));
1113
-
1114
- return {label: "Audio Track", options: tracks};
1115
- },
1116
- SetAudioTrack: index => {
1117
- hlsPlayer.audioTrack = index;
1118
- hlsPlayer.streamController.immediateLevelSwitch();
1119
- }
1120
- });
1121
- });
1122
-
1123
- this.controls.SetPlayerProfileControls({
1124
- GetProfile: () => ({
1125
- label: "Player Profile",
1126
- options: Object.keys(PlayerProfiles)
1127
- .map(key => ({
1128
- index: key,
1129
- label: PlayerProfiles[key].label,
1130
- active: this.playerOptions.playerProfile === key,
1131
- activeLabel: `Player Profile: ${PlayerProfiles[key].label}`
1132
- }))
1133
- }),
1134
- SetProfile: async key => {
1135
- const SetPlayerProfile = async ({profile, customHLSOptions={}}) => {
1136
- this.videoDuration = undefined;
1137
- this.playerOptions.playerProfile = profile;
1138
- this.customHLSOptions = customHLSOptions;
1139
-
1140
- const playing = !this.video.paused;
1141
- const currentTime = this.video.currentTime;
1142
-
1143
- this.hlsPlayer.destroy();
1144
- await this.InitializeHLS({
1145
- playoutUrl,
1146
- authorizationToken,
1147
- drm,
1148
- multiviewOptions
1149
- });
1150
-
1151
- PlayPause(this.video, playing);
1152
-
1153
- if(!this.isLive) {
1154
- this.video.currentTime = currentTime;
1155
- }
1156
- };
1157
-
1158
- if(key === EluvioPlayerParameters.playerProfile.CUSTOM) {
1159
- this.controls.ShowHLSOptionsForm({
1160
- hlsOptions: this.hlsOptions,
1161
- SetPlayerProfile,
1162
- hlsVersion: this.HLS.version
1163
- });
1164
- } else {
1165
- SetPlayerProfile({profile: key});
1166
- }
1167
- }
1168
- });
1169
- }
1170
-
1171
- hlsPlayer.on(this.HLS.Events.FRAG_LOADED, () => {
1172
- this.errors = 0;
1173
- clearTimeout(this.bufferFullRestartTimeout);
1174
- });
1175
-
1176
- hlsPlayer.on(this.HLS.Events.ERROR, async (event, error) => {
1177
- this.Log(`Encountered ${error.details}`, true);
1178
- this.Log(error, true);
1179
-
1180
- if(error && [this.HLS.ErrorDetails.BUFFER_FULL_ERROR, this.HLS.ErrorDetails.BUFFER_STALLED_ERROR].includes(error.details)) {
1181
- // Ignore HLS buffer errors
1182
- return;
1183
- }
1184
-
1185
- this.errors += 1;
1186
-
1187
- if(error.response && error.response.code === 403) {
1188
- // Not allowed to access
1189
- this.SetErrorMessage("Insufficient permissions");
1190
- } else if(this.errors < 5) {
1191
- if(error.fatal) {
1192
- if(error.type === this.HLS.ErrorTypes.MEDIA_ERROR) {
1193
- this.Log("Attempting to recover using hlsPlayer.recoverMediaError");
1194
- hlsPlayer.recoverMediaError();
1195
- } else {
1196
- this.HardReload(error);
1197
- }
1198
- }
1199
- } else {
1200
- this.HardReload(error);
1201
- }
1202
- });
1203
-
1204
- this.hlsPlayer = hlsPlayer;
1205
- this.player = hlsPlayer;
1206
- }
1207
- }
1208
-
1209
- async InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions}) {
1210
- this.Dash = (await import("dashjs")).default;
1211
- const dashPlayer = this.Dash.MediaPlayer().create();
1212
-
1213
- const customDashOptions = this.playerOptions.dashjsOptions || {};
1214
- dashPlayer.updateSettings({
1215
- ...customDashOptions,
1216
- "streaming": {
1217
- "buffer": {
1218
- "fastSwitchEnabled": true
1219
- },
1220
- "text": {
1221
- "defaultEnabled": false,
1222
- },
1223
- ...(customDashOptions.streaming || {})
1224
- },
1225
- "text": {
1226
- "defaultEnabled": false,
1227
- ...(customDashOptions.text || {})
1228
- }
1229
- });
1230
-
1231
- if(this.playerOptions.capLevelToPlayerSize) {
1232
- dashPlayer.updateSettings({
1233
- "streaming": {
1234
- "abr": {
1235
- "limitBitrateByPortal": true
1236
- }
1237
- }
1238
- });
1239
- }
1240
-
1241
- if(this.playerOptions.maxBitrate) {
1242
- dashPlayer.updateSettings({
1243
- "streaming": {
1244
- "abr": {
1245
- "maxBitrate": { "video": this.playerOptions.maxBitrate / 1000 }
1246
- }
1247
- }
1248
- });
1249
- }
1250
-
1251
- playoutUrl.searchParams.delete("authorization");
1252
- dashPlayer.extend("RequestModifier", function () {
1253
- return {
1254
- modifyRequestHeader: xhr => {
1255
- xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
1256
-
1257
- return xhr;
1258
- },
1259
- modifyRequestURL: url => url
1260
- };
1261
- });
1262
-
1263
- // Widevine
1264
- if(drm === EluvioPlayerParameters.drms.WIDEVINE) {
1265
- const widevineUrl = drms.widevine.licenseServers[0];
1266
-
1267
- dashPlayer.setProtectionData({
1268
- "com.widevine.alpha": {
1269
- "serverURL": widevineUrl
1270
- }
1271
- });
1272
- }
1273
-
1274
- dashPlayer.initialize(
1275
- this.video,
1276
- playoutUrl.toString(),
1277
- this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON
1278
- );
1279
-
1280
- if(this.controls && multiviewOptions.enabled) {
1281
- this.controls.InitializeMultiViewControls(multiviewOptions);
1282
- }
1283
-
1284
- const UpdateQualityOptions = () => {
1285
- try {
1286
- this.controls.SetQualityControls({
1287
- GetLevels: () => {
1288
- let levels = dashPlayer.getBitrateInfoListFor("video")
1289
- .map((level) => ({
1290
- index: level.qualityIndex,
1291
- active: level.qualityIndex === this.player.getQualityFor("video"),
1292
- resolution: `${level.width}x${level.height}`,
1293
- bitrate: level.bitrate,
1294
- label: `${level.width}x${level.height} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
1295
- activeLabel: `Quality: ${level.width}x${level.height}`,
1296
- }))
1297
- .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
1298
-
1299
- levels.unshift({index: -1, label: "Auto"});
1300
-
1301
- return { label: "Quality", options: levels };
1302
- },
1303
- SetLevel: levelIndex => {
1304
- dashPlayer.setQualityFor("video", levelIndex);
1305
- dashPlayer.updateSettings({
1306
- streaming: {
1307
- trackSwitchMode: "alwaysReplace",
1308
- fastSwitchEnabled: true,
1309
- abr: {
1310
- autoSwitchBitrate: {
1311
- video: levelIndex === -1
1312
- }
1313
- }
1314
- }
1315
- });
1316
- }
1317
- });
1318
- } catch (error) {
1319
- // eslint-disable-next-line no-console
1320
- console.error("ELUVIO PLAYER:", error);
1321
- }
1322
- };
1323
-
1324
- const UpdateAudioTracks = () => {
1325
- this.controls.SetAudioTrackControls({
1326
- GetAudioTracks: () => {
1327
- const tracks = this.player.getTracksFor("audio").map(track => ({
1328
- index: track.index,
1329
- label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
1330
- active: track.index === dashPlayer.getCurrentTrackFor("audio").index,
1331
- activeLabel: `Audio: ${track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang}`
1332
- }));
1333
-
1334
- return { label: "Audio Track", options: tracks };
1335
- },
1336
- SetAudioTrack: index => {
1337
- const track = dashPlayer.getTracksFor("audio").find(track => track.index === index);
1338
- dashPlayer.setCurrentTrack(track);
1339
- }
1340
- });
1341
- };
1342
-
1343
- dashPlayer.on(this.Dash.MediaPlayer.events.QUALITY_CHANGE_RENDERED, () => UpdateQualityOptions());
1344
- dashPlayer.on(this.Dash.MediaPlayer.events.TRACK_CHANGE_RENDERED, () => {
1345
- UpdateAudioTracks();
1346
- this.UpdateTextTracks({dashPlayer});
1347
- });
1348
- dashPlayer.on(this.Dash.MediaPlayer.events.MANIFEST_LOADED, () => {
1349
- UpdateQualityOptions();
1350
- UpdateAudioTracks();
1351
- });
1352
-
1353
- this.player = dashPlayer;
1354
- this.dashPlayer = dashPlayer;
1355
- }
1356
-
1357
- UpdateTextTracks({dashPlayer}={}) {
1358
- const tracks = dashPlayer ?
1359
- dashPlayer.getTracksFor("text") : Array.from(this.video.textTracks);
1360
-
1361
- if(!tracks || tracks.length === 0) {
1362
- return;
1363
- }
1364
-
1365
- this.controls.SetTextTrackControls({
1366
- GetTextTracks: () => {
1367
- const activeTrackIndex = dashPlayer ?
1368
- dashPlayer.getCurrentTextTrackIndex() :
1369
- Array.from(this.video.textTracks).findIndex(track => track.mode === "showing");
1370
-
1371
- let tracks;
1372
- if(dashPlayer) {
1373
- tracks = dashPlayer.getTracksFor("text").map((track, index) => ({
1374
- index,
1375
- label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
1376
- active: index === activeTrackIndex,
1377
- activeLabel: `Subtitles: ${track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang}`
1378
- }));
1379
- } else {
1380
- tracks = Array.from(this.video.textTracks).map((track, index) => ({
1381
- index,
1382
- label: track.label || track.language,
1383
- active: track.mode === "showing",
1384
- activeLabel: `Subtitles: ${track.label || track.language}`
1385
- }));
1386
- }
1387
-
1388
- tracks.unshift({
1389
- index: -1,
1390
- label: "Disabled",
1391
- active: activeTrackIndex < 0,
1392
- activeLabel: "Subtitles: Disabled"
1393
- });
1394
-
1395
- return { label: "Subtitles", options: tracks };
1396
- },
1397
- SetTextTrack: index => {
1398
- if(dashPlayer) {
1399
- dashPlayer.setTextTrack(parseInt(index));
1400
- } else {
1401
- const tracks = Array.from(this.video.textTracks);
1402
- tracks.map(track => track.mode = "disabled");
1403
-
1404
- if(index >= 0) {
1405
- tracks[index].mode = "showing";
1406
- }
1407
- }
1408
- }
1409
- });
1410
- }
1411
- }
1412
-
1413
- EluvioPlayer.EluvioPlayerParameters = EluvioPlayerParameters;
1414
-
1415
- export default EluvioPlayer;
1416
-