@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
@@ -1,1478 +0,0 @@
1
- import {
2
- PlayCircleIcon,
3
- PlayIcon,
4
- PauseIcon,
5
- FullscreenIcon,
6
- ExitFullscreenIcon,
7
- SettingsIcon,
8
- CloseIcon,
9
- MutedIcon,
10
- VolumeLowIcon,
11
- VolumeHighIcon,
12
- MultiViewIcon,
13
- LeftArrowIcon,
14
- PreviousTrackIcon,
15
- NextTrackIcon,
16
- CollectionIcon
17
- } from "./static/icons/Icons";
18
- // Icons are generated from .svg files to an importable JS file. To add a new icon, modify and run src/BuildIcons.js
19
-
20
- import Logo from "./static/images/ELUV.IO white 20 px V2.png";
21
-
22
- import {EluvioPlayerParameters} from "./index";
23
-
24
- const lsKeyPrefix = "@eluvio/elv-player";
25
- export const LocalStorage = {
26
- getItem: (key) => {
27
- try {
28
- return localStorage.getItem(`${lsKeyPrefix}-${key}`);
29
- // eslint-disable-next-line no-empty
30
- } catch (error) {}
31
- },
32
- setItem: (key, value) => {
33
- try {
34
- localStorage.setItem(`${lsKeyPrefix}-${key}`, value);
35
- // eslint-disable-next-line no-empty
36
- } catch (error) {}
37
- },
38
- removeItem: (key) => {
39
- try {
40
- localStorage.removeItem(`${lsKeyPrefix}-${key}`);
41
- // eslint-disable-next-line no-empty
42
- } catch (error) {}
43
- }
44
- };
45
-
46
- export const PlayPause = async (videoElement, play) => {
47
- try {
48
- play ? await videoElement.play() : videoElement.pause();
49
- } catch (error) {
50
- // eslint-disable-next-line no-console
51
- console.error(error);
52
- }
53
- };
54
-
55
- export const CreateElement = ({parent, type="div", label, options={}, classes=[], prepend=false}) => {
56
- const element = document.createElement(type);
57
- classes.filter(c => c).forEach(c => element.classList.add(c));
58
- prepend ? parent.prepend(element) : parent.appendChild(element);
59
-
60
- if(label) {
61
- element.setAttribute("aria-label", label);
62
- }
63
-
64
- Object.keys(options).forEach(key => element[key] = options[key]);
65
-
66
- return element;
67
- };
68
-
69
- const CreateImageButton = ({parent, svg, label, options={}, classes=[], noDefaultClass, prepend=false}) => {
70
- if(!noDefaultClass) {
71
- classes.unshift("eluvio-player__controls__button");
72
- }
73
-
74
- const button = CreateElement({parent, type: "button", label, options, classes, prepend});
75
- button.innerHTML = svg;
76
-
77
- button.querySelector("svg").setAttribute("alt", label);
78
-
79
- return button;
80
- };
81
-
82
- const ToggleFullscreen = (target) => {
83
- const isFullscreen = !!(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
84
-
85
- if(isFullscreen) {
86
- if(document.exitFullscreen) {
87
- document.exitFullscreen();
88
- } else if(document.webkitExitFullscreen) {
89
- document.webkitExitFullscreen();
90
- } else if(document.mozCancelFullScreen) {
91
- document.mozCancelFullScreen();
92
- } else if(document.msExitFullscreen) {
93
- document.msExitFullscreen();
94
- }
95
- } else {
96
- if(target.requestFullscreen) {
97
- target.requestFullscreen({navigationUI: "hide"});
98
- } else if(target.mozRequestFullScreen) {
99
- target.mozRequestFullScreen({navigationUI: "hide"});
100
- } else if(target.webkitRequestFullscreen) {
101
- target.webkitRequestFullscreen({navigationUI: "hide"});
102
- } else if(target.msRequestFullscreen) {
103
- target.msRequestFullscreen({navigationUI: "hide"});
104
- } else {
105
- // iPhone - Use native fullscreen on video element only
106
- target.querySelector("video").webkitEnterFullScreen();
107
- }
108
- }
109
- };
110
-
111
- const Time = (time, total) => {
112
- if(isNaN(total) || !isFinite(total) || total === 0) { return "00:00"; }
113
-
114
- const useHours = total > 60 * 60;
115
-
116
- const hours = Math.floor(time / 60 / 60);
117
- const minutes = Math.floor(time / 60 % 60);
118
- const seconds = Math.floor(time % 60);
119
-
120
- let string = `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
121
-
122
- if(useHours) {
123
- string = `${hours.toString()}:${string}`;
124
- }
125
-
126
- return string;
127
- };
128
-
129
- export const InitializeTicketPrompt = async (target, initialCode, callback) => {
130
- // If initial code is provided, attempt to automatically redeem it before rendering the form
131
- let initialError = "";
132
- if(initialCode) {
133
- try {
134
- await callback(initialCode);
135
- return;
136
- } catch (error) {
137
- // eslint-disable-next-line no-console
138
- console.error("ELUVIO PLAYER: Invalid Code");
139
- // eslint-disable-next-line no-console
140
- console.error(error);
141
-
142
- initialError = "Invalid Code";
143
- }
144
- }
145
-
146
- const ticketModal = CreateElement({
147
- parent: target,
148
- type: "div",
149
- classes: ["eluvio-player__ticket-modal"]
150
- });
151
-
152
- ticketModal.addEventListener("dblclick", event => event.stopPropagation());
153
-
154
- const form = CreateElement({
155
- parent: ticketModal,
156
- type: "form",
157
- classes: ["eluvio-player__ticket-modal__form"]
158
- });
159
-
160
- const errorMessage = CreateElement({
161
- parent: form,
162
- type: "div",
163
- classes: ["eluvio-player__ticket-modal__form__error-text", "eluvio-player__ticket-modal__form__text"]
164
- });
165
-
166
- errorMessage.innerHTML = initialError;
167
-
168
- const text = CreateElement({
169
- parent: form,
170
- type: "div",
171
- classes: ["eluvio-player__ticket-modal__form__text"]
172
- });
173
-
174
- text.innerHTML = "Enter your code";
175
-
176
- const input = CreateElement({
177
- parent: form,
178
- type: "input",
179
- classes: ["eluvio-player__ticket-modal__form__input"]
180
- });
181
-
182
- input.value = initialCode || "";
183
-
184
- const submit = CreateElement({
185
- parent: form,
186
- type: "button",
187
- classes: ["eluvio-player__ticket-modal__form__submit"]
188
- });
189
-
190
- input.focus();
191
-
192
- submit.innerHTML = "Submit";
193
-
194
- submit.addEventListener("click", async event => {
195
- try {
196
- submit.setAttribute("disabled", true);
197
- event.preventDefault();
198
- errorMessage.innerHTML = "";
199
-
200
- await callback(input.value);
201
- } catch (error) {
202
- // eslint-disable-next-line no-console
203
- console.error("ELUVIO PLAYER: Invalid Code");
204
- // eslint-disable-next-line no-console
205
- console.error(error);
206
-
207
- errorMessage.innerHTML = "Invalid Code";
208
- submit.removeAttribute("disabled");
209
- }
210
- });
211
- };
212
-
213
- class PlayerControls {
214
- constructor({player, target, video, playerOptions, posterUrl, className}) {
215
- this.player = player;
216
- this.target = target;
217
- this.video = video;
218
- this.playerOptions = playerOptions;
219
- this.timeouts = {};
220
- this.played = false;
221
- this.progressHidden = false;
222
-
223
- if(posterUrl) {
224
- this.SetPosterUrl(posterUrl);
225
- }
226
-
227
- this.HandleClickOutsideMenu = this.HandleClickOutsideMenu.bind(this);
228
-
229
- this.InitializeControls(className);
230
- }
231
-
232
- SetPosterUrl(posterUrl) {
233
- if(!posterUrl) { return; }
234
-
235
- this.posterUrl = posterUrl;
236
-
237
- if(posterUrl) {
238
- // Preload poster before setting it
239
- const imagePreload = new Image();
240
- imagePreload.src = posterUrl;
241
- imagePreload.onload = () => {
242
- this.video.poster = posterUrl;
243
- };
244
- }
245
- }
246
-
247
- FadeOut({key, elements, delay=250, unless, callback}) {
248
- if(unless && unless()) { return; }
249
-
250
- clearTimeout(this.timeouts[key]);
251
-
252
- this.timeouts[key] = setTimeout(() => {
253
- elements.forEach(element => {
254
- if(!element) { return; }
255
-
256
- element.classList.add("-elv-fade-out");
257
- element.classList.remove("-elv-fade-in");
258
- });
259
-
260
- if(callback) {
261
- callback();
262
- }
263
- }, delay);
264
- }
265
-
266
- FadeIn({key, elements, callback}) {
267
- clearTimeout(this.timeouts[key]);
268
-
269
- elements.forEach(element => {
270
- if(!element) { return; }
271
-
272
- element.classList.remove("-elv-fade-out");
273
- element.classList.add("-elv-fade-in");
274
- });
275
-
276
- if(callback) {
277
- callback();
278
- }
279
- }
280
-
281
- Seek({relative, absolute}) {
282
- if(typeof absolute !== "undefined") {
283
- this.video.currentTime = absolute;
284
- } else {
285
- this.video.currentTime = Math.max(0, Math.min(this.video.duration, (this.video.currentTime || 0) + parseFloat(relative)));
286
-
287
- this.target.classList.remove("eluvio-player--seek-left");
288
- this.target.classList.remove("eluvio-player--seek-right");
289
-
290
- setTimeout(() => {
291
- this.target.classList.add(relative < 0 ? "eluvio-player--seek-left" : "eluvio-player--seek-right");
292
- }, 50);
293
- }
294
- }
295
-
296
- AutohideControls({controls, titleOnly=false}) {
297
- this.video.addEventListener("play", () => {
298
- this.played = true;
299
- });
300
-
301
- /*
302
- Controls should stay visible if:
303
- - Video hasn't started yet
304
- - Settings menu is open
305
- - Currently hovering over controls
306
- - Currently keyboard-selecting controls
307
- */
308
- const ControlsShouldShow = () => (
309
- (!this.played && this.video.paused) ||
310
- (this.settingsMenu && this.settingsMenu.dataset.mode !== "hidden") ||
311
- (this.controls && !!Array.from(document.querySelectorAll(":hover")).find(element => this.controls.contains(element))) ||
312
- (this.controls && this.controls.contains(document.activeElement) && document.activeElement.classList.contains("focus-visible"))
313
- );
314
-
315
- const PlayerMove = () => {
316
- this.FadeIn({
317
- key: "controls",
318
- elements: titleOnly ? [this.titleContainer] : [controls, this.settingsMenu, this.toolTip, this.titleContainer],
319
- callback: () => {
320
- this.target.classList.remove("-elv-no-cursor");
321
- }
322
- });
323
- this.FadeOut({
324
- key: "controls",
325
- elements: titleOnly ? [this.titleContainer] : [controls, this.settingsMenu, this.toolTip, this.titleContainer],
326
- delay: 3000,
327
- unless: () => ControlsShouldShow(),
328
- callback: () => {
329
- this.target.classList.add("-elv-no-cursor");
330
- }
331
- });
332
-
333
- this.target.style.cursor = "unset";
334
- };
335
-
336
- // Play / Pause / Volume / Seek events
337
- this.video.addEventListener("play", PlayerMove);
338
- this.video.addEventListener("pause", PlayerMove);
339
- this.video.addEventListener("volumechange", PlayerMove);
340
- this.video.addEventListener("seeking", PlayerMove);
341
-
342
- // Mouse events
343
- this.target.addEventListener("mousemove", PlayerMove);
344
-
345
-
346
- // Touch events
347
- this.target.addEventListener("touchmove", PlayerMove);
348
-
349
- // Keyboard events
350
- this.target.addEventListener("blur", () => setTimeout(() => {
351
- if(!this.target.contains(document.activeElement)) {
352
- PlayerMove();
353
- }
354
- }), 2000);
355
-
356
- window.addEventListener("blur", PlayerMove);
357
-
358
- Array.from(this.target.querySelectorAll("button, input")).forEach(button => {
359
- button.addEventListener("focus", PlayerMove);
360
- });
361
-
362
- PlayerMove();
363
- }
364
-
365
- InitializeAccountWatermark(address) {
366
- if(this.accountWatermark) {
367
- return;
368
- }
369
-
370
- this.accountWatermark = CreateElement({
371
- parent: this.target,
372
- type: "div",
373
- classes: ["eluvio-player__account-watermark"]
374
- });
375
-
376
- this.accountWatermark.innerText = address;
377
- }
378
-
379
- InitializeContentTitle({title, description}) {
380
- if(!title && !description) { return; }
381
-
382
- this.titleContainer = CreateElement({
383
- parent: this.target,
384
- type: "div",
385
- classes: ["eluvio-player__title-container"],
386
- prepend: true
387
- });
388
-
389
- if(title) {
390
- const titleElement = CreateElement({
391
- parent: this.titleContainer,
392
- type: "div",
393
- classes: ["eluvio-player__title"]
394
- });
395
-
396
- titleElement.innerHTML = title;
397
- }
398
-
399
- if(description) {
400
- const descriptionElement = CreateElement({
401
- parent: this.titleContainer,
402
- type: "div",
403
- classes: ["eluvio-player__description"]
404
- });
405
-
406
- descriptionElement.innerHTML = description;
407
- }
408
- }
409
-
410
- InitializeControls(className="") {
411
- const collectionInfo = this.player.collectionInfo;
412
-
413
- this.target.setAttribute("tabindex", "0");
414
-
415
- if(this.playerOptions.watermark) {
416
- // Watermark
417
- const watermark = CreateElement({
418
- parent: this.target,
419
- type: "img",
420
- classes: ["eluvio-player__watermark"]
421
- });
422
-
423
- watermark.setAttribute("aria-label", "Eluvio");
424
- watermark.src = Logo;
425
- }
426
-
427
- // Poster
428
- /*
429
- if(this.posterUrl) {
430
- this.poster = CreateElement({
431
- parent: this.target,
432
- type: "img",
433
- classes: ["eluvio-player__poster-image"],
434
- label: "Poster Image",
435
- options: {
436
- src: this.posterUrl
437
- }
438
- });
439
-
440
- this.video.addEventListener("play", () => {
441
- if(this.poster) {
442
- if(this.poster.parentNode) {
443
- this.poster.parentNode.removeChild(this.poster);
444
- }
445
-
446
- this.poster = undefined;
447
- }
448
- });
449
-
450
- this.poster.addEventListener("click", () => this.video.play());
451
- this.poster.addEventListener("error", () => {
452
- if(this.poster.parentNode) {
453
- this.poster.parentNode.removeChild(this.poster);
454
- }
455
-
456
- this.poster = undefined;
457
- });
458
- }
459
-
460
- */
461
-
462
- if([EluvioPlayerParameters.controls.DEFAULT, EluvioPlayerParameters.controls.OFF].includes(this.playerOptions.controls)) {
463
- // Custom controls disabled
464
- return;
465
- }
466
-
467
- if(this.playerOptions.controls === EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE) {
468
- // Controls hidden but need to show volume controls
469
-
470
- let controlsCreated = false;
471
- const CreateVolumeControl = () => {
472
- if(controlsCreated) { return; }
473
-
474
- controlsCreated = true;
475
- const controls = CreateElement({
476
- parent: this.target,
477
- type: "div",
478
- classes: ["eluvio-player__hidden-audio-controls"]
479
- });
480
-
481
- const volumeButton = CreateImageButton({
482
- parent: controls,
483
- svg: this.video.muted || this.video.volume === 0 ? MutedIcon : (this.video.volume < 0.5 ? VolumeLowIcon : VolumeHighIcon),
484
- classes: ["eluvio-player__controls__button--fixed-size"],
485
- label: this.video.muted ? "Unmute" : "Mute"
486
- });
487
-
488
- volumeButton.addEventListener("click", () => {
489
- this.video.muted = !this.video.muted;
490
- });
491
-
492
- this.video.addEventListener("volumechange", () => {
493
- volumeButton.innerHTML = this.video.muted || this.video.volume === 0 ? MutedIcon : (this.video.volume < 0.5 ? VolumeLowIcon : VolumeHighIcon);
494
- });
495
-
496
- this.AutohideControls({controls, titleOnly: false});
497
- };
498
-
499
- const HasAudio = () => (this.video.mozHasAudio || Boolean(this.video.webkitAudioDecodedByteCount) || Boolean(this.video.audioTracks && this.video.audioTracks.length));
500
-
501
- if(HasAudio()) {
502
- CreateVolumeControl();
503
- } else {
504
- this.video.addEventListener("loadeddata", function() {
505
- if(HasAudio()) {
506
- CreateVolumeControl();
507
- }
508
- });
509
- }
510
-
511
- return;
512
- }
513
-
514
- this.video.addEventListener("click", () => {
515
- if(window.matchMedia("(hover: none)").matches) {
516
- // Touch screen - don't start/stop on video click
517
- return;
518
- }
519
-
520
- clearTimeout(this.timeouts.playPause);
521
- this.timeouts.playPause = setTimeout(() => PlayPause(this.video, this.video.paused), 200);
522
- });
523
-
524
- // Big play icon
525
- this.bigPlayButton = CreateImageButton({
526
- parent: this.target,
527
- svg: PlayCircleIcon,
528
- classes: ["eluvio-player__big-play-button"],
529
- label: "Play"
530
- });
531
-
532
- this.video.addEventListener("play", () => {
533
- this.FadeOut({key: "big-play-button", elements: [this.bigPlayButton]});
534
-
535
- // Prevent big play button from flashing
536
- setTimeout(() => this.target.classList.remove("eluvio-player-restarted"), 1000);
537
- });
538
- this.video.addEventListener("pause", () => this.FadeIn({key: "big-play-button", elements: [this.bigPlayButton]}));
539
-
540
- this.bigPlayButton.style.display = this.video.paused ? null : "none";
541
- this.bigPlayButton.addEventListener("click", () => PlayPause(this.video, true));
542
-
543
- // Controls container
544
- const controls = CreateElement({
545
- parent: this.target,
546
- type: "div",
547
- classes: ["eluvio-player__controls", className]
548
- });
549
-
550
- this.controls = controls;
551
-
552
- // Play / Pause
553
- const playPauseButton = CreateImageButton({
554
- parent: controls,
555
- svg: this.video.paused ? PlayIcon : PauseIcon,
556
- classes: ["eluvio-player__controls__button-play", this.video.paused ? "" : "eluvio-player__controls__button-pause"],
557
- label: this.video.paused ? "Play" : "Pause"
558
- });
559
-
560
- playPauseButton.addEventListener("click", () => PlayPause(this.video, this.video.paused));
561
-
562
- // Volume
563
- const volumeButton = CreateImageButton({
564
- parent: controls,
565
- svg: this.video.muted || this.video.volume === 0 ? MutedIcon : (this.video.volume < 0.5 ? VolumeLowIcon : VolumeHighIcon),
566
- classes: ["eluvio-player__controls__button-volume"],
567
- label: this.video.muted ? "Unmute" : "Mute"
568
- });
569
-
570
- const volumeContainer = CreateElement({
571
- parent: controls,
572
- type: "div",
573
- classes: ["eluvio-player__controls__volume-container", "eluvio-player__controls__slider-container"]
574
- });
575
-
576
- // Volume Slider
577
- const volumeSlider = CreateElement({
578
- parent: volumeContainer,
579
- type: "input",
580
- options: {
581
- type: "range",
582
- min: 0,
583
- step: 0.05,
584
- max: 1,
585
- value: this.video.muted ? 0 : this.video.volume
586
- },
587
- classes: ["eluvio-player__controls__volume-slider", "eluvio-player__controls__slider-container__input"]
588
- });
589
- volumeSlider.setAttribute("aria-label", "Volume");
590
-
591
- // Progress Bar
592
- const volumeBar = CreateElement({
593
- parent: volumeContainer,
594
- type: "progress",
595
- options: {
596
- min: 0,
597
- max: 1,
598
- value: this.video.muted ? 0 : this.video.volume
599
- },
600
- classes: ["eluvio-player__controls__volume", "eluvio-player__controls__slider-container__progress"]
601
- });
602
- volumeBar.setAttribute("aria-label", "Volume");
603
-
604
- volumeButton.addEventListener("click", () => {
605
- this.video.muted = !this.video.muted;
606
- volumeBar.value = this.video.muted ? 0 : this.video.volume;
607
- });
608
-
609
- volumeSlider.addEventListener("change", () => {
610
- this.video.muted = parseFloat(volumeSlider.value) === 0;
611
- this.video.volume = parseFloat(volumeSlider.value);
612
- volumeBar.value = volumeSlider.value;
613
- });
614
-
615
- volumeSlider.addEventListener("input", () => {
616
- this.video.muted = parseFloat(volumeSlider.value) === 0;
617
- this.video.volume = parseFloat(volumeSlider.value);
618
- volumeBar.value = volumeSlider.value;
619
- });
620
-
621
- // Collection previous track
622
- if(collectionInfo && collectionInfo.isPlaylist) {
623
- const collectionPreviousButton = CreateImageButton({
624
- parent: controls,
625
- svg: PreviousTrackIcon,
626
- classes: ["eluvio-player__controls__previous-track"],
627
- label: "Previous Track",
628
- options: {
629
- disabled: collectionInfo.mediaIndex === 0
630
- }
631
- });
632
-
633
- collectionPreviousButton.addEventListener("click", () => this.player.CollectionPlayPrevious());
634
- }
635
-
636
- const progressTime = CreateElement({
637
- parent: controls,
638
- type: "div",
639
- classes: ["eluvio-player__controls__time", "eluvio-player__controls__progress-time"]
640
- });
641
-
642
- progressTime.innerHTML = "00:00";
643
-
644
- const progressContainer = CreateElement({
645
- parent: controls,
646
- type: "div",
647
- classes: ["eluvio-player__controls__slider-container", "eluvio-player__controls__progress-container"]
648
- });
649
-
650
- // Progress Bar
651
- const progressSlider = CreateElement({
652
- parent: progressContainer,
653
- type: "input",
654
- options: {
655
- type: "range",
656
- min: 0,
657
- step: 0.01,
658
- max: 1,
659
- value: 0
660
- },
661
- classes: ["eluvio-player__controls__slider-container__input", "eluvio-player__controls__progress-slider"]
662
- });
663
-
664
- progressSlider.addEventListener("input", () => {
665
- if(!this.video.duration) { return; }
666
-
667
- this.video.currentTime = this.video.duration * parseFloat(progressSlider.value || 0);
668
- });
669
- progressSlider.setAttribute("aria-label", "Video Progress");
670
-
671
- // Progress Bar
672
- const progressBar = CreateElement({
673
- parent: progressContainer,
674
- type: "progress",
675
- options: {
676
- min: 0,
677
- max: 1,
678
- value: 0
679
- },
680
- classes: ["eluvio-player__controls__slider-container__progress", "eluvio-player__controls__progress"]
681
- });
682
- progressBar.setAttribute("aria-label", "Video Progress");
683
-
684
- // Progress Bar
685
- const bufferProgressBar = CreateElement({
686
- parent: progressContainer,
687
- type: "progress",
688
- options: {
689
- min: 0,
690
- step: 0.0001,
691
- max: 1,
692
- value: 0
693
- },
694
- classes: ["eluvio-player__controls__slider-container__progress", "eluvio-player__controls__progress-buffer"]
695
- });
696
-
697
- this.video.addEventListener("progress", () => {
698
- if(isNaN(this.video.duration)) { return; }
699
-
700
- const buffer = this.video.buffered;
701
- let end = 0;
702
- for(let i = 0; i < buffer.length; i++) {
703
- if(buffer.start(i) > this.video.currentTime) { continue; }
704
-
705
- if(buffer.end(i) > end) {
706
- end = buffer.end(i);
707
- }
708
- }
709
-
710
- bufferProgressBar.value = 1 - (this.video.duration - end) / this.video.duration;
711
- });
712
-
713
- const totalTime = CreateElement({
714
- parent: controls,
715
- type: "div",
716
- classes: ["eluvio-player__controls__time", "eluvio-player__controls__total-time"]
717
- });
718
-
719
- totalTime.innerHTML = "00:00";
720
-
721
- // Collection previous track
722
- if(collectionInfo && collectionInfo.isPlaylist) {
723
- const collectionNextButton = CreateImageButton({
724
- parent: controls,
725
- svg: NextTrackIcon,
726
- classes: ["eluvio-player__controls__next-track"],
727
- label: "Next Track",
728
- options: {
729
- disabled: collectionInfo.mediaIndex >= collectionInfo.mediaLength - 1
730
- }
731
- });
732
-
733
- collectionNextButton.addEventListener("click", () => this.player.CollectionPlayNext());
734
- }
735
-
736
- if(collectionInfo) {
737
- this.collectionButton = CreateImageButton({
738
- parent: controls,
739
- svg: CollectionIcon,
740
- classes: ["eluvio-player__controls__collection"],
741
- label: "Collection Info"
742
- });
743
-
744
- this.collectionButton.addEventListener("click", () => {
745
- this.settingsMenu.dataset.mode === "collection" ?
746
- this.HideSettingsMenu() :
747
- this.ShowCollectionMenu();
748
- });
749
- }
750
-
751
- // Right buttons container
752
- this.rightButtonsContainer = CreateElement({
753
- parent: controls,
754
- type: "div",
755
- classes: ["eluvio-player__controls__right-buttons"]
756
- });
757
-
758
- this.settingsButton = CreateImageButton({
759
- parent: this.rightButtonsContainer,
760
- svg: SettingsIcon,
761
- classes: ["eluvio-player__controls__button-settings"],
762
- prepend: true,
763
- label: "Settings"
764
- });
765
-
766
- this.settingsButton.addEventListener("click", () => {
767
- this.settingsMenu.dataset.mode.startsWith("settings") ?
768
- this.HideSettingsMenu() :
769
- this.ShowSettingsMenu();
770
- });
771
-
772
- // Fullscreen
773
- const fullscreenButton = CreateImageButton({
774
- parent: this.rightButtonsContainer,
775
- svg: FullscreenIcon,
776
- classes: ["eluvio-player__controls__button-fullscreen"],
777
- label: "Full Screen"
778
- });
779
-
780
- fullscreenButton.addEventListener("click", () => ToggleFullscreen(this.target));
781
-
782
- // Settings Menu
783
- this.settingsMenu = CreateElement({
784
- parent: this.target,
785
- type: "div",
786
- classes: ["eluvio-player__controls__settings-menu", "eluvio-player__controls__settings-menu-hidden"]
787
- });
788
- this.settingsMenu.setAttribute("data-mode", "hidden");
789
-
790
- this.target.addEventListener("keydown", event => event && (event.key || "").toLowerCase() === "escape" && this.HideSettingsMenu());
791
-
792
- // Settings Menu
793
- this.toolTip = CreateElement({
794
- parent: this.target,
795
- type: "div",
796
- classes: ["eluvio-player__controls__tooltip"]
797
- });
798
-
799
- // Event Listeners
800
-
801
- const ProgressSlider = () => {
802
- progressSlider.value = isNaN(this.video.duration) ? 0 : this.video.currentTime / this.video.duration;
803
- progressBar.value = isNaN(this.video.duration) ? 0 : this.video.currentTime / this.video.duration;
804
- };
805
-
806
- const ProgressTime = () => {
807
- progressTime.innerHTML = Time(this.video.currentTime, this.video.duration);
808
- };
809
-
810
- this.video.addEventListener("seeking", () => {
811
- ProgressSlider();
812
- ProgressTime();
813
- });
814
-
815
- this.video.addEventListener("durationchange", () => {
816
- if(isNaN(this.video.duration) || !isFinite(this.video.duration)) {
817
- if(!this.progressHidden) {
818
- controls.classList.add("eluvio-player__controls-no-progress");
819
- }
820
-
821
- this.progressHidden = true;
822
- } else {
823
- progressSlider.step = Math.min(1 / (this.video.duration / 5), 0.01).toFixed(4);
824
-
825
- if(this.progressHidden) {
826
- this.progressHidden = false;
827
- controls.classList.remove("eluvio-player__controls-no-progress");
828
- }
829
- }
830
-
831
- totalTime.innerHTML = Time(this.video.duration, this.video.duration);
832
-
833
- ProgressTime();
834
- ProgressSlider();
835
- });
836
-
837
- this.target.addEventListener("dblclick", event => {
838
- clearTimeout(this.timeouts.playPause);
839
-
840
- const { width, left } = event.target.getBoundingClientRect();
841
-
842
- const relativeX = (event.clientX - left) / width;
843
-
844
- if(relativeX < 0.15) {
845
- this.Seek({relative: -10});
846
- } else if(relativeX > 0.85) {
847
- this.Seek({relative: 10});
848
- } else {
849
- ToggleFullscreen(this.target);
850
- }
851
- });
852
-
853
- // Prevent double clicking on controls from going fullscreen
854
- controls.addEventListener("dblclick", event => event.stopPropagation());
855
-
856
- this.video.addEventListener("play", () => {
857
- playPauseButton.innerHTML = PauseIcon;
858
- playPauseButton.classList.add("eluvio-player__controls__button-pause");
859
- playPauseButton.setAttribute("aria-label", "Pause");
860
-
861
- clearTimeout(this.timeouts.progressTime);
862
- clearTimeout(this.timeouts.progressSlider);
863
- this.timeouts.progressTime = setInterval(ProgressTime, 100);
864
- this.timeouts.progressSlider = setInterval(ProgressSlider, 16.6);
865
-
866
- if(this.playerOptions.controls === EluvioPlayerParameters.controls.AUTO_HIDE) {
867
- this.target.dispatchEvent(new Event("mousemove"));
868
- }
869
- });
870
-
871
- this.video.addEventListener("pause", () => {
872
- playPauseButton.innerHTML = PlayIcon;
873
- playPauseButton.classList.remove("eluvio-player__controls__button-pause");
874
- playPauseButton.setAttribute("aria-label", "Play");
875
-
876
- clearTimeout(this.timeouts.progressTime);
877
- clearTimeout(this.timeouts.progressSlider);
878
- });
879
-
880
- this.video.addEventListener("volumechange", () => {
881
- volumeButton.innerHTML = this.video.muted || this.video.volume === 0 ? MutedIcon : (this.video.volume < 0.5 ? VolumeLowIcon : VolumeHighIcon);
882
- volumeSlider.value = this.video.muted ? 0 : Math.min(1, Math.max(0, this.video.volume));
883
- volumeBar.value = this.video.muted ? 0 : Math.min(1, Math.max(0, this.video.volume));
884
- });
885
-
886
- this.video.addEventListener("seeked", () => progressSlider.value = this.video.currentTime / this.video.duration);
887
-
888
- this.target.addEventListener("fullscreenchange", () => {
889
- if(!document.fullscreenElement) {
890
- fullscreenButton.innerHTML = FullscreenIcon;
891
- } else if(this.target === document.fullscreenElement) {
892
- fullscreenButton.innerHTML = ExitFullscreenIcon;
893
- }
894
- });
895
-
896
- this.target.addEventListener("keydown", async event => {
897
- switch (event.key) {
898
- case "ArrowLeft":
899
- this.Seek({relative: -10});
900
- event.preventDefault();
901
- break;
902
-
903
- case "ArrowRight":
904
- this.Seek({relative: 10});
905
- event.preventDefault();
906
- break;
907
-
908
- case "ArrowDown":
909
- this.video.volume = Math.max(0, (this.video.volume || 0) - 0.1);
910
- event.preventDefault();
911
- break;
912
-
913
- case "ArrowUp":
914
- this.video.volume = Math.min(1, (this.video.volume || 0) + 0.1);
915
- event.preventDefault();
916
- break;
917
-
918
- case "Enter":
919
- case " ":
920
- PlayPause(this.video, this.video.paused);
921
- event.preventDefault();
922
- break;
923
- }
924
- });
925
-
926
- this.AutohideControls({controls, titleOnly: this.playerOptions.controls !== EluvioPlayerParameters.controls.AUTO_HIDE});
927
- }
928
-
929
- ShowHLSOptionsForm({hlsOptions={}, SetPlayerProfile, hlsVersion}) {
930
- let options = JSON.stringify({ ...hlsOptions }, null, 2);
931
-
932
- this.hlsOptionsFormContainer = CreateElement({
933
- parent: this.target,
934
- classes: ["eluvio-player__hls-options-form-container"]
935
- });
936
-
937
- // Hide on clicking container, prevent play/pause/fullscreen listeners
938
- this.hlsOptionsFormContainer.addEventListener("click", event => {
939
- event.stopPropagation();
940
-
941
- this.HideHLSOptionsMenu();
942
- });
943
-
944
- const hlsOptionsForm = CreateElement({
945
- parent: this.hlsOptionsFormContainer,
946
- type: "form",
947
- classes: ["eluvio-player__hls-options-form"]
948
- });
949
-
950
- // Prevent keyboard shortcuts and container close listeners
951
- hlsOptionsForm.addEventListener("keydown", event => event.stopPropagation());
952
- hlsOptionsForm.addEventListener("click", event => event.stopPropagation());
953
- hlsOptionsForm.addEventListener("dblclick", event => event.stopPropagation());
954
-
955
- const title = CreateElement({
956
- parent: hlsOptionsForm,
957
- type: "h2",
958
- classes: ["eluvio-player__hls-options-form__title"]
959
- });
960
-
961
- title.innerHTML = "Custom HLS.js Options";
962
-
963
- const hlsOptionsInput = CreateElement({
964
- parent: hlsOptionsForm,
965
- type: "textarea",
966
- classes: ["eluvio-player__hls-options-form__input"]
967
- });
968
-
969
- hlsOptionsInput.setAttribute("aria-label", "HLS.js options");
970
- hlsOptionsInput.value = options;
971
- hlsOptionsInput.addEventListener("change", event => {
972
- options = event.currentTarget.value;
973
- });
974
- hlsOptionsInput.addEventListener("blur", event => {
975
- try {
976
- options = JSON.stringify(JSON.parse(options), null, 2);
977
- hlsOptionsInput.value = options;
978
-
979
- event.currentTarget.classList.remove("eluvio-player__hls-options-form__input--invalid");
980
- document.querySelector(".eluvio-player__hls-options-form__submit")
981
- .removeAttribute("disabled");
982
- hlsOptionsInput.title = "";
983
- // eslint-disable-next-line no-empty
984
- } catch (error) {
985
- event.currentTarget.classList.add("eluvio-player__hls-options-form__input--invalid");
986
- document.querySelector(".eluvio-player__hls-options-form__submit")
987
- .setAttribute("disabled", true);
988
- hlsOptionsInput.title = error.toString();
989
- }
990
- });
991
-
992
- const apiInfo = CreateElement({
993
- parent: hlsOptionsForm,
994
- classes: ["eluvio-player__hls-options-form__api-info"]
995
- });
996
-
997
- const apiLink = CreateElement({
998
- parent: apiInfo,
999
- type: "a",
1000
- classes: ["eluvio-player__hls-options-form__api-link"]
1001
- });
1002
- apiLink.setAttribute("target", "_blank");
1003
- apiLink.setAttribute("rel", "noopener");
1004
- apiLink.href = "https://github.com/video-dev/hls.js/blob/master/docs/API.md";
1005
- apiLink.innerHTML = "API Docs";
1006
-
1007
- const version = CreateElement({
1008
- parent: apiInfo,
1009
- classes: ["eluvio-player__hls-options-form__version"]
1010
- });
1011
- version.innerHTML = `HLS.js ${hlsVersion}`;
1012
-
1013
- const actions = CreateElement({
1014
- parent: hlsOptionsForm,
1015
- classes: ["eluvio-player__hls-options-form__actions"]
1016
- });
1017
- const cancel = CreateElement({
1018
- parent: actions,
1019
- type: "button",
1020
- classes: ["eluvio-player__hls-options-form__action"]
1021
- });
1022
- cancel.innerHTML = "Cancel";
1023
- cancel.setAttribute("type", "button");
1024
- cancel.addEventListener("click", event => {
1025
- event.preventDefault();
1026
- this.HideHLSOptionsMenu();
1027
- });
1028
-
1029
- const submit = CreateElement({
1030
- parent: actions,
1031
- type: "button",
1032
- classes: ["eluvio-player__hls-options-form__action", "eluvio-player__hls-options-form__submit"]
1033
- });
1034
- submit.innerHTML = "Submit";
1035
- submit.setAttribute("type", "button");
1036
-
1037
- submit.addEventListener("click", () => {
1038
- try {
1039
- SetPlayerProfile({
1040
- profile: EluvioPlayerParameters.playerProfile.CUSTOM,
1041
- customHLSOptions: JSON.parse(options)
1042
- });
1043
-
1044
- this.HideHLSOptionsMenu();
1045
- // eslint-disable-next-line no-empty
1046
- } catch (error) {}
1047
- });
1048
- }
1049
-
1050
- HideHLSOptionsMenu() {
1051
- this.hlsOptionsFormContainer && this.hlsOptionsFormContainer.remove();
1052
- }
1053
-
1054
- HandleClickOutsideMenu(event) {
1055
- if(!this.settingsMenu.contains(event.target)) {
1056
- this.HideSettingsMenu();
1057
- }
1058
- }
1059
-
1060
- InitializeMenu(mode) {
1061
- this.HideSettingsMenu();
1062
- this.settingsMenu.innerHTML = "";
1063
- this.settingsMenu.classList.remove("eluvio-player__controls__settings-menu-hidden");
1064
- this.settingsMenu.setAttribute("data-mode", mode);
1065
-
1066
- const closeButton = CreateImageButton({
1067
- parent: this.settingsMenu,
1068
- svg: CloseIcon,
1069
- type: "button",
1070
- classes: ["eluvio-player__controls__settings-menu__close"],
1071
- noDefaultClass: true
1072
- });
1073
-
1074
- closeButton.addEventListener("click", () => this.HideSettingsMenu());
1075
-
1076
- setTimeout(() => {
1077
- document.addEventListener("click", this.HandleClickOutsideMenu);
1078
- }, 100);
1079
-
1080
- if(mode === "collection") {
1081
- this.collectionButton.classList.add("eluvio-player__controls__button--active");
1082
- } else if(mode.includes("setting")) {
1083
- this.settingsButton.classList.add("eluvio-player__controls__button--active");
1084
- }
1085
- }
1086
-
1087
- AddSetting({Retrieve, Set}) {
1088
- if(!Retrieve) { return; }
1089
-
1090
- const { label, options } = Retrieve();
1091
-
1092
- const currentOption = options.find(option => option.active);
1093
-
1094
- const optionSelectionButton = CreateElement({
1095
- parent: this.settingsMenu,
1096
- type: Set ? "button" : "div",
1097
- classes: ["eluvio-player__controls__settings-menu__option"]
1098
- });
1099
-
1100
- optionSelectionButton.innerHTML = currentOption && currentOption.activeLabel || label;
1101
-
1102
- if(Set) {
1103
- optionSelectionButton.addEventListener("click", () => {
1104
- this.InitializeMenu("settings-submenu");
1105
-
1106
- const backButton = CreateElement({
1107
- parent: this.settingsMenu,
1108
- type: "button",
1109
- classes: ["eluvio-player__controls__settings-menu__option", "eluvio-player__controls__settings-menu__option-back"],
1110
- });
1111
-
1112
- CreateElement({
1113
- parent: backButton,
1114
- classes: ["eluvio-player__controls__settings-menu__option-back__icon"]
1115
- }).innerHTML = LeftArrowIcon;
1116
-
1117
- CreateElement({
1118
- parent: backButton,
1119
- classes: ["eluvio-player__controls__settings-menu__option-back__text"]
1120
- }).innerHTML = label;
1121
-
1122
- backButton.addEventListener("click", () => this.ShowSettingsMenu());
1123
-
1124
- options
1125
- .forEach(option => {
1126
- const optionButton = CreateElement({
1127
- parent: this.settingsMenu,
1128
- type: "button",
1129
- classes: ["eluvio-player__controls__settings-menu__option", option.active ? "eluvio-player__controls__settings-menu__option-selected" : ""]
1130
- });
1131
-
1132
- optionButton.innerHTML = option.label;
1133
-
1134
- optionButton.addEventListener("click", () => {
1135
- Set(option.index);
1136
- this.HideSettingsMenu();
1137
- });
1138
- });
1139
-
1140
- // Focus on first element in list when menu opened
1141
- const firstItem = this.settingsMenu.querySelector("button");
1142
- if(firstItem) {
1143
- firstItem.focus();
1144
- }
1145
- });
1146
- }
1147
- }
1148
-
1149
- ShowSettingsMenu() {
1150
- this.InitializeMenu("settings");
1151
-
1152
- if(this.GetLevels) {
1153
- this.AddSetting({Retrieve: this.GetLevels, Set: this.SetLevel});
1154
- }
1155
-
1156
- if(this.GetAudioTracks) {
1157
- const tracks = (this.GetAudioTracks() || {}).options || [];
1158
-
1159
- if(tracks.length > 1) {
1160
- this.AddSetting({Retrieve: this.GetAudioTracks, Set: this.SetAudioTrack});
1161
- }
1162
- }
1163
-
1164
- if(this.GetTextTracks) {
1165
- this.AddSetting({Retrieve: this.GetTextTracks, Set: this.SetTextTrack});
1166
- }
1167
-
1168
- if(this.GetPlayerProfile) {
1169
- this.AddSetting({Retrieve: this.GetPlayerProfile, Set: this.SetPlayerProfile});
1170
- }
1171
-
1172
- // Focus on first element in list when menu opened
1173
- const firstItem = this.settingsMenu.querySelector("button");
1174
- if(firstItem) {
1175
- firstItem.focus();
1176
- }
1177
- }
1178
-
1179
- HideSettingsMenu() {
1180
- document.removeEventListener("click", this.HandleClickOutsideMenu);
1181
-
1182
- const mode = this.settingsMenu.dataset.mode;
1183
- if(mode === "settings") {
1184
- this.settingsButton.focus();
1185
- } else if(mode === "multiview") {
1186
- this.multiviewButton.focus();
1187
- } else if(mode === "collection") {
1188
- this.collectionButton.focus();
1189
- }
1190
-
1191
- this.settingsMenu.innerHTML = "";
1192
- this.settingsMenu.classList.add("eluvio-player__controls__settings-menu-hidden");
1193
- this.settingsMenu.setAttribute("data-mode", "hidden");
1194
-
1195
- this.settingsButton.classList.remove("eluvio-player__controls__button--active");
1196
- this.collectionButton && this.collectionButton.classList.remove("eluvio-player__controls__button--active");
1197
- this.multiviewButton && this.multiviewButton.classList.remove("eluvio-player__controls__button--active");
1198
- }
1199
-
1200
- // Settings were updated - if the menu is already open, force it to refresh
1201
- UpdateSettings() {
1202
- if(this.settingsMenu.dataset.mode === "settings") {
1203
- this.ShowSettingsMenu();
1204
- }
1205
- }
1206
-
1207
- ShowCollectionMenu() {
1208
- if(
1209
- !this.player.collectionInfo ||
1210
- !this.player.collectionInfo.content ||
1211
- this.player.collectionInfo.content.length <= 1
1212
- ) {
1213
- return;
1214
- }
1215
-
1216
- this.InitializeMenu("collection");
1217
-
1218
- const collectionTitle = CreateElement({
1219
- parent: this.settingsMenu,
1220
- type: "div",
1221
- classes: ["eluvio-player__controls__settings-menu__title"]
1222
- });
1223
-
1224
- collectionTitle.innerHTML = this.player.collectionInfo.title;
1225
-
1226
- this.player.collectionInfo.content
1227
- .forEach((option, index) => {
1228
- const active = this.player.collectionInfo.mediaIndex === index;
1229
- const optionButton = CreateElement({
1230
- parent: this.settingsMenu,
1231
- type: "button",
1232
- classes: ["eluvio-player__controls__settings-menu__option", active ? "eluvio-player__controls__settings-menu__option-selected" : ""]
1233
- });
1234
-
1235
- optionButton.innerHTML = option.title || option.mediaHash;
1236
-
1237
- optionButton.addEventListener("click", () => {
1238
- this.player.CollectionPlay({mediaIndex: index});
1239
- this.HideSettingsMenu();
1240
- });
1241
- });
1242
-
1243
- // Focus on first element in list when menu opened
1244
- const firstItem = this.settingsMenu.querySelector("button");
1245
- if(firstItem) {
1246
- firstItem.focus();
1247
- }
1248
- }
1249
-
1250
- SetAudioTrackControls({GetAudioTracks, SetAudioTrack}) {
1251
- if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
1252
- // Controls disabled
1253
- return;
1254
- }
1255
-
1256
- this.GetAudioTracks = GetAudioTracks;
1257
- this.SetAudioTrack = SetAudioTrack;
1258
-
1259
- this.UpdateSettings();
1260
- }
1261
-
1262
- SetTextTrackControls({GetTextTracks, SetTextTrack}) {
1263
- if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
1264
- // Controls disabled
1265
- return;
1266
- }
1267
-
1268
- this.GetTextTracks = GetTextTracks;
1269
- this.SetTextTrack = SetTextTrack;
1270
-
1271
- this.UpdateSettings();
1272
- }
1273
-
1274
- SetQualityControls({GetLevels, SetLevel}) {
1275
- if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
1276
- // Controls disabled
1277
- return;
1278
- }
1279
-
1280
- this.GetLevels = GetLevels;
1281
- this.SetLevel = SetLevel;
1282
-
1283
- this.UpdateSettings();
1284
- }
1285
-
1286
- SetPlayerProfileControls({GetProfile, SetProfile}) {
1287
- if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
1288
- // Controls disabled
1289
- return;
1290
- }
1291
-
1292
- this.GetPlayerProfile = GetProfile;
1293
- this.SetPlayerProfile = SetProfile;
1294
-
1295
- this.UpdateSettings();
1296
- }
1297
-
1298
- InitializeMultiViewControls({AvailableViews, SwitchView}) {
1299
- // Fullscreen
1300
- this.multiviewButton = CreateImageButton({
1301
- parent: this.rightButtonsContainer,
1302
- svg: MultiViewIcon,
1303
- classes: ["eluvio-player__controls__button-multiview"],
1304
- prepend: true,
1305
- label: "Select View"
1306
- });
1307
-
1308
- this.multiviewButton.addEventListener("click", () => this.ToggleMultiviewControls({AvailableViews, SwitchView}));
1309
-
1310
- AvailableViews().then(views => {
1311
- const currentView = views.find(view => view.currently_selected);
1312
-
1313
- if(currentView.hot_spots) {
1314
- const hot_spots = currentView.hot_spots.map(spot => ({
1315
- ...spot,
1316
- target: views.find(view => view.view === spot.next_view)
1317
- }));
1318
-
1319
- this.InitializeMultiviewHotspots(hot_spots, SwitchView);
1320
- }
1321
- });
1322
-
1323
- if(!LocalStorage.getItem("multiview-tooltip")) {
1324
- setTimeout(() => {
1325
- this.toolTip.innerHTML = `${MultiViewIcon}<div>This stream has multiple views! Click this button to switch between them.</div>`;
1326
-
1327
- const ClearTooltip = () => {
1328
- this.toolTip.innerHTML = "";
1329
- LocalStorage.setItem("multiview-tooltip", "1");
1330
- };
1331
-
1332
- this.toolTip.addEventListener("click", ClearTooltip);
1333
- this.controls.addEventListener("click", ClearTooltip);
1334
- }, 2000);
1335
- }
1336
- }
1337
-
1338
- async ToggleMultiviewControls({AvailableViews, SwitchView}={}) {
1339
- this.settingsMenu.innerHTML = "";
1340
-
1341
- if(this.settingsMenu.dataset.mode === "multiview") {
1342
- this.HideSettingsMenu();
1343
- return;
1344
- }
1345
-
1346
- this.multiviewButton.classList.add("eluvio-player__controls__button--active");
1347
-
1348
- this.settingsMenu.setAttribute("data-mode", "multiview");
1349
- this.settingsMenu.classList.remove("eluvio-player__controls__settings-menu-hidden");
1350
-
1351
- const views = await AvailableViews();
1352
- views.map(({view, view_display_label, currently_selected, hot_spots}, i) => {
1353
- const selection = CreateElement({
1354
- parent: this.settingsMenu,
1355
- type: "button",
1356
- classes: ["eluvio-player__controls__settings-menu__option", currently_selected ? "eluvio-player__controls__settings-menu__option-selected" : ""]
1357
- });
1358
-
1359
- selection.innerHTML = view_display_label;
1360
-
1361
- if(hot_spots) {
1362
- hot_spots = hot_spots.map(spot => ({
1363
- ...spot,
1364
- target: views.find(view => view.view === spot.next_view)
1365
- }));
1366
- }
1367
-
1368
- selection.addEventListener("click", () => {
1369
- this.HideSettingsMenu();
1370
-
1371
- if(this.hotspotOverlay) {
1372
- this.hotspotOverlay.parentNode.removeChild(this.hotspotOverlay);
1373
- this.hotspotOverlay = undefined;
1374
- }
1375
-
1376
- SwitchView(view);
1377
-
1378
- if(hot_spots) {
1379
- setTimeout(() => this.InitializeMultiviewHotspots(hot_spots, SwitchView), 3000);
1380
- }
1381
-
1382
- // Make button spin to show something is happening
1383
- clearTimeout(this.timeouts["spin"]);
1384
- this.multiviewButton.classList.add("eluvio-player__controls__button-multiview-spinning");
1385
- this.timeouts["spin"] = setTimeout(() => {
1386
- this.multiviewButton.classList.remove("eluvio-player__controls__button-multiview-spinning");
1387
- }, 3000);
1388
- });
1389
-
1390
- // Focus on first element in list when menu opened
1391
- if(i === 0) { selection.focus(); }
1392
- });
1393
-
1394
- // Focus on first element in list when menu opened
1395
- const firstItem = this.settingsMenu.querySelector("button");
1396
- if(firstItem) {
1397
- firstItem.focus();
1398
- }
1399
- }
1400
-
1401
- InitializeMultiviewHotspots(hotSpots, SwitchView) {
1402
- this.hotspotOverlay = CreateElement({
1403
- parent: this.target,
1404
- type: "div",
1405
- classes: ["eluvio-player__hotspot-overlay"]
1406
- });
1407
-
1408
- this.HandleResize(this.target.getBoundingClientRect());
1409
-
1410
- hotSpots.forEach(({top, right, bottom, left, target, next_view}) => {
1411
- const spot = CreateElement({
1412
- parent: this.hotspotOverlay,
1413
- type: "div",
1414
- classes: ["eluvio-player__hotspot-overlay__target"]
1415
- });
1416
-
1417
- const title = CreateElement({
1418
- parent: spot,
1419
- type: "h2",
1420
- classes: ["eluvio-player__hotspot-overlay__target__title"]
1421
- });
1422
-
1423
- title.innerHTML = target.view_display_label;
1424
-
1425
- spot.style.top = `${top * 100}%`;
1426
- spot.style.right = `${(1-right) * 100}%`;
1427
- spot.style.bottom = `${(1-bottom) * 100}%`;
1428
- spot.style.left = `${left * 100}%`;
1429
-
1430
- spot.addEventListener("click", async () => {
1431
- // On mobile devices, first touch should show the options
1432
- if("ontouchstart" in window && !this.hotspotOverlay.classList.contains("eluvio-player__hotspot-overlay-visible")) {
1433
- this.hotspotOverlay.classList.add("eluvio-player__hotspot-overlay-visible");
1434
- setTimeout(() => this.hotspotOverlay.classList.remove("eluvio-player__hotspot-overlay-visible"), 3000);
1435
- return;
1436
- }
1437
-
1438
- this.hotspotOverlay.classList.remove("eluvio-player__hotspot-overlay-visible");
1439
- spot.classList.add("eluvio-player__hotspot-overlay__target-switching");
1440
- setTimeout(() => {
1441
- this.hotspotOverlay.parentNode.removeChild(this.hotspotOverlay);
1442
- this.hotspotOverlay = undefined;
1443
- }, 1000);
1444
-
1445
- await SwitchView(next_view);
1446
- });
1447
- });
1448
- }
1449
-
1450
- HandleResize({width, height}) {
1451
- const ratio = width / height;
1452
- const targetRatio = 16 / 9;
1453
-
1454
- if(this.hotspotOverlay) {
1455
- let top = 0, right = 0, bottom = 0, left = 0;
1456
- if(Math.abs(ratio - targetRatio) > 0.05) {
1457
- if(ratio < 16 / 9) {
1458
- // Taller
1459
- const heightDiff = Math.floor((height - (width * 9 / 16)) / 2);
1460
- top = heightDiff;
1461
- bottom = heightDiff;
1462
- } else if(ratio > 16 / 9) {
1463
- // Wider
1464
- const widthDiff = Math.floor((width - (height * 16 / 9)) / 2);
1465
- left = widthDiff;
1466
- right = widthDiff;
1467
- }
1468
- }
1469
-
1470
- this.hotspotOverlay.style.top = `${top}px`;
1471
- this.hotspotOverlay.style.right = `${right}px`;
1472
- this.hotspotOverlay.style.bottom = `${bottom}px`;
1473
- this.hotspotOverlay.style.left = `${left}px`;
1474
- }
1475
- }
1476
- }
1477
-
1478
- export default PlayerControls;