@eluvio/elv-player-js 1.0.140 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +35 -6
  2. package/dist/.vite/manifest.json +67 -0
  3. package/dist/Analytics-HWXR7tWt.mjs +2028 -0
  4. package/dist/Analytics-IUVysdzU.js +29 -0
  5. package/dist/dash.all.min-1QS9Xbir.js +25 -0
  6. package/dist/dash.all.min-9V1xYBRv.mjs +19428 -0
  7. package/dist/elv-player-js.cjs.js +1 -0
  8. package/dist/elv-player-js.css +1 -0
  9. package/dist/elv-player-js.es.js +5 -0
  10. package/dist/hls-1eCRapWm.mjs +15461 -0
  11. package/dist/hls-6O5SV1FQ.js +26 -0
  12. package/dist/index-6cMQneJf.mjs +2273 -0
  13. package/dist/index-C8mwW09z.js +23 -0
  14. package/dist/index-J4QpmTkA.js +367 -0
  15. package/dist/index-hvQzQ6UX.mjs +67432 -0
  16. package/lib/index.js +7 -0
  17. package/{src → lib/player}/Analytics.js +9 -8
  18. package/lib/player/Controls.js +913 -0
  19. package/{src → lib/player}/FairPlay.js +2 -0
  20. package/lib/player/Player.js +881 -0
  21. package/lib/player/PlayerParameters.js +173 -0
  22. package/lib/static/icons/Icons.js +29 -0
  23. package/lib/static/icons/svgs/backward-circle.svg +5 -0
  24. package/lib/static/icons/svgs/backward.svg +4 -0
  25. package/lib/static/icons/svgs/captions-off.svg +7 -0
  26. package/lib/static/icons/svgs/captions.svg +6 -0
  27. package/lib/static/icons/svgs/check.svg +1 -0
  28. package/lib/static/icons/svgs/chevron-left.svg +1 -0
  29. package/lib/static/icons/svgs/chevron-right.svg +1 -0
  30. package/lib/static/icons/svgs/forward-circle.svg +5 -0
  31. package/lib/static/icons/svgs/forward.svg +4 -0
  32. package/{src/static/icons/media/Full Screen icon.svg → lib/static/icons/svgs/full-screen.svg} +1 -1
  33. package/lib/static/icons/svgs/large-play-circle.svg +4 -0
  34. package/lib/static/icons/svgs/list.svg +1 -0
  35. package/{src/static/icons → lib/static/icons/svgs}/minimize.svg +1 -1
  36. package/{src/static/icons/media/Pause icon.svg → lib/static/icons/svgs/pause-circle.svg} +3 -3
  37. package/lib/static/icons/svgs/pause.svg +1 -0
  38. package/{src/static/icons/media/Play icon.svg → lib/static/icons/svgs/play-circle.svg} +1 -1
  39. package/lib/static/icons/svgs/play.svg +1 -0
  40. package/lib/static/icons/svgs/rotate-cw.svg +1 -0
  41. package/lib/static/icons/svgs/settings.svg +11 -0
  42. package/{src/static/icons/media/skip back icon.svg → lib/static/icons/svgs/skip-backward.svg} +2 -3
  43. package/{src/static/icons/media/Skip forward icon.svg → lib/static/icons/svgs/skip-forward.svg} +2 -3
  44. package/{src/static/icons/media/Volume icon.svg → lib/static/icons/svgs/volume-high.svg} +3 -3
  45. package/lib/static/icons/svgs/volume-low.svg +10 -0
  46. package/{src/static/icons/media/low volume icon.svg → lib/static/icons/svgs/volume-medium.svg} +2 -2
  47. package/{src/static/icons/media/no volume icon.svg → lib/static/icons/svgs/volume-off.svg} +3 -3
  48. package/lib/static/stylesheets/common.module.scss +486 -0
  49. package/lib/static/stylesheets/controls-tv.module.scss +488 -0
  50. package/lib/static/stylesheets/controls-web.module.scss +422 -0
  51. package/lib/static/stylesheets/player-profile-form.module.scss +141 -0
  52. package/lib/static/stylesheets/player.module.scss +92 -0
  53. package/lib/static/stylesheets/reset.module.scss +79 -0
  54. package/lib/static/stylesheets/ticket-form.module.scss +123 -0
  55. package/lib/ui/BuildIcons.cjs +44 -0
  56. package/lib/ui/Common.js +210 -0
  57. package/lib/ui/Components.jsx +342 -0
  58. package/lib/ui/Observers.js +449 -0
  59. package/lib/ui/PlayerProfileForm.jsx +106 -0
  60. package/lib/ui/PlayerUI.jsx +317 -0
  61. package/lib/ui/TVControls.jsx +337 -0
  62. package/lib/ui/TicketForm.jsx +147 -0
  63. package/lib/ui/WebControls.jsx +290 -0
  64. package/package.json +35 -47
  65. package/dist/index.js +0 -2
  66. package/dist/index.js.LICENSE.txt +0 -80
  67. package/src/BuildIcons.js +0 -27
  68. package/src/PlayerControls.js +0 -1478
  69. package/src/index.js +0 -1417
  70. package/src/static/icons/Icons.js +0 -15
  71. package/src/static/icons/Settings icon.svg +0 -4
  72. package/src/static/icons/chat icon collapse.svg +0 -1
  73. package/src/static/icons/chat icon.svg +0 -11
  74. package/src/static/icons/chat send.svg +0 -1
  75. package/src/static/icons/full screen.svg +0 -1
  76. package/src/static/icons/media/LargePlayIcon.svg +0 -4
  77. package/src/static/icons/media/Settings icon.svg +0 -4
  78. package/src/static/icons/media/Skip backward icon.svg +0 -4
  79. package/src/static/icons/media/list.svg +0 -1
  80. package/src/static/icons/media/loop icon.svg +0 -12
  81. package/src/static/icons/media/shuffle icon.svg +0 -13
  82. package/src/static/icons/muted.svg +0 -11
  83. package/src/static/icons/pause.svg +0 -1
  84. package/src/static/icons/play circle.svg +0 -1
  85. package/src/static/icons/play.svg +0 -1
  86. package/src/static/icons/settings.svg +0 -1
  87. package/src/static/icons/slider circle.svg +0 -1
  88. package/src/static/icons/unmuted.svg +0 -10
  89. package/src/static/images/ELUV.IO logo embed player.png +0 -0
  90. package/src/static/images/ELUV.IO logo embed player.svg +0 -1
  91. package/src/static/images/ELUV.IO white 20 px V2.png +0 -0
  92. package/src/static/images/ELUVIO white.svg +0 -26
  93. package/src/static/images/Logo.png +0 -0
  94. package/src/static/stylesheets/player.scss +0 -1065
  95. package/webpack.config.js +0 -152
  96. /package/{src/static/icons → lib/static/icons/svgs}/arrow-left.svg +0 -0
  97. /package/{src/static/icons/live icon.svg → lib/static/icons/svgs/live.svg} +0 -0
  98. /package/{src/static/icons → lib/static/icons/svgs}/multiview.svg +0 -0
  99. /package/{src/static/icons/media → lib/static/icons/svgs}/next.svg +0 -0
  100. /package/{src/static/icons/media → lib/static/icons/svgs}/previous.svg +0 -0
  101. /package/{src/static/icons → lib/static/icons/svgs}/x.svg +0 -0
  102. /package/{dist/5897e28fa3e8ac0a2fae.png → lib/static/images/Logo.png} +0 -0
@@ -0,0 +1,317 @@
1
+ import ResetStyle from "../static/stylesheets/reset.module.scss";
2
+ import PlayerStyles from "../static/stylesheets/player.module.scss";
3
+
4
+ import React, {useEffect, useRef, useState} from "react";
5
+ import ReactDOM from "react-dom/client";
6
+
7
+ import EluvioPlayer from "../player/Player.js";
8
+ import EluvioPlayerParameters from "../player/PlayerParameters.js";
9
+ import {
10
+ ObserveInteraction,
11
+ ObserveKeydown,
12
+ ObserveMediaSession,
13
+ ObserveResize,
14
+ ObserveVisibility
15
+ } from "./Observers.js";
16
+ import WebControls from "./WebControls.jsx";
17
+ import TicketForm from "./TicketForm.jsx";
18
+ import {Spinner, UserActionIndicator} from "./Components.jsx";
19
+ import TVControls from "./TVControls.jsx";
20
+ import PlayerProfileForm from "./PlayerProfileForm.jsx";
21
+ import {ImageUrl, MergeDefaultParameters} from "./Common.js";
22
+
23
+ const Poster = ({player}) => {
24
+ const [imageUrl, setImageUrl] = useState(undefined);
25
+
26
+ const { posterImage } = (player.controls.GetContentInfo() || {});
27
+
28
+ useEffect(() => {
29
+ setImageUrl(undefined);
30
+
31
+ if(!posterImage) { return; }
32
+
33
+ ImageUrl({player, pathOrUrl: posterImage, width: 200})
34
+ .then(imageUrl => setImageUrl(imageUrl));
35
+ }, [posterImage]);
36
+
37
+ if(!imageUrl) { return null; }
38
+
39
+ return (
40
+ <img
41
+ alt="Video Poster"
42
+ src={imageUrl}
43
+ className={PlayerStyles["poster"]}
44
+ />
45
+ );
46
+ };
47
+
48
+ const PlayerUI = ({target, parameters, InitCallback, ErrorCallback, Unmount, Reset}) => {
49
+ const [player, setPlayer] = useState(undefined);
50
+ const [client, setClient] = useState(undefined);
51
+ const [size, setSize] = useState("lg");
52
+ const [orientation, setOrientation] = useState("landscape");
53
+ const [dimensions, setDimensions] = useState({
54
+ width: target.getBoundingClientRect().width,
55
+ height: target.getBoundingClientRect().height
56
+ });
57
+ const [errorMessage, setErrorMessage] = useState(undefined);
58
+ const [playerInitialized, setPlayerInitialized] = useState(false);
59
+ const [playbackStarted, setPlaybackStarted] = useState(false);
60
+ const [canPlay, setCanPlay] = useState(false);
61
+ const [mounted, setMounted] = useState(false);
62
+ const [showPlayerProfileForm, setShowPlayerProfileForm] = useState(false);
63
+ const [recentlyInteracted, setRecentlyInteracted] = useState(true);
64
+ const [recentUserAction, setRecentUserAction] = useState(undefined);
65
+ const [allowRotation, setAllowRotation] = useState(undefined);
66
+ const videoRef = useRef();
67
+
68
+ const playerSet = !!player;
69
+
70
+ const onUserAction = ({action, text}) => setRecentUserAction({action, text, key: Math.random()});
71
+
72
+ useEffect(() => {
73
+ setMounted(true);
74
+
75
+ // Observe target portal size
76
+ const disposeResizeObserver = ObserveResize({target, setSize, setOrientation, setDimensions});
77
+
78
+ return () => {
79
+ setMounted(false);
80
+ disposeResizeObserver && disposeResizeObserver();
81
+ };
82
+ }, []);
83
+
84
+ useEffect(() => {
85
+ if(!videoRef || !videoRef.current || !mounted) {
86
+ return;
87
+ }
88
+
89
+ try {
90
+ setPlayerInitialized(false);
91
+ setPlaybackStarted(false);
92
+
93
+ // Use ticket client if present
94
+ parameters.clientOptions.client = client || parameters.clientOptions.client;
95
+
96
+ const newPlayer = new EluvioPlayer({
97
+ target,
98
+ video: videoRef.current,
99
+ parameters,
100
+ SetErrorMessage: setErrorMessage
101
+ });
102
+
103
+ window.__elvPlayer = newPlayer;
104
+
105
+ // Observe player settings to keep track of whether playback has started
106
+ const disposePlayerSettingsListener = newPlayer.controls.RegisterSettingsListener(
107
+ () => {
108
+ setAllowRotation(newPlayer.controls.AllowRotation());
109
+ setPlaybackStarted(newPlayer.playbackStarted);
110
+ setPlayerInitialized(!newPlayer.loading);
111
+ setShowPlayerProfileForm(newPlayer.__showPlayerProfileForm);
112
+ }
113
+ );
114
+
115
+ // Destroy method for external use - destroys internal player and unmounts react
116
+ newPlayer.Destroy = () => {
117
+ newPlayer.__DestroyPlayer();
118
+ Unmount();
119
+ };
120
+
121
+ newPlayer.Reset = () => {
122
+ Reset();
123
+ };
124
+
125
+ // Observe whether player is visible for autoplay/mute on visibility functionality
126
+ const disposeVisibilityObserver = ObserveVisibility({player: newPlayer});
127
+
128
+ // Observe interaction for autohiding control elements
129
+ const disposeInteractionObserver = ObserveInteraction({
130
+ player: newPlayer,
131
+ inactivityPeriod: 5000,
132
+ onWake: () => setRecentlyInteracted(true),
133
+ onSleep: () => setRecentlyInteracted(false)
134
+ });
135
+
136
+ // Keyboard controls
137
+ const disposeKeyboardControls = ObserveKeydown({
138
+ player: newPlayer,
139
+ setRecentUserAction: onUserAction
140
+ });
141
+
142
+ // Media session
143
+ const disposeMediaSessionObserver = ObserveMediaSession({player: newPlayer});
144
+
145
+ InitCallback(newPlayer);
146
+ setPlayer(newPlayer);
147
+
148
+ // Can play
149
+ const disposeCanPlayListener = newPlayer.controls.RegisterVideoEventListener("canplay", () => setCanPlay(true));
150
+
151
+ return () => {
152
+ videoRef && videoRef.current && videoRef.current.removeEventListener("play", setPlaybackStarted);
153
+
154
+ disposePlayerSettingsListener && disposePlayerSettingsListener();
155
+ disposeVisibilityObserver && disposeVisibilityObserver();
156
+ disposeInteractionObserver && disposeInteractionObserver();
157
+ disposeKeyboardControls && disposeKeyboardControls();
158
+ disposeMediaSessionObserver && disposeMediaSessionObserver();
159
+ disposeCanPlayListener && disposeCanPlayListener();
160
+
161
+ newPlayer && newPlayer.__DestroyPlayer();
162
+ };
163
+ } catch(error) {
164
+ ErrorCallback(error);
165
+ Unmount();
166
+ }
167
+ }, [videoRef, mounted, client]);
168
+
169
+ useEffect(() => {
170
+ if(!playerSet) { return; }
171
+
172
+ // Clean up player when unmounting
173
+ return () => {
174
+ player && player.__DestroyPlayer();
175
+ setPlayer(undefined);
176
+ };
177
+ }, [playerSet]);
178
+
179
+ if(parameters.clientOptions.promptTicket && !client) {
180
+ return (
181
+ <TicketForm
182
+ parameters={parameters}
183
+ dimensions={{size, orientation, ...dimensions}}
184
+ onComplete={ticketClient => setClient(ticketClient)}
185
+ />
186
+ );
187
+ }
188
+
189
+ const shouldRotate =
190
+ parameters.playerOptions.ui === EluvioPlayerParameters.ui.WEB &&
191
+ player &&
192
+ player.controls.IsRotatable() &&
193
+ allowRotation;
194
+
195
+ return (
196
+ <div
197
+ role="complementary"
198
+ aria-label="Eluvio Video Player"
199
+ tabIndex={0}
200
+ style={{
201
+ backgroundColor: parameters.playerOptions.backgroundColor || "transparent",
202
+ "--portal-width": `${dimensions.width}px`,
203
+ "--portal-height": `${dimensions.height}px`
204
+ }}
205
+ className={[PlayerStyles["player-container"], shouldRotate ? PlayerStyles["player-container--rotated"] : "", `__eluvio-player--size-${size}`, `__eluvio-player--orientation-${orientation}`].join(" ")}
206
+ >
207
+ {
208
+ !showPlayerProfileForm || !playerInitialized ? null :
209
+ <PlayerProfileForm player={player} Close={() => player.controls.HidePlayerProfileForm()} />
210
+ }
211
+ <video
212
+ playsInline
213
+ disablePictureInPicture
214
+ ref={videoRef}
215
+ muted={[EluvioPlayerParameters.muted.ON, EluvioPlayerParameters.muted.WHEN_NOT_VISIBLE].includes(parameters.playerOptions.muted)}
216
+ controls={parameters.playerOptions.controls === EluvioPlayerParameters.controls.DEFAULT}
217
+ loop={parameters.playerOptions.loop === EluvioPlayerParameters.loop.ON}
218
+ className={PlayerStyles.video}
219
+ />
220
+ {
221
+ !player || playbackStarted || !canPlay ? null :
222
+ <Poster player={player} />
223
+ }
224
+ {
225
+ playerInitialized || errorMessage ? null :
226
+ <div className={PlayerStyles["spinner-container"]}>
227
+ <Spinner className={PlayerStyles["spinner"]} />
228
+ </div>
229
+ }
230
+ {
231
+ !errorMessage ? null :
232
+ <div className={PlayerStyles["error-message"]}>{ errorMessage }</div>
233
+ }
234
+ {
235
+ !player ? null :
236
+ parameters.playerOptions.ui === EluvioPlayerParameters.ui.WEB ?
237
+ <WebControls
238
+ player={player}
239
+ playbackStarted={!!playbackStarted}
240
+ canPlay={canPlay}
241
+ recentlyInteracted={recentlyInteracted}
242
+ setRecentUserAction={onUserAction}
243
+ className={PlayerStyles.controls}
244
+ /> :
245
+ <TVControls
246
+ player={player}
247
+ playbackStarted={!!playbackStarted}
248
+ canPlay={canPlay}
249
+ recentlyInteracted={recentlyInteracted}
250
+ setRecentUserAction={onUserAction}
251
+ className={PlayerStyles.controls}
252
+ />
253
+ }
254
+ {
255
+ !recentUserAction ? null :
256
+ <UserActionIndicator
257
+ action={recentUserAction}
258
+ key={`action-indicator-${recentUserAction && recentUserAction.key}`}
259
+ />
260
+ }
261
+ </div>
262
+ );
263
+ };
264
+
265
+ const PlayerWrapper = (args) => {
266
+ const [playerKey, setPlayerKey] = useState(Math.random());
267
+
268
+ return (
269
+ <PlayerUI
270
+ {...args}
271
+ key={`player-${playerKey}`}
272
+ Reset={() => setPlayerKey(Math.random())}
273
+ />
274
+ );
275
+ };
276
+
277
+ const Initialize = (target, parameters) => {
278
+ target.innerHTML = "";
279
+ target.classList.add(ResetStyle.reset);
280
+ target.classList.add(PlayerStyles["player-target"]);
281
+
282
+ parameters = MergeDefaultParameters(parameters);
283
+
284
+ if(parameters.playerOptions && parameters.playerOptions.backgroundColor) {
285
+ target.style.backgroundColor = parameters.playerOptions.backgroundColor;
286
+ }
287
+
288
+ return new Promise((resolve, reject) => {
289
+ const root = ReactDOM.createRoot(target);
290
+
291
+ root.render(
292
+ <React.StrictMode>
293
+ <PlayerWrapper
294
+ target={target}
295
+ parameters={parameters}
296
+ InitCallback={resolve}
297
+ ErrorCallback={reject}
298
+ Unmount={async () => {
299
+ try {
300
+ await root.unmount();
301
+ } catch(error) {
302
+ // eslint-disable-next-line no-console
303
+ console.error("Failed to unmount Eluvio Player");
304
+ // eslint-disable-next-line no-console
305
+ console.error(error);
306
+ }
307
+ }
308
+ }
309
+ />
310
+ </React.StrictMode>
311
+ );
312
+ });
313
+ };
314
+
315
+
316
+ export default Initialize;
317
+
@@ -0,0 +1,337 @@
1
+ import ControlStyles from "../static/stylesheets/controls-tv.module.scss";
2
+
3
+ // eslint-disable-next-line no-unused-vars
4
+ import React, {useEffect, useState} from "react";
5
+ import * as Icons from "../static/icons/Icons.js";
6
+ import {ObserveVideo, ObserveVideoTime} from "./Observers.js";
7
+ import "focus-visible";
8
+ import {ImageUrl, PlayerClick, Time} from "./Common.js";
9
+ import EluvioPlayerParameters from "../player/PlayerParameters.js";
10
+
11
+ import EluvioLogo from "../static/images/Logo.png";
12
+ import {CollectionMenu, SeekBar, SettingsMenu, SVG, VolumeControls} from "./Components.jsx";
13
+
14
+ export const IconButton = ({icon, ...props}) => {
15
+ return (
16
+ <button {...props} className={`${ControlStyles["icon-button"]} ${props.className || ""}`} dangerouslySetInnerHTML={{__html: icon}} />
17
+ );
18
+ };
19
+
20
+ const TimeIndicator = ({player, videoState}) => {
21
+ const [currentTime, setCurrentTime] = useState(player.video.currentTime);
22
+
23
+ useEffect(() => {
24
+ const disposeVideoTimeObserver = ObserveVideoTime({video: player.video, setCurrentTime, rate: 10});
25
+
26
+ return () => disposeVideoTimeObserver && disposeVideoTimeObserver();
27
+ }, []);
28
+
29
+ if(player.isLive) {
30
+ return null;
31
+ }
32
+
33
+ return (
34
+ <div className={ControlStyles["time-container"]}>
35
+ <div className={ControlStyles["time"]}>
36
+ { Time(currentTime, videoState.duration) }
37
+ </div>
38
+ <div className={ControlStyles["spacer"]} />
39
+ <div className={ControlStyles["time"]}>
40
+ { Time(videoState.duration, videoState.duration) }
41
+ </div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ const CenterButtons = ({player, videoState}) => {
47
+ const collectionInfo = player.controls.GetCollectionInfo();
48
+
49
+ const previousMedia = collectionInfo && collectionInfo.isPlaylist && collectionInfo.content[collectionInfo.mediaIndex - 1];
50
+ const nextMedia = collectionInfo && collectionInfo.isPlaylist && collectionInfo.content[collectionInfo.mediaIndex + 1];
51
+
52
+ const playerReady = player.controls.IsReady();
53
+ return (
54
+ <div className={ControlStyles["center-buttons"]}>
55
+ {
56
+ !previousMedia && !nextMedia ? null :
57
+ <IconButton
58
+ disabled={!playerReady || !previousMedia}
59
+ icon={Icons.PreviousTrackIcon}
60
+ onClick={() => player.controls.CollectionPlayPrevious()}
61
+ className={`${ControlStyles["track-button"]} ${ControlStyles["icon-button--drop-shadow-focus"]}`}
62
+ />
63
+ }
64
+ <IconButton
65
+ aria-label="Back 10 Seconds"
66
+ icon={Icons.BackwardCircleIcon}
67
+ onClick={() => player.controls.Seek({relativeSeconds: -10})}
68
+ className={ControlStyles["icon-button--drop-shadow-focus"]}
69
+ />
70
+ <IconButton
71
+ aria-label={videoState.playing ? "Pause" : "Play"}
72
+ icon={videoState.playing ? Icons.PauseCircleIcon : Icons.PlayCircleIcon}
73
+ onClick={() => player.controls.TogglePlay()}
74
+ className={`${ControlStyles["play-pause-button"]} ${ControlStyles["icon-button--drop-shadow-focus"]}`}
75
+ />
76
+ <IconButton
77
+ aria-label="Forward 10 Seconds"
78
+ icon={Icons.ForwardCircleIcon}
79
+ onClick={() => player.controls.Seek({relativeSeconds: 10})}
80
+ className={ControlStyles["icon-button--drop-shadow-focus"]}
81
+ />
82
+ {
83
+ !previousMedia && !nextMedia ? null :
84
+ <IconButton
85
+ disabled={!playerReady || !nextMedia}
86
+ icon={Icons.NextTrackIcon}
87
+ onClick={() => player.controls.CollectionPlayNext()}
88
+ className={`${ControlStyles["track-button"]} ${ControlStyles["icon-button--drop-shadow-focus"]}`}
89
+ />
90
+ }
91
+ </div>
92
+ );
93
+ };
94
+
95
+ const MenuButton = ({label, icon, children, player, setMenuActive, MenuComponent}) => {
96
+ const [show, setShow] = useState(false);
97
+
98
+ return (
99
+ <div className={ControlStyles["menu-control-container"]}>
100
+ {
101
+ icon ?
102
+ <IconButton
103
+ aria-label={show ? `Hide ${label} Menu` : label}
104
+ aria-haspopup
105
+ icon={icon}
106
+ onClick={() => {
107
+ setMenuActive(!show);
108
+ setShow(!show);
109
+ }}
110
+ className={`${ControlStyles["icon-button--circle-focus"]} ${show ? ControlStyles["icon-button-active"] : ""}`}
111
+ /> :
112
+ <button
113
+ onClick={() => {
114
+ setMenuActive(!show);
115
+ setShow(!show);
116
+ }}
117
+ className={`${ControlStyles["text-button"]} ${show ? ControlStyles["text-button--active"] : ""}`}
118
+ >
119
+ { children }
120
+ </button>
121
+ }
122
+ {
123
+ !show ? null :
124
+ <MenuComponent
125
+ player={player}
126
+ Hide={() => {
127
+ setShow(false);
128
+ setMenuActive(false);
129
+ }}
130
+ className={ControlStyles["menu"]}
131
+ />
132
+ }
133
+ </div>
134
+ );
135
+ };
136
+
137
+ const InfoBox = ({player, Hide}) => {
138
+ const [imageUrl, setImageUrl] = useState(undefined);
139
+
140
+ const {title, description, image, headers} = player.controls.GetContentInfo() || {};
141
+
142
+ useEffect(() => {
143
+ setImageUrl(undefined);
144
+
145
+ if(!image) { return; }
146
+
147
+ ImageUrl({player, pathOrUrl: image, width: 200})
148
+ .then(imageUrl => setImageUrl(imageUrl));
149
+ }, [image]);
150
+
151
+ useEffect(() => {
152
+ const onEscape = event => {
153
+ if(event && (event.key || "").toLowerCase() === "escape") {
154
+ Hide();
155
+ }
156
+ };
157
+
158
+ document.body.addEventListener("keydown", onEscape);
159
+
160
+ return () => document.body.removeEventListener("keydown", onEscape);
161
+ }, []);
162
+
163
+ return (
164
+ <div className={ControlStyles["info-box-container"]}>
165
+ <button
166
+ autoFocus
167
+ onClick={() => Hide()}
168
+ className={`${ControlStyles["info-box-button"]} ${ControlStyles["info-box-button--info"]}`}
169
+ >
170
+ Info
171
+ </button>
172
+ <div className={ControlStyles["info-box"]}>
173
+ {
174
+ !imageUrl ? null :
175
+ <div className={ControlStyles["info-box-image-container"]}>
176
+ <img src={imageUrl} alt="Image" className={ControlStyles["info-box-image"]}/>
177
+ </div>
178
+ }
179
+ <div className={`${ControlStyles["info-box-text"]} ${imageUrl && headers && headers.length === 0 ? ControlStyles["info-box-text--top-padding"] : ""}`}>
180
+ {
181
+ !headers || headers.length === 0 ? null :
182
+ <div className={ControlStyles["info-box-headers"]}>
183
+ {headers.map((text, index) =>
184
+ <div key={`header-${index}`} className={ControlStyles["info-box-header"]}>
185
+ { text }
186
+ </div>
187
+ )}
188
+ </div>
189
+ }
190
+ <div className={ControlStyles["info-box-title"]}>
191
+ { title || "" }
192
+ </div>
193
+ <div className={ControlStyles["info-box-description"]}>
194
+ { description || "" }
195
+ </div>
196
+ </div>
197
+ <div className={ControlStyles["info-box-actions"]}>
198
+ <button
199
+ onClick={() => {
200
+ player.controls.Seek({time: 0});
201
+ Hide();
202
+ }}
203
+ className={`${ControlStyles["info-box-button"]} ${ControlStyles["info-box-button--restart"]}`}
204
+ >
205
+ <SVG icon={Icons.PlayIcon} />
206
+ From Beginning
207
+ </button>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ );
212
+ };
213
+
214
+
215
+ const TVControls = ({player, playbackStarted, canPlay, recentlyInteracted, setRecentUserAction, className=""}) => {
216
+ const [videoState, setVideoState] = useState(undefined);
217
+ const [playerClickHandler, setPlayerClickHandler] = useState(undefined);
218
+ const [menuActive, setMenuActive] = useState(false);
219
+ const [showInfo, setShowInfo] = useState(false);
220
+
221
+ useEffect(() => {
222
+ setPlayerClickHandler(PlayerClick({player, setRecentUserAction}));
223
+
224
+ const disposeVideoObserver = ObserveVideo({target: player.target, video: player.video, setVideoState});
225
+
226
+ return () => disposeVideoObserver && disposeVideoObserver();
227
+ }, []);
228
+
229
+ if(!videoState) {
230
+ return null;
231
+ }
232
+
233
+ const { title } = (player.controls.GetContentInfo() || {});
234
+
235
+ const collectionInfo = player.controls.GetCollectionInfo();
236
+
237
+ // Title autohide is not dependent on controls settings
238
+ const showUI = recentlyInteracted || !playbackStarted || menuActive;
239
+ const hideControls = !showUI && player.playerOptions.controls === EluvioPlayerParameters.controls.AUTO_HIDE;
240
+
241
+ player.__SetControlsVisibility(!hideControls);
242
+
243
+ return (
244
+ <div
245
+ key="controls"
246
+ onClick={playerClickHandler}
247
+ className={[
248
+ className,
249
+ ControlStyles["container"],
250
+ showUI ? "" : ControlStyles["autohide"],
251
+ player.playerOptions.controls !== EluvioPlayerParameters.controls.DEFAULT ? "" : ControlStyles["container--default-controls"],
252
+ menuActive ? "menu-active" : ""
253
+ ].join(" ")}
254
+ >
255
+ <IconButton
256
+ aria-label="Play"
257
+ tabIndex={playbackStarted ? -1 : 0}
258
+ icon={Icons.CenterPlayCircleIcon}
259
+ onClick={() => {
260
+ player.controls.Play();
261
+ // Take focus off of this button because it should no longer be selectable after playback starts
262
+ player.target.firstChild.focus();
263
+ }}
264
+ className={`${ControlStyles["center-play-button"]} ${ControlStyles["icon-button--drop-shadow-focus"]} ${canPlay && !playbackStarted ? "" : ControlStyles["center-play-button--hidden"]}`}
265
+ />
266
+ {
267
+ showInfo ?
268
+ <InfoBox player={player} Hide={() => setShowInfo(false)} /> :
269
+ <div className={`${ControlStyles["bottom-controls-container"]} ${hideControls ? ControlStyles["bottom-controls-container--autohide"] : ""}`}>
270
+ <div className={ControlStyles["bottom-controls-gradient"]} />
271
+ <div className={ControlStyles["title-container"]}>
272
+ <div className={ControlStyles["title"]}>
273
+ {
274
+ (
275
+ player.playerOptions.title === EluvioPlayerParameters.title.OFF ||
276
+ (player.playerOptions.title === EluvioPlayerParameters.title.FULLSCREEN_ONLY && !player.controls.IsFullscreen())
277
+ ) ? "" : title || ""
278
+ }
279
+ </div>
280
+ <div className={ControlStyles["spacer"]}/>
281
+ {
282
+ !player.isLive ? null :
283
+ <div className={ControlStyles["live-indicator"]}>
284
+ Live
285
+ </div>
286
+ }
287
+ {
288
+ !collectionInfo ? null :
289
+ <MenuButton
290
+ label="Collection Menu"
291
+ icon={Icons.CollectionIcon}
292
+ player={player}
293
+ setMenuActive={setMenuActive}
294
+ MenuComponent={CollectionMenu}
295
+ />
296
+ }
297
+
298
+ </div>
299
+ <SeekBar player={player} videoState={videoState} setRecentUserAction={setRecentUserAction} />
300
+ <TimeIndicator player={player} videoState={videoState}/>
301
+ <div className={ControlStyles["bottom-controls"]}>
302
+ <div className={ControlStyles["bottom-left-controls"]}>
303
+ {
304
+ !title || player.playerOptions.title === EluvioPlayerParameters.title.OFF ? null :
305
+ <button className={ControlStyles["text-button"]} onClick={() => setShowInfo(true)}>Info</button>
306
+ }
307
+ </div>
308
+ <CenterButtons player={player} videoState={videoState}/>
309
+ <div className={ControlStyles["bottom-right-controls"]}>
310
+ {
311
+ !player.controls.GetOptions().hasAnyOptions ? null :
312
+ <MenuButton
313
+ key="settings-button"
314
+ label="Settings"
315
+ player={player}
316
+ MenuComponent={SettingsMenu}
317
+ setMenuActive={setMenuActive}
318
+ >
319
+ Settings
320
+ </MenuButton>
321
+ }
322
+ </div>
323
+ </div>
324
+ </div>
325
+ }
326
+ {
327
+ // Watermark
328
+ player.playerOptions.watermark === EluvioPlayerParameters.watermark.OFF ? null :
329
+ <div className={ControlStyles["watermark"]}>
330
+ <img src={EluvioLogo} alt="Eluvio" />
331
+ </div>
332
+ }
333
+ </div>
334
+ );
335
+ };
336
+
337
+ export default TVControls;