myetv-player 1.7.1 → 1.7.2

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.
@@ -629,6 +629,9 @@ constructor(videoElement, options = {}) {
629
629
  showPictureInPicture: true, // Enable PiP button
630
630
  showSubtitles: true, // Enable subtitles button
631
631
  subtitlesEnabled: false, // Enable subtitles by default if available
632
+ externalSubtitleTracks: [],// external subtitles track from url
633
+ externalSubtitleLabel: 'Default Subtitles', //label for the subtitle track url
634
+ externalSubtitleLang: 'en', // language of the subtitles track url
632
635
  showSettingsMenu: true, // Show settings menu in top bar
633
636
  autoHide: true, // auto-hide controls when idle
634
637
  autoHideDelay: 3000, // hide controls after ... seconds of inactivity (specificed in milliseconds)
@@ -781,8 +784,31 @@ constructor(videoElement, options = {}) {
781
784
  VideoPlayerTranslations.setLanguage(this.options.language);
782
785
  }
783
786
 
784
- if (options.autoplay) {
785
- this.video.autoplay = true;
787
+ this.savedAutoplayIntent = options.autoplay || false;
788
+ this.options.autoplay = false;
789
+
790
+ if (this.video) {
791
+ if (this.video.hasAttribute('autoplay')) {
792
+ this.savedAutoplayIntent = true;
793
+ this.video.removeAttribute('autoplay');
794
+ }
795
+
796
+ this.video.pause();
797
+ this.video.preload = 'none';
798
+
799
+ if (this.video.hasAttribute('poster')) {
800
+ this.savedPoster = this.video.getAttribute('poster');
801
+ this.video.setAttribute('poster', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
802
+ }
803
+
804
+ this.video.pause();
805
+ this.video.preload = 'none';
806
+
807
+ this.video.removeAttribute('src');
808
+ this.video.load();
809
+
810
+ this.video.style.opacity = '0';
811
+ this.video.style.visibility = 'hidden';
786
812
  }
787
813
 
788
814
  try {
@@ -872,17 +898,8 @@ constructor(videoElement, options = {}) {
872
898
  this.updateVolumeSliderVisual();
873
899
  this.initVolumeTooltip();
874
900
  this.updateTooltips();
875
- this.markPlayerReady();
876
- this.initializePluginSystem();
877
- this.restoreSourcesAsync();
878
-
879
- this.initializeSubtitles();
880
- this.initializeQualityMonitoring();
881
901
 
882
- this.initializeResolution();
883
- this.initializeChapters();
884
- this.initializePoster();
885
- this.initializeWatermark();
902
+ this.initializePluginSystem();
886
903
 
887
904
  } catch (error) {
888
905
  if (this.options.debug) console.error('Video player initialization error:', error);
@@ -1005,6 +1022,8 @@ interceptAutoLoading() {
1005
1022
 
1006
1023
  this.hideNativePlayer();
1007
1024
 
1025
+ this.video.load();
1026
+
1008
1027
  if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
1009
1028
  }
1010
1029
 
@@ -1167,6 +1186,7 @@ markPlayerReady() {
1167
1186
  this.video.style.visibility = '';
1168
1187
  this.video.style.opacity = '';
1169
1188
  this.video.style.pointerEvents = '';
1189
+ this.video.style.display = '';
1170
1190
  }
1171
1191
 
1172
1192
  if (typeof this.updateSettingsMenuVisibility === 'function') {
@@ -1867,6 +1887,12 @@ updateSeekTooltip(e) {
1867
1887
  }
1868
1888
 
1869
1889
  play() {
1890
+ if (!this.isPlayerReady) {
1891
+ this.savedAutoplayIntent = true;
1892
+ if (this.options.debug) console.log('🛑 Play requested but player is booting. Intent saved for later.');
1893
+ return Promise ? Promise.resolve() : undefined;
1894
+ }
1895
+
1870
1896
  if (!this.video || this.isChangingQuality) return;
1871
1897
 
1872
1898
  this.video.play().catch(err => {
@@ -1883,6 +1909,11 @@ play() {
1883
1909
  }
1884
1910
 
1885
1911
  pause() {
1912
+ if (!this.isPlayerReady) {
1913
+ this.savedAutoplayIntent = false;
1914
+ return;
1915
+ }
1916
+
1886
1917
  if (!this.video) return;
1887
1918
 
1888
1919
  this.video.pause();
@@ -3979,6 +4010,48 @@ populateSettingsMenu() {
3979
4010
  settingsMenu.innerHTML = menuHTML;
3980
4011
  }
3981
4012
 
4013
+ updateSettingsMenuUI() {
4014
+ const settingsMenu = this.container?.querySelector('.settings-menu');
4015
+ if (!settingsMenu) return;
4016
+
4017
+ if (this.options.showSubtitles) {
4018
+ const subtitlesTrigger = settingsMenu.querySelector('[data-action="subtitles_expand"]');
4019
+
4020
+ if (subtitlesTrigger) {
4021
+
4022
+ const label = subtitlesTrigger.querySelector('.settings-option-label');
4023
+ if (label) {
4024
+ const subtitlesLabel = this.t('subtitles') || 'Subtitles';
4025
+ const currentTrack = this.currentSubtitleTrack;
4026
+
4027
+ let currentLabelText = this.t('subtitlesoff') || 'Off';
4028
+ if (this.subtitlesEnabled && currentTrack) {
4029
+ currentLabelText = currentTrack.label || 'Unknown';
4030
+ }
4031
+
4032
+ label.innerHTML = `${subtitlesLabel} <strong>${currentLabelText}</strong>`;
4033
+ }
4034
+
4035
+ const submenuContent = subtitlesTrigger.nextElementSibling;
4036
+ if (submenuContent && submenuContent.classList.contains('settings-expandable-content')) {
4037
+ submenuContent.querySelectorAll('.settings-suboption').forEach(opt => {
4038
+ const trackData = opt.getAttribute('data-track');
4039
+ opt.classList.remove('active');
4040
+
4041
+ if (trackData === 'off' && !this.subtitlesEnabled) {
4042
+ opt.classList.add('active');
4043
+ } else if (this.subtitlesEnabled && trackData !== 'off') {
4044
+ const trackIndex = parseInt(trackData);
4045
+ if (this.textTracks[trackIndex] && this.textTracks[trackIndex].track === this.currentSubtitleTrack) {
4046
+ opt.classList.add('active');
4047
+ }
4048
+ }
4049
+ });
4050
+ }
4051
+ }
4052
+ }
4053
+ }
4054
+
3982
4055
  addSettingsMenuScrollbar() {
3983
4056
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3984
4057
  if (!settingsMenu) return;
@@ -5534,6 +5607,31 @@ createCustomSubtitleOverlay() {
5534
5607
  if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
5535
5608
  }
5536
5609
 
5610
+ parseTimeToSecondsUniversal(timeString) {
5611
+ if (!timeString) return 0;
5612
+
5613
+ let normalized = timeString.replace(',', '.');
5614
+ let parts = normalized.split('.');
5615
+
5616
+ let timeParts = parts[0].split(':');
5617
+ let ms = parts[1] ? parseInt(parts[1], 10) / 1000 : 0;
5618
+
5619
+ let hours = 0, minutes = 0, seconds = 0;
5620
+
5621
+ if (timeParts.length === 3) {
5622
+
5623
+ hours = parseInt(timeParts[0], 10);
5624
+ minutes = parseInt(timeParts[1], 10);
5625
+ seconds = parseInt(timeParts[2], 10);
5626
+ } else if (timeParts.length === 2) {
5627
+
5628
+ minutes = parseInt(timeParts[0], 10);
5629
+ seconds = parseInt(timeParts[1], 10);
5630
+ }
5631
+
5632
+ return (hours * 3600) + (minutes * 60) + seconds + ms;
5633
+ }
5634
+
5537
5635
  customTimeToSeconds(timeString) {
5538
5636
  if (!timeString) return 0;
5539
5637
 
@@ -5559,6 +5657,31 @@ customTimeToSeconds(timeString) {
5559
5657
  return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
5560
5658
  }
5561
5659
 
5660
+ parseTimeToSecondsUniversal(timeString) {
5661
+ if (!timeString) return 0;
5662
+
5663
+ const normalized = timeString.replace(',', '.');
5664
+ const parts = normalized.split('.');
5665
+
5666
+ const timeParts = parts[0].split(':');
5667
+ const ms = parts[1] ? parseInt(parts[1], 10) / 1000 : 0;
5668
+
5669
+ let hours = 0, minutes = 0, seconds = 0;
5670
+
5671
+ if (timeParts.length === 3) {
5672
+
5673
+ hours = parseInt(timeParts[0], 10);
5674
+ minutes = parseInt(timeParts[1], 10);
5675
+ seconds = parseInt(timeParts[2], 10);
5676
+ } else if (timeParts.length === 2) {
5677
+
5678
+ minutes = parseInt(timeParts[0], 10);
5679
+ seconds = parseInt(timeParts[1], 10);
5680
+ }
5681
+
5682
+ return (hours * 3600) + (minutes * 60) + seconds + ms;
5683
+ }
5684
+
5562
5685
  parseCustomSRT(srtText) {
5563
5686
  var subtitles = [];
5564
5687
  var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
@@ -5592,6 +5715,57 @@ parseCustomSRT(srtText) {
5592
5715
  return subtitles;
5593
5716
  }
5594
5717
 
5718
+ injectExternalSubtitleTracks(tracksArray) {
5719
+
5720
+ if (!this.video || !Array.isArray(tracksArray) || tracksArray.length === 0) return;
5721
+
5722
+ this.video.crossOrigin = "anonymous";
5723
+ let loadedCount = 0;
5724
+
5725
+ tracksArray.forEach(trackData => {
5726
+ const trackEl = document.createElement('track');
5727
+ trackEl.kind = 'subtitles';
5728
+ trackEl.label = trackData.label;
5729
+ trackEl.srclang = trackData.lang;
5730
+ trackEl.src = trackData.src;
5731
+
5732
+ if (trackData.default) {
5733
+ trackEl.default = true;
5734
+ trackEl.setAttribute('default', 'default');
5735
+ trackEl.setAttribute('data-default', 'true');
5736
+ }
5737
+
5738
+ trackEl.addEventListener('load', () => {
5739
+ if (trackEl.track) {
5740
+
5741
+ trackEl.track.mode = trackData.default ? 'showing' : 'hidden';
5742
+ }
5743
+
5744
+ loadedCount++;
5745
+
5746
+ if (loadedCount === tracksArray.length) {
5747
+ console.log(`MYETV Player: ${loadedCount} external subtitle tracks loaded.`);
5748
+
5749
+ if (typeof this.initializeSubtitles === 'function') {
5750
+ this.initializeSubtitles();
5751
+ } else if (typeof this.initializeCustomSubtitles === 'function') {
5752
+ this.initializeCustomSubtitles();
5753
+ } else if (typeof this.loadCustomSubtitleTracks === 'function') {
5754
+ this.loadCustomSubtitleTracks();
5755
+ }
5756
+ }
5757
+ });
5758
+
5759
+ trackEl.addEventListener('error', () => {
5760
+ console.warn(`MYETV Player: Failed to load track ${trackData.lang}`);
5761
+
5762
+ loadedCount++;
5763
+ });
5764
+
5765
+ this.video.appendChild(trackEl);
5766
+ });
5767
+ }
5768
+
5595
5769
  loadCustomSubtitleTracks() {
5596
5770
  var self = this;
5597
5771
  var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
@@ -5602,6 +5776,8 @@ loadCustomSubtitleTracks() {
5602
5776
  var label = track.getAttribute('label') || 'Unknown';
5603
5777
  var srclang = track.getAttribute('srclang') || '';
5604
5778
 
5779
+ var isDefault = track.default || track.hasAttribute('default');
5780
+
5605
5781
  var trackObj = {
5606
5782
  label: label,
5607
5783
  language: srclang,
@@ -5615,30 +5791,45 @@ loadCustomSubtitleTracks() {
5615
5791
  return response.text();
5616
5792
  })
5617
5793
  .then(function (srtText) {
5794
+
5618
5795
  var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
5796
+
5619
5797
  var blocks = normalizedText.trim().split('\n\n');
5620
5798
 
5621
5799
  for (var i = 0; i < blocks.length; i++) {
5622
5800
  var block = blocks[i].trim();
5623
- if (!block) continue;
5801
+ if (!block || block.toUpperCase().startsWith('WEBVTT')) continue;
5802
+
5624
5803
  var lines = block.split('\n');
5804
+ var timeMatch = null;
5805
+ var textLines = [];
5625
5806
 
5626
- if (lines.length >= 3) {
5627
- var timeLine = lines[1].trim();
5628
- var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
5807
+ for (var j = 0; j < lines.length; j++) {
5808
+ var line = lines[j].trim();
5629
5809
 
5630
- if (timeMatch) {
5631
- var startParts = timeMatch[1].split(',');
5632
- var startTimeParts = startParts[0].split(':');
5633
- var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
5810
+ if (!timeMatch) {
5811
+ var match = line.match(/(\d{2,}:\d{2}:\d{2}[.,]\d{3}|\d{2}:\d{2}[.,]\d{3})\s*-->\s*(\d{2,}:\d{2}:\d{2}[.,]\d{3}|\d{2}:\d{2}[.,]\d{3})/);
5812
+ if (match) {
5813
+ timeMatch = match;
5814
+ continue;
5815
+ }
5816
+ }
5634
5817
 
5635
- var endParts = timeMatch[2].split(',');
5636
- var endTimeParts = endParts[0].split(':');
5637
- var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
5818
+ if (!timeMatch && /^\d+$/.test(line)) {
5819
+ continue;
5820
+ }
5638
5821
 
5639
- var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
5822
+ if (timeMatch) {
5823
+ textLines.push(line);
5824
+ }
5825
+ }
5640
5826
 
5641
- if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
5827
+ if (timeMatch && textLines.length > 0) {
5828
+ var startTime = self.parseTimeToSecondsUniversal(timeMatch[1]);
5829
+ var endTime = self.parseTimeToSecondsUniversal(timeMatch[2]);
5830
+ var text = textLines.join('\n').trim().replace(/<[^>]*>/g, '');
5831
+
5832
+ if (text && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
5642
5833
  trackObj.subtitles.push({
5643
5834
  start: startTime,
5644
5835
  end: endTime,
@@ -5647,11 +5838,26 @@ loadCustomSubtitleTracks() {
5647
5838
  }
5648
5839
  }
5649
5840
  }
5841
+
5842
+ if (self.options.debug) {
5843
+ console.log('✅ Custom parser loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
5844
+ }
5845
+
5846
+ var isDefault = track.default || track.hasAttribute('default') || track.getAttribute('data-default') === 'true';
5847
+
5848
+ if (isDefault) {
5849
+ if (self.options.debug) {
5850
+ console.log('⏳ Waiting 500ms for UI alignment... track:', label);
5650
5851
  }
5651
5852
 
5853
+ setTimeout(function () {
5652
5854
  if (self.options.debug) {
5653
- console.log(' Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
5855
+ console.log('🎯 Forced auto-enable triggered now!');
5654
5856
  }
5857
+ self.enableSubtitleTrack(index);
5858
+ }, 500); // 500 milliseconds of delay
5859
+ }
5860
+
5655
5861
  })
5656
5862
  .catch(function (error) {
5657
5863
  console.error('❌ Error loading ' + label + ':', error);
@@ -5775,14 +5981,18 @@ enableSubtitleTrack(trackIndex) {
5775
5981
  this.subtitlesEnabled = true;
5776
5982
 
5777
5983
  if (this.video.textTracks && this.video.textTracks[trackIndex]) {
5778
- this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
5984
+ this.video.textTracks[trackIndex].mode = 'disabled';
5779
5985
  }
5780
5986
 
5781
5987
  this.updateSubtitlesButton();
5782
5988
  this.populateSubtitlesMenu();
5783
5989
 
5990
+ if (typeof this.updateSettingsMenuUI === 'function') {
5991
+ this.updateSettingsMenuUI();
5992
+ }
5993
+
5784
5994
  if (this.options.debug) {
5785
- console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
5995
+ console.log('✅ Custom UI subtitles strictly enforced for track', trackIndex);
5786
5996
  }
5787
5997
 
5788
5998
  this.triggerEvent('subtitlechange', {
@@ -5791,10 +6001,6 @@ enableSubtitleTrack(trackIndex) {
5791
6001
  trackLabel: this.textTracks[trackIndex].label,
5792
6002
  trackLanguage: this.textTracks[trackIndex].language
5793
6003
  });
5794
- } else {
5795
- if (this.options.debug) {
5796
- console.error('❌ Failed to enable custom subtitles for track', trackIndex);
5797
- }
5798
6004
  }
5799
6005
  }
5800
6006
 
@@ -5808,6 +6014,10 @@ disableSubtitles() {
5808
6014
  this.updateSubtitlesButton();
5809
6015
  this.populateSubtitlesMenu();
5810
6016
 
6017
+ if (typeof this.updateSettingsMenuUI === 'function') {
6018
+ this.updateSettingsMenuUI();
6019
+ }
6020
+
5811
6021
  if (this.options.debug) console.log('📝 Subtitles disabled');
5812
6022
 
5813
6023
  this.triggerEvent('subtitlechange', {
@@ -7710,35 +7920,86 @@ setDashQuality(qualityIndex) {
7710
7920
  return this;
7711
7921
  }
7712
7922
 
7713
- initializePluginSystem() {
7714
- this.plugins = {};
7923
+ initializePluginSystem() {
7924
+ this.plugins = {};
7715
7925
  this.pluginHooks = {
7716
- 'beforeInit': [],
7717
- 'afterInit': [],
7718
- 'beforePlay': [],
7719
- 'afterPlay': [],
7720
- 'beforePause': [],
7721
- 'afterPause': [],
7722
- 'beforeQualityChange': [],
7723
- 'afterQualityChange': [],
7724
- 'beforeDestroy': [],
7725
- 'afterDestroy': []
7926
+ 'beforeInit': [], 'afterInit': [], 'beforePlay': [], 'afterPlay': [],
7927
+ 'beforePause': [], 'afterPause': [], 'beforeQualityChange': [],
7928
+ 'afterQualityChange': [], 'beforeDestroy': [], 'afterDestroy': []
7726
7929
  };
7727
7930
 
7728
- if (this.options.debug) {
7729
- console.log('🔌 Plugin system initialized');
7730
- }
7931
+ if (this.options.debug) console.log('🔌 Plugin system initialized');
7731
7932
  if (this.options.plugins && typeof this.options.plugins === 'object') {
7732
- this.loadPlugins(this.options.plugins);
7933
+ this.loadPluginsSequentially(this.options.plugins);
7934
+ } else {
7935
+ this.finalizePlayerSetup();
7936
+ }
7937
+ }
7938
+
7939
+ async loadPluginsSequentially(pluginsConfig) {
7940
+ const pluginList = Object.keys(pluginsConfig).map(pluginName => {
7941
+ return {
7942
+ name: pluginName,
7943
+ config: pluginsConfig[pluginName],
7944
+ order: pluginsConfig[pluginName] && pluginsConfig[pluginName].order !== undefined
7945
+ ? pluginsConfig[pluginName].order : 99
7946
+ };
7947
+ }).sort((a, b) => a.order - b.order);
7948
+ for (const pluginData of pluginList) {
7949
+ if (this.options.debug) console.log(`🔌 Loading plugin: ${pluginData.name} (order: ${pluginData.order})`);
7950
+ const pluginInstance = this.usePlugin(pluginData.name, pluginData.config);
7951
+ if (pluginInstance && typeof pluginInstance.waitForCompletion === 'function') {
7952
+ if (this.options.debug) console.log(`🔌 Pausing queue. Waiting for ${pluginData.name} to complete...`);
7953
+ await pluginInstance.waitForCompletion();
7954
+ if (this.options.debug) console.log(`🔌 ${pluginData.name} completed. Resuming queue.`);
7733
7955
  }
7956
+ }
7957
+ this.finalizePlayerSetup();
7734
7958
  }
7735
7959
 
7736
- loadPlugins(pluginsConfig) {
7737
- for (const pluginName in pluginsConfig) {
7738
- if (pluginsConfig.hasOwnProperty(pluginName)) {
7739
- const pluginOptions = pluginsConfig[pluginName];
7740
- this.usePlugin(pluginName, pluginOptions);
7960
+ finalizePlayerSetup() {
7961
+ if (this.options.debug) {
7962
+ console.log('🚀 Finalizing main player setup...');
7963
+ }
7964
+ if (this.savedAutoplayIntent) {
7965
+ this.options.autoplay = true;
7966
+ }
7967
+ this.markPlayerReady();
7968
+
7969
+ this.restoreSourcesAsync();
7970
+ this.initializeSubtitles();
7971
+ this.initializeQualityMonitoring();
7972
+ this.initializeResolution();
7973
+ this.initializeChapters();
7974
+ const hasExternalIframe = this.options.plugins && (
7975
+ this.options.plugins.youtube ||
7976
+ this.options.plugins.vimeo ||
7977
+ this.options.plugins.twitch
7978
+ );
7979
+
7980
+ if (!hasExternalIframe) {
7981
+ this.initializePoster();
7741
7982
  }
7983
+
7984
+ this.initializeWatermark();
7985
+ if (this.options.externalSubtitleTracks && this.options.externalSubtitleTracks.length > 0) {
7986
+ this.injectExternalSubtitleTracks(this.options.externalSubtitleTracks);
7987
+ }
7988
+ if (this.savedAutoplayIntent) {
7989
+ if (this.options.debug) console.log('🚀 Triggering delayed autoplay...');
7990
+
7991
+ if (this.video) {
7992
+ this.video.setAttribute('autoplay', '');
7993
+ this.video.style.opacity = '1';
7994
+ this.video.style.visibility = '';
7995
+ }
7996
+
7997
+ setTimeout(() => {
7998
+ this.play();
7999
+ }, 300);
8000
+ } else if (this.video) {
8001
+ this.video.style.opacity = '1';
8002
+ this.video.style.visibility = '';
7742
8003
  }
7743
8004
  }
7744
8005
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myetv-player",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "MYETV Video Player - Modular HTML5 video player with plugin support for YouTube, Vimeo, Twitch, Facebook, Cloudflare Stream and streaming protocols (HLS/DASH)",
5
5
  "main": "dist/myetv-player.js",
6
6
  "files": [