@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
package/src/index.js DELETED
@@ -1,1417 +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:
629
- ((content.media || {})["/"] || "").split("/").find(segment => segment.startsWith("hq__"))
630
- }));
631
-
632
- this.collectionInfo = {
633
- ...collectionInfo,
634
- isPlaylist: collectionInfo.type === "playlist",
635
- mediaIndex: 0,
636
- mediaLength: collectionInfo.content.length
637
- };
638
- } catch (error) {
639
- this.Log("Failed to load collection:");
640
- throw error;
641
- }
642
- }
643
-
644
- async Initialize(target, parameters, restartParameters) {
645
- if(this.__destroyed) { return; }
646
-
647
- this.__DestroyPlayer();
648
-
649
- this.initTime = Date.now();
650
-
651
- this.target = target;
652
-
653
- // Clear target
654
- this.target.innerHTML = "";
655
-
656
- if(parameters) {
657
- this.originalParameters = MergeWith({}, parameters);
658
-
659
- parameters = MergeWith(
660
- Clone(DefaultParameters),
661
- parameters
662
- );
663
-
664
- this.clientOptions = parameters.clientOptions;
665
- this.sourceOptions = parameters.sourceOptions;
666
- this.playerOptions = parameters.playerOptions;
667
-
668
- // If ticket redemption required, ensure new client is used unless specified
669
- if(
670
- this.clientOptions.promptTicket &&
671
- !this.ticketInitialized &&
672
- !this.clientOptions.allowClientTicketRedemption
673
- ) {
674
- this.clientOptions.client = undefined;
675
- }
676
- }
677
-
678
- this.errors = 0;
679
-
680
- this.target.classList.add("eluvio-player");
681
-
682
- // Start client loading
683
- this.Client();
684
-
685
- // Handle ticket authorization
686
- if(this.clientOptions.promptTicket && !this.ticketInitialized) {
687
- if(!this.clientOptions.tenantId || !this.clientOptions.ntpId) {
688
- throw { displayMessage: "Tenant ID and NTP ID must be provided if ticket code is needed." };
689
- }
690
-
691
- InitializeTicketPrompt(
692
- this.target,
693
- this.clientOptions.ticketCode,
694
- async code => {
695
- await this.RedeemCode(code);
696
-
697
- this.Initialize(target, parameters);
698
- }
699
- );
700
-
701
- return;
702
- }
703
-
704
- try {
705
- this.target.classList.add("eluvio-player");
706
-
707
- // Load collection info, if present
708
- await this.LoadCollection();
709
-
710
- if(this.restarted) {
711
- // Prevent big play button from flashing on restart
712
- this.target.classList.add("eluvio-player-restarted");
713
- }
714
-
715
- if(this.playerOptions.controls === EluvioPlayerParameters.controls.AUTO_HIDE) {
716
- this.target.classList.add("eluvio-player-autohide");
717
- }
718
-
719
- if(this.playerOptions.className) {
720
- this.target.classList.add(this.playerOptions.className);
721
- }
722
-
723
- this.video = CreateElement({
724
- parent: this.target,
725
- type: "video",
726
- options: {
727
- muted: [EluvioPlayerParameters.muted.ON, EluvioPlayerParameters.muted.WHEN_NOT_VISIBLE].includes(this.playerOptions.muted),
728
- controls: this.playerOptions.controls === EluvioPlayerParameters.controls.DEFAULT,
729
- loop: this.playerOptions.loop === EluvioPlayerParameters.loop.ON
730
- },
731
- classes: ["eluvio-player__video"]
732
- });
733
-
734
- this.video.setAttribute("playsinline", "playsinline");
735
-
736
- this.controls = new PlayerControls({
737
- player: this,
738
- target: this.target,
739
- video: this.video,
740
- playerOptions: this.playerOptions,
741
- className: this.playerOptions.controlsClassName
742
- });
743
-
744
- if(this.playerOptions.title !== false && this.playerOptions.controls !== EluvioPlayerParameters.controls.DEFAULT) {
745
- if(this.ActiveCollectionMedia()) {
746
- const {title, description} = this.ActiveCollectionMedia();
747
-
748
- this.controls.InitializeContentTitle({title, description});
749
- } else if(this.sourceOptions.contentOptions.title) {
750
- this.controls.InitializeContentTitle({
751
- title: this.sourceOptions.contentOptions.title,
752
- description: this.sourceOptions.contentOptions.description
753
- });
754
- }
755
- }
756
-
757
- if(restartParameters) {
758
- this.video.addEventListener("loadedmetadata", async () => {
759
- this.video.volume = restartParameters.volume;
760
- this.video.muted = restartParameters.muted;
761
-
762
- if(restartParameters.currentTime) {
763
- this.video.currentTime = restartParameters.currentTime;
764
- }
765
-
766
- if(restartParameters.playing) {
767
- PlayPause(this.video, true);
768
- }
769
- });
770
- }
771
-
772
- // Detect live video
773
- this.video.addEventListener("durationchange", () => {
774
- if(this.video.duration && this.videoDuration > 0 && this.video.duration !== this.videoDuration) {
775
- this.isLive = true;
776
- }
777
-
778
- this.videoDuration = this.video.duration;
779
- });
780
-
781
- // Detect removal of video to ensure player is properly destroyed
782
- this.mutationObserver = new MutationObserver(() => {
783
- if(this.mutationTimeout) { return; }
784
-
785
- this.mutationTimeout = setTimeout(this.DetectRemoval, 2000);
786
- });
787
- this.mutationObserver.observe(document.body, {childList: true, subtree: true});
788
-
789
- this.resizeObserver = new ResizeObserver(entries => {
790
- if(this.__destroyed) {
791
- return;
792
- }
793
-
794
- const dimensions = entries[0].contentRect;
795
-
796
- if(this.controls) {
797
- this.controls.HandleResize(dimensions);
798
- }
799
-
800
- const sizes = ["xl", "l", "m", "s"];
801
- sizes.forEach(size => this.target.classList.remove(`eluvio-player-${size}`));
802
-
803
- // Use actual player size instead of media queries
804
- if(dimensions.width > 1400) {
805
- this.target.classList.add("eluvio-player-xl");
806
- } else if(dimensions.width > 750) {
807
- this.target.classList.add("eluvio-player-l");
808
- } else if(dimensions.width > 500) {
809
- this.target.classList.add("eluvio-player-m");
810
- } else {
811
- this.target.classList.add("eluvio-player-s");
812
- }
813
-
814
- if(dimensions.width > dimensions.height) {
815
- this.target.classList.add("eluvio-player-landscape");
816
- this.target.classList.remove("eluvio-player-portrait");
817
- } else {
818
- this.target.classList.add("eluvio-player-portrait");
819
- this.target.classList.remove("eluvio-player-landscape");
820
- }
821
- });
822
-
823
- this.resizeObserver.observe(this.target);
824
-
825
- if(this.collectionInfo && this.collectionInfo.isPlaylist && this.collectionInfo.mediaIndex < this.collectionInfo.mediaLength - 1) {
826
- this.video.addEventListener("ended", () => this.CollectionPlayNext());
827
- }
828
-
829
- let { protocol, drm, playoutUrl, drms, multiviewOptions } = await this.PlayoutOptions();
830
-
831
- this.PosterUrl().then(posterUrl => this.controls.SetPosterUrl(posterUrl));
832
-
833
- multiviewOptions.target = this.target;
834
-
835
- playoutUrl = new URL(playoutUrl);
836
- const authorizationToken =
837
- this.sourceOptions.playoutParameters.authorizationToken ||
838
- playoutUrl.searchParams.get("authorization");
839
-
840
- if(protocol === "hls") {
841
- await this.InitializeHLS({playoutUrl, authorizationToken, drm, drms, multiviewOptions});
842
- } else {
843
- await this.InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions});
844
- }
845
-
846
- if(this.playerOptions.collectVideoAnalytics) {
847
- import("./Analytics")
848
- .then(({InitializeMuxMonitoring}) => InitializeMuxMonitoring({
849
- appName: this.playerOptions.appName || "elv-player-js",
850
- elvPlayer: this,
851
- playoutUrl,
852
- authorizationToken,
853
- disableCookies: this.playerOptions.collectVideoAnalytics === EluvioPlayerParameters.collectVideoAnalytics.DISABLE_COOKIES
854
- }));
855
- }
856
-
857
- if(this.playerOptions.playerCallback) {
858
- this.playerOptions.playerCallback({
859
- player: this,
860
- videoElement: this.video,
861
- hlsPlayer: this.hlsPlayer,
862
- dashPlayer: this.dashPlayer,
863
- posterUrl: this.posterUrl
864
- });
865
- }
866
-
867
- if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON) {
868
- PlayPause(this.video, true);
869
-
870
- setTimeout(async () => {
871
- if(this.playerOptions.muted === EluvioPlayerParameters.muted.OFF_IF_POSSIBLE && this.video.paused && !this.video.muted) {
872
- this.video.muted = true;
873
- PlayPause(this.video, true);
874
- }
875
- }, 250);
876
- }
877
-
878
- this.RegisterVisibilityCallback();
879
-
880
- if(this.controls && this.playerOptions.accountWatermark) {
881
- // Watermark
882
- this.controls.InitializeAccountWatermark(
883
- (await this.Client()).CurrentAccountAddress()
884
- );
885
- }
886
-
887
- if(this.__destroyed) {
888
- // If Destroy was called during the initialization process, ensure that the player is properly destroyed
889
- this.Destroy();
890
- }
891
- } catch (error) {
892
- // If playout failed due to a permission issue, check the content to see if there is a message to display
893
- let permissionErrorMessage;
894
- if(error && [401, 403].includes(error.status) || [401, 403].includes(error.code)) {
895
- try {
896
- const client = await this.Client();
897
-
898
- const targetHash =
899
- this.sourceOptions.playoutParameters.linkPath ?
900
- await client.LinkTarget({...this.sourceOptions.playoutParameters}) :
901
- this.sourceOptions.playoutParameters.versionHash ||
902
- await client.LatestVersionHash({objectId: this.sourceOptions.playoutParameters.objectId});
903
-
904
- permissionErrorMessage = await client.ContentObjectMetadata({
905
- versionHash: targetHash,
906
- metadataSubtree: "public/asset_metadata/permission_message",
907
- authorizationToken: this.sourceOptions.playoutParameters.authorizationToken
908
- });
909
-
910
- if(permissionErrorMessage) {
911
- error.permission_message = permissionErrorMessage;
912
- this.SetErrorMessage(permissionErrorMessage);
913
-
914
- if(typeof error === "object") {
915
- error.permission_message = permissionErrorMessage;
916
- } else {
917
- this.Log(permissionErrorMessage, true);
918
- }
919
- } else {
920
- this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
921
- }
922
- // eslint-disable-next-line no-empty
923
- } catch (error) {
924
- this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
925
- }
926
- } else if(error.status === 500) {
927
- this.HardReload(error, 10000);
928
- } else {
929
- this.SetErrorMessage(error.displayMessage || "Something went wrong");
930
- }
931
-
932
- if(this.playerOptions.errorCallback) {
933
- this.playerOptions.errorCallback(error, this);
934
- }
935
- }
936
- }
937
-
938
- async InitializeHLS({playoutUrl, authorizationToken, drm, multiviewOptions}) {
939
- this.HLS = (await import("hls.js")).default;
940
-
941
- if(["fairplay", "sample-aes"].includes(drm) || !this.HLS.isSupported()) {
942
- // HLS JS NOT SUPPORTED - Handle native player
943
-
944
- if(drm === "fairplay") {
945
- InitializeFairPlayStream({playoutOptions: this.sourceOptions.playoutOptions, video: this.video});
946
- } else {
947
- this.video.src = playoutUrl.toString();
948
- }
949
-
950
- if(multiviewOptions.enabled) {
951
- const Switch = multiviewOptions.SwitchView;
952
-
953
- multiviewOptions.SwitchView = async (view) => {
954
- await Switch(view);
955
- };
956
-
957
- if(this.controls) {
958
- this.controls.InitializeMultiViewControls(multiviewOptions);
959
- }
960
- }
961
-
962
- const UpdateAudioTracks = () => {
963
- if(!this.video.audioTracks || this.video.audioTracks.length <= 1) { return; }
964
-
965
- this.controls.SetAudioTrackControls({
966
- GetAudioTracks: () => {
967
- const tracks = Array.from(this.video.audioTracks).map(track => ({
968
- index: track.id,
969
- label: track.label || track.language,
970
- active: track.enabled,
971
- activeLabel: `Audio: ${track.label || track.language}`
972
- }));
973
-
974
- return {label: "Audio Track", options: tracks};
975
- },
976
- SetAudioTrack: index => {
977
- Array.from(this.video.audioTracks).forEach(track =>
978
- track.enabled = index.toString() === track.id
979
- );
980
- }
981
- });
982
- };
983
-
984
- // Set up audio and subtitle tracks
985
- if(this.controls) {
986
- if(this.video.textTracks) {
987
- this.video.textTracks.addEventListener("addtrack", this.UpdateTextTracks());
988
- this.video.textTracks.addEventListener("removetrack", this.UpdateTextTracks());
989
- }
990
-
991
- if(this.video.audioTracks) {
992
- this.video.audioTracks.addEventListener("addtrack", UpdateAudioTracks);
993
- this.video.audioTracks.addEventListener("removetrack", UpdateAudioTracks);
994
- }
995
- }
996
- } else {
997
- // HLS JS
998
- playoutUrl.searchParams.delete("authorization");
999
-
1000
- const profileSettings = (PlayerProfiles[this.playerOptions.playerProfile] || {}).hlsSettings || {};
1001
- const customProfileSettings = this.playerOptions.playerProfile === EluvioPlayerParameters.playerProfile.CUSTOM ? this.customHLSOptions : {};
1002
-
1003
- this.hlsOptions = {
1004
- capLevelToPlayerSize: this.playerOptions.capLevelToPlayerSize,
1005
- ...profileSettings,
1006
- ...customProfileSettings
1007
- };
1008
-
1009
- const hlsPlayer = new this.HLS({
1010
- xhrSetup: xhr => {
1011
- xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
1012
-
1013
- if((this.playerOptions.hlsjsOptions || {}).xhrSetup) {
1014
- this.playerOptions.hlsjsOptions.xhrSetup(xhr);
1015
- }
1016
-
1017
- return xhr;
1018
- },
1019
- ...this.hlsOptions
1020
- });
1021
-
1022
- // Limit playback to maximum bitrate, if specified
1023
- if(this.playerOptions.maxBitrate) {
1024
- hlsPlayer.on(this.HLS.Events.MANIFEST_PARSED, (_, {levels, firstLevel}) => {
1025
- let levelsToRemove = levels
1026
- .map((level, i) => level.bitrate > this.playerOptions.maxBitrate ? i : undefined)
1027
- .filter(i => typeof i !== "undefined")
1028
- // Note: Remove levels from highest to lowest index
1029
- .reverse();
1030
-
1031
- if(levelsToRemove.length === levels.length) {
1032
- this.Log(`Warning: Max bitrate '${this.playerOptions.maxBitrate}bps' is less than all available levels for this content.`);
1033
- // Keep first level
1034
- levelsToRemove = levelsToRemove.filter(i => i > 0);
1035
- }
1036
-
1037
- this.Log("Removing the following levels due to maxBitrate setting:");
1038
- this.Log(levelsToRemove.map(i => [levels[i].width, "x", levels[i].height, ` (${(levels[i].bitrate / 1000 / 1000).toFixed(1)}Mbps)`].join("")).join(", "));
1039
-
1040
- if(levelsToRemove.find(i => firstLevel === i)) {
1041
- // Player will start on level that is being removed - switch to highest level that will not be removed
1042
- hlsPlayer.startLevel = levels.map((_, i) => i).filter(i => !levelsToRemove.includes(i)).reverse()[0];
1043
- }
1044
-
1045
- levelsToRemove.map(i => hlsPlayer.removeLevel(i));
1046
- });
1047
- }
1048
-
1049
- hlsPlayer.loadSource(playoutUrl.toString());
1050
- hlsPlayer.attachMedia(this.video);
1051
-
1052
- if(this.controls && multiviewOptions.enabled) {
1053
- const Switch = multiviewOptions.SwitchView;
1054
-
1055
- multiviewOptions.SwitchView = async (view) => {
1056
- await Switch(view);
1057
- hlsPlayer.nextLevel = hlsPlayer.currentLevel;
1058
- };
1059
-
1060
- this.controls.InitializeMultiViewControls(multiviewOptions);
1061
- }
1062
-
1063
- if(this.controls) {
1064
- const UpdateQualityOptions = () => {
1065
- try {
1066
- this.controls.SetQualityControls({
1067
- GetLevels: () => {
1068
- let levels = hlsPlayer.levels
1069
- .map((level, index) => ({
1070
- index,
1071
- active: hlsPlayer.currentLevel === index,
1072
- resolution: level.attrs.RESOLUTION,
1073
- bitrate: level.bitrate,
1074
- audioTrack: !level.videoCodec,
1075
- label:
1076
- level.audioTrack ?
1077
- `${level.bitrate / 1000}kbps` :
1078
- `${level.attrs.RESOLUTION} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
1079
- activeLabel:
1080
- level.audioTrack ?
1081
- `Quality: ${level.bitrate / 1000}kbps` :
1082
- `Quality: ${level.attrs.RESOLUTION}`
1083
- }))
1084
- .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
1085
-
1086
- levels.unshift({index: -1, label: "Auto"});
1087
-
1088
- return {label: "Quality", options: levels};
1089
- },
1090
- SetLevel: levelIndex => {
1091
- hlsPlayer.nextLevel = levelIndex;
1092
- hlsPlayer.streamController.immediateLevelSwitch();
1093
- }
1094
- });
1095
- } catch (error) {
1096
- // eslint-disable-next-line no-console
1097
- console.error("ELUVIO PLAYER:", error);
1098
- }
1099
- };
1100
-
1101
- hlsPlayer.on(this.HLS.Events.SUBTITLE_TRACKS_UPDATED, () => this.UpdateTextTracks());
1102
- hlsPlayer.on(this.HLS.Events.LEVEL_LOADED, () => UpdateQualityOptions());
1103
- hlsPlayer.on(this.HLS.Events.LEVEL_SWITCHED, () => UpdateQualityOptions());
1104
- hlsPlayer.on(this.HLS.Events.SUBTITLE_TRACK_SWITCH, () => this.UpdateTextTracks());
1105
- hlsPlayer.on(this.HLS.Events.AUDIO_TRACKS_UPDATED, () => {
1106
- this.controls.SetAudioTrackControls({
1107
- GetAudioTracks: () => {
1108
- const tracks = hlsPlayer.audioTracks.map(track => ({
1109
- index: track.id,
1110
- label: track.name,
1111
- active: track.id === hlsPlayer.audioTrack,
1112
- activeLabel: `Audio: ${track.name}`
1113
- }));
1114
-
1115
- return {label: "Audio Track", options: tracks};
1116
- },
1117
- SetAudioTrack: index => {
1118
- hlsPlayer.audioTrack = index;
1119
- hlsPlayer.streamController.immediateLevelSwitch();
1120
- }
1121
- });
1122
- });
1123
-
1124
- this.controls.SetPlayerProfileControls({
1125
- GetProfile: () => ({
1126
- label: "Player Profile",
1127
- options: Object.keys(PlayerProfiles)
1128
- .map(key => ({
1129
- index: key,
1130
- label: PlayerProfiles[key].label,
1131
- active: this.playerOptions.playerProfile === key,
1132
- activeLabel: `Player Profile: ${PlayerProfiles[key].label}`
1133
- }))
1134
- }),
1135
- SetProfile: async key => {
1136
- const SetPlayerProfile = async ({profile, customHLSOptions={}}) => {
1137
- this.videoDuration = undefined;
1138
- this.playerOptions.playerProfile = profile;
1139
- this.customHLSOptions = customHLSOptions;
1140
-
1141
- const playing = !this.video.paused;
1142
- const currentTime = this.video.currentTime;
1143
-
1144
- this.hlsPlayer.destroy();
1145
- await this.InitializeHLS({
1146
- playoutUrl,
1147
- authorizationToken,
1148
- drm,
1149
- multiviewOptions
1150
- });
1151
-
1152
- PlayPause(this.video, playing);
1153
-
1154
- if(!this.isLive) {
1155
- this.video.currentTime = currentTime;
1156
- }
1157
- };
1158
-
1159
- if(key === EluvioPlayerParameters.playerProfile.CUSTOM) {
1160
- this.controls.ShowHLSOptionsForm({
1161
- hlsOptions: this.hlsOptions,
1162
- SetPlayerProfile,
1163
- hlsVersion: this.HLS.version
1164
- });
1165
- } else {
1166
- SetPlayerProfile({profile: key});
1167
- }
1168
- }
1169
- });
1170
- }
1171
-
1172
- hlsPlayer.on(this.HLS.Events.FRAG_LOADED, () => {
1173
- this.errors = 0;
1174
- clearTimeout(this.bufferFullRestartTimeout);
1175
- });
1176
-
1177
- hlsPlayer.on(this.HLS.Events.ERROR, async (event, error) => {
1178
- this.Log(`Encountered ${error.details}`, true);
1179
- this.Log(error, true);
1180
-
1181
- if(error && [this.HLS.ErrorDetails.BUFFER_FULL_ERROR, this.HLS.ErrorDetails.BUFFER_STALLED_ERROR].includes(error.details)) {
1182
- // Ignore HLS buffer errors
1183
- return;
1184
- }
1185
-
1186
- this.errors += 1;
1187
-
1188
- if(error.response && error.response.code === 403) {
1189
- // Not allowed to access
1190
- this.SetErrorMessage("Insufficient permissions");
1191
- } else if(this.errors < 5) {
1192
- if(error.fatal) {
1193
- if(error.type === this.HLS.ErrorTypes.MEDIA_ERROR) {
1194
- this.Log("Attempting to recover using hlsPlayer.recoverMediaError");
1195
- hlsPlayer.recoverMediaError();
1196
- } else {
1197
- this.HardReload(error);
1198
- }
1199
- }
1200
- } else {
1201
- this.HardReload(error);
1202
- }
1203
- });
1204
-
1205
- this.hlsPlayer = hlsPlayer;
1206
- this.player = hlsPlayer;
1207
- }
1208
- }
1209
-
1210
- async InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions}) {
1211
- this.Dash = (await import("dashjs")).default;
1212
- const dashPlayer = this.Dash.MediaPlayer().create();
1213
-
1214
- const customDashOptions = this.playerOptions.dashjsOptions || {};
1215
- dashPlayer.updateSettings({
1216
- ...customDashOptions,
1217
- "streaming": {
1218
- "buffer": {
1219
- "fastSwitchEnabled": true
1220
- },
1221
- "text": {
1222
- "defaultEnabled": false,
1223
- },
1224
- ...(customDashOptions.streaming || {})
1225
- },
1226
- "text": {
1227
- "defaultEnabled": false,
1228
- ...(customDashOptions.text || {})
1229
- }
1230
- });
1231
-
1232
- if(this.playerOptions.capLevelToPlayerSize) {
1233
- dashPlayer.updateSettings({
1234
- "streaming": {
1235
- "abr": {
1236
- "limitBitrateByPortal": true
1237
- }
1238
- }
1239
- });
1240
- }
1241
-
1242
- if(this.playerOptions.maxBitrate) {
1243
- dashPlayer.updateSettings({
1244
- "streaming": {
1245
- "abr": {
1246
- "maxBitrate": { "video": this.playerOptions.maxBitrate / 1000 }
1247
- }
1248
- }
1249
- });
1250
- }
1251
-
1252
- playoutUrl.searchParams.delete("authorization");
1253
- dashPlayer.extend("RequestModifier", function () {
1254
- return {
1255
- modifyRequestHeader: xhr => {
1256
- xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
1257
-
1258
- return xhr;
1259
- },
1260
- modifyRequestURL: url => url
1261
- };
1262
- });
1263
-
1264
- // Widevine
1265
- if(drm === EluvioPlayerParameters.drms.WIDEVINE) {
1266
- const widevineUrl = drms.widevine.licenseServers[0];
1267
-
1268
- dashPlayer.setProtectionData({
1269
- "com.widevine.alpha": {
1270
- "serverURL": widevineUrl
1271
- }
1272
- });
1273
- }
1274
-
1275
- dashPlayer.initialize(
1276
- this.video,
1277
- playoutUrl.toString(),
1278
- this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON
1279
- );
1280
-
1281
- if(this.controls && multiviewOptions.enabled) {
1282
- this.controls.InitializeMultiViewControls(multiviewOptions);
1283
- }
1284
-
1285
- const UpdateQualityOptions = () => {
1286
- try {
1287
- this.controls.SetQualityControls({
1288
- GetLevels: () => {
1289
- let levels = dashPlayer.getBitrateInfoListFor("video")
1290
- .map((level) => ({
1291
- index: level.qualityIndex,
1292
- active: level.qualityIndex === this.player.getQualityFor("video"),
1293
- resolution: `${level.width}x${level.height}`,
1294
- bitrate: level.bitrate,
1295
- label: `${level.width}x${level.height} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
1296
- activeLabel: `Quality: ${level.width}x${level.height}`,
1297
- }))
1298
- .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
1299
-
1300
- levels.unshift({index: -1, label: "Auto"});
1301
-
1302
- return { label: "Quality", options: levels };
1303
- },
1304
- SetLevel: levelIndex => {
1305
- dashPlayer.setQualityFor("video", levelIndex);
1306
- dashPlayer.updateSettings({
1307
- streaming: {
1308
- trackSwitchMode: "alwaysReplace",
1309
- fastSwitchEnabled: true,
1310
- abr: {
1311
- autoSwitchBitrate: {
1312
- video: levelIndex === -1
1313
- }
1314
- }
1315
- }
1316
- });
1317
- }
1318
- });
1319
- } catch (error) {
1320
- // eslint-disable-next-line no-console
1321
- console.error("ELUVIO PLAYER:", error);
1322
- }
1323
- };
1324
-
1325
- const UpdateAudioTracks = () => {
1326
- this.controls.SetAudioTrackControls({
1327
- GetAudioTracks: () => {
1328
- const tracks = this.player.getTracksFor("audio").map(track => ({
1329
- index: track.index,
1330
- label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
1331
- active: track.index === dashPlayer.getCurrentTrackFor("audio").index,
1332
- activeLabel: `Audio: ${track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang}`
1333
- }));
1334
-
1335
- return { label: "Audio Track", options: tracks };
1336
- },
1337
- SetAudioTrack: index => {
1338
- const track = dashPlayer.getTracksFor("audio").find(track => track.index === index);
1339
- dashPlayer.setCurrentTrack(track);
1340
- }
1341
- });
1342
- };
1343
-
1344
- dashPlayer.on(this.Dash.MediaPlayer.events.QUALITY_CHANGE_RENDERED, () => UpdateQualityOptions());
1345
- dashPlayer.on(this.Dash.MediaPlayer.events.TRACK_CHANGE_RENDERED, () => {
1346
- UpdateAudioTracks();
1347
- this.UpdateTextTracks({dashPlayer});
1348
- });
1349
- dashPlayer.on(this.Dash.MediaPlayer.events.MANIFEST_LOADED, () => {
1350
- UpdateQualityOptions();
1351
- UpdateAudioTracks();
1352
- });
1353
-
1354
- this.player = dashPlayer;
1355
- this.dashPlayer = dashPlayer;
1356
- }
1357
-
1358
- UpdateTextTracks({dashPlayer}={}) {
1359
- const tracks = dashPlayer ?
1360
- dashPlayer.getTracksFor("text") : Array.from(this.video.textTracks);
1361
-
1362
- if(!tracks || tracks.length === 0) {
1363
- return;
1364
- }
1365
-
1366
- this.controls.SetTextTrackControls({
1367
- GetTextTracks: () => {
1368
- const activeTrackIndex = dashPlayer ?
1369
- dashPlayer.getCurrentTextTrackIndex() :
1370
- Array.from(this.video.textTracks).findIndex(track => track.mode === "showing");
1371
-
1372
- let tracks;
1373
- if(dashPlayer) {
1374
- tracks = dashPlayer.getTracksFor("text").map((track, index) => ({
1375
- index,
1376
- label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
1377
- active: index === activeTrackIndex,
1378
- activeLabel: `Subtitles: ${track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang}`
1379
- }));
1380
- } else {
1381
- tracks = Array.from(this.video.textTracks).map((track, index) => ({
1382
- index,
1383
- label: track.label || track.language,
1384
- active: track.mode === "showing",
1385
- activeLabel: `Subtitles: ${track.label || track.language}`
1386
- }));
1387
- }
1388
-
1389
- tracks.unshift({
1390
- index: -1,
1391
- label: "Disabled",
1392
- active: activeTrackIndex < 0,
1393
- activeLabel: "Subtitles: Disabled"
1394
- });
1395
-
1396
- return { label: "Subtitles", options: tracks };
1397
- },
1398
- SetTextTrack: index => {
1399
- if(dashPlayer) {
1400
- dashPlayer.setTextTrack(parseInt(index));
1401
- } else {
1402
- const tracks = Array.from(this.video.textTracks);
1403
- tracks.map(track => track.mode = "disabled");
1404
-
1405
- if(index >= 0) {
1406
- tracks[index].mode = "showing";
1407
- }
1408
- }
1409
- }
1410
- });
1411
- }
1412
- }
1413
-
1414
- EluvioPlayer.EluvioPlayerParameters = EluvioPlayerParameters;
1415
-
1416
- export default EluvioPlayer;
1417
-