myetv-player 1.1.0 → 1.1.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.
@@ -14,7 +14,8 @@
14
14
  autoplay: options.autoplay !== undefined ? options.autoplay : false,
15
15
  showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
16
16
  autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
17
- quality: options.quality || 'auto',
17
+ quality: options.quality || 'default',
18
+
18
19
  enableQualityControl: options.enableQualityControl !== undefined ? options.enableQualityControl : true,
19
20
  enableCaptions: options.enableCaptions !== undefined ? options.enableCaptions : true,
20
21
 
@@ -31,6 +32,13 @@
31
32
  ...options
32
33
  };
33
34
 
35
+ // Normalize 'auto' to 'default' for YouTube API compatibility
36
+ if (this.options.quality === 'auto') {
37
+ this.options.quality = 'default';
38
+ }
39
+ // Track original user choice for UI display
40
+ this.userQualityChoice = options.quality || 'default';
41
+
34
42
  this.ytPlayer = null;
35
43
  this.isYouTubeReady = false;
36
44
  this.videoId = this.options.videoId;
@@ -46,6 +54,7 @@
46
54
  this.mouseMoveOverlay = null;
47
55
  this.qualityCheckAttempts = 0;
48
56
  this.captionCheckAttempts = 0;
57
+ this.liveStreamChecked = false;
49
58
  this.captionStateCheckInterval = null;
50
59
  this.qualityMonitorInterval = null;
51
60
  this.resizeListenerAdded = false;
@@ -69,6 +78,7 @@
69
78
  { code: 'ar', name: 'Arabic' },
70
79
  { code: 'pt', name: 'Portuguese' },
71
80
  { code: 'ru', name: 'Russian' },
81
+ { code: 'zh', name: 'Chinese' },
72
82
  { code: 'ja', name: 'Japanese' },
73
83
  { code: 'de', name: 'German' },
74
84
  { code: 'fr', name: 'French' },
@@ -716,6 +726,131 @@ width: fit-content;
716
726
  this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
717
727
  }
718
728
 
729
+ setAdaptiveQuality() {
730
+ if (!this.ytPlayer) return;
731
+
732
+ try {
733
+ // Check network connection speed if available
734
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
735
+ let suggestedQuality = 'default';
736
+
737
+ if (connection) {
738
+ const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
739
+ const downlink = connection.downlink; // Mbps
740
+
741
+ if (this.api.player.options.debug) {
742
+ console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
743
+ }
744
+
745
+ // Set quality based on connection speed
746
+ if (effectiveType === 'slow-2g' || downlink < 0.5) {
747
+ suggestedQuality = 'small'; // 240p
748
+ } else if (effectiveType === '2g' || downlink < 1) {
749
+ suggestedQuality = 'medium'; // 360p
750
+ } else if (effectiveType === '3g' || downlink < 2.5) {
751
+ suggestedQuality = 'large'; // 480p
752
+ } else if (downlink < 5) {
753
+ suggestedQuality = 'hd720'; // 720p
754
+ } else if (downlink < 10) {
755
+ suggestedQuality = 'hd1080'; // 1080p
756
+ } else if (downlink < 20) {
757
+ suggestedQuality = 'hd1440'; // 1440p (2K)
758
+ } else if (downlink < 35) {
759
+ suggestedQuality = 'hd2160'; // 2160p (4K)
760
+ } else {
761
+ suggestedQuality = 'highres'; // 8K o migliore disponibile
762
+ }
763
+
764
+ if (this.api.player.options.debug) {
765
+ console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
766
+ }
767
+
768
+ this.ytPlayer.setPlaybackQuality(suggestedQuality);
769
+ } else {
770
+ // Fallback: start with medium quality on unknown devices
771
+ if (this.api.player.options.debug) {
772
+ console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
773
+ }
774
+ this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
775
+ }
776
+ } catch (error) {
777
+ if (this.api.player.options.debug) {
778
+ console.error('[YT Plugin] Error setting adaptive quality:', error);
779
+ }
780
+ }
781
+ }
782
+
783
+ startBufferMonitoring() {
784
+ if (this.bufferMonitorInterval) {
785
+ clearInterval(this.bufferMonitorInterval);
786
+ }
787
+
788
+ let bufferingCount = 0;
789
+ let lastState = null;
790
+
791
+ this.bufferMonitorInterval = setInterval(() => {
792
+ if (!this.ytPlayer) return;
793
+
794
+ try {
795
+ const state = this.ytPlayer.getPlayerState();
796
+
797
+ // Detect buffering (state 3)
798
+ if (state === YT.PlayerState.BUFFERING) {
799
+ bufferingCount++;
800
+
801
+ if (this.api.player.options.debug) {
802
+ console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
803
+ }
804
+
805
+ // If buffering happens too often, reduce quality
806
+ if (bufferingCount >= 3) {
807
+ const currentQuality = this.ytPlayer.getPlaybackQuality();
808
+ const availableQualities = this.ytPlayer.getAvailableQualityLevels();
809
+
810
+ if (this.api.player.options.debug) {
811
+ console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
812
+ console.log('[YT Plugin] Available qualities:', availableQualities);
813
+ }
814
+
815
+ // Quality hierarchy (highest to lowest)
816
+ const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
817
+ const currentIndex = qualityLevels.indexOf(currentQuality);
818
+
819
+ // Try to go to next lower quality
820
+ if (currentIndex < qualityLevels.length - 1) {
821
+ const lowerQuality = qualityLevels[currentIndex + 1];
822
+
823
+ // Check if lower quality is available
824
+ if (availableQualities.includes(lowerQuality)) {
825
+ if (this.api.player.options.debug) {
826
+ console.log('[YT Plugin] Reducing quality to:', lowerQuality);
827
+ }
828
+
829
+ this.ytPlayer.setPlaybackQuality(lowerQuality);
830
+ bufferingCount = 0; // Reset counter
831
+ }
832
+ }
833
+ }
834
+ } else if (state === YT.PlayerState.PLAYING) {
835
+ // Reset buffering count when playing smoothly
836
+ if (lastState === YT.PlayerState.BUFFERING) {
837
+ setTimeout(() => {
838
+ if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
839
+ bufferingCount = Math.max(0, bufferingCount - 1);
840
+ }
841
+ }, 5000); // Wait 5 seconds of smooth playback
842
+ }
843
+ }
844
+
845
+ lastState = state;
846
+ } catch (error) {
847
+ if (this.api.player.options.debug) {
848
+ console.error('[YT Plugin] Error in buffer monitoring:', error);
849
+ }
850
+ }
851
+ }, 1000); // Check every second
852
+ }
853
+
719
854
  createMouseMoveOverlay() {
720
855
  if (this.mouseMoveOverlay) return;
721
856
 
@@ -1010,6 +1145,8 @@ width: fit-content;
1010
1145
  this.injectYouTubeCSSOverride();
1011
1146
 
1012
1147
  this.syncControls();
1148
+ // Start buffer monitoring for auto quality adjustment
1149
+ this.startBufferMonitoring();
1013
1150
 
1014
1151
  // Hide custom controls when YouTube native UI is enabled
1015
1152
  if (this.options.showYouTubeUI) {
@@ -1044,13 +1181,16 @@ width: fit-content;
1044
1181
  // Handle responsive layout for PiP and subtitles buttons
1045
1182
  this.handleResponsiveLayout();
1046
1183
 
1184
+ this.playAttemptTimeout = null;
1185
+
1047
1186
  // Hide PiP from settings menu (separate function, called after responsive layout)
1048
1187
  setTimeout(() => this.hidePipFromSettingsMenuOnly(), 500);
1049
- setTimeout(() => this.hidePipFromSettingsMenuOnly(), 1500);
1050
- setTimeout(() => this.hidePipFromSettingsMenuOnly(), 3000);
1188
+
1051
1189
  // Check if this is a live stream
1052
1190
  setTimeout(() => this.checkIfLiveStream(), 2000);
1053
1191
  setTimeout(() => this.checkIfLiveStream(), 5000);
1192
+ // Set adaptive quality based on connection
1193
+ setTimeout(() => this.setAdaptiveQuality(), 1000);
1054
1194
 
1055
1195
 
1056
1196
  // Listen for window resize
@@ -1062,13 +1202,9 @@ width: fit-content;
1062
1202
 
1063
1203
  // Load qualities with multiple attempts
1064
1204
  setTimeout(() => this.loadAvailableQualities(), 500);
1065
- setTimeout(() => this.loadAvailableQualities(), 1000);
1066
- setTimeout(() => this.loadAvailableQualities(), 2000);
1067
1205
 
1068
1206
  // Load captions with multiple attempts
1069
1207
  setTimeout(() => this.loadAvailableCaptions(), 500);
1070
- setTimeout(() => this.loadAvailableCaptions(), 1000);
1071
- setTimeout(() => this.loadAvailableCaptions(), 2000);
1072
1208
 
1073
1209
  if (this.options.quality && this.options.quality !== 'default') {
1074
1210
  setTimeout(() => this.setQuality(this.options.quality), 1000);
@@ -1700,7 +1836,7 @@ width: fit-content;
1700
1836
  const actualQuality = this.ytPlayer.getPlaybackQuality();
1701
1837
 
1702
1838
  // Only update UI if in AUTO mode, otherwise respect manual selection
1703
- if (this.currentQuality === 'default' || this.currentQuality === 'auto') {
1839
+ if (this.userQualityChoice === 'default' || this.userQualityChoice === 'auto') {
1704
1840
  if (actualQuality !== this.currentPlayingQuality) {
1705
1841
  this.currentPlayingQuality = actualQuality;
1706
1842
  if (this.api.player.options.debug) {
@@ -1880,6 +2016,8 @@ width: fit-content;
1880
2016
  if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
1881
2017
 
1882
2018
  try {
2019
+ // Track user's quality choice for display
2020
+ this.userQualityChoice = quality;
1883
2021
  if (this.api.player.options.debug) {
1884
2022
  console.log('[YT Plugin] Setting quality to:', quality);
1885
2023
  console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
@@ -2562,6 +2700,49 @@ width: fit-content;
2562
2700
  };
2563
2701
  if (this.api.player.options.debug) console.log('[YT Plugin] State:', states[event.data], event.data);
2564
2702
 
2703
+ // Know if video have some problems to start
2704
+ if (event.data === YT.PlayerState.UNSTARTED || event.data === -1) {
2705
+ // start timeout when video is unstarted
2706
+ if (this.playAttemptTimeout) {
2707
+ clearTimeout(this.playAttemptTimeout);
2708
+ }
2709
+
2710
+ this.playAttemptTimeout = setTimeout(() => {
2711
+ if (!this.ytPlayer) return;
2712
+
2713
+ const currentState = this.ytPlayer.getPlayerState();
2714
+
2715
+ // If video is unstrated after timeout, consider it restricted
2716
+ if (currentState === YT.PlayerState.UNSTARTED || currentState === -1) {
2717
+ if (this.api.player.options.debug) {
2718
+ console.log('YT Plugin: Video stuck in UNSTARTED - possibly members-only or restricted');
2719
+ }
2720
+
2721
+ // Trigger ended event
2722
+ this.api.triggerEvent('ended', {
2723
+ reason: 'video_restricted_or_membership',
2724
+ state: currentState
2725
+ });
2726
+
2727
+ // Show poster overlay if present
2728
+ this.showPosterOverlay();
2729
+
2730
+ // Trigger custom event
2731
+ this.api.triggerEvent('youtubeplugin:membershiprestricted', {
2732
+ videoId: this.videoId
2733
+ });
2734
+ }
2735
+ }, 15000); // 15 seconds of timeout
2736
+
2737
+ } else if (event.data === YT.PlayerState.PLAYING ||
2738
+ event.data === YT.PlayerState.BUFFERING) {
2739
+ // Clear the timeout if video starts correctly
2740
+ if (this.playAttemptTimeout) {
2741
+ clearTimeout(this.playAttemptTimeout);
2742
+ this.playAttemptTimeout = null;
2743
+ }
2744
+ }
2745
+
2565
2746
  // Get play/pause icons
2566
2747
  const playIcon = this.api.container.querySelector('.play-icon');
2567
2748
  const pauseIcon = this.api.container.querySelector('.pause-icon');
@@ -2651,18 +2832,39 @@ width: fit-content;
2651
2832
  }
2652
2833
 
2653
2834
  onPlayerError(event) {
2654
- const errorMessages = {
2655
- 2: 'Invalid video ID',
2656
- 5: 'HTML5 player error',
2657
- 100: 'Video not found or private',
2658
- 101: 'Video not allowed in embedded players',
2659
- 150: 'Video not allowed in embedded players'
2660
- };
2661
- const errorMsg = errorMessages[event.data] || 'Unknown error';
2662
- if (this.api.player.options.debug) console.error('[YT Plugin] Error:', errorMsg);
2835
+ const errorCode = event.data;
2836
+
2837
+ if (this.api.player.options.debug) {
2838
+ console.error('[YT Plugin] Player error:', errorCode);
2839
+ }
2840
+
2841
+ // Error codes che indicano video non disponibile
2842
+ const unavailableErrors = [
2843
+ 2, // Invalid video ID
2844
+ 5, // HTML5 player error
2845
+ 100, // Video not found / removed
2846
+ 101, // Video not allowed to be played in embedded players (private/restricted)
2847
+ 150 // Same as 101
2848
+ ];
2849
+
2850
+ if (unavailableErrors.includes(errorCode)) {
2851
+ if (this.api.player.options.debug) {
2852
+ console.log('[YT Plugin] Video unavailable, triggering ended event');
2853
+ }
2854
+
2855
+ // Trigger the ended event from your player API
2856
+ this.api.triggerEvent('ended', {
2857
+ reason: 'video_unavailable',
2858
+ errorCode: errorCode
2859
+ });
2860
+
2861
+ // Optional: show poster overlay again
2862
+ this.showPosterOverlay();
2863
+ }
2864
+
2663
2865
  this.api.triggerEvent('youtubeplugin:error', {
2664
- errorCode: event.data,
2665
- errorMessage: errorMsg
2866
+ errorCode: errorCode,
2867
+ videoId: this.videoId
2666
2868
  });
2667
2869
  }
2668
2870
 
@@ -3126,6 +3328,12 @@ width: fit-content;
3126
3328
  dispose() {
3127
3329
  if (this.api.player.options.debug) console.log('[YT Plugin] Disposing');
3128
3330
 
3331
+ // Cleanup timeout
3332
+ if (this.playAttemptTimeout) {
3333
+ clearTimeout(this.playAttemptTimeout);
3334
+ this.playAttemptTimeout = null;
3335
+ }
3336
+
3129
3337
  if (this.timeUpdateInterval) {
3130
3338
  clearInterval(this.timeUpdateInterval);
3131
3339
  this.timeUpdateInterval = null;
@@ -3151,6 +3359,16 @@ width: fit-content;
3151
3359
  this.ytPlayerContainer = null;
3152
3360
  }
3153
3361
 
3362
+ if (this.bufferMonitorInterval) {
3363
+ clearInterval(this.bufferMonitorInterval);
3364
+ this.bufferMonitorInterval = null;
3365
+ }
3366
+
3367
+ if (this.qualityMonitorInterval) {
3368
+ clearInterval(this.qualityMonitorInterval);
3369
+ this.qualityMonitorInterval = null;
3370
+ }
3371
+
3154
3372
  this.removeMouseMoveOverlay();
3155
3373
 
3156
3374
  this.api.container.classList.remove('youtube-active');