@eluvio/elv-player-js 2.0.30 → 2.0.31

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.
@@ -1053,6 +1053,26 @@ class PlayerControls {
1053
1053
  Destroy() {
1054
1054
  this.player.Destroy();
1055
1055
  }
1056
+
1057
+ GetDebugInfo() {
1058
+ const sourceOptions = JSON.parse(JSON.stringify(this.player.sourceOptions));
1059
+ delete sourceOptions.playoutOptions;
1060
+
1061
+ let error = this.player.__error && JSON.parse(JSON.stringify(this.player.__error));
1062
+ if(error && Object.keys(error).length === 0) {
1063
+ error = this.player.__error.toString();
1064
+ }
1065
+
1066
+ return {
1067
+ appUrl: window.location.href,
1068
+ userAddress: this.player.userAddress,
1069
+ userAgent: navigator.userAgent,
1070
+ userLanguage: navigator.language,
1071
+ playoutUrl: this.player.playoutUrl,
1072
+ sourceOptions,
1073
+ error
1074
+ };
1075
+ }
1056
1076
  }
1057
1077
 
1058
1078
  export default PlayerControls;
@@ -48,7 +48,6 @@ export class EluvioPlayer {
48
48
  this.loading = true;
49
49
  this.target = target;
50
50
  this.video = video;
51
- this.SetErrorMessage = SetErrorMessage;
52
51
  this.controls = new PlayerControls({player: this});
53
52
  this.__settingsListeners = [];
54
53
  this.__listenerDisposers = [];
@@ -68,6 +67,12 @@ export class EluvioPlayer {
68
67
  EluvioPlayerParameters.controls.ON,
69
68
  EluvioPlayerParameters.controls.AUTO_HIDE
70
69
  ].includes(parameters.playerOptions.controls);
70
+ this.__error = undefined;
71
+
72
+ this.SetErrorMessage = (message, error) => {
73
+ SetErrorMessage(message);
74
+ this.__error = error;
75
+ };
71
76
 
72
77
  try {
73
78
  // If custom HLS parameters are specified, set profile to custom
@@ -124,6 +129,8 @@ export class EluvioPlayer {
124
129
  this.__tokenResolved = true;
125
130
  }
126
131
 
132
+ this.userAddress = this.clientOptions.client.CurrentAccountAddress();
133
+
127
134
  return this.clientOptions.client;
128
135
  }
129
136
 
@@ -326,8 +333,6 @@ export class EluvioPlayer {
326
333
 
327
334
  this.__Reset();
328
335
 
329
-
330
-
331
336
  this.initialized = false;
332
337
  this.loading = true;
333
338
  this.initTime = Date.now();
@@ -401,6 +406,10 @@ export class EluvioPlayer {
401
406
  this.video.textTracks && this.video.textTracks.addEventListener("change", () => this.__SettingsUpdate());
402
407
  this.video.audioTracks && this.video.audioTracks.addEventListener("change", () => this.__SettingsUpdate());
403
408
 
409
+ if(this.playerOptions.startTime) {
410
+ this.controls.Seek({time: this.playerOptions.startTime});
411
+ }
412
+
404
413
  if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON) {
405
414
  this.controls.Play();
406
415
  }
@@ -515,7 +524,7 @@ export class EluvioPlayer {
515
524
 
516
525
  if(permissionErrorMessage) {
517
526
  error.permission_message = permissionErrorMessage;
518
- this.SetErrorMessage(permissionErrorMessage);
527
+ this.SetErrorMessage(permissionErrorMessage, error);
519
528
 
520
529
  if(typeof error === "object") {
521
530
  error.permission_message = permissionErrorMessage;
@@ -523,11 +532,11 @@ export class EluvioPlayer {
523
532
  this.Log(permissionErrorMessage, true);
524
533
  }
525
534
  } else {
526
- this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
535
+ this.SetErrorMessage(error.displayMessage || "Insufficient permissions", error);
527
536
  }
528
537
  // eslint-disable-next-line no-empty
529
538
  } catch (error) {
530
- this.SetErrorMessage(error.displayMessage || "Insufficient permissions");
539
+ this.SetErrorMessage(error.displayMessage || "Insufficient permissions", error);
531
540
  }
532
541
  } else if(error.status >= 500) {
533
542
  this.__HardReload(error, 10000);
@@ -721,7 +730,7 @@ export class EluvioPlayer {
721
730
 
722
731
  if(error.response && error.response.code === 403) {
723
732
  // Not allowed to access
724
- this.SetErrorMessage("Insufficient permissions");
733
+ this.SetErrorMessage("Insufficient permissions", error);
725
734
  } else if(this.errors < 5) {
726
735
  if(error.fatal) {
727
736
  if(error.data && error.data.type === this.HLS.ErrorTypes.MEDIA_ERROR) {
@@ -1119,6 +1128,7 @@ export class EluvioPlayer {
1119
1128
  this.airplayAvailable = false;
1120
1129
  this.chromecastAvailable = chromecastAvailable && this.playerOptions.allowCasting;
1121
1130
  this.casting = false;
1131
+ this.__error = undefined;
1122
1132
  }
1123
1133
 
1124
1134
  async __HardReload(error, delay=6000) {
@@ -1128,7 +1138,7 @@ export class EluvioPlayer {
1128
1138
  this.reloads += 1;
1129
1139
 
1130
1140
  if(this.reloads > 10) {
1131
- this.SetErrorMessage((error && error.displayMessage) || "Unable to play content");
1141
+ this.SetErrorMessage((error && error.displayMessage) || "Unable to play content", error);
1132
1142
  return;
1133
1143
  }
1134
1144
 
@@ -1147,7 +1157,10 @@ export class EluvioPlayer {
1147
1157
  }
1148
1158
  }
1149
1159
 
1150
- this.SetErrorMessage((error && error.displayMessage) || "Something went wrong, reloading player...");
1160
+ this.SetErrorMessage(
1161
+ (error && error.displayMessage) || "Something went wrong, reloading player...",
1162
+ error
1163
+ );
1151
1164
  await new Promise(resolve => setTimeout(resolve, delay));
1152
1165
 
1153
1166
  if(this.__destroyed) { return; }
@@ -139,11 +139,6 @@ export const DefaultParameters = {
139
139
  liveDVR: PlayerParameters.liveDVR.OFF,
140
140
  headers: []
141
141
  },
142
- mediaCollectionOptions: {
143
- mediaCatalogObjectId: undefined,
144
- mediaCatalogVersionHash: undefined,
145
- collectionId: undefined
146
- },
147
142
  playoutOptions: undefined,
148
143
  playoutParameters: {
149
144
  objectId: undefined,
@@ -180,6 +175,7 @@ export const DefaultParameters = {
180
175
  showLoader: PlayerParameters.showLoader.ON,
181
176
  permanentPoster: PlayerParameters.permanentPoster.OFF,
182
177
  allowCasting: PlayerParameters.allowCasting.ON,
178
+ startTime: undefined,
183
179
  hlsjsOptions: undefined,
184
180
  dashjsOptions: undefined,
185
181
  debugLogging: false,
@@ -34,3 +34,4 @@ export const ContentCredentialsIcon = "<svg width=\"26\" height=\"26\" viewBox=\
34
34
  export const CopyIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-copy\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>";
35
35
  export const AirplayIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-airplay\"><path d=\"M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1\"></path><polygon points=\"12 15 17 21 7 21 12 15\"></polygon></svg>";
36
36
  export const ChromecastIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-cast\"><path d=\"M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6\"></path><line x1=\"2\" y1=\"20\" x2=\"2.01\" y2=\"20\"></line></svg>";
37
+ export const InfoIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-info\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"></line><line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"></line></svg>";
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
@@ -9,14 +9,15 @@
9
9
  --layer-error: 4;
10
10
  --layer-controls: 5;
11
11
 
12
- --color-highlight: #3784eb;
13
- --color-error: #FFF;
14
-
12
+ font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
15
13
  height: 100%;
16
14
  inset: 0;
17
15
  position: absolute;
18
16
  width: 100%;
19
17
 
18
+ --color-highlight: #3784eb;
19
+ --color-error: #FFF;
20
+
20
21
  &--rotated {
21
22
  height: 100vw;
22
23
  margin-top: -100vw;
@@ -88,8 +89,8 @@
88
89
  align-items: center;
89
90
  color: var(--color-error);
90
91
  display: flex;
91
- font-size: 24px;
92
- font-weight: 300;
92
+ flex-direction: column;
93
+ gap: 30px;
93
94
  height: 100%;
94
95
  justify-content: center;
95
96
  left: 0;
@@ -98,6 +99,35 @@
98
99
  top: 0;
99
100
  width: 100%;
100
101
  z-index: var(--layer-error);
102
+
103
+ &__message {
104
+ font-size: 24px;
105
+ font-weight: 300;
106
+ }
107
+
108
+ &__copy-debug-button {
109
+ align-items: center;
110
+ background-color: var(--color-error);
111
+ border: 1px solid var(--color-error);
112
+ border-radius: 5px;
113
+ color: #000;
114
+ display: flex;
115
+ gap: 10px;
116
+ height: 45px;
117
+ opacity: 0.9;
118
+ padding: 10px 20px;
119
+ transition: opacity 0.15s ease;
120
+
121
+ &:hover {
122
+ opacity: 1;
123
+ transition: opacity 0.35s ease;
124
+ }
125
+ }
126
+
127
+ &__icon {
128
+ height: 18px;
129
+ width: 18px;
130
+ }
101
131
  }
102
132
 
103
133
  :global(.__eluvio-player--size-md),
@@ -36,7 +36,8 @@ const iconSource = {
36
36
  ContentCredentialsIcon: Path.resolve(__dirname, "../static/icons/svgs/content-credentials.svg"),
37
37
  CopyIcon: Path.resolve(__dirname, "../static/icons/svgs/copy.svg"),
38
38
  AirplayIcon: Path.resolve(__dirname, "../static/icons/svgs/airplay.svg"),
39
- ChromecastIcon: Path.resolve(__dirname, "../static/icons/svgs/cast.svg")
39
+ ChromecastIcon: Path.resolve(__dirname, "../static/icons/svgs/cast.svg"),
40
+ InfoIcon: Path.resolve(__dirname, "../static/icons/svgs/info.svg")
40
41
  };
41
42
 
42
43
  let iconFile = "// WARNING: Do not edit this file manually\n";
@@ -17,6 +17,20 @@ export const Spinner = ({light, className=""}) => (
17
17
 
18
18
  export const SVG = ({icon, className=""}) => <div className={`${CommonStyles["svg"]} ${className}`} dangerouslySetInnerHTML={{__html: icon}} />;
19
19
 
20
+ export const Copy = async (value) => {
21
+ try {
22
+ value = (value || "").toString();
23
+
24
+ await navigator.clipboard.writeText(value);
25
+ } catch(error) {
26
+ const input = document.createElement("input");
27
+
28
+ input.value = value;
29
+ input.select();
30
+ input.setSelectionRange(0, 99999);
31
+ document.execCommand("copy");
32
+ }
33
+ };
20
34
 
21
35
  const icons = {
22
36
  [ACTIONS.PLAY]: Icons.PlayIcon,
@@ -209,6 +223,15 @@ export const SettingsMenu = ({player, Hide, className=""}) => {
209
223
  rate: {
210
224
  label: "Playback Rate",
211
225
  Update: index => player.controls.SetPlaybackRate({index})
226
+ },
227
+ advanced: {
228
+ label: "Advanced",
229
+ Update: option => {
230
+ if(option === "copy_debug_info") {
231
+ Copy(JSON.stringify(player.controls.GetDebugInfo(), null, 2));
232
+ Hide();
233
+ }
234
+ }
212
235
  }
213
236
  };
214
237
 
@@ -225,23 +248,36 @@ export const SettingsMenu = ({player, Hide, className=""}) => {
225
248
  <div>{ settings[activeMenu].label }</div>
226
249
  </button>
227
250
  {
228
- options[activeMenu].options.map(option =>
251
+ activeMenu === "advanced" ?
229
252
  <button
230
- key={`option-${option.index}`}
231
- role="menuitemradio"
232
- aria-checked={option.active}
233
- autoFocus={option.active}
234
- aria-label={`${settings[activeMenu].label}: ${option.label || ""}`}
253
+ role="button"
254
+ autoFocus
255
+ aria-label="Copy Debug Info"
235
256
  onClick={() => {
236
- settings[activeMenu].Update(option.index);
257
+ settings[activeMenu].Update("copy_debug_info");
237
258
  SetSubmenu(undefined);
238
259
  }}
239
- className={`${CommonStyles["menu-option"]} ${option.active ? CommonStyles["menu-option-active"] : ""}`}
260
+ className={CommonStyles["menu-option"]}
240
261
  >
241
- { option.label || "" }
242
- { option.active ? <SVG icon={Icons.CheckmarkIcon} className={CommonStyles["menu-option-icon"]} /> : null }
243
- </button>
244
- )
262
+ Copy Debug Info
263
+ </button> :
264
+ options[activeMenu].options.map(option =>
265
+ <button
266
+ key={`option-${option.index}`}
267
+ role="menuitemradio"
268
+ aria-checked={option.active}
269
+ autoFocus={option.active}
270
+ aria-label={`${settings[activeMenu].label}: ${option.label || ""}`}
271
+ onClick={() => {
272
+ settings[activeMenu].Update(option.index);
273
+ SetSubmenu(undefined);
274
+ }}
275
+ className={`${CommonStyles["menu-option"]} ${option.active ? CommonStyles["menu-option-active"] : ""}`}
276
+ >
277
+ {option.label || ""}
278
+ {option.active ? <SVG icon={Icons.CheckmarkIcon} className={CommonStyles["menu-option-icon"]}/> : null}
279
+ </button>
280
+ )
245
281
  }
246
282
  </div>
247
283
  );
@@ -250,47 +286,79 @@ export const SettingsMenu = ({player, Hide, className=""}) => {
250
286
  <div key="menu" role="menu" className={`${CommonStyles["menu"]} ${className}`}>
251
287
  {
252
288
  !options.hasQualityOptions ? null :
253
- <button autoFocus role="menuitem" onClick={() => SetSubmenu("quality")}
254
- className={CommonStyles["menu-option"]}>
289
+ <button
290
+ autoFocus role="menuitem"
291
+ onClick={() => SetSubmenu("quality")}
292
+ className={CommonStyles["menu-option"]}
293
+ >
255
294
  {`${settings.quality.label}: ${(options.quality.active && options.quality.active.activeLabel) || ""}`}
256
295
  <SVG icon={Icons.ChevronRightIcon} className={CommonStyles["menu-option-icon"]}/>
257
296
  </button>
258
297
  }
259
298
  {
260
299
  !options.hasAudioOptions ? null :
261
- <button autoFocus={!options.hasQualityOptions} role="menuitem" onClick={() => SetSubmenu("audio")} className={CommonStyles["menu-option"]}>
300
+ <button
301
+ autoFocus={!options.hasQualityOptions}
302
+ role="menuitem"
303
+ onClick={() => SetSubmenu("audio")}
304
+ className={CommonStyles["menu-option"]}
305
+ >
262
306
  {`${settings.audio.label}: ${(options.audio.active && options.audio.active.label) || ""}`}
263
307
  <SVG icon={Icons.ChevronRightIcon} className={CommonStyles["menu-option-icon"]}/>
264
308
  </button>
265
309
  }
266
310
  {
267
311
  !options.hasTextOptions ? null :
268
- <button autoFocus={!options.hasQualityOptions && !options.hasAudioOptions} role="menuitem" onClick={() => SetSubmenu("text")} className={CommonStyles["menu-option"]}>
312
+ <button
313
+ autoFocus={!options.hasQualityOptions && !options.hasAudioOptions}
314
+ role="menuitem"
315
+ onClick={() => SetSubmenu("text")}
316
+ className={CommonStyles["menu-option"]}
317
+ >
269
318
  {`${settings.text.label}: ${(options.text.active && options.text.active.label) || ""}`}
270
319
  <SVG icon={Icons.ChevronRightIcon} className={CommonStyles["menu-option-icon"]}/>
271
320
  </button>
272
321
  }
273
322
  {
274
323
  !options.hasProfileOptons ? null :
275
- <button autoFocus={!options.hasQualityOptions && !options.hasAudioOptions && !options.hasTextOptions} role="menuitem" onClick={() => SetSubmenu("profile")} className={CommonStyles["menu-option"]}>
324
+ <button
325
+ autoFocus={!options.hasQualityOptions && !options.hasAudioOptions && !options.hasTextOptions}
326
+ role="menuitem"
327
+ onClick={() => SetSubmenu("profile")}
328
+ className={CommonStyles["menu-option"]}
329
+ >
276
330
  {`${settings.profile.label}: ${(options.profile.active && options.profile.active.label) || ""}`}
277
331
  <SVG icon={Icons.ChevronRightIcon} className={CommonStyles["menu-option-icon"]}/>
278
332
  </button>
279
333
  }
280
334
  {
281
335
  !options.hasRateOptions ? null :
282
- <button autoFocus={!options.hasQualityOptions && !options.hasAudioOptions && !options.hasTextOptions && !options.hasProfileOptons} role="menuitem" onClick={() => SetSubmenu("rate")} className={CommonStyles["menu-option"]}>
336
+ <button
337
+ autoFocus={!options.hasQualityOptions && !options.hasAudioOptions && !options.hasTextOptions && !options.hasProfileOptons}
338
+ role="menuitem"
339
+ onClick={() => SetSubmenu("rate")}
340
+ className={CommonStyles["menu-option"]}
341
+ >
283
342
  {`${settings.rate.label}: ${(options.rate.active && options.rate.active.label) || ""}`}
284
343
  <SVG icon={Icons.ChevronRightIcon} className={CommonStyles["menu-option-icon"]}/>
285
344
  </button>
286
345
  }
346
+ <button
347
+ autoFocus={!options.hasRateOptions && !options.hasQualityOptions && !options.hasAudioOptions && !options.hasTextOptions && !options.hasProfileOptons}
348
+ role="button"
349
+ onClick={() => SetSubmenu("advanced")}
350
+ className={CommonStyles["menu-option"]}
351
+ >
352
+ { settings.advanced.label }
353
+ <SVG icon={Icons.ChevronRightIcon} className={CommonStyles["menu-option-icon"]}/>
354
+ </button>
287
355
  </div>
288
356
  );
289
357
  }
290
358
 
291
359
  return (
292
360
  <div ref={menuRef}>
293
- { content }
361
+ {content}
294
362
  </div>
295
363
  );
296
364
  };
@@ -302,7 +370,7 @@ export const DVRToggle = ({player}) => {
302
370
  const disposer = player.controls.RegisterSettingsListener(() => {
303
371
  if(!player.controls) { return; }
304
372
 
305
- setDVREnabled(player.controls.IsDVREnabled())
373
+ setDVREnabled(player.controls.IsDVREnabled());
306
374
  });
307
375
 
308
376
  return () => disposer && disposer();
@@ -327,21 +395,6 @@ export const DVRToggle = ({player}) => {
327
395
  );
328
396
  };
329
397
 
330
- export const Copy = async (value) => {
331
- try {
332
- value = (value || "").toString();
333
-
334
- await navigator.clipboard.writeText(value);
335
- } catch(error) {
336
- const input = document.createElement("input");
337
-
338
- input.value = value;
339
- input.select();
340
- input.setSelectionRange(0, 99999);
341
- document.execCommand("copy");
342
- }
343
- };
344
-
345
398
  export const CopyButton = ({label, value, className=""}) => {
346
399
  const [copied, setCopied] = useState(false);
347
400
 
@@ -15,11 +15,12 @@ import {
15
15
  } from "./Observers.js";
16
16
  import WebControls from "./WebControls.jsx";
17
17
  import TicketForm from "./TicketForm.jsx";
18
- import {Spinner, UserActionIndicator} from "./Components.jsx";
18
+ import {Copy, Spinner, SVG, UserActionIndicator} from "./Components.jsx";
19
19
  import TVControls from "./TVControls.jsx";
20
20
  import PlayerProfileForm from "./PlayerProfileForm.jsx";
21
21
  import {ImageUrl, MergeDefaultParameters} from "./Common.js";
22
22
  import {ChromecastIcon} from "../static/icons/Icons.js";
23
+ import * as Icons from "../static/icons/Icons";
23
24
 
24
25
  const Poster = ({player}) => {
25
26
  const [imageUrl, setImageUrl] = useState(undefined);
@@ -249,7 +250,18 @@ const PlayerUI = ({target, parameters, InitCallback, ErrorCallback, Unmount, Res
249
250
  }
250
251
  {
251
252
  !errorMessage ? null :
252
- <div className={PlayerStyles["error-message"]}>{ errorMessage }</div>
253
+ <div className={PlayerStyles["error-message"]}>
254
+ <div className={PlayerStyles["error-message__message"]}>
255
+ { errorMessage }
256
+ </div>
257
+ <button
258
+ className={PlayerStyles["error-message__copy-debug-button"]}
259
+ onClick={() => Copy(JSON.stringify(player.controls.GetDebugInfo(), null, 2))}
260
+ >
261
+ <SVG icon={Icons.InfoIcon} className={PlayerStyles["error-message__icon"]}/>
262
+ Copy Troubleshooting Info
263
+ </button>
264
+ </div>
253
265
  }
254
266
  {
255
267
  !player ? null :
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-player-js",
3
- "version": "2.0.30",
3
+ "version": "2.0.31",
4
4
  "description": "![Eluvio Logo](lib/static/images/Logo.png \"Eluvio Logo\")",
5
5
  "main": "dist/elv-player-js.es.js",
6
6
  "license": "MIT",