myetv-player 1.0.10 → 1.1.1

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.
@@ -15,6 +15,7 @@
15
15
  showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
16
16
  autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
17
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
 
@@ -24,10 +25,20 @@
24
25
  // Auto caption language option
25
26
  autoCaptionLanguage: options.autoCaptionLanguage || null, // e.g., 'it', 'en', 'es', 'de', 'fr'
26
27
 
28
+ // Enable or disable click over youtube player
29
+ mouseClick: options.mouseClick !== undefined ? options.mouseClick : false,
30
+
27
31
  debug: true,
28
32
  ...options
29
33
  };
30
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
+
31
42
  this.ytPlayer = null;
32
43
  this.isYouTubeReady = false;
33
44
  this.videoId = this.options.videoId;
@@ -154,6 +165,14 @@
154
165
  * Update main player watermark options with channel data
155
166
  */
156
167
  async updatePlayerWatermark() {
168
+ // Don't create watermark when YouTube native UI is active
169
+ if (this.options.showYouTubeUI) {
170
+ if (this.api.player.options.debug) {
171
+ console.log('[YT Plugin] Skipping watermark - YouTube UI active');
172
+ }
173
+ return;
174
+ }
175
+
157
176
  if (!this.options.enableChannelWatermark || !this.videoId) {
158
177
  return;
159
178
  }
@@ -673,6 +692,7 @@ width: fit-content;
673
692
  cc_lang_pref: this.options.autoCaptionLanguage || 'en',
674
693
  hl: this.options.autoCaptionLanguage || 'en',
675
694
  iv_load_policy: 3,
695
+ showinfo: 0,
676
696
  ...options.playerVars
677
697
  };
678
698
 
@@ -704,65 +724,334 @@ width: fit-content;
704
724
  this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
705
725
  }
706
726
 
727
+ setAdaptiveQuality() {
728
+ if (!this.ytPlayer) return;
729
+
730
+ try {
731
+ // Check network connection speed if available
732
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
733
+ let suggestedQuality = 'default';
734
+
735
+ if (connection) {
736
+ const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
737
+ const downlink = connection.downlink; // Mbps
738
+
739
+ if (this.api.player.options.debug) {
740
+ console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
741
+ }
742
+
743
+ // Set quality based on connection speed
744
+ if (effectiveType === 'slow-2g' || downlink < 0.5) {
745
+ suggestedQuality = 'small'; // 240p
746
+ } else if (effectiveType === '2g' || downlink < 1) {
747
+ suggestedQuality = 'medium'; // 360p
748
+ } else if (effectiveType === '3g' || downlink < 2.5) {
749
+ suggestedQuality = 'large'; // 480p
750
+ } else if (downlink < 5) {
751
+ suggestedQuality = 'hd720'; // 720p
752
+ } else if (downlink < 10) {
753
+ suggestedQuality = 'hd1080'; // 1080p
754
+ } else if (downlink < 20) {
755
+ suggestedQuality = 'hd1440'; // 1440p (2K)
756
+ } else if (downlink < 35) {
757
+ suggestedQuality = 'hd2160'; // 2160p (4K)
758
+ } else {
759
+ suggestedQuality = 'highres'; // 8K o migliore disponibile
760
+ }
761
+
762
+ if (this.api.player.options.debug) {
763
+ console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
764
+ }
765
+
766
+ this.ytPlayer.setPlaybackQuality(suggestedQuality);
767
+ } else {
768
+ // Fallback: start with medium quality on unknown devices
769
+ if (this.api.player.options.debug) {
770
+ console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
771
+ }
772
+ this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
773
+ }
774
+ } catch (error) {
775
+ if (this.api.player.options.debug) {
776
+ console.error('[YT Plugin] Error setting adaptive quality:', error);
777
+ }
778
+ }
779
+ }
780
+
781
+ startBufferMonitoring() {
782
+ if (this.bufferMonitorInterval) {
783
+ clearInterval(this.bufferMonitorInterval);
784
+ }
785
+
786
+ let bufferingCount = 0;
787
+ let lastState = null;
788
+
789
+ this.bufferMonitorInterval = setInterval(() => {
790
+ if (!this.ytPlayer) return;
791
+
792
+ try {
793
+ const state = this.ytPlayer.getPlayerState();
794
+
795
+ // Detect buffering (state 3)
796
+ if (state === YT.PlayerState.BUFFERING) {
797
+ bufferingCount++;
798
+
799
+ if (this.api.player.options.debug) {
800
+ console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
801
+ }
802
+
803
+ // If buffering happens too often, reduce quality
804
+ if (bufferingCount >= 3) {
805
+ const currentQuality = this.ytPlayer.getPlaybackQuality();
806
+ const availableQualities = this.ytPlayer.getAvailableQualityLevels();
807
+
808
+ if (this.api.player.options.debug) {
809
+ console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
810
+ console.log('[YT Plugin] Available qualities:', availableQualities);
811
+ }
812
+
813
+ // Quality hierarchy (highest to lowest)
814
+ const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
815
+ const currentIndex = qualityLevels.indexOf(currentQuality);
816
+
817
+ // Try to go to next lower quality
818
+ if (currentIndex < qualityLevels.length - 1) {
819
+ const lowerQuality = qualityLevels[currentIndex + 1];
820
+
821
+ // Check if lower quality is available
822
+ if (availableQualities.includes(lowerQuality)) {
823
+ if (this.api.player.options.debug) {
824
+ console.log('[YT Plugin] Reducing quality to:', lowerQuality);
825
+ }
826
+
827
+ this.ytPlayer.setPlaybackQuality(lowerQuality);
828
+ bufferingCount = 0; // Reset counter
829
+ }
830
+ }
831
+ }
832
+ } else if (state === YT.PlayerState.PLAYING) {
833
+ // Reset buffering count when playing smoothly
834
+ if (lastState === YT.PlayerState.BUFFERING) {
835
+ setTimeout(() => {
836
+ if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
837
+ bufferingCount = Math.max(0, bufferingCount - 1);
838
+ }
839
+ }, 5000); // Wait 5 seconds of smooth playback
840
+ }
841
+ }
842
+
843
+ lastState = state;
844
+ } catch (error) {
845
+ if (this.api.player.options.debug) {
846
+ console.error('[YT Plugin] Error in buffer monitoring:', error);
847
+ }
848
+ }
849
+ }, 1000); // Check every second
850
+ }
851
+
707
852
  createMouseMoveOverlay() {
708
853
  if (this.mouseMoveOverlay) return;
709
854
 
855
+ // Do NOT create overlay if YouTube native UI is enabled (ToS compliant)
856
+ if (this.options.showYouTubeUI) {
857
+ if (this.api.player.options.debug) {
858
+ console.log('[YT Plugin] Skipping overlay - YouTube native UI enabled (ToS compliant)');
859
+ }
860
+
861
+ // Enable clicks on YouTube player
862
+ if (this.options.mouseClick !== false) {
863
+ this.enableYouTubeClicks();
864
+ }
865
+
866
+ // Setup mouse detection for custom controls visibility
867
+ this.setupMouseMoveDetection();
868
+ return;
869
+ }
870
+
710
871
  this.mouseMoveOverlay = document.createElement('div');
711
872
  this.mouseMoveOverlay.className = 'yt-mousemove-overlay';
712
- this.mouseMoveOverlay.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;z-index:2;background:transparent;pointer-events:auto;cursor:default;';
713
-
714
- this.api.container.insertBefore(this.mouseMoveOverlay, this.api.controls);
715
873
 
716
- // Pass mousemove to core player WITHOUT constantly resetting timer
717
- this.mouseMoveOverlay.addEventListener('mousemove', (e) => {
718
- // Let the core handle mousemove - it has its own autoHide logic
719
- if (this.api.player.onMouseMove) {
720
- this.api.player.onMouseMove(e);
721
- }
722
- });
874
+ // Apply pointer-events based on mouseClick option
875
+ const pointerEvents = this.options.mouseClick ? 'none' : 'auto';
876
+
877
+ this.mouseMoveOverlay.style.cssText = `
878
+ position: absolute;
879
+ top: 0;
880
+ left: 0;
881
+ width: 100%;
882
+ height: 100%;
883
+ z-index: 2;
884
+ background: transparent;
885
+ pointer-events: ${pointerEvents};
886
+ cursor: default;
887
+ `;
723
888
 
724
- this.mouseMoveOverlay.addEventListener('click', (e) => {
725
- const doubleTap = this.api.player.options.doubleTapPause;
726
- const pauseClick = this.api.player.options.pauseClick;
889
+ this.api.container.insertBefore(this.mouseMoveOverlay, this.api.controls);
727
890
 
728
- if (doubleTap) {
729
- let controlsHidden = false;
891
+ // Setup mouse detection
892
+ this.setupMouseMoveDetection();
730
893
 
731
- if (this.api.controls) {
732
- controlsHidden = this.api.controls.classList.contains('hide');
894
+ // Only add event listeners if mouseClick is disabled
895
+ if (!this.options.mouseClick) {
896
+ this.mouseMoveOverlay.addEventListener('mousemove', (e) => {
897
+ if (this.api.player.onMouseMove) {
898
+ this.api.player.onMouseMove(e);
733
899
  }
900
+ });
901
+
902
+ this.mouseMoveOverlay.addEventListener('click', (e) => {
903
+ const doubleTap = this.api.player.options.doubleTapPause;
904
+ const pauseClick = this.api.player.options.pauseClick;
734
905
 
735
- if (!controlsHidden) {
736
- const controls = this.player.container.querySelector('.controls');
737
- if (controls) {
738
- controlsHidden = controls.classList.contains('hide');
906
+ if (doubleTap) {
907
+ let controlsHidden = false;
908
+ if (this.api.controls) {
909
+ controlsHidden = this.api.controls.classList.contains('hide');
739
910
  }
740
- }
741
911
 
742
- if (!controlsHidden && this.api.controls) {
743
- const style = window.getComputedStyle(this.api.controls);
744
- controlsHidden = style.opacity === '0' || style.visibility === 'hidden';
745
- }
912
+ if (!controlsHidden) {
913
+ const controls = this.player.container.querySelector('.controls');
914
+ if (controls) {
915
+ controlsHidden = controls.classList.contains('hide');
916
+ }
917
+ }
746
918
 
747
- if (controlsHidden) {
748
- if (this.api.player.showControlsNow) {
749
- this.api.player.showControlsNow();
919
+ if (!controlsHidden && this.api.controls) {
920
+ const style = window.getComputedStyle(this.api.controls);
921
+ controlsHidden = style.opacity === '0' || style.visibility === 'hidden';
750
922
  }
751
- if (this.api.player.resetAutoHideTimer) {
752
- this.api.player.resetAutoHideTimer();
923
+
924
+ if (controlsHidden) {
925
+ if (this.api.player.showControlsNow) {
926
+ this.api.player.showControlsNow();
927
+ }
928
+ if (this.api.player.resetAutoHideTimer) {
929
+ this.api.player.resetAutoHideTimer();
930
+ }
931
+ return;
753
932
  }
754
- return;
933
+
934
+ this.togglePlayPauseYT();
935
+ } else if (pauseClick) {
936
+ this.togglePlayPauseYT();
755
937
  }
938
+ });
939
+ }
940
+ }
756
941
 
757
- // Controls visible: toggle play/pause
758
- this.togglePlayPauseYT();
759
- } else if (pauseClick) {
760
- // Always toggle on click when pauseClick is enabled
761
- this.togglePlayPauseYT();
942
+ // monitor mouse movement over container
943
+ setupMouseMoveDetection() {
944
+ // track last mouse position
945
+ this.lastMouseX = null;
946
+ this.lastMouseY = null;
947
+ this.mouseCheckInterval = null;
948
+
949
+ // Listener on container
950
+ this.api.container.addEventListener('mouseenter', () => {
951
+ if (this.api.player.options.debug) {
952
+ console.log('[YT Plugin] Mouse entered player container');
953
+ }
954
+
955
+ // show controls immediately
956
+ if (this.api.player.showControlsNow) {
957
+ this.api.player.showControlsNow();
958
+ }
959
+ if (this.api.player.resetAutoHideTimer) {
960
+ this.api.player.resetAutoHideTimer();
961
+ }
962
+
963
+ // start monitoring
964
+ this.startMousePositionTracking();
965
+ });
966
+
967
+ this.api.container.addEventListener('mouseleave', () => {
968
+ if (this.api.player.options.debug) {
969
+ console.log('[YT Plugin] Mouse left player container');
970
+ }
971
+
972
+ // stop monitoring
973
+ this.stopMousePositionTracking();
974
+ });
975
+
976
+ // capture mouse move on container
977
+ this.api.container.addEventListener('mousemove', (e) => {
978
+ this.lastMouseX = e.clientX;
979
+ this.lastMouseY = e.clientY;
980
+
981
+ if (this.api.player.onMouseMove) {
982
+ this.api.player.onMouseMove(e);
983
+ }
984
+
985
+ if (this.api.player.resetAutoHideTimer) {
986
+ this.api.player.resetAutoHideTimer();
762
987
  }
763
988
  });
764
989
  }
765
990
 
991
+ // check mouse position on iframe
992
+ startMousePositionTracking() {
993
+ if (this.mouseCheckInterval) return;
994
+
995
+ this.mouseCheckInterval = setInterval(() => {
996
+ // Listener to capture mouse position on iframe
997
+ const handleGlobalMove = (e) => {
998
+ const newX = e.clientX;
999
+ const newY = e.clientY;
1000
+
1001
+ // if mouse is moving
1002
+ if (this.lastMouseX !== newX || this.lastMouseY !== newY) {
1003
+ this.lastMouseX = newX;
1004
+ this.lastMouseY = newY;
1005
+
1006
+ // verify if mouse is enter the container
1007
+ const rect = this.api.container.getBoundingClientRect();
1008
+ const isInside = (
1009
+ newX >= rect.left &&
1010
+ newX <= rect.right &&
1011
+ newY >= rect.top &&
1012
+ newY <= rect.bottom
1013
+ );
1014
+
1015
+ if (isInside) {
1016
+ if (this.api.player.showControlsNow) {
1017
+ this.api.player.showControlsNow();
1018
+ }
1019
+ if (this.api.player.resetAutoHideTimer) {
1020
+ this.api.player.resetAutoHideTimer();
1021
+ }
1022
+ }
1023
+ }
1024
+ };
1025
+
1026
+ // Listener temp
1027
+ document.addEventListener('mousemove', handleGlobalMove, { once: true, passive: true });
1028
+ }, 100); // Check ogni 100ms
1029
+ }
1030
+
1031
+ stopMousePositionTracking() {
1032
+ if (this.mouseCheckInterval) {
1033
+ clearInterval(this.mouseCheckInterval);
1034
+ this.mouseCheckInterval = null;
1035
+ }
1036
+ }
1037
+
1038
+
1039
+ // enable or disable clicks over youtube player
1040
+ enableYouTubeClicks() {
1041
+ if (this.ytPlayerContainer) {
1042
+ this.ytPlayerContainer.style.pointerEvents = 'auto';
1043
+
1044
+ const iframe = this.ytPlayerContainer.querySelector('iframe');
1045
+ if (iframe) {
1046
+ iframe.style.pointerEvents = 'auto';
1047
+ }
1048
+
1049
+ if (this.api.player.options.debug) {
1050
+ console.log('[YT Plugin] YouTube clicks enabled - overlay transparent');
1051
+ }
1052
+ }
1053
+ }
1054
+
766
1055
  togglePlayPauseYT() {
767
1056
  if (!this.ytPlayer) return;
768
1057
 
@@ -788,6 +1077,9 @@ width: fit-content;
788
1077
  this.mouseMoveOverlay.remove();
789
1078
  this.mouseMoveOverlay = null;
790
1079
  }
1080
+
1081
+ // stop tracking mouse
1082
+ this.stopMousePositionTracking();
791
1083
  }
792
1084
 
793
1085
  hidePosterOverlay() {
@@ -842,11 +1134,47 @@ width: fit-content;
842
1134
  document.head.appendChild(forceVisibilityCSS);
843
1135
  if (this.api.player.options.debug) console.log('[YT Plugin] 🎨 CSS force visibility injected');
844
1136
 
1137
+ // Enable YouTube clicks if option is set
1138
+ if (this.options.mouseClick) {
1139
+ this.enableYouTubeClicks();
1140
+ }
845
1141
  this.hideLoadingOverlay();
846
1142
  this.hideInitialLoading();
847
1143
  this.injectYouTubeCSSOverride();
848
1144
 
849
1145
  this.syncControls();
1146
+ // Start buffer monitoring for auto quality adjustment
1147
+ this.startBufferMonitoring();
1148
+
1149
+ // Hide custom controls when YouTube native UI is enabled
1150
+ if (this.options.showYouTubeUI) {
1151
+ // Hide controls
1152
+ if (this.api.controls) {
1153
+ this.api.controls.style.display = 'none';
1154
+ this.api.controls.style.opacity = '0';
1155
+ this.api.controls.style.visibility = 'hidden';
1156
+ this.api.controls.style.pointerEvents = 'none';
1157
+ }
1158
+
1159
+ // Hide overlay title
1160
+ const overlayTitle = this.api.container.querySelector('.title-overlay');
1161
+ if (overlayTitle) {
1162
+ overlayTitle.style.display = 'none';
1163
+ overlayTitle.style.opacity = '0';
1164
+ overlayTitle.style.visibility = 'hidden';
1165
+ }
1166
+
1167
+ // Hide watermark
1168
+ const watermark = this.api.container.querySelector('.watermark');
1169
+ if (watermark) {
1170
+ watermark.style.display = 'none';
1171
+ watermark.style.opacity = '0';
1172
+ watermark.style.visibility = 'hidden';
1173
+ }
1174
+
1175
+ // Force hide via CSS
1176
+ this.forceHideCustomControls();
1177
+ }
850
1178
 
851
1179
  // Handle responsive layout for PiP and subtitles buttons
852
1180
  this.handleResponsiveLayout();
@@ -858,6 +1186,8 @@ width: fit-content;
858
1186
  // Check if this is a live stream
859
1187
  setTimeout(() => this.checkIfLiveStream(), 2000);
860
1188
  setTimeout(() => this.checkIfLiveStream(), 5000);
1189
+ // Set adaptive quality based on connection
1190
+ setTimeout(() => this.setAdaptiveQuality(), 1000);
861
1191
 
862
1192
 
863
1193
  // Listen for window resize
@@ -895,14 +1225,41 @@ width: fit-content;
895
1225
 
896
1226
  }
897
1227
 
1228
+ forceHideCustomControls() {
1229
+ const existingStyle = document.getElementById('yt-force-hide-controls');
1230
+ if (existingStyle) {
1231
+ return;
1232
+ }
1233
+
1234
+ const style = document.createElement('style');
1235
+ style.id = 'yt-force-hide-controls';
1236
+ style.textContent = `
1237
+ .video-wrapper.youtube-native-ui .controls,
1238
+ .video-wrapper.youtube-native-ui .title-overlay,
1239
+ .video-wrapper.youtube-native-ui .watermark {
1240
+ display: none !important;
1241
+ opacity: 0 !important;
1242
+ visibility: hidden !important;
1243
+ pointer-events: none !important;
1244
+ }
1245
+ `;
1246
+ document.head.appendChild(style);
1247
+
1248
+ this.api.container.classList.add('youtube-native-ui');
1249
+
1250
+ if (this.api.player.options.debug) {
1251
+ console.log('[YT Plugin] CSS injected - custom elements hidden (simple method)');
1252
+ }
1253
+ }
1254
+
898
1255
  checkIfLiveStream() {
899
1256
  if (this.api.player.options.debug) {
900
- console.log('[YT Plugin] 🔍 Starting live stream check...');
1257
+ console.log('[YT Plugin] Starting live stream check...');
901
1258
  }
902
1259
 
903
1260
  if (!this.ytPlayer) {
904
1261
  if (this.api.player.options.debug) {
905
- console.log('[YT Plugin] ytPlayer not available');
1262
+ console.log('[YT Plugin] ytPlayer not available');
906
1263
  }
907
1264
  return false;
908
1265
  }
@@ -911,14 +1268,15 @@ width: fit-content;
911
1268
  // Method 1: Check video data for isLive property
912
1269
  if (this.ytPlayer.getVideoData) {
913
1270
  const videoData = this.ytPlayer.getVideoData();
1271
+
914
1272
  if (this.api.player.options.debug) {
915
- console.log('[YT Plugin] 📹 Video Data:', videoData);
1273
+ console.log('[YT Plugin] Video Data:', videoData);
916
1274
  }
917
1275
 
918
1276
  // Check if video data indicates it's live
919
1277
  if (videoData.isLive || videoData.isLiveBroadcast) {
920
1278
  if (this.api.player.options.debug) {
921
- console.log('[YT Plugin] LIVE detected via videoData.isLive');
1279
+ console.log('[YT Plugin] LIVE detected via videoData.isLive');
922
1280
  }
923
1281
  this.isLiveStream = true;
924
1282
  this.handleLiveStreamUI();
@@ -926,19 +1284,18 @@ width: fit-content;
926
1284
  }
927
1285
  }
928
1286
 
929
- // Method 2: Check duration (live streams have special duration values)
1287
+ // Method 2: Check duration - live streams have special duration values
930
1288
  if (this.ytPlayer.getDuration) {
931
1289
  const duration = this.ytPlayer.getDuration();
1290
+
932
1291
  if (this.api.player.options.debug) {
933
- console.log('[YT Plugin] ⏱️ Initial duration:', duration);
1292
+ console.log('[YT Plugin] Initial duration:', duration);
934
1293
  }
935
1294
 
936
- // For live streams, duration changes over time
937
- // Wait 3 seconds and check again
938
1295
  setTimeout(() => {
939
1296
  if (!this.ytPlayer || !this.ytPlayer.getDuration) {
940
1297
  if (this.api.player.options.debug) {
941
- console.log('[YT Plugin] ytPlayer lost during duration check');
1298
+ console.log('[YT Plugin] ytPlayer lost during duration check');
942
1299
  }
943
1300
  return;
944
1301
  }
@@ -947,37 +1304,36 @@ width: fit-content;
947
1304
  const difference = Math.abs(newDuration - duration);
948
1305
 
949
1306
  if (this.api.player.options.debug) {
950
- console.log('[YT Plugin] ⏱️ Duration after 3s:', newDuration);
951
- console.log('[YT Plugin] 📊 Duration difference:', difference);
1307
+ console.log('[YT Plugin] Duration after 5s:', newDuration);
1308
+ console.log('[YT Plugin] Duration difference:', difference);
952
1309
  }
953
1310
 
954
- // If duration increased by more than 0.5 seconds, it's live
955
- if (difference > 0.5) {
1311
+ if (difference > 10) {
956
1312
  if (this.api.player.options.debug) {
957
- console.log('[YT Plugin] LIVE STREAM DETECTED (duration changing)');
1313
+ console.log('[YT Plugin] LIVE STREAM DETECTED - duration changing significantly');
958
1314
  }
959
1315
  this.isLiveStream = true;
960
1316
  this.handleLiveStreamUI();
961
1317
  } else {
962
1318
  if (this.api.player.options.debug) {
963
- console.log('[YT Plugin] ℹ️ Regular video (duration stable)');
1319
+ console.log('[YT Plugin] Regular video - duration stable');
964
1320
  }
965
1321
  this.isLiveStream = false;
966
1322
  }
967
- }, 3000);
1323
+ }, 5000);
968
1324
  }
969
1325
 
970
1326
  // Method 3: Check player state
971
1327
  if (this.ytPlayer.getPlayerState) {
972
1328
  const state = this.ytPlayer.getPlayerState();
973
1329
  if (this.api.player.options.debug) {
974
- console.log('[YT Plugin] 🎮 Player state:', state);
1330
+ console.log('[YT Plugin] Player state:', state);
975
1331
  }
976
1332
  }
977
1333
 
978
1334
  } catch (error) {
979
1335
  if (this.api.player.options.debug) {
980
- console.error('[YT Plugin] Error checking live stream:', error);
1336
+ console.error('[YT Plugin] Error checking live stream:', error);
981
1337
  }
982
1338
  }
983
1339
 
@@ -1008,20 +1364,36 @@ width: fit-content;
1008
1364
 
1009
1365
  this.startLiveMonitoring();
1010
1366
 
1367
+ // Force progress bar to 100% for live streams
1368
+ this.liveProgressInterval = setInterval(() => {
1369
+ if (this.isLiveStream && this.api.player.progressFilled) {
1370
+ this.api.player.progressFilled.style.width = '100%';
1371
+
1372
+ if (this.api.player.progressHandle) {
1373
+ this.api.player.progressHandle.style.left = '100%';
1374
+ }
1375
+ }
1376
+ }, 100); // Every 100ms to override any other updates
1377
+
1011
1378
  if (this.api.player.options.debug) {
1012
1379
  console.log('[YT Plugin] ✅ Live UI setup complete');
1013
1380
  }
1014
1381
  }
1015
1382
 
1016
1383
  checkDVRAvailability() {
1017
- // Disable progress bar immediately while testing
1018
1384
  const progressContainer = this.api.container.querySelector('.progress-container');
1385
+ const progressFill = this.api.container.querySelector('.progress-fill');
1386
+
1019
1387
  if (progressContainer) {
1020
1388
  progressContainer.style.opacity = '0.3';
1021
1389
  progressContainer.style.pointerEvents = 'none';
1022
1390
  }
1023
1391
 
1024
- // Wait a bit for YouTube to fully initialize
1392
+ // Set darkgoldenrod during test
1393
+ if (progressFill) {
1394
+ progressFill.style.backgroundColor = 'darkgoldenrod';
1395
+ }
1396
+
1025
1397
  setTimeout(() => {
1026
1398
  if (!this.ytPlayer) return;
1027
1399
 
@@ -1043,25 +1415,31 @@ width: fit-content;
1043
1415
  const seekDifference = Math.abs(newCurrentTime - testSeekPosition);
1044
1416
 
1045
1417
  const progressContainer = this.api.container.querySelector('.progress-container');
1418
+ const progressFill = this.api.container.querySelector('.progress-fill');
1046
1419
 
1047
1420
  if (seekDifference < 2) {
1048
- // DVR enabled - restore progress bar
1421
+ // DVR enabled - restore with theme color
1049
1422
  if (progressContainer) {
1050
1423
  progressContainer.style.opacity = '';
1051
1424
  progressContainer.style.pointerEvents = '';
1052
1425
  }
1053
1426
 
1427
+ // Remove inline style to use theme color
1428
+ if (progressFill) {
1429
+ progressFill.style.backgroundColor = ''; // Let theme CSS handle color
1430
+ }
1431
+
1054
1432
  if (this.api.player.options.debug) {
1055
- console.log('[YT Plugin] ✅ DVR ENABLED - progress bar active');
1433
+ console.log('[YT Plugin] ✅ DVR ENABLED - progress bar active with theme color');
1056
1434
  }
1057
1435
 
1058
1436
  this.ytPlayer.seekTo(duration, true);
1059
1437
  } else {
1060
- // No DVR - keep progress bar disabled
1438
+ // No DVR - keep darkgoldenrod
1061
1439
  this.modifyProgressBarForLive();
1062
1440
 
1063
1441
  if (this.api.player.options.debug) {
1064
- console.log('[YT Plugin] ❌ DVR DISABLED - progress bar locked');
1442
+ console.log('[YT Plugin] ❌ DVR DISABLED - progress bar locked with darkgoldenrod');
1065
1443
  }
1066
1444
  }
1067
1445
  }, 500);
@@ -1209,6 +1587,7 @@ width: fit-content;
1209
1587
  modifyProgressBarForLive() {
1210
1588
  const progressContainer = this.api.container.querySelector('.progress-container');
1211
1589
  const progressHandle = this.api.container.querySelector('.progress-handle');
1590
+ const progressFill = this.api.container.querySelector('.progress-fill');
1212
1591
 
1213
1592
  if (progressContainer) {
1214
1593
  // Disable all pointer events on progress bar
@@ -1224,11 +1603,21 @@ width: fit-content;
1224
1603
  if (progressHandle) {
1225
1604
  progressHandle.style.display = 'none';
1226
1605
  }
1606
+
1607
+ // Change color to darkgoldenrod when disabled
1608
+ if (progressFill) {
1609
+ progressFill.style.backgroundColor = 'darkgoldenrod';
1610
+
1611
+ if (this.api.player.options.debug) {
1612
+ console.log('[YT Plugin] Progress fill color changed to darkgoldenrod');
1613
+ }
1614
+ }
1227
1615
  }
1228
1616
 
1229
1617
  restoreProgressBarNormal() {
1230
1618
  const progressContainer = this.api.container.querySelector('.progress-container');
1231
1619
  const progressHandle = this.api.container.querySelector('.progress-handle');
1620
+ const progressFill = this.api.container.querySelector('.progress-fill');
1232
1621
 
1233
1622
  if (progressContainer) {
1234
1623
  progressContainer.style.pointerEvents = '';
@@ -1239,6 +1628,15 @@ width: fit-content;
1239
1628
  if (progressHandle) {
1240
1629
  progressHandle.style.display = '';
1241
1630
  }
1631
+
1632
+ // Remove inline backgroundColor to let CSS theme take over
1633
+ if (progressFill) {
1634
+ progressFill.style.backgroundColor = ''; // Reset to theme color
1635
+
1636
+ if (this.api.player.options.debug) {
1637
+ console.log('[YT Plugin] Progress fill color restored to theme default');
1638
+ }
1639
+ }
1242
1640
  }
1243
1641
 
1244
1642
  startLiveMonitoring() {
@@ -1270,7 +1668,7 @@ width: fit-content;
1270
1668
  } else if (playerState === YT.PlayerState.PLAYING) {
1271
1669
  // Only update color if playing
1272
1670
  // Check latency only if duration is reasonable
1273
- if (latency > 60 && duration < 7200) {
1671
+ if (latency > 60) {
1274
1672
  // DE-SYNCED - Black background
1275
1673
  badge.style.background = '#1a1a1a';
1276
1674
  badge.textContent = 'LIVE';
@@ -1348,9 +1746,9 @@ width: fit-content;
1348
1746
  // Show time display again
1349
1747
  this.showTimeDisplay();
1350
1748
 
1351
- // Restart normal time updates
1352
- if (!this.timeUpdateInterval) {
1353
- this.setupYouTubeSync();
1749
+ if (this.liveProgressInterval) {
1750
+ clearInterval(this.liveProgressInterval);
1751
+ this.liveProgressInterval = null;
1354
1752
  }
1355
1753
 
1356
1754
  if (this.api.player.options.debug) {
@@ -1386,12 +1784,11 @@ width: fit-content;
1386
1784
  overflow: hidden !important;
1387
1785
  }
1388
1786
  `;
1389
-
1390
1787
  document.head.appendChild(style);
1391
1788
  this.api.container.classList.add('youtube-active');
1392
1789
 
1393
1790
  if (this.api.player.options.debug) {
1394
- console.log('YT Plugin: CSS override injected');
1791
+ console.log('[YT Plugin] CSS override injected (ToS compliant)');
1395
1792
  }
1396
1793
  }
1397
1794
 
@@ -1440,7 +1837,7 @@ width: fit-content;
1440
1837
  const actualQuality = this.ytPlayer.getPlaybackQuality();
1441
1838
 
1442
1839
  // Only update UI if in AUTO mode, otherwise respect manual selection
1443
- if (this.currentQuality === 'default' || this.currentQuality === 'auto') {
1840
+ if (this.userQualityChoice === 'default' || this.userQualityChoice === 'auto') {
1444
1841
  if (actualQuality !== this.currentPlayingQuality) {
1445
1842
  this.currentPlayingQuality = actualQuality;
1446
1843
  if (this.api.player.options.debug) {
@@ -1620,27 +2017,59 @@ width: fit-content;
1620
2017
  if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
1621
2018
 
1622
2019
  try {
1623
- // Try multiple methods to force quality change
1624
- this.ytPlayer.setPlaybackQuality(quality);
2020
+ // Track user's quality choice for display
2021
+ this.userQualityChoice = quality;
2022
+ if (this.api.player.options.debug) {
2023
+ console.log('[YT Plugin] Setting quality to:', quality);
2024
+ console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
2025
+ console.log('[YT Plugin] Available qualities:', this.ytPlayer.getAvailableQualityLevels());
2026
+ }
1625
2027
 
1626
- // Also try setPlaybackQualityRange if available
1627
- if (this.ytPlayer.setPlaybackQualityRange) {
1628
- this.ytPlayer.setPlaybackQualityRange(quality, quality);
2028
+ // Check if requested quality is actually available
2029
+ const availableLevels = this.ytPlayer.getAvailableQualityLevels();
2030
+ if (quality !== 'default' && quality !== 'auto' && !availableLevels.includes(quality)) {
2031
+ if (this.api.player.options.debug) {
2032
+ console.warn('[YT Plugin] Requested quality not available:', quality);
2033
+ }
1629
2034
  }
1630
2035
 
1631
2036
  // Update state
1632
2037
  this.currentQuality = quality;
1633
- this.currentPlayingQuality = quality; // Force UI update
2038
+
2039
+ // Set the quality
2040
+ this.ytPlayer.setPlaybackQuality(quality);
2041
+
2042
+ // Also try setPlaybackQualityRange for better enforcement
2043
+ if (this.ytPlayer.setPlaybackQualityRange) {
2044
+ this.ytPlayer.setPlaybackQualityRange(quality, quality);
2045
+ }
1634
2046
 
1635
2047
  // Force UI update immediately
1636
2048
  this.updateQualityMenuPlayingState(quality);
1637
-
1638
- // Update button display
1639
2049
  const qualityLabel = this.getQualityLabel(quality);
1640
- this.updateQualityButtonDisplay(qualityLabel, '');
2050
+
2051
+ // For manual quality selection, show only the selected quality
2052
+ if (quality !== 'default' && quality !== 'auto') {
2053
+ this.updateQualityButtonDisplay(qualityLabel, '');
2054
+ } else {
2055
+ // For auto mode, show "Auto" and let monitoring update the actual quality
2056
+ this.updateQualityButtonDisplay('Auto', '');
2057
+ }
1641
2058
 
1642
2059
  if (this.api.player.options.debug) {
1643
- console.log('[YT Plugin] Quality set to:', quality);
2060
+ // Check actual quality after a moment
2061
+ setTimeout(() => {
2062
+ if (this.ytPlayer && this.ytPlayer.getPlaybackQuality) {
2063
+ const actualQuality = this.ytPlayer.getPlaybackQuality();
2064
+ console.log('[YT Plugin] Actual quality after 1s:', actualQuality);
2065
+ if (actualQuality !== quality && quality !== 'default' && quality !== 'auto') {
2066
+ console.warn('[YT Plugin] YouTube did not apply requested quality. This may mean:');
2067
+ console.warn(' - The quality is not available for this video');
2068
+ console.warn(' - Embedding restrictions apply');
2069
+ console.warn(' - Network/bandwidth limitations');
2070
+ }
2071
+ }
2072
+ }, 1000); // Check every 1 second for faster updates
1644
2073
  }
1645
2074
 
1646
2075
  this.api.triggerEvent('youtubeplugin:qualitychanged', { quality });
@@ -1653,7 +2082,7 @@ width: fit-content;
1653
2082
  }
1654
2083
  }
1655
2084
 
1656
- // ===== RECONSTRUCTED SUBTITLE METHODS =====
2085
+ // ===== SUBTITLE METHODS =====
1657
2086
 
1658
2087
  /**
1659
2088
  * Load available captions and create subtitle control
@@ -2311,31 +2740,6 @@ width: fit-content;
2311
2740
  }
2312
2741
  }
2313
2742
 
2314
- // Check for de-sync when user seeks during live
2315
- if (this.isLiveStream && event.data === YT.PlayerState.PLAYING) {
2316
- setTimeout(() => {
2317
- if (!this.ytPlayer) return;
2318
-
2319
- const currentTime = this.ytPlayer.getCurrentTime();
2320
- const duration = this.ytPlayer.getDuration();
2321
- const latency = duration - currentTime;
2322
-
2323
- // If latency > 60s and duration is reasonable, user has seeked back
2324
- if (latency > 60 && duration < 7200) {
2325
- const badge = this.api.container.querySelector('.live-badge');
2326
- if (badge) {
2327
- badge.style.background = '#1a1a1a';
2328
- badge.title = `${Math.floor(latency)} seconds back from the live`;
2329
- this.isAtLiveEdge = false;
2330
-
2331
- if (this.api.player.options.debug) {
2332
- console.log('[YT Plugin] ⚫ User seeked back, de-synced from live');
2333
- }
2334
- }
2335
- }
2336
- }, 500);
2337
- }
2338
-
2339
2743
  switch (event.data) {
2340
2744
  case YT.PlayerState.PLAYING:
2341
2745
  this.api.triggerEvent('played', {});
@@ -2386,18 +2790,39 @@ width: fit-content;
2386
2790
  }
2387
2791
 
2388
2792
  onPlayerError(event) {
2389
- const errorMessages = {
2390
- 2: 'Invalid video ID',
2391
- 5: 'HTML5 player error',
2392
- 100: 'Video not found or private',
2393
- 101: 'Video not allowed in embedded players',
2394
- 150: 'Video not allowed in embedded players'
2395
- };
2396
- const errorMsg = errorMessages[event.data] || 'Unknown error';
2397
- if (this.api.player.options.debug) console.error('[YT Plugin] Error:', errorMsg);
2793
+ const errorCode = event.data;
2794
+
2795
+ if (this.api.player.options.debug) {
2796
+ console.error('[YT Plugin] Player error:', errorCode);
2797
+ }
2798
+
2799
+ // Error codes che indicano video non disponibile
2800
+ const unavailableErrors = [
2801
+ 2, // Invalid video ID
2802
+ 5, // HTML5 player error
2803
+ 100, // Video not found / removed
2804
+ 101, // Video not allowed to be played in embedded players (private/restricted)
2805
+ 150 // Same as 101
2806
+ ];
2807
+
2808
+ if (unavailableErrors.includes(errorCode)) {
2809
+ if (this.api.player.options.debug) {
2810
+ console.log('[YT Plugin] Video unavailable, triggering ended event');
2811
+ }
2812
+
2813
+ // Trigger the ended event from your player API
2814
+ this.api.triggerEvent('ended', {
2815
+ reason: 'video_unavailable',
2816
+ errorCode: errorCode
2817
+ });
2818
+
2819
+ // Optional: show poster overlay again
2820
+ this.showPosterOverlay();
2821
+ }
2822
+
2398
2823
  this.api.triggerEvent('youtubeplugin:error', {
2399
- errorCode: event.data,
2400
- errorMessage: errorMsg
2824
+ errorCode: errorCode,
2825
+ videoId: this.videoId
2401
2826
  });
2402
2827
  }
2403
2828
 
@@ -2700,7 +3125,24 @@ width: fit-content;
2700
3125
  const duration = this.ytPlayer.getDuration();
2701
3126
 
2702
3127
  if (this.api.player.progressFilled && duration) {
2703
- const progress = (currentTime / duration) * 100;
3128
+ let progress;
3129
+
3130
+ // For live streams, always show progress at 100%
3131
+ if (this.isLiveStream) {
3132
+ progress = 100;
3133
+ } else {
3134
+ // For regular videos, calculate normally
3135
+ progress = (currentTime / duration) * 100;
3136
+ }
3137
+
3138
+ // Check if live badge exists = it's a live stream
3139
+ const liveBadge = this.api.container.querySelector('.live-badge');
3140
+
3141
+ if (liveBadge) {
3142
+ // Force 100% for live streams
3143
+ progress = 100;
3144
+ }
3145
+
2704
3146
  this.api.player.progressFilled.style.width = `${progress}%`;
2705
3147
  if (this.api.player.progressHandle) {
2706
3148
  this.api.player.progressHandle.style.left = `${progress}%`;
@@ -2869,6 +3311,16 @@ width: fit-content;
2869
3311
  this.ytPlayerContainer = null;
2870
3312
  }
2871
3313
 
3314
+ if (this.bufferMonitorInterval) {
3315
+ clearInterval(this.bufferMonitorInterval);
3316
+ this.bufferMonitorInterval = null;
3317
+ }
3318
+
3319
+ if (this.qualityMonitorInterval) {
3320
+ clearInterval(this.qualityMonitorInterval);
3321
+ this.qualityMonitorInterval = null;
3322
+ }
3323
+
2872
3324
  this.removeMouseMoveOverlay();
2873
3325
 
2874
3326
  this.api.container.classList.remove('youtube-active');
@@ -2922,6 +3374,10 @@ width: fit-content;
2922
3374
  this.isLiveStream = false;
2923
3375
  this.isAtLiveEdge = false;
2924
3376
 
3377
+ if (this.liveProgressInterval) {
3378
+ clearInterval(this.liveProgressInterval);
3379
+ this.liveProgressInterval = null;
3380
+ }
2925
3381
  }
2926
3382
  }
2927
3383