myetv-player 1.1.5 → 1.1.6

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.
@@ -13,6 +13,13 @@
13
13
  apiKey: options.apiKey || null,
14
14
  autoplay: options.autoplay !== undefined ? options.autoplay : false,
15
15
  showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
16
+ showNativeControlsButton: options.showNativeControlsButton !== undefined ? options.showNativeControlsButton : true,
17
+ controlBarOpacity: options.controlBarOpacity !== undefined
18
+ ? options.controlBarOpacity
19
+ : (player.options.controlBarOpacity !== undefined ? player.options.controlBarOpacity : 0.95),
20
+ titleOverlayOpacity: options.titleOverlayOpacity !== undefined
21
+ ? options.titleOverlayOpacity
22
+ : (player.options.titleOverlayOpacity !== undefined ? player.options.titleOverlayOpacity : 0.95),
16
23
  autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
17
24
  quality: options.quality || 'default',
18
25
 
@@ -890,7 +897,7 @@
890
897
 
891
898
  const playerVars = {
892
899
  autoplay: this.options.autoplay ? 1 : 0,
893
- controls: this.options.showYouTubeUI ? 1 : 0,
900
+ controls: 1,
894
901
  fs: this.options.showYouTubeUI ? 1 : 0,
895
902
  disablekb: 1,
896
903
  modestbranding: 1,
@@ -931,130 +938,74 @@
931
938
  this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
932
939
  }
933
940
 
934
- setAdaptiveQuality() {
935
- if (!this.ytPlayer) return;
936
-
937
- try {
938
- // Check network connection speed if available
939
- const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
940
- let suggestedQuality = 'default';
941
-
942
- if (connection) {
943
- const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
944
- const downlink = connection.downlink; // Mbps
945
-
946
- if (this.api.player.options.debug) {
947
- console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
948
- }
949
-
950
- // Set quality based on connection speed
951
- if (effectiveType === 'slow-2g' || downlink < 0.5) {
952
- suggestedQuality = 'small'; // 240p
953
- } else if (effectiveType === '2g' || downlink < 1) {
954
- suggestedQuality = 'medium'; // 360p
955
- } else if (effectiveType === '3g' || downlink < 2.5) {
956
- suggestedQuality = 'large'; // 480p
957
- } else if (downlink < 5) {
958
- suggestedQuality = 'hd720'; // 720p
959
- } else if (downlink < 10) {
960
- suggestedQuality = 'hd1080'; // 1080p
961
- } else if (downlink < 20) {
962
- suggestedQuality = 'hd1440'; // 1440p (2K)
963
- } else if (downlink < 35) {
964
- suggestedQuality = 'hd2160'; // 2160p (4K)
965
- } else {
966
- suggestedQuality = 'highres'; // 8K o migliore disponibile
967
- }
968
-
969
- if (this.api.player.options.debug) {
970
- console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
971
- }
972
-
973
- this.ytPlayer.setPlaybackQuality(suggestedQuality);
974
- } else {
975
- // Fallback: start with medium quality on unknown devices
976
- if (this.api.player.options.debug) {
977
- console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
978
- }
979
- this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
980
- }
981
- } catch (error) {
982
- if (this.api.player.options.debug) {
983
- console.error('[YT Plugin] Error setting adaptive quality:', error);
984
- }
985
- }
986
- }
987
-
988
- startBufferMonitoring() {
989
- if (this.bufferMonitorInterval) {
990
- clearInterval(this.bufferMonitorInterval);
991
- }
992
-
993
- let bufferingCount = 0;
994
- let lastState = null;
995
-
996
- this.bufferMonitorInterval = setInterval(() => {
997
- if (!this.ytPlayer) return;
998
-
999
- try {
1000
- const state = this.ytPlayer.getPlayerState();
941
+ setAdaptiveQuality() {
942
+ if (!this.ytPlayer) return;
1001
943
 
1002
- // Detect buffering (state 3)
1003
- if (state === YT.PlayerState.BUFFERING) {
1004
- bufferingCount++;
944
+ try {
945
+ // Check video duration first
946
+ const duration = this.ytPlayer.getDuration();
947
+ const durationMinutes = duration / 60;
1005
948
 
1006
949
  if (this.api.player.options.debug) {
1007
- console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
950
+ console.log('[YT Plugin] Video duration:', Math.floor(durationMinutes), 'minutes');
1008
951
  }
1009
952
 
1010
- // If buffering happens too often, reduce quality
1011
- if (bufferingCount >= 3) {
1012
- const currentQuality = this.ytPlayer.getPlaybackQuality();
1013
- const availableQualities = this.ytPlayer.getAvailableQualityLevels();
1014
-
953
+ // For videos longer than 2 hours, cap at 1080p
954
+ if (durationMinutes > 120) {
1015
955
  if (this.api.player.options.debug) {
1016
- console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
1017
- console.log('[YT Plugin] Available qualities:', availableQualities);
956
+ console.log('[YT Plugin] Long video detected - capping at 1080p');
1018
957
  }
958
+ this.ytPlayer.setPlaybackQuality('hd1080');
959
+ return; // Exit early, don't check connection
960
+ }
1019
961
 
1020
- // Quality hierarchy (highest to lowest)
1021
- const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
1022
- const currentIndex = qualityLevels.indexOf(currentQuality);
962
+ // Normal adaptive quality for shorter videos
963
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
964
+ let suggestedQuality = 'default';
1023
965
 
1024
- // Try to go to next lower quality
1025
- if (currentIndex < qualityLevels.length - 1) {
1026
- const lowerQuality = qualityLevels[currentIndex + 1];
966
+ if (connection) {
967
+ const effectiveType = connection.effectiveType;
968
+ const downlink = connection.downlink;
1027
969
 
1028
- // Check if lower quality is available
1029
- if (availableQualities.includes(lowerQuality)) {
1030
- if (this.api.player.options.debug) {
1031
- console.log('[YT Plugin] Reducing quality to:', lowerQuality);
1032
- }
970
+ if (this.api.player.options.debug) {
971
+ console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
972
+ }
1033
973
 
1034
- this.ytPlayer.setPlaybackQuality(lowerQuality);
1035
- bufferingCount = 0; // Reset counter
1036
- }
974
+ if (effectiveType === 'slow-2g' || downlink < 0.5) {
975
+ suggestedQuality = 'small';
976
+ } else if (effectiveType === '2g' || downlink < 1) {
977
+ suggestedQuality = 'medium';
978
+ } else if (effectiveType === '3g' || downlink < 2.5) {
979
+ suggestedQuality = 'large';
980
+ } else if (downlink < 5) {
981
+ suggestedQuality = 'hd720';
982
+ } else if (downlink < 10) {
983
+ suggestedQuality = 'hd1080';
984
+ } else if (downlink < 20) {
985
+ suggestedQuality = 'hd1440';
986
+ } else if (downlink < 35) {
987
+ suggestedQuality = 'hd2160';
988
+ } else {
989
+ suggestedQuality = 'highres';
1037
990
  }
991
+
992
+ if (this.api.player.options.debug) {
993
+ console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
994
+ }
995
+
996
+ this.ytPlayer.setPlaybackQuality(suggestedQuality);
997
+ } else {
998
+ if (this.api.player.options.debug) {
999
+ console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
1000
+ }
1001
+ this.ytPlayer.setPlaybackQuality('large');
1038
1002
  }
1039
- } else if (state === YT.PlayerState.PLAYING) {
1040
- // Reset buffering count when playing smoothly
1041
- if (lastState === YT.PlayerState.BUFFERING) {
1042
- setTimeout(() => {
1043
- if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
1044
- bufferingCount = Math.max(0, bufferingCount - 1);
1045
- }
1046
- }, 5000); // Wait 5 seconds of smooth playback
1003
+ } catch (error) {
1004
+ if (this.api.player.options.debug) {
1005
+ console.error('[YT Plugin] Error setting adaptive quality:', error);
1047
1006
  }
1048
1007
  }
1049
-
1050
- lastState = state;
1051
- } catch (error) {
1052
- if (this.api.player.options.debug) {
1053
- console.error('[YT Plugin] Error in buffer monitoring:', error);
1054
- }
1055
1008
  }
1056
- }, 1000); // Check every second
1057
- }
1058
1009
 
1059
1010
  createMouseMoveOverlay() {
1060
1011
  if (this.mouseMoveOverlay) return;
@@ -1348,11 +1299,17 @@ startBufferMonitoring() {
1348
1299
  this.hideLoadingOverlay();
1349
1300
  this.hideInitialLoading();
1350
1301
  this.injectYouTubeCSSOverride();
1302
+ // inject controlbar gradient styles
1303
+ this.injectControlbarGradientStyles();
1351
1304
 
1352
1305
  this.syncControls();
1306
+
1353
1307
  // Start buffer monitoring for auto quality adjustment
1354
1308
  this.startBufferMonitoring();
1355
1309
 
1310
+ // Create YouTube controls button
1311
+ this.createYouTubeControlsButton();
1312
+
1356
1313
  // Hide custom controls when YouTube native UI is enabled
1357
1314
  if (this.options.showYouTubeUI) {
1358
1315
  // Hide controls
@@ -1362,6 +1319,17 @@ startBufferMonitoring() {
1362
1319
  this.api.controls.style.visibility = 'hidden';
1363
1320
  this.api.controls.style.pointerEvents = 'none';
1364
1321
  }
1322
+ // Hide YouTube controls with CSS if showYouTubeUI is false
1323
+ if (!this.options.showYouTubeUI) {
1324
+ const iframe = this.ytPlayerContainer.querySelector('iframe');
1325
+ if (iframe) {
1326
+ iframe.style.pointerEvents = 'none'; // Block clicks to YouTube controls
1327
+
1328
+ if (this.api.player.options.debug) {
1329
+ console.log('[YT Plugin] YouTube controls hidden with CSS (showYouTubeUI: false)');
1330
+ }
1331
+ }
1332
+ }
1365
1333
 
1366
1334
  // Hide overlay title
1367
1335
  const overlayTitle = this.api.container.querySelector('.title-overlay');
@@ -1428,12 +1396,101 @@ startBufferMonitoring() {
1428
1396
  // Check initial caption state AFTER captions are loaded and menu is built
1429
1397
  setTimeout(() => {
1430
1398
  this.checkInitialCaptionState();
1431
- }, 2500); // Dopo che tutto è stato inizializzato
1432
-
1399
+ }, 2500); // after 2.5s
1400
+
1401
+ // Initialize cursor state based on controls visibility
1402
+ if (!this.options.showYouTubeUI && this.api.player.options.hideCursor) {
1403
+ // Check if controls are visible
1404
+ const controlsVisible = this.api.controls && this.api.controls.classList.contains('show');
1405
+ if (!controlsVisible) {
1406
+ this.hideCursor();
1407
+ }
1408
+ }
1409
+ if (this.api.player.options.debug) console.log('YT Plugin: Setup completed');
1433
1410
  this.api.triggerEvent('youtubeplugin:playerready', {});
1434
1411
 
1435
1412
  }
1436
1413
 
1414
+ showYouTubeControls() {
1415
+ console.log('[YT Plugin] 🔴 Showing YouTube controls');
1416
+
1417
+ // Find iframe
1418
+ let iframe = this.api.container.querySelector('iframe');
1419
+ if (!iframe) iframe = document.querySelector('iframe');
1420
+
1421
+ // Find video wrapper (contains all overlays and controls)
1422
+ const videoWrapper = this.api.container.querySelector('.video-wrapper');
1423
+ const controlbar = this.api.container.querySelector('.controls');
1424
+
1425
+ console.log('[YT Plugin] iframe:', iframe);
1426
+ console.log('[YT Plugin] videoWrapper:', videoWrapper);
1427
+ console.log('[YT Plugin] controlbar:', controlbar);
1428
+
1429
+ if (iframe) {
1430
+ console.log('[YT Plugin] ✅ Showing YouTube controls');
1431
+
1432
+ // Enable clicks on YouTube iframe
1433
+ iframe.style.pointerEvents = 'auto';
1434
+ iframe.style.zIndex = '9999'; // Bring to front
1435
+
1436
+ // Hide entire video wrapper (this hides ALL overlays and controls)
1437
+ if (videoWrapper) {
1438
+ // Save original position
1439
+ this.originalWrapperChildren = Array.from(videoWrapper.children).filter(child => child !== iframe);
1440
+
1441
+ // Hide all children except iframe
1442
+ this.originalWrapperChildren.forEach(child => {
1443
+ child.style.display = 'none';
1444
+ });
1445
+
1446
+ console.log('[YT Plugin] Video wrapper children hidden');
1447
+ }
1448
+
1449
+ // OR hide controlbar directly
1450
+ if (controlbar) {
1451
+ controlbar.style.display = 'none';
1452
+ }
1453
+
1454
+ // Auto-restore after 10 seconds
1455
+ this.youtubeControlsTimeout = setTimeout(() => {
1456
+ console.log('[YT Plugin] ⏰ Restoring custom controls');
1457
+ this.hideYouTubeControls();
1458
+ }, 10000);
1459
+ } else {
1460
+ console.log('[YT Plugin] ❌ iframe not found');
1461
+ }
1462
+ }
1463
+
1464
+ hideYouTubeControls() {
1465
+ console.log('[YT Plugin] 🔙 Hiding YouTube controls');
1466
+
1467
+ const iframe = this.api.container.querySelector('iframe');
1468
+ const controlbar = this.api.container.querySelector('.controls');
1469
+
1470
+ if (iframe) {
1471
+ // Disable clicks on YouTube controls
1472
+ if (!this.options.showYouTubeUI) {
1473
+ iframe.style.pointerEvents = 'none';
1474
+ }
1475
+ iframe.style.zIndex = ''; // Reset z-index
1476
+ }
1477
+
1478
+ // Restore video wrapper children
1479
+ if (this.originalWrapperChildren) {
1480
+ this.originalWrapperChildren.forEach(child => {
1481
+ child.style.display = '';
1482
+ });
1483
+ this.originalWrapperChildren = null;
1484
+ }
1485
+
1486
+ // Restore controlbar
1487
+ if (controlbar) {
1488
+ controlbar.style.display = '';
1489
+ }
1490
+
1491
+ console.log('[YT Plugin] ✅ Custom controls restored');
1492
+ }
1493
+
1437
1494
  forceHideCustomControls() {
1438
1495
  const existingStyle = document.getElementById('yt-force-hide-controls');
1439
1496
  if (existingStyle) {
@@ -1984,7 +2041,7 @@ startBufferMonitoring() {
1984
2041
  visibility: visible !important;
1985
2042
  opacity: 1 !important;
1986
2043
  }
1987
-
2044
+
1988
2045
  /* Make watermark circular */
1989
2046
  .video-wrapper .watermark,
1990
2047
  .video-wrapper .watermark-image,
@@ -1992,12 +2049,113 @@ startBufferMonitoring() {
1992
2049
  border-radius: 50% !important;
1993
2050
  overflow: hidden !important;
1994
2051
  }
2052
+
2053
+ /* Hide cursor */
2054
+ .video-wrapper.hide-cursor,
2055
+ .video-wrapper.hide-cursor * {
2056
+ cursor: none !important;
2057
+ }
2058
+
2059
+ /* Ensure cursor is visible on controls */
2060
+ .video-wrapper.hide-cursor .controls,
2061
+ .video-wrapper.hide-cursor .controls * {
2062
+ cursor: default !important;
2063
+ }
2064
+
2065
+ /* Ensure cursor is visible on buttons */
2066
+ .video-wrapper.hide-cursor .control-btn,
2067
+ .video-wrapper.hide-cursor .control-btn * {
2068
+ cursor: pointer !important;
2069
+ }
2070
+
2071
+ /* Ensure iframe doesn't override */
2072
+ .video-wrapper.hide-cursor iframe {
2073
+ cursor: auto !important;
2074
+ }
1995
2075
  `;
2076
+
1996
2077
  document.head.appendChild(style);
1997
2078
  this.api.container.classList.add('youtube-active');
1998
2079
 
1999
2080
  if (this.api.player.options.debug) {
2000
- console.log('[YT Plugin] CSS override injected (ToS compliant)');
2081
+ console.log('YT Plugin: CSS override injected');
2082
+ }
2083
+ }
2084
+
2085
+ /**
2086
+ * Inject CSS styles for controlbar and title overlay gradients
2087
+ * Uses YouTube-specific selectors to avoid conflicts with other plugins
2088
+ */
2089
+ injectControlbarGradientStyles() {
2090
+ // Check if styles are already injected
2091
+ if (document.getElementById('yt-controlbar-gradient-styles')) {
2092
+ return;
2093
+ }
2094
+
2095
+ // Validate opacity values (must be between 0 and 1)
2096
+ const controlBarOpacity = Math.max(0, Math.min(1, this.options.controlBarOpacity));
2097
+ const titleOverlayOpacity = Math.max(0, Math.min(1, this.options.titleOverlayOpacity));
2098
+
2099
+ // Create style element
2100
+ const style = document.createElement('style');
2101
+ style.id = 'yt-controlbar-gradient-styles';
2102
+
2103
+ // CSS with YouTube-specific selectors to avoid conflicts
2104
+ style.textContent = `
2105
+ /* Controlbar gradient - dark opaque at bottom, semi-transparent at top */
2106
+ /* ONLY applied when YouTube plugin is active */
2107
+ .video-wrapper.youtube-active .controls {
2108
+ background: linear-gradient(
2109
+ to top,
2110
+ rgba(0, 0, 0, ${controlBarOpacity}) 0%, /* Maximum opacity at bottom */
2111
+ rgba(0, 0, 0, ${controlBarOpacity * 0.89}) 20%, /* 89% of max opacity */
2112
+ rgba(0, 0, 0, ${controlBarOpacity * 0.74}) 40%, /* 74% */
2113
+ rgba(0, 0, 0, ${controlBarOpacity * 0.53}) 60%, /* 53% */
2114
+ rgba(0, 0, 0, ${controlBarOpacity * 0.32}) 80%, /* 32% */
2115
+ rgba(0, 0, 0, ${controlBarOpacity * 0.21}) 100% /* 21% at top */
2116
+ ) !important;
2117
+ backdrop-filter: blur(3px);
2118
+ min-height: 60px;
2119
+ padding-bottom: 10px;
2120
+ }
2121
+
2122
+ /* Title overlay gradient - dark opaque at top, semi-transparent at bottom */
2123
+ /* ONLY applied when YouTube plugin is active */
2124
+ .video-wrapper.youtube-active .title-overlay {
2125
+ background: linear-gradient(
2126
+ to bottom,
2127
+ rgba(0, 0, 0, ${titleOverlayOpacity}) 0%, /* Maximum opacity at top */
2128
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.89}) 20%, /* 89% of max opacity */
2129
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.74}) 40%, /* 74% */
2130
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.53}) 60%, /* 53% */
2131
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.32}) 80%, /* 32% */
2132
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.21}) 100% /* 21% at bottom */
2133
+ ) !important;
2134
+ backdrop-filter: blur(3px);
2135
+ min-height: 80px;
2136
+ padding-top: 20px;
2137
+ }
2138
+
2139
+ /* Keep controlbar visible when video is paused */
2140
+ .video-wrapper.youtube-active.video-paused .controls.show {
2141
+ opacity: 1 !important;
2142
+ visibility: visible !important;
2143
+ }
2144
+
2145
+ /* Keep title overlay visible when video is paused */
2146
+ .video-wrapper.youtube-active.video-paused .title-overlay.show {
2147
+ opacity: 1 !important;
2148
+ visibility: visible !important;
2149
+ }
2150
+ `;
2151
+
2152
+ // Append style to document head
2153
+ document.head.appendChild(style);
2154
+
2155
+ // Debug logging
2156
+ if (this.api.player.options.debug) {
2157
+ console.log('[YT Plugin] Controlbar and title overlay gradient styles injected');
2158
+ console.log(`[YT Plugin] ControlBar opacity: ${controlBarOpacity}, TitleOverlay opacity: ${titleOverlayOpacity}`);
2001
2159
  }
2002
2160
  }
2003
2161
 
@@ -2085,9 +2243,10 @@ startBufferMonitoring() {
2085
2243
  return;
2086
2244
  }
2087
2245
 
2246
+ const qualityText = this.api.player.t('video_quality');
2088
2247
  const qualityHTML = `
2089
2248
  <div class="quality-control">
2090
- <button class="control-btn quality-btn" data-tooltip="Video Quality">
2249
+ <button class="control-btn quality-btn" title="${qualityText}">
2091
2250
  <div class="quality-btn-text">
2092
2251
  <div class="selected-quality">Auto</div>
2093
2252
  <div class="current-quality"></div>
@@ -2145,23 +2304,178 @@ startBufferMonitoring() {
2145
2304
  });
2146
2305
  qualityMenu.appendChild(autoItem);
2147
2306
 
2148
- // Add quality options
2307
+ // Add quality options (all disabled)
2149
2308
  this.availableQualities.forEach(quality => {
2150
2309
  const menuItem = document.createElement('div');
2151
- menuItem.className = 'quality-option';
2310
+ menuItem.className = 'quality-option disabled';
2152
2311
  menuItem.textContent = quality.label;
2153
2312
  menuItem.dataset.quality = quality.value;
2154
- menuItem.addEventListener('click', () => {
2155
- this.setQuality(quality.value);
2156
- this.updateQualityMenuActiveState(quality.value);
2157
- this.updateQualityButtonDisplay(quality.label, '');
2158
- });
2313
+ menuItem.style.opacity = '0.5';
2314
+ menuItem.style.cursor = 'not-allowed';
2315
+ menuItem.style.pointerEvents = 'none';
2159
2316
  qualityMenu.appendChild(menuItem);
2160
2317
  });
2161
2318
 
2319
+ // Add separator
2320
+ const separator = document.createElement('div');
2321
+ separator.style.cssText = 'height: 1px; background: rgba(255,255,255,0.1); margin: 8px 0;';
2322
+ qualityMenu.appendChild(separator);
2323
+
2324
+ // Add YouTube native controls button with icon
2325
+ const ytBtn = document.createElement('div');
2326
+ ytBtn.className = 'quality-option youtube-controls-option';
2327
+ ytBtn.style.cssText = 'color: #ff0000; font-weight: 500; cursor: pointer; display: flex; align-items: center; gap: 8px;';
2328
+
2329
+ // Add YouTube icon
2330
+ ytBtn.innerHTML = `
2331
+ <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18" style="flex-shrink: 0;">
2332
+ <path d="M21.582,6.186c-0.23-0.86-0.908-1.538-1.768-1.768C18.254,4,12,4,12,4S5.746,4,4.186,4.418 c-0.86,0.23-1.538,0.908-1.768,1.768C2,7.746,2,12,2,12s0,4.254,0.418,5.814c0.23,0.86,0.908,1.538,1.768,1.768 C5.746,20,12,20,12,20s6.254,0,7.814-0.418c0.861-0.23,1.538-0.908,1.768-1.768C22,16.254,22,12,22,12S22,7.746,21.582,6.186z M10,15.464V8.536L16,12L10,15.464z"/>
2333
+ </svg>
2334
+ <span>Show Native Controls</span>
2335
+ `;
2336
+
2337
+ ytBtn.addEventListener('click', () => {
2338
+ this.showYouTubeControls();
2339
+ qualityMenu.classList.remove('show');
2340
+ });
2341
+ qualityMenu.appendChild(ytBtn);
2342
+
2162
2343
  if (this.api.player.options.debug) console.log('[YT Plugin] Quality menu updated');
2163
2344
  }
2164
2345
 
2346
+ showYouTubeControls() {
2347
+ if (this.api.player.options.debug) {
2348
+ console.log('[YT Plugin] 🔴 Showing YouTube controls');
2349
+ }
2350
+
2351
+ let iframe = this.api.container.querySelector('iframe');
2352
+ if (!iframe) iframe = document.querySelector('iframe');
2353
+
2354
+ const controlbar = this.api.container.querySelector('.controls');
2355
+ const titleOverlay = this.api.container.querySelector('.title-overlay');
2356
+
2357
+ if (iframe) {
2358
+ if (this.api.player.options.debug) {
2359
+ console.log('[YT Plugin] ✅ Showing YouTube controls');
2360
+ }
2361
+
2362
+ // Bring iframe to front
2363
+ iframe.style.pointerEvents = 'auto';
2364
+ iframe.style.zIndex = '9999';
2365
+
2366
+ // Hide controlbar and title
2367
+ if (controlbar) {
2368
+ controlbar.style.display = 'none';
2369
+ }
2370
+ if (titleOverlay) {
2371
+ titleOverlay.style.display = 'none';
2372
+ }
2373
+
2374
+ // Auto-restore after 10 seconds
2375
+ this.youtubeControlsTimeout = setTimeout(() => {
2376
+ if (this.api.player.options.debug) {
2377
+ console.log('[YT Plugin] ⏰ Restoring custom controls after 10 seconds');
2378
+ }
2379
+ this.hideYouTubeControls();
2380
+ }, 10000);
2381
+ }
2382
+ }
2383
+
2384
+ hideYouTubeControls() {
2385
+ if (this.api.player.options.debug) {
2386
+ console.log('[YT Plugin] 🔙 Hiding YouTube controls');
2387
+ }
2388
+
2389
+ // Clear timeout if exists
2390
+ if (this.youtubeControlsTimeout) {
2391
+ clearTimeout(this.youtubeControlsTimeout);
2392
+ this.youtubeControlsTimeout = null;
2393
+ }
2394
+
2395
+ const iframe = this.api.container.querySelector('iframe');
2396
+ const controlbar = this.api.container.querySelector('.controls');
2397
+ const titleOverlay = this.api.container.querySelector('.title-overlay');
2398
+
2399
+ if (iframe) {
2400
+ // Disable clicks on YouTube controls
2401
+ if (!this.options.showYouTubeUI) {
2402
+ iframe.style.pointerEvents = 'none';
2403
+ }
2404
+ // Keep iframe at low z-index to prevent poster
2405
+ iframe.style.zIndex = '1';
2406
+ }
2407
+
2408
+ // Restore controlbar and title
2409
+ if (controlbar) {
2410
+ controlbar.style.display = '';
2411
+ controlbar.style.zIndex = '10'; // Above iframe
2412
+ }
2413
+ if (titleOverlay) {
2414
+ titleOverlay.style.display = '';
2415
+ }
2416
+
2417
+ if (this.api.player.options.debug) {
2418
+ console.log('[YT Plugin] ✅ Custom controls restored');
2419
+ }
2420
+ }
2421
+
2422
+ createYouTubeControlsButton() {
2423
+ // Check if button is enabled
2424
+ if (!this.options.showNativeControlsButton) {
2425
+ if (this.api.player.options.debug) {
2426
+ console.log('[YT Plugin] Native controls button disabled by option');
2427
+ }
2428
+ return;
2429
+ }
2430
+
2431
+ // Check if button already exists
2432
+ if (this.api.container.querySelector('.youtube-controls-btn')) {
2433
+ return;
2434
+ }
2435
+
2436
+ const controlsRight = this.api.container.querySelector('.controls-right');
2437
+ if (!controlsRight) return;
2438
+
2439
+ const buttonHTML = `
2440
+ <button class="control-btn youtube-controls-btn" title="Show YouTube Controls">
2441
+ <svg viewBox="0 0 24 24" fill="currentColor" width="24" height="24">
2442
+ <path d="M21.582,6.186c-0.23-0.86-0.908-1.538-1.768-1.768C18.254,4,12,4,12,4S5.746,4,4.186,4.418 c-0.86,0.23-1.538,0.908-1.768,1.768C2,7.746,2,12,2,12s0,4.254,0.418,5.814c0.23,0.86,0.908,1.538,1.768,1.768 C5.746,20,12,20,12,20s6.254,0,7.814-0.418c0.861-0.23,1.538-0.908,1.768-1.768C22,16.254,22,12,22,12S22,7.746,21.582,6.186z M10,15.464V8.536L16,12L10,15.464z"/>
2443
+ </svg>
2444
+ </button>
2445
+ `;
2446
+
2447
+ // Insert before quality control
2448
+ const qualityControl = controlsRight.querySelector('.quality-control');
2449
+ if (qualityControl) {
2450
+ qualityControl.insertAdjacentHTML('beforebegin', buttonHTML);
2451
+ } else {
2452
+ const fullscreenBtn = controlsRight.querySelector('.fullscreen-btn');
2453
+ if (fullscreenBtn) {
2454
+ fullscreenBtn.insertAdjacentHTML('beforebegin', buttonHTML);
2455
+ } else {
2456
+ controlsRight.insertAdjacentHTML('beforeend', buttonHTML);
2457
+ }
2458
+ }
2459
+
2460
+ // Add click listener
2461
+ const btn = this.api.container.querySelector('.youtube-controls-btn');
2462
+ if (btn) {
2463
+ btn.addEventListener('click', () => {
2464
+ if (this.api.player.options.debug) {
2465
+ console.log('[YT Plugin] YouTube controls button clicked');
2466
+ }
2467
+ this.showYouTubeControls();
2468
+ });
2469
+
2470
+ // Add custom styling to make it red like YouTube
2471
+ btn.style.color = '#ff0000';
2472
+
2473
+ if (this.api.player.options.debug) {
2474
+ console.log('[YT Plugin] YouTube controls button created');
2475
+ }
2476
+ }
2477
+ }
2478
+
2165
2479
  updateQualityMenuActiveState(qualityValue) {
2166
2480
  const qualityMenu = this.api.container.querySelector('.quality-menu');
2167
2481
  if (!qualityMenu) return;
@@ -2223,72 +2537,202 @@ startBufferMonitoring() {
2223
2537
  }
2224
2538
 
2225
2539
  setQuality(quality) {
2226
- if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
2540
+ console.log('[YT Plugin] 🎯 CSS Trick - Setting quality to:', quality);
2227
2541
 
2228
- try {
2229
- // Track user's quality choice for display
2230
- this.userQualityChoice = quality;
2231
- if (this.api.player.options.debug) {
2232
- console.log('[YT Plugin] Setting quality to:', quality);
2233
- console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
2234
- console.log('[YT Plugin] Available qualities:', this.ytPlayer.getAvailableQualityLevels());
2235
- }
2542
+ if (!this.ytPlayer) return;
2236
2543
 
2237
- // Check if requested quality is actually available
2238
- const availableLevels = this.ytPlayer.getAvailableQualityLevels();
2239
- if (quality !== 'default' && quality !== 'auto' && !availableLevels.includes(quality)) {
2240
- if (this.api.player.options.debug) {
2241
- console.warn('[YT Plugin] Requested quality not available:', quality);
2242
- }
2243
- }
2544
+ this.userQualityChoice = quality;
2545
+ this.currentQuality = quality;
2546
+
2547
+ const iframe = this.ytPlayerContainer.querySelector('iframe');
2548
+ if (!iframe) return;
2549
+
2550
+ // Map quality to iframe size that forces that quality
2551
+ const qualityToSize = {
2552
+ 'tiny': { width: 200, height: 113 }, // 144p
2553
+ 'small': { width: 320, height: 180 }, // 240p
2554
+ 'medium': { width: 480, height: 270 }, // 360p
2555
+ 'large': { width: 640, height: 360 }, // 480p
2556
+ 'hd720': { width: 1280, height: 720 }, // 720p
2557
+ 'hd1080': { width: 1920, height: 1080 }, // 1080p
2558
+ 'hd1440': { width: 2560, height: 1440 }, // 1440p
2559
+ 'hd2160': { width: 3840, height: 2160 }, // 4K
2560
+ 'default': { width: 1920, height: 1080 } // Auto = 1080p max
2561
+ };
2244
2562
 
2245
- // Update state
2246
- this.currentQuality = quality;
2563
+ const targetSize = qualityToSize[quality] || qualityToSize['hd720'];
2247
2564
 
2248
- // Set the quality
2249
- this.ytPlayer.setPlaybackQuality(quality);
2565
+ // Get actual container size
2566
+ const container = this.ytPlayerContainer;
2567
+ const containerWidth = container.offsetWidth;
2568
+ const containerHeight = container.offsetHeight;
2250
2569
 
2251
- // Also try setPlaybackQualityRange for better enforcement
2252
- if (this.ytPlayer.setPlaybackQualityRange) {
2253
- this.ytPlayer.setPlaybackQualityRange(quality, quality);
2254
- }
2570
+ console.log('[YT Plugin] 📐 Container:', containerWidth, 'x', containerHeight);
2571
+ console.log('[YT Plugin] 🎯 Target iframe:', targetSize.width, 'x', targetSize.height);
2572
+
2573
+ // Calculate scale to fit target size into container
2574
+ const scaleX = containerWidth / targetSize.width;
2575
+ const scaleY = containerHeight / targetSize.height;
2576
+ const scale = Math.max(scaleX, scaleY); // Use larger scale to cover container
2577
+
2578
+ console.log('[YT Plugin] 📏 Scale:', scale);
2579
+
2580
+ // Set iframe to target size
2581
+ iframe.style.width = targetSize.width + 'px';
2582
+ iframe.style.height = targetSize.height + 'px';
2583
+
2584
+ // Scale up with CSS transform to fill container
2585
+ iframe.style.transform = `scale(${scale})`;
2586
+ iframe.style.transformOrigin = 'top left';
2587
+
2588
+ // Reload video with new size
2589
+ const currentTime = this.ytPlayer.getCurrentTime();
2590
+ const wasPlaying = this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING;
2591
+
2592
+ this.ytPlayer.loadVideoById({
2593
+ videoId: this.videoId,
2594
+ startSeconds: currentTime,
2595
+ suggestedQuality: quality
2596
+ });
2597
+
2598
+ if (wasPlaying) {
2599
+ setTimeout(() => {
2600
+ this.ytPlayer.playVideo();
2601
+ console.log('[YT Plugin] ▶️ Auto-play');
2602
+ }, 800);
2603
+ }
2255
2604
 
2256
- // Force UI update immediately
2257
- this.updateQualityMenuPlayingState(quality);
2258
- const qualityLabel = this.getQualityLabel(quality);
2605
+ // Verify after 2 seconds
2606
+ setTimeout(() => {
2607
+ if (!this.ytPlayer) return;
2608
+ const actualQuality = this.ytPlayer.getPlaybackQuality();
2609
+ console.log('[YT Plugin] 🎬 Quality AFTER trick:', actualQuality);
2259
2610
 
2260
- // For manual quality selection, show only the selected quality
2261
- if (quality !== 'default' && quality !== 'auto') {
2262
- this.updateQualityButtonDisplay(qualityLabel, '');
2611
+ if (actualQuality === quality || quality === 'default') {
2612
+ console.log('[YT Plugin] CSS TRICK WORKED!');
2263
2613
  } else {
2264
- // For auto mode, show "Auto" and let monitoring update the actual quality
2265
- this.updateQualityButtonDisplay('Auto', '');
2614
+ console.log('[YT Plugin] ⚠️ Partially worked - got:', actualQuality);
2266
2615
  }
2616
+ }, 2000);
2267
2617
 
2268
- if (this.api.player.options.debug) {
2269
- // Check actual quality after a moment
2270
- setTimeout(() => {
2271
- if (this.ytPlayer && this.ytPlayer.getPlaybackQuality) {
2272
- const actualQuality = this.ytPlayer.getPlaybackQuality();
2273
- console.log('[YT Plugin] Actual quality after 1s:', actualQuality);
2274
- if (actualQuality !== quality && quality !== 'default' && quality !== 'auto') {
2275
- console.warn('[YT Plugin] YouTube did not apply requested quality. This may mean:');
2276
- console.warn(' - The quality is not available for this video');
2277
- console.warn(' - Embedding restrictions apply');
2278
- console.warn(' - Network/bandwidth limitations');
2618
+ this.updateQualityMenuActiveState(quality);
2619
+ }
2620
+
2621
+ startBufferMonitoring() {
2622
+ if (this.bufferMonitorInterval) {
2623
+ clearInterval(this.bufferMonitorInterval);
2624
+ }
2625
+
2626
+ let consecutiveBuffers = 0;
2627
+ let bufferingStartTime = null;
2628
+ let lastBufferTime = 0;
2629
+ let lastQualityDowngrade = 0;
2630
+
2631
+ this.bufferMonitorInterval = setInterval(() => {
2632
+ if (!this.ytPlayer) return;
2633
+
2634
+ try {
2635
+ const state = this.ytPlayer.getPlayerState();
2636
+ const currentTime = Date.now();
2637
+
2638
+ // Detect buffering
2639
+ if (state === YT.PlayerState.BUFFERING) {
2640
+ if (bufferingStartTime === null) {
2641
+ bufferingStartTime = currentTime;
2642
+
2643
+ // Count consecutive buffers (within 45 seconds)
2644
+ if (currentTime - lastBufferTime < 45000) {
2645
+ consecutiveBuffers++;
2646
+ } else {
2647
+ consecutiveBuffers = 1;
2648
+ }
2649
+
2650
+ lastBufferTime = currentTime;
2651
+
2652
+ if (this.api.player.options.debug) {
2653
+ console.log('[YT Plugin] 🔴 Buffer #' + consecutiveBuffers);
2279
2654
  }
2280
2655
  }
2281
- }, 1000); // Check every 1 second for faster updates
2282
- }
2283
2656
 
2284
- this.api.triggerEvent('youtubeplugin:qualitychanged', { quality });
2285
- return true;
2286
- } catch (error) {
2287
- if (this.api.player.options.debug) {
2288
- console.error('[YT Plugin] Error setting quality:', error);
2657
+ const bufferingDuration = currentTime - bufferingStartTime;
2658
+ const timeSinceLastDowngrade = currentTime - lastQualityDowngrade;
2659
+
2660
+ // Only intervene if:
2661
+ // - 4+ buffers in 45 seconds, OR
2662
+ // - Single buffer lasts 3+ seconds
2663
+ // - AND at least 10 seconds since last downgrade (prevent loops)
2664
+ if ((consecutiveBuffers >= 4 || bufferingDuration > 3000) && timeSinceLastDowngrade > 10000) {
2665
+
2666
+ // Only auto-reduce in AUTO mode
2667
+ if (this.userQualityChoice === 'auto' || this.userQualityChoice === 'default') {
2668
+ const currentQuality = this.ytPlayer.getPlaybackQuality();
2669
+ const availableQualities = this.ytPlayer.getAvailableQualityLevels();
2670
+
2671
+ if (this.api.player.options.debug) {
2672
+ console.log('[YT Plugin] 🔻 Excessive buffering - attempting quality reduction');
2673
+ console.log('[YT Plugin] Current:', currentQuality);
2674
+ console.log('[YT Plugin] Available:', availableQualities);
2675
+ }
2676
+
2677
+ // Quality hierarchy (highest to lowest)
2678
+ const qualityLevels = ['highres', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
2679
+ const currentIndex = qualityLevels.indexOf(currentQuality);
2680
+
2681
+ // Go down 2 levels for noticeable improvement
2682
+ if (currentIndex !== -1 && currentIndex < qualityLevels.length - 2) {
2683
+ const lowerQuality = qualityLevels[currentIndex + 2];
2684
+
2685
+ if (availableQualities.includes(lowerQuality)) {
2686
+ if (this.api.player.options.debug) {
2687
+ console.log('[YT Plugin] 📉 Downgrading to:', lowerQuality);
2688
+ }
2689
+
2690
+ // Set quality WITHOUT interrupting playback
2691
+ this.ytPlayer.setPlaybackQuality(lowerQuality);
2692
+
2693
+ // Update tracking
2694
+ lastQualityDowngrade = currentTime;
2695
+ consecutiveBuffers = 0;
2696
+ bufferingStartTime = null;
2697
+
2698
+ // Update UI
2699
+ setTimeout(() => {
2700
+ const actualQuality = this.ytPlayer.getPlaybackQuality();
2701
+ const qualityLabel = this.getQualityLabel(actualQuality);
2702
+ this.updateQualityButtonDisplay('Auto', qualityLabel);
2703
+ }, 1000);
2704
+ }
2705
+ } else {
2706
+ if (this.api.player.options.debug) {
2707
+ console.log('[YT Plugin] ⚠️ Already at lowest quality');
2708
+ }
2709
+ }
2710
+ }
2711
+ }
2712
+
2713
+ } else if (state === YT.PlayerState.PLAYING) {
2714
+ // Reset buffering timer
2715
+ bufferingStartTime = null;
2716
+
2717
+ // Gradually reduce counter during smooth playback (every 15 seconds)
2718
+ if (consecutiveBuffers > 0) {
2719
+ setTimeout(() => {
2720
+ if (this.ytPlayer && this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
2721
+ consecutiveBuffers = Math.max(0, consecutiveBuffers - 1);
2722
+ if (this.api.player.options.debug) {
2723
+ console.log('[YT Plugin] ✅ Smooth playback - buffer count reduced to:', consecutiveBuffers);
2724
+ }
2725
+ }
2726
+ }, 15000);
2727
+ }
2728
+ }
2729
+
2730
+ } catch (error) {
2731
+ if (this.api.player.options.debug) {
2732
+ console.error('[YT Plugin] Error in buffer monitoring:', error);
2733
+ }
2289
2734
  }
2290
- return false;
2291
- }
2735
+ }, 500); // Check every 500ms
2292
2736
  }
2293
2737
 
2294
2738
  // ===== SUBTITLE METHODS =====
@@ -3087,6 +3531,20 @@ startBufferMonitoring() {
3087
3531
  }
3088
3532
  break;
3089
3533
  }
3534
+
3535
+ if (event.data === YT.PlayerState.PAUSED) {
3536
+ // add pause class
3537
+ if (this.api.container) {
3538
+ this.api.container.classList.add('video-paused');
3539
+ }
3540
+ }
3541
+
3542
+ if (event.data === YT.PlayerState.PLAYING) {
3543
+ // remove pause class
3544
+ if (this.api.container) {
3545
+ this.api.container.classList.remove('video-paused');
3546
+ }
3547
+ }
3090
3548
  }
3091
3549
 
3092
3550
  onPlaybackQualityChange(event) {
@@ -3105,7 +3563,7 @@ startBufferMonitoring() {
3105
3563
  console.error('[YT Plugin] Player error:', errorCode);
3106
3564
  }
3107
3565
 
3108
- // Error codes che indicano video non disponibile
3566
+ // Error codes that indicate video is unavailable
3109
3567
  const unavailableErrors = [
3110
3568
  2, // Invalid video ID
3111
3569
  5, // HTML5 player error
@@ -3438,6 +3896,73 @@ startBufferMonitoring() {
3438
3896
  }
3439
3897
  }
3440
3898
  }, 250);
3899
+
3900
+ // **Cursor sync interval - only if the option is true**
3901
+ if (!this.options.showYouTubeUI && this.api.player.options.hideCursor) {
3902
+ if (this.cursorSyncInterval) {
3903
+ clearInterval(this.cursorSyncInterval);
3904
+ }
3905
+
3906
+ this.cursorSyncInterval = setInterval(() => {
3907
+ if (this.api.controls) {
3908
+ const controlsVisible = this.api.controls.classList.contains('show');
3909
+
3910
+ if (controlsVisible) {
3911
+ // Controls are visible, show cursor
3912
+ this.showCursor();
3913
+ } else {
3914
+ // Controls are hidden, hide cursor
3915
+ this.hideCursor();
3916
+ }
3917
+ }
3918
+ }, 100); // Check every 100ms
3919
+
3920
+ if (this.api.player.options.debug) {
3921
+ console.log('[YT Plugin] Cursor sync enabled');
3922
+ }
3923
+ } else {
3924
+ // if cursor sync was previously enabled, clear it
3925
+ if (this.cursorSyncInterval) {
3926
+ clearInterval(this.cursorSyncInterval);
3927
+ this.cursorSyncInterval = null;
3928
+ this.showCursor(); // Assicurati che il cursore sia visibile
3929
+ }
3930
+ }
3931
+ }
3932
+
3933
+ /**
3934
+ * Hide mouse cursor in YouTube player
3935
+ * Only works when showYouTubeUI is false (custom controls)
3936
+ */
3937
+ hideCursor() {
3938
+ // Don't hide cursor if YouTube native UI is active
3939
+ if (this.options.showYouTubeUI) {
3940
+ return;
3941
+ }
3942
+
3943
+ // Add hide-cursor class to MAIN PLAYER CONTAINER
3944
+ // This ensures cursor is hidden everywhere in the player
3945
+ if (this.api.container) {
3946
+ this.api.container.classList.add('hide-cursor');
3947
+ }
3948
+
3949
+ if (this.api.player.options.debug) {
3950
+ console.log('[YT Plugin] Cursor hidden on main container');
3951
+ }
3952
+ }
3953
+
3954
+ /**
3955
+ * Show mouse cursor in YouTube player
3956
+ */
3957
+ showCursor() {
3958
+ // Remove hide-cursor class from MAIN PLAYER CONTAINER
3959
+ if (this.api.container) {
3960
+ this.api.container.classList.remove('hide-cursor');
3961
+ }
3962
+
3963
+ if (this.api.player.options.debug) {
3964
+ console.log('[YT Plugin] Cursor shown on main container');
3965
+ }
3441
3966
  }
3442
3967
 
3443
3968
  bindVolumeSlider() {
@@ -3568,6 +4093,18 @@ startBufferMonitoring() {
3568
4093
  dispose() {
3569
4094
  if (this.api.player.options.debug) console.log('[YT Plugin] Disposing');
3570
4095
 
4096
+ // Stop cursor sync interval
4097
+ if (this.cursorSyncInterval) {
4098
+ clearInterval(this.cursorSyncInterval);
4099
+ this.cursorSyncInterval = null;
4100
+ }
4101
+
4102
+ // Remove cursor hide styles
4103
+ const cursorStyleEl = document.getElementById('youtube-cursor-hide-styles');
4104
+ if (cursorStyleEl) {
4105
+ cursorStyleEl.remove();
4106
+ }
4107
+
3571
4108
  // Cleanup timeout
3572
4109
  if (this.playAttemptTimeout) {
3573
4110
  clearTimeout(this.playAttemptTimeout);