@eluvio/elv-player-js 2.0.3 → 2.0.5

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 (34) hide show
  1. package/dist/.vite/manifest.json +43 -35
  2. package/dist/Analytics-Cp5elXm3.js +29 -0
  3. package/dist/{Analytics-tzT177LT.mjs → Analytics-VkbTMe8P.mjs} +794 -949
  4. package/dist/dash.all.min-DSIJKoVG.js +26 -0
  5. package/dist/dash.all.min-DsA1Pw5n.mjs +18042 -0
  6. package/dist/elv-player-js.cjs.js +1 -1
  7. package/dist/elv-player-js.css +1 -1
  8. package/dist/elv-player-js.es.js +1 -1
  9. package/dist/{hls-1eCRapWm.mjs → hls-B8WS8h-m.mjs} +42 -80
  10. package/dist/{hls-6O5SV1FQ.js → hls-agwebW2Y.js} +1 -1
  11. package/dist/{index-eJYk3d_H.mjs → index-Cz4NKJpw.mjs} +194 -236
  12. package/dist/{index-fK0-ixyS.js → index-DIJ0DS7K.js} +2 -2
  13. package/dist/index-d2hIwQjH.js +366 -0
  14. package/dist/{index-OO3pB9-0.mjs → index-jYy0T-98.mjs} +29889 -29431
  15. package/lib/player/Controls.js +24 -0
  16. package/lib/player/Player.js +115 -1
  17. package/lib/player/PlayerParameters.js +6 -1
  18. package/lib/static/icons/Icons.js +31 -28
  19. package/lib/static/icons/svgs/content-credentials.svg +3 -0
  20. package/lib/static/icons/svgs/copy.svg +1 -0
  21. package/lib/static/icons/svgs/shield.svg +4 -0
  22. package/lib/static/stylesheets/common.module.scss +162 -3
  23. package/lib/static/stylesheets/controls-web.module.scss +32 -1
  24. package/lib/ui/BuildIcons.cjs +4 -1
  25. package/lib/ui/Components.jsx +183 -3
  26. package/lib/ui/Observers.js +13 -2
  27. package/lib/ui/PlayerUI.jsx +2 -2
  28. package/lib/ui/TVControls.jsx +30 -1
  29. package/lib/ui/WebControls.jsx +44 -5
  30. package/package.json +2 -2
  31. package/dist/Analytics-PI86_hOX.js +0 -29
  32. package/dist/dash.all.min-BHQ284vX.mjs +0 -19428
  33. package/dist/dash.all.min-YnvCBRhl.js +0 -25
  34. package/dist/index-uOiYcy0J.js +0 -367
@@ -32,12 +32,15 @@ const iconSource = {
32
32
  CaptionsIcon: Path.resolve(__dirname, "../static/icons/svgs/captions.svg"),
33
33
  CaptionsOffIcon: Path.resolve(__dirname, "../static/icons/svgs/captions-off.svg"),
34
34
  RotateIcon: Path.resolve(__dirname, "../static/icons/svgs/rotate-cw.svg"),
35
+ ContentShieldIcon: Path.resolve(__dirname, "../static/icons/svgs/shield.svg"),
36
+ ContentCredentialsIcon: Path.resolve(__dirname, "../static/icons/svgs/content-credentials.svg"),
37
+ CopyIcon: Path.resolve(__dirname, "../static/icons/svgs/copy.svg"),
35
38
  };
36
39
 
37
40
  let iconFile = "// WARNING: Do not edit this file manually\n"
38
41
  iconFile += "// Built using `npm run build-icons`\n\n";
39
42
  Object.keys(iconSource).map(iconName => {
40
- iconFile += `export const ${iconName} = "${fs.readFileSync(iconSource[iconName]).toString("utf8").replaceAll("\n", "").replaceAll("\"", "\\\"")}";\n`;
43
+ iconFile += `export const ${iconName} = \`${fs.readFileSync(iconSource[iconName]).toString("utf8").replaceAll("\n", "").replaceAll("\"", "\\\"")}\`;\n`;
41
44
  });
42
45
 
43
46
  fs.writeFileSync(
@@ -208,7 +208,7 @@ export const SettingsMenu = ({player, Hide, className=""}) => {
208
208
  let content;
209
209
  if(activeMenu) {
210
210
  content = (
211
- <div key="submenu" role="menu" className={`${CommonStyles["menu"]} ${CommonStyles["submenu"]} ${CommonStyles["settings-menu"]} ${className}`}>
211
+ <div key="submenu" role="menu" className={`${CommonStyles["menu"]} ${CommonStyles["submenu"]} ${className}`}>
212
212
  <button
213
213
  onClick={() => SetSubmenu(undefined)}
214
214
  aria-label="Back to settings menu"
@@ -240,7 +240,7 @@ export const SettingsMenu = ({player, Hide, className=""}) => {
240
240
  );
241
241
  } else {
242
242
  content = (
243
- <div key="menu" role="menu" className={`${CommonStyles["menu"]} ${CommonStyles["settings-menu"]} ${className}`}>
243
+ <div key="menu" role="menu" className={`${CommonStyles["menu"]} ${className}`}>
244
244
  {
245
245
  !options.hasQualityOptions ? null :
246
246
  <button autoFocus role="menuitem" onClick={() => SetSubmenu("quality")}
@@ -282,12 +282,192 @@ export const SettingsMenu = ({player, Hide, className=""}) => {
282
282
  }
283
283
 
284
284
  return (
285
- <div ref={menuRef} className={`${CommonStyles["menu-container"]}`}>
285
+ <div ref={menuRef}>
286
286
  { content }
287
287
  </div>
288
288
  );
289
289
  };
290
290
 
291
+ export const Copy = async (value) => {
292
+ try {
293
+ value = (value || "").toString();
294
+
295
+ await navigator.clipboard.writeText(value);
296
+ } catch(error) {
297
+ const input = document.createElement("input");
298
+
299
+ input.value = value;
300
+ input.select();
301
+ input.setSelectionRange(0, 99999);
302
+ document.execCommand("copy");
303
+ }
304
+ };
305
+
306
+ export const CopyButton = ({label, value, className=""}) => {
307
+ const [copied, setCopied] = useState(false);
308
+
309
+ return (
310
+ <button
311
+ onClick={() => {
312
+ if(copied) { return; }
313
+
314
+ Copy(value);
315
+
316
+ setCopied(true);
317
+ setTimeout(() => setCopied(false), 100);
318
+ }}
319
+ dangerouslySetInnerHTML={{__html: Icons.CopyIcon}}
320
+ className={[CommonStyles["copy-button"], copied ? CommonStyles["copy-button--copied"] : "", className].join(" ")}
321
+ title={`Copy ${label}`}
322
+ />
323
+ );
324
+ };
325
+
326
+ const ContentDetail = ({label, value, copyable}) => {
327
+ if(!value) { return null; }
328
+
329
+ return (
330
+ <div className={CommonStyles["verification-menu__detail"]}>
331
+ <label className={CommonStyles["verification-menu__detail-label"]}>{label}:</label>
332
+ <div className={CommonStyles["verification-menu__detail-value"]}>{value}</div>
333
+ {
334
+ !copyable ? null :
335
+ <CopyButton
336
+ label={label}
337
+ value={value}
338
+ className={CommonStyles["verification-menu__detail-copy"]}
339
+ />
340
+ }
341
+ </div>
342
+ );
343
+ };
344
+
345
+ export const ContentVerificationMenu = ({player, Hide, className=""}) => {
346
+ const menuRef = createRef();
347
+ const [audit, setAudit] = useState();
348
+ const [showDetails, setShowDetails] = useState(false);
349
+ const [_, setLoaded] = useState(false);
350
+
351
+ useEffect(() => {
352
+ player.__LoadVerificationDetails()
353
+ .then(() => setLoaded(true));
354
+
355
+ const UpdateSettings = () => setAudit(player.controls.ContentVerificationDetails());
356
+
357
+ UpdateSettings();
358
+
359
+ const disposePlayerSettingsListener = player.controls.RegisterSettingsListener(UpdateSettings);
360
+
361
+ return () => disposePlayerSettingsListener && disposePlayerSettingsListener();
362
+ }, []);
363
+
364
+ useEffect(() => {
365
+ if(!menuRef || !menuRef.current) { return; }
366
+
367
+ const RemoveMenuListener = RegisterModal({element: menuRef.current.parentElement, Hide});
368
+
369
+ return () => {
370
+ RemoveMenuListener && RemoveMenuListener();
371
+ };
372
+ }, [menuRef]);
373
+
374
+ if(!audit) {
375
+ return null;
376
+ }
377
+
378
+ let content;
379
+ if(!showDetails) {
380
+ content = (
381
+ <>
382
+ <div className={CommonStyles["verification-menu__group"]}>
383
+ <div dangerouslySetInnerHTML={{__html: Icons.ContentShieldIcon}} className={CommonStyles["verification-menu__group-icon"]} />
384
+ <div className={CommonStyles["verification-menu__group-text"]}>
385
+ <div className={CommonStyles["verification-menu__group-title"]}>
386
+ This content has been verified as authentic
387
+ </div>
388
+ <div className={CommonStyles["verification-menu__group-subtitle"]}>
389
+ Last Verified: { new Date(audit.verifiedAt).toLocaleTimeString(navigator.language || "en-us", {year: "numeric", "month": "long", day: "numeric"}) }
390
+ </div>
391
+ </div>
392
+ </div>
393
+ <div className={CommonStyles["verification-menu__group"]}>
394
+ <div dangerouslySetInnerHTML={{__html: Icons.ContentCredentialsIcon}} className={CommonStyles["verification-menu__group-icon"]} />
395
+ <div className={CommonStyles["verification-menu__group-text"]}>
396
+ <button onClick={() => setShowDetails(true)} className={CommonStyles["verification-menu__group-title"]}>
397
+ View Content Credentials
398
+ <div className={CommonStyles["verification-menu__inline-icon"]} dangerouslySetInnerHTML={{__html: Icons.ChevronRightIcon}} />
399
+ </button>
400
+ </div>
401
+ </div>
402
+ </>
403
+ );
404
+ } else {
405
+ content = (
406
+ <>
407
+ <div className={CommonStyles["verification-menu__group"]}>
408
+ <div dangerouslySetInnerHTML={{__html: Icons.ContentShieldIcon}} className={CommonStyles["verification-menu__group-icon"]} />
409
+ <div className={CommonStyles["verification-menu__group-text"]}>
410
+ <div className={CommonStyles["verification-menu__group-title"]}>
411
+ This content has been verified as authentic
412
+ </div>
413
+ <div className={CommonStyles["verification-menu__group-subtitle"]}>
414
+ Last Verified: { new Date(audit.verifiedAt).toLocaleTimeString(navigator.language || "en-us", {year: "numeric", "month": "long", day: "numeric"}) }
415
+ </div>
416
+ </div>
417
+ </div>
418
+ <div className={CommonStyles["verification-menu__group"]}>
419
+ <div dangerouslySetInnerHTML={{__html: Icons.ContentCredentialsIcon}} className={CommonStyles["verification-menu__group-icon"]} />
420
+ <div className={CommonStyles["verification-menu__group-text"]}>
421
+ <button onClick={() => setShowDetails(true)} className={CommonStyles["verification-menu__group-title"]}>
422
+ Content Credentials
423
+ </button>
424
+ <div className={CommonStyles["verification-menu__group-subtitle"]}>
425
+ Issued by the
426
+ <a href="https://main.net955305.contentfabric.io/config" target="_blank" rel="noreferrer">
427
+ Content Fabric
428
+ </a>
429
+ </div>
430
+ </div>
431
+ </div>
432
+ <div className={CommonStyles["verification-menu__details"]}>
433
+ <ContentDetail label="Content Fabric Object ID" value={audit.details.objectId} copyable />
434
+ <ContentDetail label="Organization Address" value={audit.details.tenantAddress} copyable />
435
+ <ContentDetail label="Organization Name" value={audit.details.tenantName} />
436
+ <ContentDetail label="Owner Address" value={audit.details.ownerAddress} copyable />
437
+ <ContentDetail label="Content Object Contract Address" value={audit.details.address} copyable />
438
+ <ContentDetail label="Content Version Hash" value={audit.details.versionHash} copyable />
439
+ {
440
+ !audit.details.lastCommittedAt ? null :
441
+ <ContentDetail label="Last Commit" value={new Date(audit.details.lastCommittedAt).toLocaleTimeString(navigator.language || "en-us", {year: "numeric", "month": "long", day: "numeric"})} />
442
+ }
443
+ <ContentDetail label="Versions" value={audit.details.versionCount} />
444
+ {
445
+ !audit.details.latestTransactionHash ? null :
446
+ <ContentDetail
447
+ label="Latest Transaction"
448
+ value={
449
+ <a href={audit.details.latestTransactionHashUrl} target="_blank" rel="noreferrer">
450
+ { audit.details.latestTransactionHash }
451
+ </a>
452
+ }
453
+ />
454
+ }
455
+ <ContentDetail label="Signature Algorithm" value={audit.details.signatureMethod} />
456
+ </div>
457
+ </>
458
+ );
459
+ }
460
+
461
+
462
+ return (
463
+ <div ref={menuRef}>
464
+ <div key="menu" role="menu" className={`${CommonStyles["menu"]} ${CommonStyles["verification-menu"]} ${showDetails ? CommonStyles["verification-menu--details"] : ""} ${className}`}>
465
+ { content }
466
+ </div>
467
+ </div>
468
+ );
469
+ };
470
+
291
471
  export const CollectionMenu = ({player, Hide, className=""}) => {
292
472
  const menuRef = createRef();
293
473
  const [collectionInfo, setCollectionInfo] = useState(undefined);
@@ -12,6 +12,7 @@ export const RegisterModal = ({element, Hide}) => {
12
12
  }
13
13
  };
14
14
 
15
+ /*
15
16
  const onFocusOut = () => {
16
17
  // Without timeout, document.activeElement is always body
17
18
  setTimeout(() => {
@@ -25,16 +26,26 @@ export const RegisterModal = ({element, Hide}) => {
25
26
  });
26
27
  };
27
28
 
29
+ */
30
+
31
+ const onClickOut = event => {
32
+ if(!element.contains(event.target)) {
33
+ Hide();
34
+ }
35
+ };
36
+
28
37
  // Wrap handlers in timeout so that the click that spawned the modal does not cause it to close
29
38
  let registerTimeout = setTimeout(() => {
30
39
  document.body.addEventListener("keydown", onEscape);
31
- element.addEventListener("focusout", onFocusOut);
40
+ document.body.addEventListener("click", onClickOut, true);
41
+ //element.addEventListener("focusout", onFocusOut);
32
42
  }, 0);
33
43
 
34
44
  return () => {
35
45
  clearTimeout(registerTimeout);
36
46
  document.body.removeEventListener("keydown", onEscape);
37
- element.removeEventListener("focusout", onFocusOut);
47
+ document.body.removeEventListener("click", onClickOut, true);
48
+ //element.removeEventListener("focusout", onFocusOut);
38
49
  };
39
50
  };
40
51
 
@@ -199,8 +199,8 @@ const PlayerUI = ({target, parameters, InitCallback, ErrorCallback, Unmount, Res
199
199
  tabIndex={0}
200
200
  style={{
201
201
  backgroundColor: parameters.playerOptions.backgroundColor || "transparent",
202
- "--portal-width": `${dimensions.width}px`,
203
- "--portal-height": `${dimensions.height}px`
202
+ "--portal-width": `${shouldRotate ? dimensions.height : dimensions.width}px`,
203
+ "--portal-height": `${shouldRotate ? dimensions.width : dimensions.height}px`
204
204
  }}
205
205
  className={[PlayerStyles["player-container"], shouldRotate ? PlayerStyles["player-container--rotated"] : "", `__eluvio-player--size-${size}`, `__eluvio-player--orientation-${orientation}`].join(" ")}
206
206
  >
@@ -9,7 +9,7 @@ import {ImageUrl, PlayerClick, Time} from "./Common.js";
9
9
  import EluvioPlayerParameters from "../player/PlayerParameters.js";
10
10
 
11
11
  import EluvioLogo from "../static/images/Logo.png";
12
- import {CollectionMenu, SeekBar, SettingsMenu, SVG, VolumeControls} from "./Components.jsx";
12
+ import {CollectionMenu, ContentVerificationMenu, SeekBar, SettingsMenu, SVG, VolumeControls} from "./Components.jsx";
13
13
 
14
14
  export const IconButton = ({icon, ...props}) => {
15
15
  return (
@@ -211,6 +211,34 @@ const InfoBox = ({player, Hide}) => {
211
211
  );
212
212
  };
213
213
 
214
+ const ContentVerificationControls = ({player, setMenuActive}) => {
215
+ const [contentVerified, setContentVerified] = useState(false);
216
+
217
+ useEffect(() => {
218
+ const UpdateVerification = () => setContentVerified(player.controls.ContentVerified());
219
+
220
+ UpdateVerification();
221
+
222
+ player.controls.RegisterSettingsListener(UpdateVerification);
223
+ }, []);
224
+
225
+ if(!contentVerified) {
226
+ return null;
227
+ }
228
+
229
+ return (
230
+ <MenuButton
231
+ label="Content Verification Menu"
232
+ icon={Icons.ContentShieldIcon}
233
+ player={player}
234
+ setMenuActive={setMenuActive}
235
+ MenuComponent={ContentVerificationMenu}
236
+ className={ControlStyles["content-verification-menu-button"]}
237
+ >
238
+ Verified
239
+ </MenuButton>
240
+ );
241
+ };
214
242
 
215
243
  const TVControls = ({player, playbackStarted, canPlay, recentlyInteracted, setRecentUserAction, className=""}) => {
216
244
  const [videoState, setVideoState] = useState(undefined);
@@ -307,6 +335,7 @@ const TVControls = ({player, playbackStarted, canPlay, recentlyInteracted, setRe
307
335
  </div>
308
336
  <CenterButtons player={player} videoState={videoState}/>
309
337
  <div className={ControlStyles["bottom-right-controls"]}>
338
+ <ContentVerificationControls player={player} setMenuActive={setMenuActive} />
310
339
  {
311
340
  !player.controls.GetOptions().hasAnyOptions ? null :
312
341
  <MenuButton
@@ -9,11 +9,11 @@ import {ImageUrl, PlayerClick, Time} from "./Common.js";
9
9
  import EluvioPlayerParameters from "../player/PlayerParameters.js";
10
10
 
11
11
  import EluvioLogo from "../static/images/Logo.png";
12
- import {CollectionMenu, SeekBar, SettingsMenu, VolumeControls} from "./Components.jsx";
12
+ import {CollectionMenu, ContentVerificationMenu, SeekBar, SettingsMenu, VolumeControls} from "./Components.jsx";
13
13
 
14
- export const IconButton = ({icon, ...props}) => {
14
+ export const IconButton = ({icon, className="", ...props}) => {
15
15
  return (
16
- <button {...props} className={`${ControlStyles["icon-button"]} ${props.className || ""}`} dangerouslySetInnerHTML={{__html: icon}} />
16
+ <button {...props} className={`${ControlStyles["icon-button"]} ${className}`} dangerouslySetInnerHTML={{__html: icon}} />
17
17
  );
18
18
  };
19
19
 
@@ -76,11 +76,11 @@ const CollectionControls = ({player}) => {
76
76
  );
77
77
  };
78
78
 
79
- const MenuButton = ({label, icon, player, setMenuActive, MenuComponent}) => {
79
+ const MenuButton = ({label, icon, player, setMenuActive, MenuComponent, className=""}) => {
80
80
  const [show, setShow] = useState(false);
81
81
 
82
82
  return (
83
- <div className={ControlStyles["menu-control-container"]}>
83
+ <div className={[ControlStyles["menu-control-container"], className].join(" ")}>
84
84
  <IconButton
85
85
  aria-label={show ? `Hide ${label} Menu` : label}
86
86
  aria-haspopup
@@ -154,6 +154,38 @@ const ContentInfo = ({player}) => {
154
154
  );
155
155
  };
156
156
 
157
+ const ContentVerificationControls = ({player, setMenuActive}) => {
158
+ const [contentVerified, setContentVerified] = useState(false);
159
+
160
+ useEffect(() => {
161
+ const UpdateVerification = () => setContentVerified(player.controls.ContentVerified());
162
+
163
+ UpdateVerification();
164
+
165
+ player.controls.RegisterSettingsListener(UpdateVerification);
166
+ }, []);
167
+
168
+ if(!contentVerified) {
169
+ return null;
170
+ }
171
+
172
+ return (
173
+ <>
174
+ <div className={ControlStyles["content-verified-badge"]}>
175
+ VERIFIED
176
+ </div>
177
+ <MenuButton
178
+ label="Content Verification Menu"
179
+ icon={Icons.ContentShieldIcon}
180
+ player={player}
181
+ setMenuActive={setMenuActive}
182
+ MenuComponent={ContentVerificationMenu}
183
+ className={ControlStyles["content-verification-menu-button"]}
184
+ />
185
+ </>
186
+ );
187
+ };
188
+
157
189
  const WebControls = ({player, playbackStarted, canPlay, recentlyInteracted, setRecentUserAction, className=""}) => {
158
190
  const [videoState, setVideoState] = useState(undefined);
159
191
  const [playerClickHandler, setPlayerClickHandler] = useState(undefined);
@@ -226,6 +258,11 @@ const WebControls = ({player, playbackStarted, canPlay, recentlyInteracted, setR
226
258
 
227
259
  <div className={ControlStyles["spacer"]}/>
228
260
 
261
+ <ContentVerificationControls
262
+ player={player}
263
+ setMenuActive={setMenuActive}
264
+ />
265
+
229
266
  {
230
267
  !collectionInfo ? null :
231
268
  <MenuButton
@@ -242,6 +279,7 @@ const WebControls = ({player, playbackStarted, canPlay, recentlyInteracted, setR
242
279
  aria-label="Rotate Video"
243
280
  icon={Icons.RotateIcon}
244
281
  onClick={() => player.controls.SetAllowRotation(!player.controls.AllowRotation())}
282
+ className={ControlStyles["right-control-button"]}
245
283
  />
246
284
  }
247
285
  {
@@ -258,6 +296,7 @@ const WebControls = ({player, playbackStarted, canPlay, recentlyInteracted, setR
258
296
  aria-label={videoState.fullscreen ? "Exit Fullscreen" : "Fullscreen"}
259
297
  icon={videoState.fullscreen ? Icons.ExitFullscreenIcon : Icons.FullscreenIcon}
260
298
  onClick={() => player.controls.ToggleFullscreen()}
299
+ className={ControlStyles["right-control-button"]}
261
300
  />
262
301
  </div>
263
302
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-player-js",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "![Eluvio Logo](lib/static/images/Logo.png \"Eluvio Logo\")",
5
5
  "main": "lib/index.js",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "package-lock.json"
37
37
  ],
38
38
  "dependencies": {
39
- "@eluvio/elv-client-js": "^4.0.76",
39
+ "@eluvio/elv-client-js": "^4.0.87",
40
40
  "dashjs": "~4.7.0",
41
41
  "focus-visible": "^5.2.0",
42
42
  "hls.js": "~1.4.12",