@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.
- package/README.md +35 -6
- package/dist/.vite/manifest.json +67 -0
- package/dist/Analytics-HWXR7tWt.mjs +2028 -0
- package/dist/Analytics-IUVysdzU.js +29 -0
- package/dist/dash.all.min-1QS9Xbir.js +25 -0
- package/dist/dash.all.min-9V1xYBRv.mjs +19428 -0
- package/dist/elv-player-js.cjs.js +1 -0
- package/dist/elv-player-js.css +1 -0
- package/dist/elv-player-js.es.js +5 -0
- package/dist/hls-1eCRapWm.mjs +15461 -0
- package/dist/hls-6O5SV1FQ.js +26 -0
- package/dist/index-6cMQneJf.mjs +2273 -0
- package/dist/index-C8mwW09z.js +23 -0
- package/dist/index-J4QpmTkA.js +367 -0
- package/dist/index-hvQzQ6UX.mjs +67432 -0
- package/lib/index.js +7 -0
- package/{src → lib/player}/Analytics.js +9 -8
- package/lib/player/Controls.js +913 -0
- package/{src → lib/player}/FairPlay.js +2 -0
- package/lib/player/Player.js +881 -0
- package/lib/player/PlayerParameters.js +173 -0
- package/lib/static/icons/Icons.js +29 -0
- package/lib/static/icons/svgs/backward-circle.svg +5 -0
- package/lib/static/icons/svgs/backward.svg +4 -0
- package/lib/static/icons/svgs/captions-off.svg +7 -0
- package/lib/static/icons/svgs/captions.svg +6 -0
- package/lib/static/icons/svgs/check.svg +1 -0
- package/lib/static/icons/svgs/chevron-left.svg +1 -0
- package/lib/static/icons/svgs/chevron-right.svg +1 -0
- package/lib/static/icons/svgs/forward-circle.svg +5 -0
- package/lib/static/icons/svgs/forward.svg +4 -0
- package/{src/static/icons/media/Full Screen icon.svg → lib/static/icons/svgs/full-screen.svg} +1 -1
- package/lib/static/icons/svgs/large-play-circle.svg +4 -0
- package/lib/static/icons/svgs/list.svg +1 -0
- package/{src/static/icons → lib/static/icons/svgs}/minimize.svg +1 -1
- package/{src/static/icons/media/Pause icon.svg → lib/static/icons/svgs/pause-circle.svg} +3 -3
- package/lib/static/icons/svgs/pause.svg +1 -0
- package/{src/static/icons/media/Play icon.svg → lib/static/icons/svgs/play-circle.svg} +1 -1
- package/lib/static/icons/svgs/play.svg +1 -0
- package/lib/static/icons/svgs/rotate-cw.svg +1 -0
- package/lib/static/icons/svgs/settings.svg +11 -0
- package/{src/static/icons/media/skip back icon.svg → lib/static/icons/svgs/skip-backward.svg} +2 -3
- package/{src/static/icons/media/Skip forward icon.svg → lib/static/icons/svgs/skip-forward.svg} +2 -3
- package/{src/static/icons/media/Volume icon.svg → lib/static/icons/svgs/volume-high.svg} +3 -3
- package/lib/static/icons/svgs/volume-low.svg +10 -0
- package/{src/static/icons/media/low volume icon.svg → lib/static/icons/svgs/volume-medium.svg} +2 -2
- package/{src/static/icons/media/no volume icon.svg → lib/static/icons/svgs/volume-off.svg} +3 -3
- package/lib/static/stylesheets/common.module.scss +486 -0
- package/lib/static/stylesheets/controls-tv.module.scss +488 -0
- package/lib/static/stylesheets/controls-web.module.scss +422 -0
- package/lib/static/stylesheets/player-profile-form.module.scss +141 -0
- package/lib/static/stylesheets/player.module.scss +92 -0
- package/lib/static/stylesheets/reset.module.scss +79 -0
- package/lib/static/stylesheets/ticket-form.module.scss +123 -0
- package/lib/ui/BuildIcons.cjs +44 -0
- package/lib/ui/Common.js +210 -0
- package/lib/ui/Components.jsx +342 -0
- package/lib/ui/Observers.js +449 -0
- package/lib/ui/PlayerProfileForm.jsx +106 -0
- package/lib/ui/PlayerUI.jsx +317 -0
- package/lib/ui/TVControls.jsx +337 -0
- package/lib/ui/TicketForm.jsx +147 -0
- package/lib/ui/WebControls.jsx +290 -0
- package/package.json +35 -47
- package/dist/index.js +0 -2
- package/dist/index.js.LICENSE.txt +0 -80
- package/src/BuildIcons.js +0 -27
- package/src/PlayerControls.js +0 -1478
- package/src/index.js +0 -1417
- package/src/static/icons/Icons.js +0 -15
- package/src/static/icons/Settings icon.svg +0 -4
- package/src/static/icons/chat icon collapse.svg +0 -1
- package/src/static/icons/chat icon.svg +0 -11
- package/src/static/icons/chat send.svg +0 -1
- package/src/static/icons/full screen.svg +0 -1
- package/src/static/icons/media/LargePlayIcon.svg +0 -4
- package/src/static/icons/media/Settings icon.svg +0 -4
- package/src/static/icons/media/Skip backward icon.svg +0 -4
- package/src/static/icons/media/list.svg +0 -1
- package/src/static/icons/media/loop icon.svg +0 -12
- package/src/static/icons/media/shuffle icon.svg +0 -13
- package/src/static/icons/muted.svg +0 -11
- package/src/static/icons/pause.svg +0 -1
- package/src/static/icons/play circle.svg +0 -1
- package/src/static/icons/play.svg +0 -1
- package/src/static/icons/settings.svg +0 -1
- package/src/static/icons/slider circle.svg +0 -1
- package/src/static/icons/unmuted.svg +0 -10
- package/src/static/images/ELUV.IO logo embed player.png +0 -0
- package/src/static/images/ELUV.IO logo embed player.svg +0 -1
- package/src/static/images/ELUV.IO white 20 px V2.png +0 -0
- package/src/static/images/ELUVIO white.svg +0 -26
- package/src/static/images/Logo.png +0 -0
- package/src/static/stylesheets/player.scss +0 -1065
- package/webpack.config.js +0 -152
- /package/{src/static/icons → lib/static/icons/svgs}/arrow-left.svg +0 -0
- /package/{src/static/icons/live icon.svg → lib/static/icons/svgs/live.svg} +0 -0
- /package/{src/static/icons → lib/static/icons/svgs}/multiview.svg +0 -0
- /package/{src/static/icons/media → lib/static/icons/svgs}/next.svg +0 -0
- /package/{src/static/icons/media → lib/static/icons/svgs}/previous.svg +0 -0
- /package/{src/static/icons → lib/static/icons/svgs}/x.svg +0 -0
- /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;
|