@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
@@ -0,0 +1,449 @@
1
+ // Observe player size for reactive UI
2
+ import ResizeObserver from "resize-observer-polyfill";
3
+ import EluvioPlayerParameters from "../player/PlayerParameters.js";
4
+ import {ACTIONS, SeekSliderKeyDown} from "./Common.js";
5
+
6
+ export const RegisterModal = ({element, Hide}) => {
7
+ if(!element) { return; }
8
+
9
+ const onEscape = event => {
10
+ if(event && (event.key || "").toLowerCase() === "escape") {
11
+ Hide();
12
+ }
13
+ };
14
+
15
+ const onFocusOut = () => {
16
+ // Without timeout, document.activeElement is always body
17
+ setTimeout(() => {
18
+ if(!element.contains(document.activeElement)) {
19
+ setTimeout(() => {
20
+ if(!element.contains(document.activeElement)) {
21
+ Hide();
22
+ }
23
+ }, 250);
24
+ }
25
+ });
26
+ };
27
+
28
+ // Wrap handlers in timeout so that the click that spawned the modal does not cause it to close
29
+ let registerTimeout = setTimeout(() => {
30
+ document.body.addEventListener("keydown", onEscape);
31
+ element.addEventListener("focusout", onFocusOut);
32
+ }, 0);
33
+
34
+ return () => {
35
+ clearTimeout(registerTimeout);
36
+ document.body.removeEventListener("keydown", onEscape);
37
+ element.removeEventListener("focusout", onFocusOut);
38
+ };
39
+ };
40
+
41
+ export const ObserveVideo = ({target, video, setVideoState}) => {
42
+ const UpdateVideoState = function () {
43
+ const buffer = video.buffered;
44
+ let end = 0;
45
+ for(let i = 0; i < buffer.length; i++) {
46
+ if(buffer.start(i) > video.currentTime) { continue; }
47
+
48
+ if(buffer.end(i) > end) {
49
+ end = buffer.end(i);
50
+ }
51
+ }
52
+
53
+ setVideoState({
54
+ playing: !video.paused,
55
+ duration: video.duration,
56
+ volume: video.volume,
57
+ muted: video.muted,
58
+ rate: video.playbackRate,
59
+ fullscreen: !!(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement)
60
+ });
61
+ };
62
+
63
+ const events = [
64
+ "play",
65
+ "pause",
66
+ "volumechange",
67
+ "seeked",
68
+ "durationchange",
69
+ "ratechange"
70
+ ];
71
+
72
+ events.map(event => video.addEventListener(event, UpdateVideoState));
73
+ target.addEventListener("fullscreenchange", UpdateVideoState);
74
+
75
+ return () => {
76
+ events.map(event => video.removeEventListener(event, UpdateVideoState));
77
+ target.removeEventListener("fullscreenchange", UpdateVideoState);
78
+ };
79
+ };
80
+
81
+ export const ObserveVideoBuffer = ({video, setBufferFraction}) => {
82
+ const UpdateBufferState = () => {
83
+ if(!isFinite(video.duration)) {
84
+ return 1;
85
+ }
86
+
87
+ const buffer = video.buffered;
88
+ let end = 0;
89
+ for(let i = 0; i < buffer.length; i++) {
90
+ if(buffer.start(i) > video.currentTime) { continue; }
91
+
92
+ if(buffer.end(i) > end) {
93
+ end = buffer.end(i);
94
+ }
95
+ }
96
+
97
+ setBufferFraction(1 - (video.duration - end) / video.duration);
98
+ };
99
+
100
+ video.addEventListener("progress", UpdateBufferState);
101
+
102
+ return () => video.removeEventListener("progress", UpdateBufferState);
103
+ };
104
+
105
+ export const ObserveVideoTime = ({video, rate=10, setCurrentTime}) => {
106
+ // Current time doesn't update quickly enough from events for smooth movement - use interval instead
107
+ const currentTimeInterval = setInterval(() => {
108
+ setCurrentTime(video.currentTime);
109
+ }, 1000 / rate);
110
+
111
+ return () => {
112
+ clearInterval(currentTimeInterval);
113
+ };
114
+ };
115
+
116
+ export const ObserveResize = ({target, setSize, setOrientation, setDimensions}) => {
117
+ let dimensionsUpdateTimeout;
118
+ const observer = new ResizeObserver(entries => {
119
+ clearTimeout(dimensionsUpdateTimeout);
120
+
121
+ const dimensions = entries[0].contentRect;
122
+
123
+ let size = "sm";
124
+ let orientation = "landscape";
125
+ // Use actual player size instead of media queries
126
+ if(dimensions.width > 1400) {
127
+ size = "xl";
128
+ } else if(dimensions.width > 1000) {
129
+ size = "lg";
130
+ } else if(dimensions.width > 650) {
131
+ size = "md";
132
+ }
133
+
134
+ if(dimensions.width < dimensions.height) {
135
+ orientation = "portrait";
136
+ }
137
+
138
+ setSize(size);
139
+ setOrientation(orientation);
140
+
141
+ dimensionsUpdateTimeout = setTimeout(() => {
142
+ setDimensions({width: dimensions.width, height: dimensions.height});
143
+ }, 500);
144
+ });
145
+
146
+ observer.observe(target);
147
+
148
+ return () => observer.disconnect();
149
+ };
150
+
151
+ // For 'when visible' options (autoplay, muted), handle when the video moves in and out of user visibility
152
+ export const ObserveVisibility = ({player}) => {
153
+ const video = player.video;
154
+ const autoplay = player.playerOptions.autoplay === EluvioPlayerParameters.autoplay.WHEN_VISIBLE;
155
+ const automute = player.playerOptions.muted === EluvioPlayerParameters.muted.WHEN_NOT_VISIBLE;
156
+
157
+ if(!autoplay && !automute) { return; }
158
+
159
+ let lastPlayPauseAction, lastMuteAction;
160
+ const Callback = async ([bodyElement]) => {
161
+ // Play / pause when entering / leaving viewport
162
+ if(autoplay) {
163
+ if(lastPlayPauseAction !== "play" && bodyElement.isIntersecting && video.paused) {
164
+ player.controls.Play();
165
+ lastPlayPauseAction = "play";
166
+ } else if(lastPlayPauseAction !== "pause" && !bodyElement.isIntersecting && !video.paused) {
167
+ player.controls.Pause();
168
+ lastPlayPauseAction = "pause";
169
+ }
170
+ }
171
+
172
+ // Mute / unmute when entering / leaving viewport
173
+ if(automute) {
174
+ if(lastMuteAction !== "unmute" && bodyElement.isIntersecting && video.muted) {
175
+ video.muted = false;
176
+ lastMuteAction = "unmute";
177
+ } else if(lastMuteAction !== "mute" && !bodyElement.isIntersecting && !video.muted) {
178
+ video.muted = true;
179
+ lastMuteAction = "mute";
180
+ }
181
+ }
182
+ };
183
+
184
+ const intersectionObserver = new window.IntersectionObserver(Callback, { threshold: 0.1 }).observe(video);
185
+
186
+ return () => intersectionObserver && intersectionObserver.disconnect();
187
+ };
188
+
189
+ export const ObserveInteraction = ({player, inactivityPeriod=3000, onSleep, onWake}) => {
190
+ let autohideTimeout;
191
+ const Wake = event => {
192
+ clearTimeout(autohideTimeout);
193
+ onWake && onWake();
194
+
195
+ autohideTimeout = setTimeout(() => {
196
+ onSleep && onSleep();
197
+ }, event.type === "mouseout" ? 500 : inactivityPeriod);
198
+ };
199
+
200
+ const videoEvents = [
201
+ "play",
202
+ "pause",
203
+ "volumechange",
204
+ "seeking"
205
+ ];
206
+
207
+ videoEvents.forEach(event => player.video.addEventListener(event, Wake));
208
+
209
+ const targetEvents = [
210
+ "click",
211
+ "dblclick",
212
+ "keydown",
213
+ "mousemove",
214
+ "touchmove",
215
+ "blur",
216
+ "mouseout",
217
+ "fullscreenchange"
218
+ ];
219
+
220
+ targetEvents.forEach(event => player.target.addEventListener(event, Wake));
221
+
222
+ return () => {
223
+ videoEvents.map(event => player.video.removeEventListener(event, Wake));
224
+ targetEvents.map(event => player.target.removeEventListener(event, Wake));
225
+ };
226
+ };
227
+
228
+ export const ObserveKeydown = ({player, setRecentUserAction}) => {
229
+ if(player.playerOptions.keyboardControls === EluvioPlayerParameters.keyboardControls.OFF) {
230
+ return;
231
+ }
232
+
233
+ const disableArrowControls = player.playerOptions.keyboardControls === EluvioPlayerParameters.keyboardControls.ARROW_KEYS_DISABLED;
234
+ const SeekHandler = SeekSliderKeyDown(player, setRecentUserAction)();
235
+
236
+ const onKeydown = event => {
237
+ if(
238
+ // Keyboard controls should only fire if the player is in focus
239
+ !(player.target === event.target || player.target.contains(event.target)) ||
240
+ // Ignore keyboard controls if actively focused on a button or input
241
+ ["button", "input"].includes(document.activeElement && document.activeElement.tagName.toLowerCase()) ||
242
+ // Or if the player profile form is visible
243
+ player.__showPlayerProfileForm
244
+ ) {
245
+ return;
246
+ }
247
+
248
+ let result;
249
+ switch (event.key) {
250
+ case " ":
251
+ case "k":
252
+ result = player.controls.TogglePlay();
253
+ setRecentUserAction({action: result ? ACTIONS.PLAY : ACTIONS.PAUSE});
254
+ break;
255
+ case "f":
256
+ player.controls.ToggleFullscreen();
257
+ break;
258
+ case "m":
259
+ result = player.controls.ToggleMuted();
260
+ setRecentUserAction({action: result ? ACTIONS.MUTE : ACTIONS.UNMUTE});
261
+ break;
262
+ case "ArrowDown":
263
+ if(!disableArrowControls) {
264
+ result = player.controls.SetVolume({relativeFraction: -0.1});
265
+ setRecentUserAction({
266
+ action: result === 0 ? ACTIONS.MUTE : ACTIONS.VOLUME_DOWN,
267
+ text: (result * 100).toFixed(0) + "%"
268
+ });
269
+ }
270
+ break;
271
+ case "ArrowUp":
272
+ if(!disableArrowControls) {
273
+ result = player.controls.SetVolume({relativeFraction: 0.1});
274
+ setRecentUserAction({
275
+ action: ACTIONS.VOLUME_UP,
276
+ text: (result * 100).toFixed(0) + "%"
277
+ });
278
+ }
279
+ break;
280
+ case ",":
281
+ if(player.video.paused) {
282
+ player.controls.Seek({
283
+ relativeSeconds: -1 / 60
284
+ });
285
+ setRecentUserAction({action: ACTIONS.SEEK_BACK});
286
+ }
287
+ break;
288
+ case ".":
289
+ if(player.video.paused) {
290
+ player.controls.Seek({
291
+ relativeSeconds: 1 / 60
292
+ });
293
+ setRecentUserAction({action: ACTIONS.SEEK_FORWARD});
294
+ }
295
+ break;
296
+ case "ArrowLeft":
297
+ case "ArrowRight":
298
+ if(!disableArrowControls) {
299
+ SeekHandler(event);
300
+ }
301
+ break;
302
+ case "j":
303
+ player.controls.Seek({relativeSeconds: -10});
304
+ setRecentUserAction({action: ACTIONS.SEEK_BACK, text: "10 seconds"});
305
+ break;
306
+ case "l":
307
+ player.controls.Seek({relativeSeconds: 10});
308
+ setRecentUserAction({action: ACTIONS.SEEK_FORWARD, text: "10 seconds"});
309
+ break;
310
+ case "<":
311
+ case ">":
312
+ // eslint-disable-next-line no-case-declarations
313
+ const playbackRates = player.controls.GetPlaybackRates();
314
+
315
+ if(!playbackRates.active) {
316
+ result = player.controls.SetPlaybackRate({rate: 1});
317
+ } else {
318
+ result = player.controls.SetPlaybackRate({
319
+ index: playbackRates.active.index + (event.key === "<" ? -1 : 1)
320
+ });
321
+ }
322
+ setRecentUserAction({
323
+ action: result.increase ? ACTIONS.PLAYBACK_RATE_UP : ACTIONS.PLAYBACK_RATE_DOWN,
324
+ text: `${result.rate.toFixed(2)}x`
325
+ });
326
+ break;
327
+ case "c":
328
+ result = player.controls.ToggleTextTrack();
329
+ setRecentUserAction({action: result ? ACTIONS.SUBTITLES_ON : ACTIONS.SUBTITLES_OFF});
330
+ break;
331
+ case "P":
332
+ player.controls.CollectionPlayPrevious();
333
+ setRecentUserAction({action: ACTIONS.PLAY_PREVIOUS});
334
+ break;
335
+ case "N":
336
+ player.controls.CollectionPlayNext();
337
+ setRecentUserAction({action: ACTIONS.PLAY_NEXT});
338
+ break;
339
+ case "Home":
340
+ player.controls.Seek({fraction: 0});
341
+ setRecentUserAction({action: ACTIONS.SEEK_BACK});
342
+ break;
343
+ case "End":
344
+ player.controls.Seek({fraction: 1});
345
+ setRecentUserAction({action: ACTIONS.SEEK_FORWARD});
346
+ break;
347
+ case "0":
348
+ case "1":
349
+ case "2":
350
+ case "3":
351
+ case "4":
352
+ case "5":
353
+ case "6":
354
+ case "7":
355
+ case "8":
356
+ case "9":
357
+ result = player.controls.Seek({fraction: parseFloat(event.key) * 0.1});
358
+ setRecentUserAction({action: result ? ACTIONS.SEEK_FORWARD : ACTIONS.SEEK_BACK});
359
+ break;
360
+ default:
361
+ return;
362
+ }
363
+
364
+ event.preventDefault();
365
+ };
366
+
367
+ document.addEventListener("keydown", onKeydown);
368
+
369
+ return () => document.removeEventListener("keydown", onKeydown);
370
+ };
371
+
372
+ export const ObserveMediaSession = ({player}) => {
373
+ if("mediaSession" in navigator) {
374
+ const mediaSessionEvents = [
375
+ "play",
376
+ "pause",
377
+ "stop",
378
+ "seekbackward",
379
+ "seekforward",
380
+ "seekto",
381
+ "previoustrack",
382
+ "nexttrack"
383
+ ];
384
+
385
+ // Media button handling
386
+ mediaSessionEvents.forEach(event => {
387
+ navigator.mediaSession.setActionHandler(event, args => {
388
+ switch (event) {
389
+ case "play":
390
+ player.controls.Play();
391
+ break;
392
+ case "pause":
393
+ player.controls.Pause();
394
+ break;
395
+ case "stop":
396
+ player.controls.Stop();
397
+ break;
398
+ case "seekbackward":
399
+ player.controls.Seek({relativeSeconds: (args && args.seekOffset) || -10});
400
+ break;
401
+ case "seekforward":
402
+ player.controls.Seek({relativeSeconds: (args && args.seekOffset) || 10});
403
+ break;
404
+ case "seekto":
405
+ args && typeof args.seekTime !== "undefined" && player.controls.Seek({time: args.seekTime});
406
+ break;
407
+ case "previoustrack":
408
+ player.controls.CollectionPlayPrevious();
409
+ break;
410
+ case "nexttrack":
411
+ player.controls.CollectionPlayNext();
412
+ break;
413
+ }
414
+ });
415
+ });
416
+
417
+
418
+ // Video playback information
419
+ let positionInterval = setInterval(() => {
420
+ navigator.mediaSession.playbackState = player.video.paused ? "paused" : "playing";
421
+ navigator.mediaSession.setPositionState({
422
+ duration: isFinite(player.video.duration) ? player.video.duration || 0 : player.video.currentTime + 1,
423
+ playbackRate: player.video.playbackRate,
424
+ position: player.video.currentTime
425
+ });
426
+ }, 1000);
427
+
428
+ // Video metadata
429
+ const disposePlayerSettingsListener = player.controls.RegisterSettingsListener(() => {
430
+ const {title} = player.controls.GetContentInfo() || {};
431
+
432
+ if(!navigator.mediaSession.metadata || navigator.mediaSession.metadata.title !== title) {
433
+ navigator.mediaSession.metadata = new MediaMetadata({title});
434
+ }
435
+ });
436
+
437
+ return () => {
438
+ clearInterval(positionInterval);
439
+
440
+ disposePlayerSettingsListener && disposePlayerSettingsListener();
441
+
442
+ navigator.mediaSession.metadata = null;
443
+ mediaSessionEvents.forEach(event => {
444
+ navigator.mediaSession.setActionHandler(event, null);
445
+ });
446
+ };
447
+ }
448
+ };
449
+
@@ -0,0 +1,106 @@
1
+ import ProfileFormStyles from "../static/stylesheets/player-profile-form.module.scss";
2
+
3
+ // eslint-disable-next-line no-unused-vars
4
+ import React, {useEffect, useRef, useState} from "react";
5
+ import {Spinner} from "./Components.jsx";
6
+ import {RegisterModal} from "./Observers.js";
7
+
8
+ const PlayerProfileForm = ({player, Close}) => {
9
+ const [playerOptions, setPlayerOptions] = useState(JSON.stringify(player.hlsOptions || "{}", null, 2));
10
+ const [submitting, setSubmitting] = useState(false);
11
+ const [errorMessage, setErrorMessage] = useState("");
12
+
13
+ const formRef = useRef();
14
+
15
+ useEffect(() => {
16
+ if(!formRef || !formRef.current) { return; }
17
+
18
+ const modalHandlerDisposer = RegisterModal({element: formRef.current, Hide: Close});
19
+
20
+ return () => modalHandlerDisposer && modalHandlerDisposer();
21
+ }, [formRef]);
22
+
23
+ const Submit = async event => {
24
+ event.preventDefault();
25
+
26
+ try {
27
+ setSubmitting(true);
28
+ await player.controls.SetPlayerProfile({profile: "custom", customHLSOptions: JSON.parse(playerOptions)});
29
+
30
+ Close();
31
+ } catch(error) {
32
+ setErrorMessage(error.toString());
33
+ setSubmitting(false);
34
+ }
35
+ };
36
+
37
+ return (
38
+ <div
39
+ role="complementary"
40
+ tabIndex={-1}
41
+ className={ProfileFormStyles["container"]}
42
+ >
43
+ <div className={ProfileFormStyles["overlay"]}>
44
+ <form
45
+ onSubmit={Submit}
46
+ className={ProfileFormStyles["form"]}
47
+ ref={formRef}
48
+ >
49
+ <h2 className={ProfileFormStyles["header"]}>
50
+ Custom hls.js Options
51
+ </h2>
52
+ <div className={ProfileFormStyles["input-container"]}>
53
+ <textarea
54
+ disabled={submitting}
55
+ autoFocus
56
+ value={playerOptions}
57
+ title={errorMessage}
58
+ aria-label="Player Options"
59
+ aria-invalid={!!errorMessage}
60
+ aria-errormessage={errorMessage || ""}
61
+ onChange={event => setPlayerOptions(event.currentTarget.value)}
62
+ onFocus={() => setErrorMessage("")}
63
+ onBlur={() => {
64
+ try {
65
+ setErrorMessage("");
66
+ setPlayerOptions(JSON.stringify(JSON.parse(playerOptions || "{}"), null, 2));
67
+ } catch(error) {
68
+ setErrorMessage(error.toString());
69
+ }
70
+ }}
71
+ className={`${ProfileFormStyles["input"]} ${errorMessage ? ProfileFormStyles["input--invalid"] : ""}`}
72
+ />
73
+ <div className={ProfileFormStyles["player-info"]}>
74
+ <a tabIndex={0} href="https://github.com/video-dev/hls.js/blob/master/docs/API.md" rel="noreferrer" target="_blank" className={ProfileFormStyles["api-link"]}>
75
+ API Docs
76
+ </a>
77
+ <div className={ProfileFormStyles["player-version"]}>
78
+ hls.js { player.HLS.version }
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <div className={ProfileFormStyles["actions"]}>
83
+ <button
84
+ type="button"
85
+ aria-label="Cancel"
86
+ onClick={() => Close()}
87
+ className={ProfileFormStyles["cancel"]}
88
+ >
89
+ Cancel
90
+ </button>
91
+ <button
92
+ type="submit"
93
+ aria-label="Submit"
94
+ disabled={!!errorMessage}
95
+ className={ProfileFormStyles["submit"]}
96
+ >
97
+ { submitting ? <Spinner light /> : "Submit" }
98
+ </button>
99
+ </div>
100
+ </form>
101
+ </div>
102
+ </div>
103
+ );
104
+ };
105
+
106
+ export default PlayerProfileForm;