@eluvio/elv-player-js 1.0.85 → 1.0.87

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-player-js",
3
- "version": "1.0.85",
3
+ "version": "1.0.87",
4
4
  "description": "![Eluvio Logo](src/static/images/Logo.png \"Eluvio Logo\")",
5
5
  "main": "src/index.js",
6
6
  "license": "MIT",
@@ -8,6 +8,7 @@ import MutedIcon from "./static/icons/media/no volume icon.svg";
8
8
  import VolumeLowIcon from "./static/icons/media/low volume icon.svg";
9
9
  import VolumeHighIcon from "./static/icons/media/Volume icon.svg";
10
10
  import MultiViewIcon from "./static/icons/multiview.svg";
11
+ import LeftArrow from "./static/icons/arrow-left.svg";
11
12
 
12
13
  import Logo from "./static/images/ELUV.IO white 20 px V2.png";
13
14
 
@@ -122,7 +123,6 @@ class PlayerControls {
122
123
  };
123
124
  }
124
125
 
125
- this.settingsMenuContent = "none";
126
126
  this.timeouts = {};
127
127
  this.played = false;
128
128
  this.controlsHover = false;
@@ -487,7 +487,11 @@ class PlayerControls {
487
487
  classes: ["eluvio-player__controls__slider-container__input", "eluvio-player__controls__progress-slider"]
488
488
  });
489
489
 
490
- progressSlider.addEventListener("input", () => this.video.currentTime = this.video.duration * parseFloat(progressSlider.value));
490
+ progressSlider.addEventListener("input", () => {
491
+ if(!this.video.duration) { return; }
492
+
493
+ this.video.currentTime = this.video.duration * parseFloat(progressSlider.value || 0);
494
+ });
491
495
 
492
496
  // Progress Bar
493
497
  const progressBar = CreateElement({
@@ -561,6 +565,7 @@ class PlayerControls {
561
565
  type: "div",
562
566
  classes: ["eluvio-player__controls__settings-menu", "eluvio-player__controls__settings-menu-hidden"]
563
567
  });
568
+ this.settingsMenu.setAttribute("data-mode", "hidden");
564
569
 
565
570
  this.target.addEventListener("keydown", event => event && (event.key || "").toLowerCase() === "escape" && this.HideSettingsMenu());
566
571
 
@@ -658,93 +663,83 @@ class PlayerControls {
658
663
  }
659
664
  }
660
665
 
661
- HideSettingsMenu() {
662
- if(this.settingsMenuContent === "settings") {
663
- this.settingsButton.focus();
664
- } else if(this.settingsMenuContent === "multiview") {
665
- this.multiviewButton.focus();
666
- }
667
-
668
- this.settingsMenu.innerHTML = "";
669
- this.settingsMenu.classList.add("eluvio-player__controls__settings-menu-hidden");
670
- this.settingsMenuContent = "none";
671
- }
666
+ AddSetting({Retrieve, Set}) {
667
+ if(!Retrieve) { return; }
672
668
 
673
- ToggleSettings() {
674
- this.settingsMenu.innerHTML = "";
669
+ const { label, options } = Retrieve();
675
670
 
676
- if(this.settingsMenuContent === "settings") {
677
- this.HideSettingsMenu();
678
- return;
679
- }
671
+ const currentOption = options.find(option => option.active);
680
672
 
681
- this.settingsMenuContent = "settings";
682
- this.settingsMenu.classList.remove("eluvio-player__controls__settings-menu-hidden");
673
+ const optionSelectionButton = CreateElement({
674
+ parent: this.settingsMenu,
675
+ type: Set ? "button" : "div",
676
+ classes: ["eluvio-player__controls__settings-menu__option"]
677
+ });
683
678
 
684
- // Resolution settings
685
- if(this.GetLevels) {
686
- const levels = this.GetLevels();
679
+ optionSelectionButton.innerHTML = currentOption && currentOption.activeLabel || label;
687
680
 
688
- const currentLevel = levels.find(level => level.active);
681
+ if(Set) {
682
+ optionSelectionButton.addEventListener("click", () => {
683
+ this.settingsMenu.innerHTML = "";
684
+ this.settingsMenu.setAttribute("data-mode", "settings-submenu");
689
685
 
690
- if(currentLevel) {
691
- const resolutionButton = CreateElement({
686
+ const backButton = CreateElement({
692
687
  parent: this.settingsMenu,
693
- type: this.SetLevel ? "button" : "div",
694
- classes: ["eluvio-player__controls__settings-menu__option"]
688
+ type: "button",
689
+ classes: ["eluvio-player__controls__settings-menu__option", "eluvio-player__controls__settings-menu__option-back"],
695
690
  });
696
691
 
697
- if(currentLevel.audioTrack) {
698
- resolutionButton.innerHTML = `Quality: ${currentLevel.bitrate / 1000}kbps`;
699
- } else {
700
- resolutionButton.innerHTML = `Resolution: ${currentLevel.resolution}`;
701
- }
692
+ CreateElement({
693
+ parent: backButton,
694
+ type: "svg"
695
+ }).innerHTML = LeftArrow;
702
696
 
703
- if(this.SetLevel) {
704
- resolutionButton.addEventListener("click", () => {
705
- this.settingsMenu.innerHTML = "";
697
+ CreateElement({
698
+ parent: backButton,
699
+ }).innerHTML = label;
706
700
 
707
- const autoLevel = CreateElement({
701
+ backButton.addEventListener("click", () => this.ShowSettingsMenu());
702
+
703
+ options
704
+ .forEach(option => {
705
+ const optionButton = CreateElement({
708
706
  parent: this.settingsMenu,
709
707
  type: "button",
710
- classes: ["eluvio-player__controls__settings-menu__option"]
708
+ classes: ["eluvio-player__controls__settings-menu__option", option.active ? "eluvio-player__controls__settings-menu__option-selected" : ""]
711
709
  });
712
710
 
713
- autoLevel.innerHTML = "Auto";
714
- autoLevel.addEventListener("click", () => {
715
- this.SetLevel(-1);
711
+ optionButton.innerHTML = option.label;
712
+
713
+ optionButton.addEventListener("click", () => {
714
+ Set(option.index);
716
715
  this.HideSettingsMenu();
717
716
  });
718
-
719
- levels
720
- .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1)
721
- .forEach(level => {
722
- const levelOption = CreateElement({
723
- parent: this.settingsMenu,
724
- type: "button",
725
- classes: ["eluvio-player__controls__settings-menu__option", level.active ? "eluvio-player__controls__settings-menu__option-selected" : ""]
726
- });
727
-
728
- if(level.audioTrack) {
729
- levelOption.innerHTML = `${level.bitrate / 1000}kbps`;
730
- } else {
731
- levelOption.innerHTML = `${level.resolution} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`;
732
- }
733
-
734
- levelOption.addEventListener("click", () => {
735
- this.SetLevel(level.index);
736
- this.HideSettingsMenu();
737
- });
738
- });
739
-
740
- // Focus on first element in list when menu opened
741
- const firstItem = this.settingsMenu.querySelector("button");
742
- if(firstItem) {
743
- firstItem.focus();
744
- }
745
717
  });
718
+
719
+ // Focus on first element in list when menu opened
720
+ const firstItem = this.settingsMenu.querySelector("button");
721
+ if(firstItem) {
722
+ firstItem.focus();
746
723
  }
747
- }
724
+ });
725
+ }
726
+ }
727
+
728
+ ShowSettingsMenu() {
729
+ this.settingsMenu.innerHTML = "";
730
+ this.settingsMenu.classList.remove("eluvio-player__controls__settings-menu-hidden");
731
+ this.settingsMenu.setAttribute("data-mode", "settings");
732
+
733
+ if(this.GetLevels) {
734
+ this.AddSetting({Retrieve: this.GetLevels, Set: this.SetLevel});
735
+ }
736
+
737
+ if(this.GetAudioTracks) {
738
+ this.AddSetting({Retrieve: this.GetAudioTracks, Set: this.SetAudioTrack});
739
+ }
740
+
741
+ if(this.GetTextTracks) {
742
+ this.AddSetting({Retrieve: this.GetTextTracks, Set: this.SetTextTrack});
748
743
  }
749
744
 
750
745
  // Focus on first element in list when menu opened
@@ -754,30 +749,75 @@ class PlayerControls {
754
749
  }
755
750
  }
756
751
 
757
- InitializeSettingsButton() {
758
- if(this.settingsButton) { return; }
752
+ HideSettingsMenu() {
753
+ const mode = this.settingsMenu.dataset.mode;
754
+ if(mode === "settings") {
755
+ this.settingsButton.focus();
756
+ } else if(mode === "multiview") {
757
+ this.multiviewButton.focus();
758
+ }
759
759
 
760
- this.settingsButton = CreateImageButton({
761
- parent: this.rightButtonsContainer,
762
- svg: SettingsIcon,
763
- classes: ["eluvio-player__controls__button-settings"],
764
- prepend: true,
765
- label: "Settings"
766
- });
760
+ this.settingsMenu.innerHTML = "";
761
+ this.settingsMenu.classList.add("eluvio-player__controls__settings-menu-hidden");
762
+ this.settingsMenu.setAttribute("data-mode", "hidden");
763
+ }
764
+
765
+ UpdateSettings() {
766
+ if(!this.settingsButton) {
767
+ this.settingsButton = CreateImageButton({
768
+ parent: this.rightButtonsContainer,
769
+ svg: SettingsIcon,
770
+ classes: ["eluvio-player__controls__button-settings"],
771
+ prepend: true,
772
+ label: "Settings"
773
+ });
774
+
775
+ this.settingsButton.addEventListener("click", () => {
776
+ this.settingsMenu.dataset.mode === "hidden" ?
777
+ this.ShowSettingsMenu() :
778
+ this.HideSettingsMenu();
779
+ });
780
+ }
767
781
 
768
- this.settingsButton.addEventListener("click", () => this.ToggleSettings());
782
+ if(this.settingsMenu.dataset.mode === "settings") {
783
+ this.ShowSettingsMenu();
784
+ }
769
785
  }
770
786
 
771
- SetQualityControls({GetLevels, SetLevel}) {
787
+ SetAudioTrackControls({GetAudioTracks, SetAudioTrack}) {
772
788
  if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
773
789
  // Controls disabled
774
790
  return;
775
791
  }
776
792
 
777
- this.InitializeSettingsButton();
793
+ this.GetAudioTracks = GetAudioTracks;
794
+ this.SetAudioTrack = SetAudioTrack;
795
+
796
+ this.UpdateSettings();
797
+ }
798
+
799
+ SetTextTrackControls({GetTextTracks, SetTextTrack}) {
800
+ if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
801
+ // Controls disabled
802
+ return;
803
+ }
804
+
805
+ this.GetTextTracks = GetTextTracks;
806
+ this.SetTextTrack = SetTextTrack;
807
+
808
+ this.UpdateSettings();
809
+ }
810
+
811
+ SetQualityControls({GetLevels, SetLevel}) {
812
+ if([EluvioPlayerParameters.controls.OFF, EluvioPlayerParameters.controls.OFF_WITH_VOLUME_TOGGLE, EluvioPlayerParameters.controls.DEFAULT].includes(this.playerOptions.controls)) {
813
+ // Controls disabled
814
+ return;
815
+ }
778
816
 
779
817
  this.GetLevels = GetLevels;
780
818
  this.SetLevel = SetLevel;
819
+
820
+ this.UpdateSettings();
781
821
  }
782
822
 
783
823
  InitializeMultiViewControls({AvailableViews, SwitchView}) {
@@ -823,12 +863,12 @@ class PlayerControls {
823
863
  async ToggleMultiviewControls({AvailableViews, SwitchView}={}) {
824
864
  this.settingsMenu.innerHTML = "";
825
865
 
826
- if(this.settingsMenuContent === "multiview") {
866
+ if(this.settingsMenu.dataset.mode === "multiview") {
827
867
  this.HideSettingsMenu();
828
868
  return;
829
869
  }
830
870
 
831
- this.settingsMenuContent = "multiview";
871
+ this.settingsMenu.setAttribute("data-mode", "multiview");
832
872
  this.settingsMenu.classList.remove("eluvio-player__controls__settings-menu-hidden");
833
873
 
834
874
  const views = await AvailableViews();
package/src/index.js CHANGED
@@ -118,7 +118,7 @@ const DefaultParameters = {
118
118
  hlsjsOptions: undefined,
119
119
  dashjsOptions: undefined,
120
120
  // eslint-disable-next-line no-unused-vars
121
- playerCallback: ({videoElement, hlsPlayer, dashPlayer, posterUrl}) => {},
121
+ playerCallback: ({player, videoElement, hlsPlayer, dashPlayer, posterUrl}) => {},
122
122
  errorCallback: (error) => {
123
123
  // eslint-disable-next-line no-console
124
124
  console.error("ELUVIO PLAYER: Error");
@@ -138,6 +138,8 @@ export class EluvioPlayer {
138
138
  this.DetectRemoval = this.DetectRemoval.bind(this);
139
139
 
140
140
  this.Initialize(target, parameters);
141
+
142
+ window.EluvioPlayer = this;
141
143
  }
142
144
 
143
145
  Log(message, error=false) {
@@ -269,6 +271,7 @@ export class EluvioPlayer {
269
271
  const imageMetadata = await client.ContentObjectMetadata({
270
272
  versionHash: targetHash,
271
273
  metadataSubtree: "public",
274
+ authorizationToken: this.sourceOptions.playoutParameters.authorizationToken,
272
275
  select: [
273
276
  "display_image",
274
277
  "asset_metadata/nft/image"
@@ -319,8 +322,6 @@ export class EluvioPlayer {
319
322
  offeringURI,
320
323
  options
321
324
  });
322
-
323
- window.playoutOptions = this.sourceOptions.playoutOptions;
324
325
  }
325
326
  } else {
326
327
  if(!this.sourceOptions.playoutOptions) {
@@ -328,8 +329,6 @@ export class EluvioPlayer {
328
329
  ...this.sourceOptions.playoutParameters,
329
330
  options
330
331
  });
331
-
332
- window.playoutOptions = this.sourceOptions.playoutOptions;
333
332
  }
334
333
  }
335
334
 
@@ -584,186 +583,359 @@ export class EluvioPlayer {
584
583
  this.sourceOptions.playoutParameters.authorizationToken ||
585
584
  playoutUrl.query(true).authorization;
586
585
 
587
- //const HLSPlayer = (await import("hls.js")).default;
588
- const HLSPlayer = (await import("hls-fix")).default;
589
-
590
- let hlsPlayer, dashPlayer;
591
- if(drm === "fairplay") {
592
- InitializeFairPlayStream({playoutOptions: this.sourceOptions.playoutOptions, video: this.video});
586
+ if(protocol === "hls") {
587
+ await this.InitializeHLS({playoutUrl, authorizationToken, drm, drms, multiviewOptions, controlsPromise});
588
+ } else {
589
+ await this.InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions, controlsPromise});
590
+ }
593
591
 
594
- if(multiviewOptions.enabled) { controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions)); }
595
- } else if(!HLSPlayer.isSupported() || drm === "sample-aes") {
596
- this.video.src = playoutUrl.toString();
592
+ if(this.playerOptions.playerCallback) {
593
+ this.playerOptions.playerCallback({
594
+ player: this,
595
+ videoElement: this.video,
596
+ hlsPlayer: this.hlsPlayer,
597
+ dashPlayer: this.dashPlayer,
598
+ posterUrl: this.posterUrl
599
+ });
600
+ }
597
601
 
598
- if(multiviewOptions.enabled) { controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions)); }
599
- } else if(protocol === "hls") {
600
- playoutUrl.removeQuery("authorization");
602
+ if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON) {
603
+ this.video.play();
601
604
 
602
- // Inject
603
- hlsPlayer = new HLSPlayer({
604
- maxBufferLength: 30,
605
- maxBufferSize: 300,
606
- enableWorker: true,
607
- capLevelToPlayerSize: this.playerOptions.capLevelToPlayerSize,
608
- xhrSetup: xhr => {
609
- xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
605
+ setTimeout(() => {
606
+ if(this.playerOptions.muted === EluvioPlayerParameters.muted.OFF_IF_POSSIBLE && this.video.paused && !this.video.muted) {
607
+ this.video.muted = true;
608
+ this.video.play();
609
+ }
610
+ }, 250);
611
+ }
610
612
 
611
- if((this.playerOptions.hlsjsOptions || {}).xhrSetup) {
612
- this.playerOptions.hlsjsOptions.xhrSetup(xhr);
613
- }
613
+ this.RegisterVisibilityCallback();
614
614
 
615
- return xhr;
616
- },
617
- ...(this.playerOptions.hlsjsOptions || {})
618
- });
619
- hlsPlayer.loadSource(playoutUrl.toString());
620
- hlsPlayer.attachMedia(this.video);
615
+ if(this.__destroyed) {
616
+ // If Destroy was called during the initialization process, ensure that the player is properly destroyed
617
+ this.Destroy();
618
+ }
619
+ } catch (error) {
620
+ this.playerOptions.errorCallback(error);
621
621
 
622
- if(multiviewOptions.enabled) {
623
- const Switch = multiviewOptions.SwitchView;
622
+ if(error.status === 500) {
623
+ this.HardReload(error, 10000);
624
+ }
625
+ }
626
+ }
624
627
 
625
- multiviewOptions.SwitchView = async (view) => {
626
- await Switch(view);
627
- hlsPlayer.nextLevel = hlsPlayer.currentLevel;
628
- };
628
+ async InitializeHLS({playoutUrl, authorizationToken, drm, multiviewOptions, controlsPromise}) {
629
+ const HLSPlayer = (await import("hls-fix")).default;
629
630
 
630
- controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions));
631
- }
631
+ if(drm === "fairplay") {
632
+ InitializeFairPlayStream({playoutOptions: this.sourceOptions.playoutOptions, video: this.video});
632
633
 
633
- if(this.controls) {
634
- window.hls = hlsPlayer;
635
- this.controls.SetQualityControls({
636
- GetLevels: () => hlsPlayer.levels.map((level, index) => ({
637
- index,
638
- active: index === hlsPlayer.currentLevel,
639
- resolution: level.attrs.RESOLUTION,
640
- bitrate: level.bitrate,
641
- audioTrack: !level.videoCodec
642
- })),
643
- SetLevel: levelIndex => hlsPlayer.nextLevel = levelIndex
644
- });
645
- }
634
+ if(multiviewOptions.enabled) { controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions)); }
635
+ this.UpdateTextTracks();
636
+ } else if(!HLSPlayer.isSupported() || drm === "sample-aes") {
637
+ this.video.src = playoutUrl.toString();
646
638
 
647
- hlsPlayer.on(HLSPlayer.Events.FRAG_LOADED, () => {
648
- this.errors = 0;
649
- clearTimeout(this.bufferFullRestartTimeout);
650
- });
639
+ if(multiviewOptions.enabled) { controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions)); }
640
+ } else {
641
+ playoutUrl.removeQuery("authorization");
642
+
643
+ // Inject
644
+ const hlsPlayer = new HLSPlayer({
645
+ maxBufferLength: 30,
646
+ maxBufferSize: 300,
647
+ enableWorker: true,
648
+ capLevelToPlayerSize: this.playerOptions.capLevelToPlayerSize,
649
+ xhrSetup: xhr => {
650
+ xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
651
+
652
+ if((this.playerOptions.hlsjsOptions || {}).xhrSetup) {
653
+ this.playerOptions.hlsjsOptions.xhrSetup(xhr);
654
+ }
651
655
 
652
- hlsPlayer.on(HLSPlayer.Events.ERROR, async (event, error) => {
653
- this.errors += 1;
656
+ return xhr;
657
+ },
658
+ ...(this.playerOptions.hlsjsOptions || {})
659
+ });
660
+ hlsPlayer.loadSource(playoutUrl.toString());
661
+ hlsPlayer.attachMedia(this.video);
654
662
 
655
- this.Log(`Encountered ${error.details}`);
656
- this.Log(error);
663
+ if(multiviewOptions.enabled) {
664
+ const Switch = multiviewOptions.SwitchView;
657
665
 
658
- if(error.details === "bufferFullError") {
659
- this.bufferFullRestartTimeout = setTimeout(() => {
660
- this.Log("Buffer full error - Restarting player", true);
661
- this.HardReload(error, 5000);
662
- }, 3000);
663
- }
666
+ multiviewOptions.SwitchView = async (view) => {
667
+ await Switch(view);
668
+ hlsPlayer.nextLevel = hlsPlayer.currentLevel;
669
+ };
664
670
 
665
- if(error.details === "bufferStalledError") {
666
- const stallTime = this.video.currentTime;
671
+ controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions));
672
+ }
667
673
 
668
- setTimeout(() => {
669
- if(!this.video.paused && this.video.currentTime === stallTime) {
670
- this.Log("Buffer stalled error, no progress in 5 seconds - Restarting player", true);
674
+ if(this.controls) {
675
+ const UpdateQualityOptions = () => {
676
+ try {
677
+ this.controls.SetQualityControls({
678
+ GetLevels: () => {
679
+ let levels = hlsPlayer.levels
680
+ .map((level, index) => ({
681
+ index,
682
+ active: hlsPlayer.currentLevel === index,
683
+ resolution: level.attrs.RESOLUTION,
684
+ bitrate: level.bitrate,
685
+ audioTrack: !level.videoCodec,
686
+ label:
687
+ level.audioTrack ?
688
+ `${level.bitrate / 1000}kbps` :
689
+ `${level.attrs.RESOLUTION} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
690
+ activeLabel:
691
+ level.audioTrack ?
692
+ `Quality: ${level.bitrate / 1000}kbps` :
693
+ `Quality: ${level.attrs.RESOLUTION}`
694
+ }))
695
+ .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
696
+
697
+ levels.unshift({index: -1, label: "Auto"});
698
+
699
+ return {label: "Quality", options: levels};
700
+ },
701
+ SetLevel: levelIndex => {
702
+ hlsPlayer.nextLevel = levelIndex;
703
+ hlsPlayer.streamController.immediateLevelSwitch();
671
704
  }
672
- }, 5000);
705
+ });
706
+ } catch (error) {
707
+ // eslint-disable-next-line no-console
708
+ console.error("ELUVIO PLAYER:", error);
673
709
  }
674
-
675
- if(error.fatal || this.errors === 3) {
676
- if(error.response.code === 403) {
677
- // Not allowed to access
678
- this.Destroy();
679
- } else {
680
- this.HardReload(error);
710
+ };
711
+
712
+ hlsPlayer.on(HLSPlayer.Events.SUBTITLE_TRACKS_UPDATED, () => this.UpdateTextTracks());
713
+ hlsPlayer.on(HLSPlayer.Events.LEVEL_LOADED, () => UpdateQualityOptions());
714
+ hlsPlayer.on(HLSPlayer.Events.LEVEL_SWITCHED, () => UpdateQualityOptions());
715
+ hlsPlayer.on(HLSPlayer.Events.SUBTITLE_TRACK_SWITCH, () => this.UpdateTextTracks());
716
+ hlsPlayer.on(HLSPlayer.Events.AUDIO_TRACKS_UPDATED, () => {
717
+ this.controls.SetAudioTrackControls({
718
+ GetAudioTracks: () => {
719
+ const tracks = hlsPlayer.audioTracks.map(track => ({
720
+ index: track.id,
721
+ label: track.name,
722
+ active: track.id === hlsPlayer.audioTrack,
723
+ activeLabel: `Audio: ${track.name}`
724
+ }));
725
+
726
+ return {label: "Audio Track", options: tracks};
727
+ },
728
+ SetAudioTrack: index => {
729
+ hlsPlayer.audioTrack = index;
730
+ hlsPlayer.streamController.immediateLevelSwitch();
681
731
  }
682
- }
732
+ });
683
733
  });
734
+ }
684
735
 
685
- this.player = hlsPlayer;
686
- } else {
687
- const DashPlayer = (await import("dashjs")).default;
688
- dashPlayer = DashPlayer.MediaPlayer().create();
689
-
690
- if(this.playerOptions.capLevelToPlayerSize) {
691
- dashPlayer.updateSettings({
692
- "streaming": {
693
- "abr": {
694
- "limitBitrateByPortal": true
695
- }
736
+ hlsPlayer.on(HLSPlayer.Events.FRAG_LOADED, () => {
737
+ this.errors = 0;
738
+ clearTimeout(this.bufferFullRestartTimeout);
739
+ });
740
+
741
+ hlsPlayer.on(HLSPlayer.Events.ERROR, async (event, error) => {
742
+ this.errors += 1;
743
+
744
+ this.Log(`Encountered ${error.details}`);
745
+ this.Log(error);
746
+
747
+ if(error.details === "bufferFullError") {
748
+ this.bufferFullRestartTimeout = setTimeout(() => {
749
+ this.Log("Buffer full error - Restarting player", true);
750
+ this.HardReload(error, 5000);
751
+ }, 3000);
752
+ }
753
+
754
+ if(error.details === "bufferStalledError") {
755
+ const stallTime = this.video.currentTime;
756
+
757
+ setTimeout(() => {
758
+ if(!this.video.paused && this.video.currentTime === stallTime) {
759
+ this.Log("Buffer stalled error, no progress in 5 seconds - Restarting player", true);
696
760
  }
697
- });
761
+ }, 5000);
698
762
  }
699
763
 
700
- playoutUrl.removeQuery("authorization");
701
- dashPlayer.extend("RequestModifier", function () {
702
- return {
703
- modifyRequestHeader: xhr => {
704
- xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
764
+ if(error.fatal || this.errors === 3) {
765
+ if(error.response.code === 403) {
766
+ // Not allowed to access
767
+ this.Destroy();
768
+ } else {
769
+ this.HardReload(error);
770
+ }
771
+ }
772
+ });
705
773
 
706
- return xhr;
707
- },
708
- modifyRequestURL: url => url
709
- };
710
- });
774
+ this.hlsPlayer = hlsPlayer;
775
+ this.player = hlsPlayer;
776
+ }
777
+ }
711
778
 
712
- // Widevine
713
- if(drm === EluvioPlayerParameters.drms.WIDEVINE) {
714
- const widevineUrl = drms.widevine.licenseServers[0];
779
+ async InitializeDash({playoutUrl, authorizationToken, drm, drms, multiviewOptions, controlsPromise}) {
780
+ const DashPlayer = (await import("dashjs")).default;
781
+ const dashPlayer = DashPlayer.MediaPlayer().create();
715
782
 
716
- dashPlayer.setProtectionData({
717
- "com.widevine.alpha": {
718
- "serverURL": widevineUrl
719
- }
720
- });
783
+ if(this.playerOptions.capLevelToPlayerSize) {
784
+ dashPlayer.updateSettings({
785
+ "streaming": {
786
+ "fastSwitchEnabled": true,
787
+ "abr": {
788
+ "limitBitrateByPortal": true
789
+ }
721
790
  }
791
+ });
792
+ }
722
793
 
723
- dashPlayer.initialize(
724
- this.video,
725
- playoutUrl.toString(),
726
- this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON
727
- );
794
+ playoutUrl.removeQuery("authorization");
795
+ dashPlayer.extend("RequestModifier", function () {
796
+ return {
797
+ modifyRequestHeader: xhr => {
798
+ xhr.setRequestHeader("Authorization", `Bearer ${authorizationToken}`);
728
799
 
729
- if(multiviewOptions.enabled) { controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions)); }
800
+ return xhr;
801
+ },
802
+ modifyRequestURL: url => url
803
+ };
804
+ });
730
805
 
731
- this.player = dashPlayer;
732
- }
806
+ // Widevine
807
+ if(drm === EluvioPlayerParameters.drms.WIDEVINE) {
808
+ const widevineUrl = drms.widevine.licenseServers[0];
733
809
 
734
- if(this.playerOptions.playerCallback) {
735
- this.playerOptions.playerCallback({
736
- videoElement: this.video,
737
- hlsPlayer,
738
- dashPlayer,
739
- posterUrl: this.posterUrl
810
+ dashPlayer.setProtectionData({
811
+ "com.widevine.alpha": {
812
+ "serverURL": widevineUrl
813
+ }
814
+ });
815
+ }
816
+
817
+ dashPlayer.initialize(
818
+ this.video,
819
+ playoutUrl.toString(),
820
+ this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON
821
+ );
822
+
823
+ if(multiviewOptions.enabled) { controlsPromise.then(() => this.controls.InitializeMultiViewControls(multiviewOptions)); }
824
+
825
+ const UpdateQualityOptions = () => {
826
+ try {
827
+ this.controls.SetQualityControls({
828
+ GetLevels: () => {
829
+ let levels = dashPlayer.getBitrateInfoListFor("video")
830
+ .map((level) => ({
831
+ index: level.qualityIndex,
832
+ active: level.qualityIndex === this.player.getQualityFor("video"),
833
+ resolution: `${level.width}x${level.height}`,
834
+ bitrate: level.bitrate,
835
+ label: `${level.width}x${level.height} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
836
+ activeLabel: `Quality: ${level.width}x${level.height}`,
837
+ }))
838
+ .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
839
+
840
+ levels.unshift({index: -1, label: "Auto"});
841
+
842
+ return { label: "Quality", options: levels };
843
+ },
844
+ SetLevel: levelIndex => {
845
+ dashPlayer.setQualityFor("video", levelIndex);
846
+ dashPlayer.updateSettings({
847
+ streaming: {
848
+ trackSwitchMode: "alwaysReplace",
849
+ fastSwitchEnabled: true,
850
+ abr: {
851
+ autoSwitchBitrate: {
852
+ video: levelIndex === -1
853
+ }
854
+ }
855
+ }
856
+ });
857
+ }
740
858
  });
859
+ } catch (error) {
860
+ // eslint-disable-next-line no-console
861
+ console.error("ELUVIO PLAYER:", error);
741
862
  }
863
+ };
742
864
 
743
- if(this.playerOptions.autoplay === EluvioPlayerParameters.autoplay.ON) {
744
- this.video.play();
865
+ const UpdateAudioTracks = () => {
866
+ this.controls.SetAudioTrackControls({
867
+ GetAudioTracks: () => {
868
+ const tracks = this.player.getTracksFor("audio").map(track => ({
869
+ index: track.index,
870
+ label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
871
+ active: track.index === dashPlayer.getCurrentTrackFor("audio").index,
872
+ activeLabel: `Audio: ${track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang}`
873
+ }));
874
+
875
+ return { label: "Audio Track", options: tracks };
876
+ },
877
+ SetAudioTrack: index => {
878
+ const track = dashPlayer.getTracksFor("audio").find(track => track.index === index);
879
+ dashPlayer.setCurrentTrack(track);
880
+ }
881
+ });
882
+ };
745
883
 
746
- setTimeout(() => {
747
- if(this.playerOptions.muted === EluvioPlayerParameters.muted.OFF_IF_POSSIBLE && this.video.paused && !this.video.muted) {
748
- this.video.muted = true;
749
- this.video.play();
750
- }
751
- }, 250);
752
- }
884
+ dashPlayer.on(DashPlayer.MediaPlayer.events.QUALITY_CHANGE_RENDERED, () => UpdateQualityOptions());
885
+ dashPlayer.on(DashPlayer.MediaPlayer.events.TRACK_CHANGE_RENDERED, () => {
886
+ UpdateAudioTracks();
887
+ this.UpdateTextTracks({dashPlayer});
888
+ });
889
+ dashPlayer.on(DashPlayer.MediaPlayer.events.MANIFEST_LOADED, () => {
890
+ UpdateQualityOptions();
891
+ UpdateAudioTracks();
892
+ this.UpdateTextTracks({dashPlayer});
893
+ });
894
+
895
+ this.player = dashPlayer;
896
+ this.dashPlayer = dashPlayer;
897
+ }
753
898
 
754
- this.RegisterVisibilityCallback();
899
+ UpdateTextTracks({dashPlayer}={}) {
900
+ this.controls.SetTextTrackControls({
901
+ GetTextTracks: () => {
902
+ const activeTrackIndex = Array.from(this.video.textTracks).findIndex(track => track.mode === "showing");
903
+
904
+ let tracks;
905
+ if(dashPlayer) {
906
+ tracks = dashPlayer.getTracksFor("text").map((track, index) => ({
907
+ index: index,
908
+ label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
909
+ active: index === activeTrackIndex,
910
+ activeLabel: `Subtitles: ${track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang}`
911
+ }));
912
+ } else {
913
+ tracks = Array.from(this.video.textTracks).map((track, index) => ({
914
+ index: index,
915
+ label: track.label || track.language,
916
+ active: track.mode === "showing",
917
+ activeLabel: `Subtitles: ${track.label || track.language}`
918
+ }));
919
+ }
755
920
 
756
- if(this.__destroyed) {
757
- // If Destroy was called during the initialization process, ensure that the player is properly destroyed
758
- this.Destroy();
759
- }
760
- } catch (error) {
761
- this.playerOptions.errorCallback(error);
921
+ tracks.unshift({
922
+ index: -1,
923
+ label: "Disabled",
924
+ active: activeTrackIndex < 0,
925
+ activeLabel: "Subtitles: Disabled"
926
+ });
762
927
 
763
- if(error.status === 500) {
764
- this.HardReload(error, 10000);
928
+ return { label: "Subtitles", options: tracks };
929
+ },
930
+ SetTextTrack: index => {
931
+ const tracks = Array.from(this.video.textTracks);
932
+ tracks.map(track => track.mode = "disabled");
933
+
934
+ if(index >= 0) {
935
+ tracks[index].mode = "showing";
936
+ }
765
937
  }
766
- }
938
+ });
767
939
  }
768
940
  }
769
941
 
@@ -0,0 +1,4 @@
1
+ <svg width="25" height="16" viewBox="0 0 25 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M1.5 8L24 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ <path d="M8.5 1L1.5 8L8.5 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -6,6 +6,8 @@ $black: #000;
6
6
 
7
7
  $background-color: $black;
8
8
  $controls-color: rgba(0, 0, 0, 0.4);
9
+ $menu-color: rgba(0, 0, 0, 0.8);
10
+ $menu-active-color: rgba(255, 255, 255, 0.1);
9
11
  $button-color: rgba($white, 0.8);
10
12
  $progress-color: #0885fb;
11
13
  $slider-color: rgba($gray, 0.5);
@@ -343,7 +345,7 @@ $button-height: 35px;
343
345
  }
344
346
 
345
347
  &__settings-menu {
346
- background: $controls-color;
348
+ background: $menu-color;
347
349
  border-radius: 3px;
348
350
  bottom: 45px;
349
351
  color: $button-color;
@@ -352,7 +354,7 @@ $button-height: 35px;
352
354
  overflow-y: auto;
353
355
  position: absolute;
354
356
  right: 10px;
355
- width: 200px;
357
+ width: 250px;
356
358
  z-index: 100;
357
359
 
358
360
  &-hidden {
@@ -369,16 +371,34 @@ $button-height: 35px;
369
371
  cursor: pointer;
370
372
  display: block;
371
373
  font-size: 14px;
372
- height: 100%;
374
+ height: max-content;
375
+ line-height: 1.4;
373
376
  padding: 9px 20px;
377
+ text-align: left;
374
378
  width: 100%;
375
379
 
380
+ &-back {
381
+ display: flex;
382
+ font-weight: 500;
383
+ width: 100%;
384
+
385
+ svg {
386
+ height: 15px;
387
+ margin-right: 5px;
388
+ width: 15px;
389
+ }
390
+
391
+ div {
392
+ width: 100%;
393
+ }
394
+ }
395
+
376
396
  &-selected {
377
397
  color: $button-color;
378
398
  }
379
399
 
380
400
  &:hover {
381
- background: $controls-color;
401
+ background: $menu-active-color;
382
402
  color: $button-color;
383
403
  }
384
404
 
@@ -592,11 +612,13 @@ $button-height: 35px;
592
612
  }
593
613
 
594
614
  .eluvio-player__controls__button {
595
- height: CALC(#{$button-height} * 0.9);
596
- max-height: CALC(#{$button-height} * 0.9);
597
- max-width: CALC(#{$button-height} * 0.9);
598
- min-height: CALC(#{$button-height} * 0.9);
599
- min-width: CALC(#{$button-height} * 0.9);
615
+ &:not(.eluvio-player__big-play-button) {
616
+ height: CALC(#{$button-height} * 0.9);
617
+ max-height: CALC(#{$button-height} * 0.9);
618
+ max-width: CALC(#{$button-height} * 0.9);
619
+ min-height: CALC(#{$button-height} * 0.9);
620
+ min-width: CALC(#{$button-height} * 0.9);
621
+ }
600
622
  }
601
623
 
602
624
  .eluvio-player__controls__volume-container {
@@ -604,10 +626,10 @@ $button-height: 35px;
604
626
  }
605
627
 
606
628
  .eluvio-player__big-play-button {
607
- max-height: 75px;
608
- max-width: 75px;
609
- min-height: 75px;
610
- min-width: 75px;
629
+ max-height: 55px;
630
+ max-width: 55px;
631
+ min-height: 55px;
632
+ min-width: 55px;
611
633
  }
612
634
 
613
635
  .eluvio-player__ticket-modal {
@@ -645,6 +667,14 @@ $button-height: 35px;
645
667
  }
646
668
  }
647
669
 
670
+ .eluvio-player__controls__settings-menu {
671
+ height: 100%;
672
+ padding: 20px 0;
673
+ right: 0;
674
+ top: 0;
675
+ width: 100%;
676
+ }
677
+
648
678
  .eluvio-player__hotspot-overlay {
649
679
  .eluvio-player__hotspot-overlay__target {
650
680
  .eluvio-player__hotspot-overlay__target__title {