myetv-player 1.0.10 → 1.1.0

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,7 @@
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 || 'default',
17
+ quality: options.quality || 'auto',
18
18
  enableQualityControl: options.enableQualityControl !== undefined ? options.enableQualityControl : true,
19
19
  enableCaptions: options.enableCaptions !== undefined ? options.enableCaptions : true,
20
20
 
@@ -24,6 +24,9 @@
24
24
  // Auto caption language option
25
25
  autoCaptionLanguage: options.autoCaptionLanguage || null, // e.g., 'it', 'en', 'es', 'de', 'fr'
26
26
 
27
+ // Enable or disable click over youtube player
28
+ mouseClick: options.mouseClick !== undefined ? options.mouseClick : false,
29
+
27
30
  debug: true,
28
31
  ...options
29
32
  };
@@ -154,6 +157,14 @@
154
157
  * Update main player watermark options with channel data
155
158
  */
156
159
  async updatePlayerWatermark() {
160
+ // Don't create watermark when YouTube native UI is active
161
+ if (this.options.showYouTubeUI) {
162
+ if (this.api.player.options.debug) {
163
+ console.log('[YT Plugin] Skipping watermark - YouTube UI active');
164
+ }
165
+ return;
166
+ }
167
+
157
168
  if (!this.options.enableChannelWatermark || !this.videoId) {
158
169
  return;
159
170
  }
@@ -673,6 +684,7 @@ width: fit-content;
673
684
  cc_lang_pref: this.options.autoCaptionLanguage || 'en',
674
685
  hl: this.options.autoCaptionLanguage || 'en',
675
686
  iv_load_policy: 3,
687
+ showinfo: 0,
676
688
  ...options.playerVars
677
689
  };
678
690
 
@@ -707,60 +719,204 @@ width: fit-content;
707
719
  createMouseMoveOverlay() {
708
720
  if (this.mouseMoveOverlay) return;
709
721
 
722
+ // Do NOT create overlay if YouTube native UI is enabled (ToS compliant)
723
+ if (this.options.showYouTubeUI) {
724
+ if (this.api.player.options.debug) {
725
+ console.log('[YT Plugin] Skipping overlay - YouTube native UI enabled (ToS compliant)');
726
+ }
727
+
728
+ // Enable clicks on YouTube player
729
+ if (this.options.mouseClick !== false) {
730
+ this.enableYouTubeClicks();
731
+ }
732
+
733
+ // Setup mouse detection for custom controls visibility
734
+ this.setupMouseMoveDetection();
735
+ return;
736
+ }
737
+
710
738
  this.mouseMoveOverlay = document.createElement('div');
711
739
  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
740
 
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
- });
741
+ // Apply pointer-events based on mouseClick option
742
+ const pointerEvents = this.options.mouseClick ? 'none' : 'auto';
743
+
744
+ this.mouseMoveOverlay.style.cssText = `
745
+ position: absolute;
746
+ top: 0;
747
+ left: 0;
748
+ width: 100%;
749
+ height: 100%;
750
+ z-index: 2;
751
+ background: transparent;
752
+ pointer-events: ${pointerEvents};
753
+ cursor: default;
754
+ `;
723
755
 
724
- this.mouseMoveOverlay.addEventListener('click', (e) => {
725
- const doubleTap = this.api.player.options.doubleTapPause;
726
- const pauseClick = this.api.player.options.pauseClick;
756
+ this.api.container.insertBefore(this.mouseMoveOverlay, this.api.controls);
727
757
 
728
- if (doubleTap) {
729
- let controlsHidden = false;
758
+ // Setup mouse detection
759
+ this.setupMouseMoveDetection();
730
760
 
731
- if (this.api.controls) {
732
- controlsHidden = this.api.controls.classList.contains('hide');
761
+ // Only add event listeners if mouseClick is disabled
762
+ if (!this.options.mouseClick) {
763
+ this.mouseMoveOverlay.addEventListener('mousemove', (e) => {
764
+ if (this.api.player.onMouseMove) {
765
+ this.api.player.onMouseMove(e);
733
766
  }
767
+ });
768
+
769
+ this.mouseMoveOverlay.addEventListener('click', (e) => {
770
+ const doubleTap = this.api.player.options.doubleTapPause;
771
+ const pauseClick = this.api.player.options.pauseClick;
734
772
 
735
- if (!controlsHidden) {
736
- const controls = this.player.container.querySelector('.controls');
737
- if (controls) {
738
- controlsHidden = controls.classList.contains('hide');
773
+ if (doubleTap) {
774
+ let controlsHidden = false;
775
+ if (this.api.controls) {
776
+ controlsHidden = this.api.controls.classList.contains('hide');
739
777
  }
740
- }
741
778
 
742
- if (!controlsHidden && this.api.controls) {
743
- const style = window.getComputedStyle(this.api.controls);
744
- controlsHidden = style.opacity === '0' || style.visibility === 'hidden';
745
- }
779
+ if (!controlsHidden) {
780
+ const controls = this.player.container.querySelector('.controls');
781
+ if (controls) {
782
+ controlsHidden = controls.classList.contains('hide');
783
+ }
784
+ }
746
785
 
747
- if (controlsHidden) {
748
- if (this.api.player.showControlsNow) {
749
- this.api.player.showControlsNow();
786
+ if (!controlsHidden && this.api.controls) {
787
+ const style = window.getComputedStyle(this.api.controls);
788
+ controlsHidden = style.opacity === '0' || style.visibility === 'hidden';
750
789
  }
751
- if (this.api.player.resetAutoHideTimer) {
752
- this.api.player.resetAutoHideTimer();
790
+
791
+ if (controlsHidden) {
792
+ if (this.api.player.showControlsNow) {
793
+ this.api.player.showControlsNow();
794
+ }
795
+ if (this.api.player.resetAutoHideTimer) {
796
+ this.api.player.resetAutoHideTimer();
797
+ }
798
+ return;
753
799
  }
754
- return;
800
+
801
+ this.togglePlayPauseYT();
802
+ } else if (pauseClick) {
803
+ this.togglePlayPauseYT();
755
804
  }
805
+ });
806
+ }
807
+ }
808
+
809
+ // monitor mouse movement over container
810
+ setupMouseMoveDetection() {
811
+ // track last mouse position
812
+ this.lastMouseX = null;
813
+ this.lastMouseY = null;
814
+ this.mouseCheckInterval = null;
815
+
816
+ // Listener on container
817
+ this.api.container.addEventListener('mouseenter', () => {
818
+ if (this.api.player.options.debug) {
819
+ console.log('[YT Plugin] Mouse entered player container');
820
+ }
821
+
822
+ // show controls immediately
823
+ if (this.api.player.showControlsNow) {
824
+ this.api.player.showControlsNow();
825
+ }
826
+ if (this.api.player.resetAutoHideTimer) {
827
+ this.api.player.resetAutoHideTimer();
828
+ }
829
+
830
+ // start monitoring
831
+ this.startMousePositionTracking();
832
+ });
756
833
 
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();
834
+ this.api.container.addEventListener('mouseleave', () => {
835
+ if (this.api.player.options.debug) {
836
+ console.log('[YT Plugin] Mouse left player container');
762
837
  }
838
+
839
+ // stop monitoring
840
+ this.stopMousePositionTracking();
763
841
  });
842
+
843
+ // capture mouse move on container
844
+ this.api.container.addEventListener('mousemove', (e) => {
845
+ this.lastMouseX = e.clientX;
846
+ this.lastMouseY = e.clientY;
847
+
848
+ if (this.api.player.onMouseMove) {
849
+ this.api.player.onMouseMove(e);
850
+ }
851
+
852
+ if (this.api.player.resetAutoHideTimer) {
853
+ this.api.player.resetAutoHideTimer();
854
+ }
855
+ });
856
+ }
857
+
858
+ // check mouse position on iframe
859
+ startMousePositionTracking() {
860
+ if (this.mouseCheckInterval) return;
861
+
862
+ this.mouseCheckInterval = setInterval(() => {
863
+ // Listener to capture mouse position on iframe
864
+ const handleGlobalMove = (e) => {
865
+ const newX = e.clientX;
866
+ const newY = e.clientY;
867
+
868
+ // if mouse is moving
869
+ if (this.lastMouseX !== newX || this.lastMouseY !== newY) {
870
+ this.lastMouseX = newX;
871
+ this.lastMouseY = newY;
872
+
873
+ // verify if mouse is enter the container
874
+ const rect = this.api.container.getBoundingClientRect();
875
+ const isInside = (
876
+ newX >= rect.left &&
877
+ newX <= rect.right &&
878
+ newY >= rect.top &&
879
+ newY <= rect.bottom
880
+ );
881
+
882
+ if (isInside) {
883
+ if (this.api.player.showControlsNow) {
884
+ this.api.player.showControlsNow();
885
+ }
886
+ if (this.api.player.resetAutoHideTimer) {
887
+ this.api.player.resetAutoHideTimer();
888
+ }
889
+ }
890
+ }
891
+ };
892
+
893
+ // Listener temp
894
+ document.addEventListener('mousemove', handleGlobalMove, { once: true, passive: true });
895
+ }, 100); // Check ogni 100ms
896
+ }
897
+
898
+ stopMousePositionTracking() {
899
+ if (this.mouseCheckInterval) {
900
+ clearInterval(this.mouseCheckInterval);
901
+ this.mouseCheckInterval = null;
902
+ }
903
+ }
904
+
905
+
906
+ // enable or disable clicks over youtube player
907
+ enableYouTubeClicks() {
908
+ if (this.ytPlayerContainer) {
909
+ this.ytPlayerContainer.style.pointerEvents = 'auto';
910
+
911
+ const iframe = this.ytPlayerContainer.querySelector('iframe');
912
+ if (iframe) {
913
+ iframe.style.pointerEvents = 'auto';
914
+ }
915
+
916
+ if (this.api.player.options.debug) {
917
+ console.log('[YT Plugin] YouTube clicks enabled - overlay transparent');
918
+ }
919
+ }
764
920
  }
765
921
 
766
922
  togglePlayPauseYT() {
@@ -788,6 +944,9 @@ width: fit-content;
788
944
  this.mouseMoveOverlay.remove();
789
945
  this.mouseMoveOverlay = null;
790
946
  }
947
+
948
+ // stop tracking mouse
949
+ this.stopMousePositionTracking();
791
950
  }
792
951
 
793
952
  hidePosterOverlay() {
@@ -842,12 +1001,46 @@ width: fit-content;
842
1001
  document.head.appendChild(forceVisibilityCSS);
843
1002
  if (this.api.player.options.debug) console.log('[YT Plugin] 🎨 CSS force visibility injected');
844
1003
 
1004
+ // Enable YouTube clicks if option is set
1005
+ if (this.options.mouseClick) {
1006
+ this.enableYouTubeClicks();
1007
+ }
845
1008
  this.hideLoadingOverlay();
846
1009
  this.hideInitialLoading();
847
1010
  this.injectYouTubeCSSOverride();
848
1011
 
849
1012
  this.syncControls();
850
1013
 
1014
+ // Hide custom controls when YouTube native UI is enabled
1015
+ if (this.options.showYouTubeUI) {
1016
+ // Hide controls
1017
+ if (this.api.controls) {
1018
+ this.api.controls.style.display = 'none';
1019
+ this.api.controls.style.opacity = '0';
1020
+ this.api.controls.style.visibility = 'hidden';
1021
+ this.api.controls.style.pointerEvents = 'none';
1022
+ }
1023
+
1024
+ // Hide overlay title
1025
+ const overlayTitle = this.api.container.querySelector('.title-overlay');
1026
+ if (overlayTitle) {
1027
+ overlayTitle.style.display = 'none';
1028
+ overlayTitle.style.opacity = '0';
1029
+ overlayTitle.style.visibility = 'hidden';
1030
+ }
1031
+
1032
+ // Hide watermark
1033
+ const watermark = this.api.container.querySelector('.watermark');
1034
+ if (watermark) {
1035
+ watermark.style.display = 'none';
1036
+ watermark.style.opacity = '0';
1037
+ watermark.style.visibility = 'hidden';
1038
+ }
1039
+
1040
+ // Force hide via CSS
1041
+ this.forceHideCustomControls();
1042
+ }
1043
+
851
1044
  // Handle responsive layout for PiP and subtitles buttons
852
1045
  this.handleResponsiveLayout();
853
1046
 
@@ -895,14 +1088,41 @@ width: fit-content;
895
1088
 
896
1089
  }
897
1090
 
1091
+ forceHideCustomControls() {
1092
+ const existingStyle = document.getElementById('yt-force-hide-controls');
1093
+ if (existingStyle) {
1094
+ return;
1095
+ }
1096
+
1097
+ const style = document.createElement('style');
1098
+ style.id = 'yt-force-hide-controls';
1099
+ style.textContent = `
1100
+ .video-wrapper.youtube-native-ui .controls,
1101
+ .video-wrapper.youtube-native-ui .title-overlay,
1102
+ .video-wrapper.youtube-native-ui .watermark {
1103
+ display: none !important;
1104
+ opacity: 0 !important;
1105
+ visibility: hidden !important;
1106
+ pointer-events: none !important;
1107
+ }
1108
+ `;
1109
+ document.head.appendChild(style);
1110
+
1111
+ this.api.container.classList.add('youtube-native-ui');
1112
+
1113
+ if (this.api.player.options.debug) {
1114
+ console.log('[YT Plugin] CSS injected - custom elements hidden (simple method)');
1115
+ }
1116
+ }
1117
+
898
1118
  checkIfLiveStream() {
899
1119
  if (this.api.player.options.debug) {
900
- console.log('[YT Plugin] 🔍 Starting live stream check...');
1120
+ console.log('[YT Plugin] Starting live stream check...');
901
1121
  }
902
1122
 
903
1123
  if (!this.ytPlayer) {
904
1124
  if (this.api.player.options.debug) {
905
- console.log('[YT Plugin] ytPlayer not available');
1125
+ console.log('[YT Plugin] ytPlayer not available');
906
1126
  }
907
1127
  return false;
908
1128
  }
@@ -911,14 +1131,15 @@ width: fit-content;
911
1131
  // Method 1: Check video data for isLive property
912
1132
  if (this.ytPlayer.getVideoData) {
913
1133
  const videoData = this.ytPlayer.getVideoData();
1134
+
914
1135
  if (this.api.player.options.debug) {
915
- console.log('[YT Plugin] 📹 Video Data:', videoData);
1136
+ console.log('[YT Plugin] Video Data:', videoData);
916
1137
  }
917
1138
 
918
1139
  // Check if video data indicates it's live
919
1140
  if (videoData.isLive || videoData.isLiveBroadcast) {
920
1141
  if (this.api.player.options.debug) {
921
- console.log('[YT Plugin] LIVE detected via videoData.isLive');
1142
+ console.log('[YT Plugin] LIVE detected via videoData.isLive');
922
1143
  }
923
1144
  this.isLiveStream = true;
924
1145
  this.handleLiveStreamUI();
@@ -926,19 +1147,18 @@ width: fit-content;
926
1147
  }
927
1148
  }
928
1149
 
929
- // Method 2: Check duration (live streams have special duration values)
1150
+ // Method 2: Check duration - live streams have special duration values
930
1151
  if (this.ytPlayer.getDuration) {
931
1152
  const duration = this.ytPlayer.getDuration();
1153
+
932
1154
  if (this.api.player.options.debug) {
933
- console.log('[YT Plugin] ⏱️ Initial duration:', duration);
1155
+ console.log('[YT Plugin] Initial duration:', duration);
934
1156
  }
935
1157
 
936
- // For live streams, duration changes over time
937
- // Wait 3 seconds and check again
938
1158
  setTimeout(() => {
939
1159
  if (!this.ytPlayer || !this.ytPlayer.getDuration) {
940
1160
  if (this.api.player.options.debug) {
941
- console.log('[YT Plugin] ytPlayer lost during duration check');
1161
+ console.log('[YT Plugin] ytPlayer lost during duration check');
942
1162
  }
943
1163
  return;
944
1164
  }
@@ -947,37 +1167,36 @@ width: fit-content;
947
1167
  const difference = Math.abs(newDuration - duration);
948
1168
 
949
1169
  if (this.api.player.options.debug) {
950
- console.log('[YT Plugin] ⏱️ Duration after 3s:', newDuration);
951
- console.log('[YT Plugin] 📊 Duration difference:', difference);
1170
+ console.log('[YT Plugin] Duration after 5s:', newDuration);
1171
+ console.log('[YT Plugin] Duration difference:', difference);
952
1172
  }
953
1173
 
954
- // If duration increased by more than 0.5 seconds, it's live
955
- if (difference > 0.5) {
1174
+ if (difference > 10) {
956
1175
  if (this.api.player.options.debug) {
957
- console.log('[YT Plugin] LIVE STREAM DETECTED (duration changing)');
1176
+ console.log('[YT Plugin] LIVE STREAM DETECTED - duration changing significantly');
958
1177
  }
959
1178
  this.isLiveStream = true;
960
1179
  this.handleLiveStreamUI();
961
1180
  } else {
962
1181
  if (this.api.player.options.debug) {
963
- console.log('[YT Plugin] ℹ️ Regular video (duration stable)');
1182
+ console.log('[YT Plugin] Regular video - duration stable');
964
1183
  }
965
1184
  this.isLiveStream = false;
966
1185
  }
967
- }, 3000);
1186
+ }, 5000);
968
1187
  }
969
1188
 
970
1189
  // Method 3: Check player state
971
1190
  if (this.ytPlayer.getPlayerState) {
972
1191
  const state = this.ytPlayer.getPlayerState();
973
1192
  if (this.api.player.options.debug) {
974
- console.log('[YT Plugin] 🎮 Player state:', state);
1193
+ console.log('[YT Plugin] Player state:', state);
975
1194
  }
976
1195
  }
977
1196
 
978
1197
  } catch (error) {
979
1198
  if (this.api.player.options.debug) {
980
- console.error('[YT Plugin] Error checking live stream:', error);
1199
+ console.error('[YT Plugin] Error checking live stream:', error);
981
1200
  }
982
1201
  }
983
1202
 
@@ -1008,20 +1227,36 @@ width: fit-content;
1008
1227
 
1009
1228
  this.startLiveMonitoring();
1010
1229
 
1230
+ // Force progress bar to 100% for live streams
1231
+ this.liveProgressInterval = setInterval(() => {
1232
+ if (this.isLiveStream && this.api.player.progressFilled) {
1233
+ this.api.player.progressFilled.style.width = '100%';
1234
+
1235
+ if (this.api.player.progressHandle) {
1236
+ this.api.player.progressHandle.style.left = '100%';
1237
+ }
1238
+ }
1239
+ }, 100); // Every 100ms to override any other updates
1240
+
1011
1241
  if (this.api.player.options.debug) {
1012
1242
  console.log('[YT Plugin] ✅ Live UI setup complete');
1013
1243
  }
1014
1244
  }
1015
1245
 
1016
1246
  checkDVRAvailability() {
1017
- // Disable progress bar immediately while testing
1018
1247
  const progressContainer = this.api.container.querySelector('.progress-container');
1248
+ const progressFill = this.api.container.querySelector('.progress-fill');
1249
+
1019
1250
  if (progressContainer) {
1020
1251
  progressContainer.style.opacity = '0.3';
1021
1252
  progressContainer.style.pointerEvents = 'none';
1022
1253
  }
1023
1254
 
1024
- // Wait a bit for YouTube to fully initialize
1255
+ // Set darkgoldenrod during test
1256
+ if (progressFill) {
1257
+ progressFill.style.backgroundColor = 'darkgoldenrod';
1258
+ }
1259
+
1025
1260
  setTimeout(() => {
1026
1261
  if (!this.ytPlayer) return;
1027
1262
 
@@ -1043,25 +1278,31 @@ width: fit-content;
1043
1278
  const seekDifference = Math.abs(newCurrentTime - testSeekPosition);
1044
1279
 
1045
1280
  const progressContainer = this.api.container.querySelector('.progress-container');
1281
+ const progressFill = this.api.container.querySelector('.progress-fill');
1046
1282
 
1047
1283
  if (seekDifference < 2) {
1048
- // DVR enabled - restore progress bar
1284
+ // DVR enabled - restore with theme color
1049
1285
  if (progressContainer) {
1050
1286
  progressContainer.style.opacity = '';
1051
1287
  progressContainer.style.pointerEvents = '';
1052
1288
  }
1053
1289
 
1290
+ // Remove inline style to use theme color
1291
+ if (progressFill) {
1292
+ progressFill.style.backgroundColor = ''; // Let theme CSS handle color
1293
+ }
1294
+
1054
1295
  if (this.api.player.options.debug) {
1055
- console.log('[YT Plugin] ✅ DVR ENABLED - progress bar active');
1296
+ console.log('[YT Plugin] ✅ DVR ENABLED - progress bar active with theme color');
1056
1297
  }
1057
1298
 
1058
1299
  this.ytPlayer.seekTo(duration, true);
1059
1300
  } else {
1060
- // No DVR - keep progress bar disabled
1301
+ // No DVR - keep darkgoldenrod
1061
1302
  this.modifyProgressBarForLive();
1062
1303
 
1063
1304
  if (this.api.player.options.debug) {
1064
- console.log('[YT Plugin] ❌ DVR DISABLED - progress bar locked');
1305
+ console.log('[YT Plugin] ❌ DVR DISABLED - progress bar locked with darkgoldenrod');
1065
1306
  }
1066
1307
  }
1067
1308
  }, 500);
@@ -1209,6 +1450,7 @@ width: fit-content;
1209
1450
  modifyProgressBarForLive() {
1210
1451
  const progressContainer = this.api.container.querySelector('.progress-container');
1211
1452
  const progressHandle = this.api.container.querySelector('.progress-handle');
1453
+ const progressFill = this.api.container.querySelector('.progress-fill');
1212
1454
 
1213
1455
  if (progressContainer) {
1214
1456
  // Disable all pointer events on progress bar
@@ -1224,11 +1466,21 @@ width: fit-content;
1224
1466
  if (progressHandle) {
1225
1467
  progressHandle.style.display = 'none';
1226
1468
  }
1469
+
1470
+ // Change color to darkgoldenrod when disabled
1471
+ if (progressFill) {
1472
+ progressFill.style.backgroundColor = 'darkgoldenrod';
1473
+
1474
+ if (this.api.player.options.debug) {
1475
+ console.log('[YT Plugin] Progress fill color changed to darkgoldenrod');
1476
+ }
1477
+ }
1227
1478
  }
1228
1479
 
1229
1480
  restoreProgressBarNormal() {
1230
1481
  const progressContainer = this.api.container.querySelector('.progress-container');
1231
1482
  const progressHandle = this.api.container.querySelector('.progress-handle');
1483
+ const progressFill = this.api.container.querySelector('.progress-fill');
1232
1484
 
1233
1485
  if (progressContainer) {
1234
1486
  progressContainer.style.pointerEvents = '';
@@ -1239,6 +1491,15 @@ width: fit-content;
1239
1491
  if (progressHandle) {
1240
1492
  progressHandle.style.display = '';
1241
1493
  }
1494
+
1495
+ // Remove inline backgroundColor to let CSS theme take over
1496
+ if (progressFill) {
1497
+ progressFill.style.backgroundColor = ''; // Reset to theme color
1498
+
1499
+ if (this.api.player.options.debug) {
1500
+ console.log('[YT Plugin] Progress fill color restored to theme default');
1501
+ }
1502
+ }
1242
1503
  }
1243
1504
 
1244
1505
  startLiveMonitoring() {
@@ -1270,7 +1531,7 @@ width: fit-content;
1270
1531
  } else if (playerState === YT.PlayerState.PLAYING) {
1271
1532
  // Only update color if playing
1272
1533
  // Check latency only if duration is reasonable
1273
- if (latency > 60 && duration < 7200) {
1534
+ if (latency > 60) {
1274
1535
  // DE-SYNCED - Black background
1275
1536
  badge.style.background = '#1a1a1a';
1276
1537
  badge.textContent = 'LIVE';
@@ -1348,9 +1609,9 @@ width: fit-content;
1348
1609
  // Show time display again
1349
1610
  this.showTimeDisplay();
1350
1611
 
1351
- // Restart normal time updates
1352
- if (!this.timeUpdateInterval) {
1353
- this.setupYouTubeSync();
1612
+ if (this.liveProgressInterval) {
1613
+ clearInterval(this.liveProgressInterval);
1614
+ this.liveProgressInterval = null;
1354
1615
  }
1355
1616
 
1356
1617
  if (this.api.player.options.debug) {
@@ -1386,12 +1647,11 @@ width: fit-content;
1386
1647
  overflow: hidden !important;
1387
1648
  }
1388
1649
  `;
1389
-
1390
1650
  document.head.appendChild(style);
1391
1651
  this.api.container.classList.add('youtube-active');
1392
1652
 
1393
1653
  if (this.api.player.options.debug) {
1394
- console.log('YT Plugin: CSS override injected');
1654
+ console.log('[YT Plugin] CSS override injected (ToS compliant)');
1395
1655
  }
1396
1656
  }
1397
1657
 
@@ -1620,27 +1880,57 @@ width: fit-content;
1620
1880
  if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
1621
1881
 
1622
1882
  try {
1623
- // Try multiple methods to force quality change
1624
- this.ytPlayer.setPlaybackQuality(quality);
1883
+ if (this.api.player.options.debug) {
1884
+ console.log('[YT Plugin] Setting quality to:', quality);
1885
+ console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
1886
+ console.log('[YT Plugin] Available qualities:', this.ytPlayer.getAvailableQualityLevels());
1887
+ }
1625
1888
 
1626
- // Also try setPlaybackQualityRange if available
1627
- if (this.ytPlayer.setPlaybackQualityRange) {
1628
- this.ytPlayer.setPlaybackQualityRange(quality, quality);
1889
+ // Check if requested quality is actually available
1890
+ const availableLevels = this.ytPlayer.getAvailableQualityLevels();
1891
+ if (quality !== 'default' && quality !== 'auto' && !availableLevels.includes(quality)) {
1892
+ if (this.api.player.options.debug) {
1893
+ console.warn('[YT Plugin] Requested quality not available:', quality);
1894
+ }
1629
1895
  }
1630
1896
 
1631
1897
  // Update state
1632
1898
  this.currentQuality = quality;
1633
- this.currentPlayingQuality = quality; // Force UI update
1899
+
1900
+ // Set the quality
1901
+ this.ytPlayer.setPlaybackQuality(quality);
1902
+
1903
+ // Also try setPlaybackQualityRange for better enforcement
1904
+ if (this.ytPlayer.setPlaybackQualityRange) {
1905
+ this.ytPlayer.setPlaybackQualityRange(quality, quality);
1906
+ }
1634
1907
 
1635
1908
  // Force UI update immediately
1636
1909
  this.updateQualityMenuPlayingState(quality);
1637
-
1638
- // Update button display
1639
1910
  const qualityLabel = this.getQualityLabel(quality);
1640
- this.updateQualityButtonDisplay(qualityLabel, '');
1911
+
1912
+ // For manual quality selection, show only the selected quality
1913
+ if (quality !== 'default' && quality !== 'auto') {
1914
+ this.updateQualityButtonDisplay(qualityLabel, '');
1915
+ } else {
1916
+ // For auto mode, show "Auto" and let monitoring update the actual quality
1917
+ this.updateQualityButtonDisplay('Auto', '');
1918
+ }
1641
1919
 
1642
1920
  if (this.api.player.options.debug) {
1643
- console.log('[YT Plugin] Quality set to:', quality);
1921
+ // Check actual quality after a moment
1922
+ setTimeout(() => {
1923
+ if (this.ytPlayer && this.ytPlayer.getPlaybackQuality) {
1924
+ const actualQuality = this.ytPlayer.getPlaybackQuality();
1925
+ console.log('[YT Plugin] Actual quality after 1s:', actualQuality);
1926
+ if (actualQuality !== quality && quality !== 'default' && quality !== 'auto') {
1927
+ console.warn('[YT Plugin] YouTube did not apply requested quality. This may mean:');
1928
+ console.warn(' - The quality is not available for this video');
1929
+ console.warn(' - Embedding restrictions apply');
1930
+ console.warn(' - Network/bandwidth limitations');
1931
+ }
1932
+ }
1933
+ }, 1000); // Check every 1 second for faster updates
1644
1934
  }
1645
1935
 
1646
1936
  this.api.triggerEvent('youtubeplugin:qualitychanged', { quality });
@@ -1653,7 +1943,7 @@ width: fit-content;
1653
1943
  }
1654
1944
  }
1655
1945
 
1656
- // ===== RECONSTRUCTED SUBTITLE METHODS =====
1946
+ // ===== SUBTITLE METHODS =====
1657
1947
 
1658
1948
  /**
1659
1949
  * Load available captions and create subtitle control
@@ -2311,31 +2601,6 @@ width: fit-content;
2311
2601
  }
2312
2602
  }
2313
2603
 
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
2604
  switch (event.data) {
2340
2605
  case YT.PlayerState.PLAYING:
2341
2606
  this.api.triggerEvent('played', {});
@@ -2700,7 +2965,24 @@ width: fit-content;
2700
2965
  const duration = this.ytPlayer.getDuration();
2701
2966
 
2702
2967
  if (this.api.player.progressFilled && duration) {
2703
- const progress = (currentTime / duration) * 100;
2968
+ let progress;
2969
+
2970
+ // For live streams, always show progress at 100%
2971
+ if (this.isLiveStream) {
2972
+ progress = 100;
2973
+ } else {
2974
+ // For regular videos, calculate normally
2975
+ progress = (currentTime / duration) * 100;
2976
+ }
2977
+
2978
+ // Check if live badge exists = it's a live stream
2979
+ const liveBadge = this.api.container.querySelector('.live-badge');
2980
+
2981
+ if (liveBadge) {
2982
+ // Force 100% for live streams
2983
+ progress = 100;
2984
+ }
2985
+
2704
2986
  this.api.player.progressFilled.style.width = `${progress}%`;
2705
2987
  if (this.api.player.progressHandle) {
2706
2988
  this.api.player.progressHandle.style.left = `${progress}%`;
@@ -2922,6 +3204,10 @@ width: fit-content;
2922
3204
  this.isLiveStream = false;
2923
3205
  this.isAtLiveEdge = false;
2924
3206
 
3207
+ if (this.liveProgressInterval) {
3208
+ clearInterval(this.liveProgressInterval);
3209
+ this.liveProgressInterval = null;
3210
+ }
2925
3211
  }
2926
3212
  }
2927
3213