myetv-player 1.1.4 → 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
 
@@ -367,10 +374,20 @@
367
374
 
368
375
  // Hide original speed menu option from settings (if exists)
369
376
  if (settingsMenu) {
377
+ // Hide old non-expandable speed option
370
378
  const originalSpeedOption = settingsMenu.querySelector('[data-action="speed"]');
371
379
  if (originalSpeedOption) {
372
380
  originalSpeedOption.style.display = 'none';
373
381
  }
382
+
383
+ // Hide new expandable speed option
384
+ const expandableSpeedWrapper = settingsMenu.querySelector('[data-action="speed-expand"]');
385
+ if (expandableSpeedWrapper) {
386
+ const wrapper = expandableSpeedWrapper.closest('.settings-expandable-wrapper');
387
+ if (wrapper) {
388
+ wrapper.style.display = 'none';
389
+ }
390
+ }
374
391
  }
375
392
 
376
393
  // Add subtitles option to settings menu
@@ -647,6 +664,15 @@
647
664
  if (originalSpeedOption) {
648
665
  originalSpeedOption.style.display = '';
649
666
  }
667
+
668
+ // Show expandable speed option again
669
+ const expandableSpeedWrapper = settingsMenu.querySelector('[data-action="speed-expand"]');
670
+ if (expandableSpeedWrapper) {
671
+ const wrapper = expandableSpeedWrapper.closest('.settings-expandable-wrapper');
672
+ if (wrapper) {
673
+ wrapper.style.display = '';
674
+ }
675
+ }
650
676
  }
651
677
 
652
678
  // Remove from settings
@@ -871,7 +897,7 @@
871
897
 
872
898
  const playerVars = {
873
899
  autoplay: this.options.autoplay ? 1 : 0,
874
- controls: this.options.showYouTubeUI ? 1 : 0,
900
+ controls: 1,
875
901
  fs: this.options.showYouTubeUI ? 1 : 0,
876
902
  disablekb: 1,
877
903
  modestbranding: 1,
@@ -912,130 +938,74 @@
912
938
  this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
913
939
  }
914
940
 
915
- setAdaptiveQuality() {
916
- if (!this.ytPlayer) return;
917
-
918
- try {
919
- // Check network connection speed if available
920
- const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
921
- let suggestedQuality = 'default';
922
-
923
- if (connection) {
924
- const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
925
- const downlink = connection.downlink; // Mbps
926
-
927
- if (this.api.player.options.debug) {
928
- console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
929
- }
930
-
931
- // Set quality based on connection speed
932
- if (effectiveType === 'slow-2g' || downlink < 0.5) {
933
- suggestedQuality = 'small'; // 240p
934
- } else if (effectiveType === '2g' || downlink < 1) {
935
- suggestedQuality = 'medium'; // 360p
936
- } else if (effectiveType === '3g' || downlink < 2.5) {
937
- suggestedQuality = 'large'; // 480p
938
- } else if (downlink < 5) {
939
- suggestedQuality = 'hd720'; // 720p
940
- } else if (downlink < 10) {
941
- suggestedQuality = 'hd1080'; // 1080p
942
- } else if (downlink < 20) {
943
- suggestedQuality = 'hd1440'; // 1440p (2K)
944
- } else if (downlink < 35) {
945
- suggestedQuality = 'hd2160'; // 2160p (4K)
946
- } else {
947
- suggestedQuality = 'highres'; // 8K o migliore disponibile
948
- }
949
-
950
- if (this.api.player.options.debug) {
951
- console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
952
- }
953
-
954
- this.ytPlayer.setPlaybackQuality(suggestedQuality);
955
- } else {
956
- // Fallback: start with medium quality on unknown devices
957
- if (this.api.player.options.debug) {
958
- console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
959
- }
960
- this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
961
- }
962
- } catch (error) {
963
- if (this.api.player.options.debug) {
964
- console.error('[YT Plugin] Error setting adaptive quality:', error);
965
- }
966
- }
967
- }
968
-
969
- startBufferMonitoring() {
970
- if (this.bufferMonitorInterval) {
971
- clearInterval(this.bufferMonitorInterval);
972
- }
973
-
974
- let bufferingCount = 0;
975
- let lastState = null;
976
-
977
- this.bufferMonitorInterval = setInterval(() => {
978
- if (!this.ytPlayer) return;
979
-
980
- try {
981
- const state = this.ytPlayer.getPlayerState();
941
+ setAdaptiveQuality() {
942
+ if (!this.ytPlayer) return;
982
943
 
983
- // Detect buffering (state 3)
984
- if (state === YT.PlayerState.BUFFERING) {
985
- bufferingCount++;
944
+ try {
945
+ // Check video duration first
946
+ const duration = this.ytPlayer.getDuration();
947
+ const durationMinutes = duration / 60;
986
948
 
987
949
  if (this.api.player.options.debug) {
988
- console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
950
+ console.log('[YT Plugin] Video duration:', Math.floor(durationMinutes), 'minutes');
989
951
  }
990
952
 
991
- // If buffering happens too often, reduce quality
992
- if (bufferingCount >= 3) {
993
- const currentQuality = this.ytPlayer.getPlaybackQuality();
994
- const availableQualities = this.ytPlayer.getAvailableQualityLevels();
995
-
953
+ // For videos longer than 2 hours, cap at 1080p
954
+ if (durationMinutes > 120) {
996
955
  if (this.api.player.options.debug) {
997
- console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
998
- console.log('[YT Plugin] Available qualities:', availableQualities);
956
+ console.log('[YT Plugin] Long video detected - capping at 1080p');
999
957
  }
958
+ this.ytPlayer.setPlaybackQuality('hd1080');
959
+ return; // Exit early, don't check connection
960
+ }
1000
961
 
1001
- // Quality hierarchy (highest to lowest)
1002
- const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
1003
- 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';
1004
965
 
1005
- // Try to go to next lower quality
1006
- if (currentIndex < qualityLevels.length - 1) {
1007
- const lowerQuality = qualityLevels[currentIndex + 1];
966
+ if (connection) {
967
+ const effectiveType = connection.effectiveType;
968
+ const downlink = connection.downlink;
1008
969
 
1009
- // Check if lower quality is available
1010
- if (availableQualities.includes(lowerQuality)) {
1011
- if (this.api.player.options.debug) {
1012
- console.log('[YT Plugin] Reducing quality to:', lowerQuality);
1013
- }
970
+ if (this.api.player.options.debug) {
971
+ console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
972
+ }
1014
973
 
1015
- this.ytPlayer.setPlaybackQuality(lowerQuality);
1016
- bufferingCount = 0; // Reset counter
1017
- }
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';
1018
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');
1019
1002
  }
1020
- } else if (state === YT.PlayerState.PLAYING) {
1021
- // Reset buffering count when playing smoothly
1022
- if (lastState === YT.PlayerState.BUFFERING) {
1023
- setTimeout(() => {
1024
- if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
1025
- bufferingCount = Math.max(0, bufferingCount - 1);
1026
- }
1027
- }, 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);
1028
1006
  }
1029
1007
  }
1030
-
1031
- lastState = state;
1032
- } catch (error) {
1033
- if (this.api.player.options.debug) {
1034
- console.error('[YT Plugin] Error in buffer monitoring:', error);
1035
- }
1036
1008
  }
1037
- }, 1000); // Check every second
1038
- }
1039
1009
 
1040
1010
  createMouseMoveOverlay() {
1041
1011
  if (this.mouseMoveOverlay) return;
@@ -1329,11 +1299,17 @@ startBufferMonitoring() {
1329
1299
  this.hideLoadingOverlay();
1330
1300
  this.hideInitialLoading();
1331
1301
  this.injectYouTubeCSSOverride();
1302
+ // inject controlbar gradient styles
1303
+ this.injectControlbarGradientStyles();
1332
1304
 
1333
1305
  this.syncControls();
1306
+
1334
1307
  // Start buffer monitoring for auto quality adjustment
1335
1308
  this.startBufferMonitoring();
1336
1309
 
1310
+ // Create YouTube controls button
1311
+ this.createYouTubeControlsButton();
1312
+
1337
1313
  // Hide custom controls when YouTube native UI is enabled
1338
1314
  if (this.options.showYouTubeUI) {
1339
1315
  // Hide controls
@@ -1343,6 +1319,17 @@ startBufferMonitoring() {
1343
1319
  this.api.controls.style.visibility = 'hidden';
1344
1320
  this.api.controls.style.pointerEvents = 'none';
1345
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
+ }
1346
1333
 
1347
1334
  // Hide overlay title
1348
1335
  const overlayTitle = this.api.container.querySelector('.title-overlay');
@@ -1409,12 +1396,101 @@ startBufferMonitoring() {
1409
1396
  // Check initial caption state AFTER captions are loaded and menu is built
1410
1397
  setTimeout(() => {
1411
1398
  this.checkInitialCaptionState();
1412
- }, 2500); // Dopo che tutto è stato inizializzato
1413
-
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');
1414
1410
  this.api.triggerEvent('youtubeplugin:playerready', {});
1415
1411
 
1416
1412
  }
1417
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
+
1418
1494
  forceHideCustomControls() {
1419
1495
  const existingStyle = document.getElementById('yt-force-hide-controls');
1420
1496
  if (existingStyle) {
@@ -1965,7 +2041,7 @@ startBufferMonitoring() {
1965
2041
  visibility: visible !important;
1966
2042
  opacity: 1 !important;
1967
2043
  }
1968
-
2044
+
1969
2045
  /* Make watermark circular */
1970
2046
  .video-wrapper .watermark,
1971
2047
  .video-wrapper .watermark-image,
@@ -1973,12 +2049,113 @@ startBufferMonitoring() {
1973
2049
  border-radius: 50% !important;
1974
2050
  overflow: hidden !important;
1975
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
+ }
1976
2075
  `;
2076
+
1977
2077
  document.head.appendChild(style);
1978
2078
  this.api.container.classList.add('youtube-active');
1979
2079
 
1980
2080
  if (this.api.player.options.debug) {
1981
- 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}`);
1982
2159
  }
1983
2160
  }
1984
2161
 
@@ -2066,9 +2243,10 @@ startBufferMonitoring() {
2066
2243
  return;
2067
2244
  }
2068
2245
 
2246
+ const qualityText = this.api.player.t('video_quality');
2069
2247
  const qualityHTML = `
2070
2248
  <div class="quality-control">
2071
- <button class="control-btn quality-btn" data-tooltip="Video Quality">
2249
+ <button class="control-btn quality-btn" title="${qualityText}">
2072
2250
  <div class="quality-btn-text">
2073
2251
  <div class="selected-quality">Auto</div>
2074
2252
  <div class="current-quality"></div>
@@ -2126,23 +2304,178 @@ startBufferMonitoring() {
2126
2304
  });
2127
2305
  qualityMenu.appendChild(autoItem);
2128
2306
 
2129
- // Add quality options
2307
+ // Add quality options (all disabled)
2130
2308
  this.availableQualities.forEach(quality => {
2131
2309
  const menuItem = document.createElement('div');
2132
- menuItem.className = 'quality-option';
2310
+ menuItem.className = 'quality-option disabled';
2133
2311
  menuItem.textContent = quality.label;
2134
2312
  menuItem.dataset.quality = quality.value;
2135
- menuItem.addEventListener('click', () => {
2136
- this.setQuality(quality.value);
2137
- this.updateQualityMenuActiveState(quality.value);
2138
- this.updateQualityButtonDisplay(quality.label, '');
2139
- });
2313
+ menuItem.style.opacity = '0.5';
2314
+ menuItem.style.cursor = 'not-allowed';
2315
+ menuItem.style.pointerEvents = 'none';
2140
2316
  qualityMenu.appendChild(menuItem);
2141
2317
  });
2142
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
+
2143
2343
  if (this.api.player.options.debug) console.log('[YT Plugin] Quality menu updated');
2144
2344
  }
2145
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
+
2146
2479
  updateQualityMenuActiveState(qualityValue) {
2147
2480
  const qualityMenu = this.api.container.querySelector('.quality-menu');
2148
2481
  if (!qualityMenu) return;
@@ -2204,72 +2537,202 @@ startBufferMonitoring() {
2204
2537
  }
2205
2538
 
2206
2539
  setQuality(quality) {
2207
- if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
2540
+ console.log('[YT Plugin] 🎯 CSS Trick - Setting quality to:', quality);
2208
2541
 
2209
- try {
2210
- // Track user's quality choice for display
2211
- this.userQualityChoice = quality;
2212
- if (this.api.player.options.debug) {
2213
- console.log('[YT Plugin] Setting quality to:', quality);
2214
- console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
2215
- console.log('[YT Plugin] Available qualities:', this.ytPlayer.getAvailableQualityLevels());
2216
- }
2542
+ if (!this.ytPlayer) return;
2217
2543
 
2218
- // Check if requested quality is actually available
2219
- const availableLevels = this.ytPlayer.getAvailableQualityLevels();
2220
- if (quality !== 'default' && quality !== 'auto' && !availableLevels.includes(quality)) {
2221
- if (this.api.player.options.debug) {
2222
- console.warn('[YT Plugin] Requested quality not available:', quality);
2223
- }
2224
- }
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
+ };
2225
2562
 
2226
- // Update state
2227
- this.currentQuality = quality;
2563
+ const targetSize = qualityToSize[quality] || qualityToSize['hd720'];
2228
2564
 
2229
- // Set the quality
2230
- this.ytPlayer.setPlaybackQuality(quality);
2565
+ // Get actual container size
2566
+ const container = this.ytPlayerContainer;
2567
+ const containerWidth = container.offsetWidth;
2568
+ const containerHeight = container.offsetHeight;
2231
2569
 
2232
- // Also try setPlaybackQualityRange for better enforcement
2233
- if (this.ytPlayer.setPlaybackQualityRange) {
2234
- this.ytPlayer.setPlaybackQualityRange(quality, quality);
2235
- }
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';
2236
2583
 
2237
- // Force UI update immediately
2238
- this.updateQualityMenuPlayingState(quality);
2239
- const qualityLabel = this.getQualityLabel(quality);
2584
+ // Scale up with CSS transform to fill container
2585
+ iframe.style.transform = `scale(${scale})`;
2586
+ iframe.style.transformOrigin = 'top left';
2240
2587
 
2241
- // For manual quality selection, show only the selected quality
2242
- if (quality !== 'default' && quality !== 'auto') {
2243
- this.updateQualityButtonDisplay(qualityLabel, '');
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
+ }
2604
+
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);
2610
+
2611
+ if (actualQuality === quality || quality === 'default') {
2612
+ console.log('[YT Plugin] ✅ CSS TRICK WORKED!');
2244
2613
  } else {
2245
- // For auto mode, show "Auto" and let monitoring update the actual quality
2246
- this.updateQualityButtonDisplay('Auto', '');
2614
+ console.log('[YT Plugin] ⚠️ Partially worked - got:', actualQuality);
2247
2615
  }
2616
+ }, 2000);
2248
2617
 
2249
- if (this.api.player.options.debug) {
2250
- // Check actual quality after a moment
2251
- setTimeout(() => {
2252
- if (this.ytPlayer && this.ytPlayer.getPlaybackQuality) {
2253
- const actualQuality = this.ytPlayer.getPlaybackQuality();
2254
- console.log('[YT Plugin] Actual quality after 1s:', actualQuality);
2255
- if (actualQuality !== quality && quality !== 'default' && quality !== 'auto') {
2256
- console.warn('[YT Plugin] YouTube did not apply requested quality. This may mean:');
2257
- console.warn(' - The quality is not available for this video');
2258
- console.warn(' - Embedding restrictions apply');
2259
- 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);
2260
2654
  }
2261
2655
  }
2262
- }, 1000); // Check every 1 second for faster updates
2263
- }
2264
2656
 
2265
- this.api.triggerEvent('youtubeplugin:qualitychanged', { quality });
2266
- return true;
2267
- } catch (error) {
2268
- if (this.api.player.options.debug) {
2269
- 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
+ }
2270
2734
  }
2271
- return false;
2272
- }
2735
+ }, 500); // Check every 500ms
2273
2736
  }
2274
2737
 
2275
2738
  // ===== SUBTITLE METHODS =====
@@ -3068,6 +3531,20 @@ startBufferMonitoring() {
3068
3531
  }
3069
3532
  break;
3070
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
+ }
3071
3548
  }
3072
3549
 
3073
3550
  onPlaybackQualityChange(event) {
@@ -3086,7 +3563,7 @@ startBufferMonitoring() {
3086
3563
  console.error('[YT Plugin] Player error:', errorCode);
3087
3564
  }
3088
3565
 
3089
- // Error codes che indicano video non disponibile
3566
+ // Error codes that indicate video is unavailable
3090
3567
  const unavailableErrors = [
3091
3568
  2, // Invalid video ID
3092
3569
  5, // HTML5 player error
@@ -3131,7 +3608,7 @@ startBufferMonitoring() {
3131
3608
  }
3132
3609
  };
3133
3610
 
3134
- // Override pause method
3611
+ // Override pause method
3135
3612
  const originalPause = this.player.pause;
3136
3613
  this.player.pause = () => {
3137
3614
  if (this.ytPlayer && this.ytPlayer.pauseVideo) {
@@ -3203,15 +3680,13 @@ startBufferMonitoring() {
3203
3680
  // Override mute toggle
3204
3681
  const originalToggleMute = this.player.toggleMute;
3205
3682
  this.player.toggleMute = () => {
3206
- if (this.ytPlayer && this.ytPlayer.isMuted && this.ytPlayer.mute && this.ytPlayer.unMute) {
3683
+ if (this.ytPlayer && this.ytPlayer.isMuted) {
3207
3684
  const isMuted = this.ytPlayer.isMuted();
3208
-
3209
3685
  if (isMuted) {
3210
3686
  this.ytPlayer.unMute();
3211
3687
  } else {
3212
3688
  this.ytPlayer.mute();
3213
3689
  }
3214
-
3215
3690
  this.updateMuteButtonState(!isMuted);
3216
3691
 
3217
3692
  if (!isMuted) {
@@ -3225,97 +3700,6 @@ startBufferMonitoring() {
3225
3700
  }
3226
3701
  };
3227
3702
 
3228
- // Volume tooltip events for YouTube
3229
- if (this.api.player.volumeSlider) {
3230
- const volumeSlider = this.api.player.volumeSlider;
3231
- const volumeContainer = this.api.container.querySelector('.volume-container');
3232
-
3233
- // Remove existing listeners to avoid duplicates
3234
- const newVolumeSlider = volumeSlider.cloneNode(true);
3235
- volumeSlider.parentNode.replaceChild(newVolumeSlider, volumeSlider);
3236
- this.api.player.volumeSlider = newVolumeSlider;
3237
-
3238
- // Update tooltip on input (slider drag)
3239
- newVolumeSlider.addEventListener('input', (e) => {
3240
- const value = parseFloat(e.target.value);
3241
- this.player.updateVolume(value);
3242
-
3243
- // Update tooltip position and text during drag
3244
- if (this.api.player.updateVolumeTooltipPosition) {
3245
- this.api.player.updateVolumeTooltipPosition(value / 100);
3246
- }
3247
- if (this.api.player.updateVolumeTooltip) {
3248
- this.api.player.updateVolumeTooltip();
3249
- }
3250
- });
3251
-
3252
- // Update tooltip position on mousemove over slider
3253
- newVolumeSlider.addEventListener('mousemove', (e) => {
3254
- const rect = newVolumeSlider.getBoundingClientRect();
3255
- const mouseX = e.clientX - rect.left;
3256
- const percentage = Math.max(0, Math.min(1, mouseX / rect.width));
3257
-
3258
- // Update tooltip position as mouse moves
3259
- if (this.api.player.updateVolumeTooltipPosition) {
3260
- this.api.player.updateVolumeTooltipPosition(percentage);
3261
- }
3262
-
3263
- // Update tooltip text to show value under mouse
3264
- const volumeTooltip = this.api.container.querySelector('.volume-tooltip');
3265
- if (volumeTooltip) {
3266
- volumeTooltip.textContent = Math.round(percentage * 100) + '%';
3267
- }
3268
- });
3269
-
3270
- // Show/hide tooltip on hover
3271
- if (volumeContainer) {
3272
- volumeContainer.addEventListener('mouseenter', () => {
3273
- const volumeTooltip = this.api.container.querySelector('.volume-tooltip');
3274
- if (volumeTooltip) {
3275
- volumeTooltip.classList.add('visible');
3276
- }
3277
- });
3278
-
3279
- volumeContainer.addEventListener('mouseleave', () => {
3280
- const volumeTooltip = this.api.container.querySelector('.volume-tooltip');
3281
- if (volumeTooltip) {
3282
- volumeTooltip.classList.remove('visible');
3283
- }
3284
- });
3285
- }
3286
-
3287
- if (this.api.player.options.debug) {
3288
- console.log('[YT Plugin] Volume tooltip events bound');
3289
- }
3290
- }
3291
-
3292
- // Override playback speed
3293
- const originalChangeSpeed = this.player.changeSpeed;
3294
- if (originalChangeSpeed) {
3295
- this.player.changeSpeed = (e) => {
3296
- if (!e.target.classList.contains('speed-option')) return;
3297
-
3298
- const speed = parseFloat(e.target.getAttribute('data-speed'));
3299
-
3300
- if (this.ytPlayer && this.ytPlayer.setPlaybackRate && speed > 0) {
3301
- this.ytPlayer.setPlaybackRate(speed);
3302
-
3303
- const speedBtn = this.api.container.querySelector('.speed-btn');
3304
- if (speedBtn) speedBtn.textContent = `${speed}x`;
3305
-
3306
- const speedMenu = this.api.container.querySelector('.speed-menu');
3307
- if (speedMenu) {
3308
- speedMenu.querySelectorAll('.speed-option').forEach(option => {
3309
- option.classList.remove('active');
3310
- });
3311
- e.target.classList.add('active');
3312
- }
3313
- } else {
3314
- originalChangeSpeed.call(this.player, e);
3315
- }
3316
- };
3317
- }
3318
-
3319
3703
  // Override progress bar seeking
3320
3704
  if (this.api.player.progressContainer) {
3321
3705
  const progressContainer = this.api.player.progressContainer;
@@ -3330,7 +3714,22 @@ startBufferMonitoring() {
3330
3714
  // Create tooltip for seek preview
3331
3715
  const seekTooltip = document.createElement('div');
3332
3716
  seekTooltip.className = 'yt-seek-tooltip';
3333
- seekTooltip.style.cssText = 'position:absolute;bottom:calc(100% + 10px);left:0;background:rgba(28,28,28,0.95);color:#fff;padding:6px 10px;border-radius:3px;font-size:13px;font-weight:500;white-space:nowrap;pointer-events:none;visibility:hidden;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,0.3);';
3717
+ seekTooltip.style.cssText = `
3718
+ position: absolute;
3719
+ bottom: calc(100% + 10px);
3720
+ left: 0;
3721
+ background: rgba(28,28,28,0.95);
3722
+ color: #fff;
3723
+ padding: 6px 10px;
3724
+ border-radius: 3px;
3725
+ font-size: 13px;
3726
+ font-weight: 500;
3727
+ white-space: nowrap;
3728
+ pointer-events: none;
3729
+ visibility: hidden;
3730
+ z-index: 99999;
3731
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
3732
+ `;
3334
3733
  newProgressContainer.appendChild(seekTooltip);
3335
3734
 
3336
3735
  // Format time function for tooltip
@@ -3348,25 +3747,32 @@ startBufferMonitoring() {
3348
3747
  let isSeeking = false;
3349
3748
 
3350
3749
  const handleSeek = (e) => {
3351
- if (!this.ytPlayer || !this.ytPlayer.getDuration) return;
3750
+ if (!this.ytPlayer || !this.ytPlayer.getDuration()) return;
3352
3751
 
3353
3752
  const rect = newProgressContainer.getBoundingClientRect();
3354
- const clickX = e.clientX - rect.left;
3753
+
3754
+ // Support both mouse and touch events
3755
+ const clientX = e.clientX !== undefined ? e.clientX :
3756
+ (e.touches && e.touches[0] ? e.touches[0].clientX :
3757
+ (e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : 0));
3758
+
3759
+ const clickX = clientX - rect.left;
3355
3760
  const percentage = Math.max(0, Math.min(1, clickX / rect.width));
3356
3761
  const duration = this.ytPlayer.getDuration();
3357
3762
  const targetTime = percentage * duration;
3358
3763
 
3359
3764
  this.ytPlayer.seekTo(targetTime, true);
3360
3765
 
3361
- const progress = percentage * 100 + '%';
3766
+ const progress = percentage * 100;
3362
3767
  if (this.api.player.progressFilled) {
3363
- this.api.player.progressFilled.style.width = progress;
3768
+ this.api.player.progressFilled.style.width = `${progress}%`;
3364
3769
  }
3365
3770
  if (this.api.player.progressHandle) {
3366
- this.api.player.progressHandle.style.left = progress;
3771
+ this.api.player.progressHandle.style.left = `${progress}%`;
3367
3772
  }
3368
3773
  };
3369
3774
 
3775
+ // MOUSE EVENTS
3370
3776
  newProgressContainer.addEventListener('mousedown', (e) => {
3371
3777
  isSeeking = true;
3372
3778
  handleSeek(e);
@@ -3378,7 +3784,7 @@ startBufferMonitoring() {
3378
3784
  }
3379
3785
 
3380
3786
  // Show tooltip with timestamp
3381
- if (!isSeeking && this.ytPlayer && this.ytPlayer.getDuration) {
3787
+ if (!isSeeking && this.ytPlayer && this.ytPlayer.getDuration()) {
3382
3788
  const rect = newProgressContainer.getBoundingClientRect();
3383
3789
  const mouseX = e.clientX - rect.left;
3384
3790
  const percentage = Math.max(0, Math.min(1, mouseX / rect.width));
@@ -3386,7 +3792,7 @@ startBufferMonitoring() {
3386
3792
  const time = percentage * duration;
3387
3793
 
3388
3794
  seekTooltip.textContent = formatTimeForTooltip(time);
3389
- seekTooltip.style.left = mouseX + 'px';
3795
+ seekTooltip.style.left = `${mouseX}px`;
3390
3796
  seekTooltip.style.visibility = 'visible';
3391
3797
  }
3392
3798
  });
@@ -3399,10 +3805,50 @@ startBufferMonitoring() {
3399
3805
  isSeeking = false;
3400
3806
  });
3401
3807
 
3808
+ // TOUCH EVENTS - AGGIUNTI QUI!
3809
+ newProgressContainer.addEventListener('touchstart', (e) => {
3810
+ e.preventDefault(); // scroll prevention during drag
3811
+ isSeeking = true;
3812
+ handleSeek(e);
3813
+ }, { passive: false });
3814
+
3815
+ newProgressContainer.addEventListener('touchmove', (e) => {
3816
+ e.preventDefault(); // scroll prevention during drag
3817
+
3818
+ if (isSeeking) {
3819
+ handleSeek(e);
3820
+ }
3821
+
3822
+ // Show tooltip with timestamp during touch
3823
+ if (!isSeeking && this.ytPlayer && this.ytPlayer.getDuration()) {
3824
+ const rect = newProgressContainer.getBoundingClientRect();
3825
+ const touch = e.touches[0];
3826
+ const touchX = touch.clientX - rect.left;
3827
+ const percentage = Math.max(0, Math.min(1, touchX / rect.width));
3828
+ const duration = this.ytPlayer.getDuration();
3829
+ const time = percentage * duration;
3830
+
3831
+ seekTooltip.textContent = formatTimeForTooltip(time);
3832
+ seekTooltip.style.left = `${touchX}px`;
3833
+ seekTooltip.style.visibility = 'visible';
3834
+ }
3835
+ }, { passive: false });
3836
+
3837
+ newProgressContainer.addEventListener('touchend', () => {
3838
+ isSeeking = false;
3839
+ seekTooltip.style.visibility = 'hidden';
3840
+ });
3841
+
3842
+ newProgressContainer.addEventListener('touchcancel', () => {
3843
+ isSeeking = false;
3844
+ seekTooltip.style.visibility = 'hidden';
3845
+ });
3846
+
3847
+ // CLICK EVENT
3402
3848
  newProgressContainer.addEventListener('click', handleSeek);
3403
- }
3404
3849
 
3405
- this.bindVolumeSlider();
3850
+ this.bindVolumeSlider();
3851
+ }
3406
3852
 
3407
3853
  // Time update interval
3408
3854
  if (this.timeUpdateInterval) {
@@ -3423,14 +3869,13 @@ startBufferMonitoring() {
3423
3869
  } else {
3424
3870
  // For regular videos, calculate normally
3425
3871
  progress = (currentTime / duration) * 100;
3426
- }
3427
-
3428
- // Check if live badge exists = it's a live stream
3429
- const liveBadge = this.api.container.querySelector('.live-badge');
3430
3872
 
3431
- if (liveBadge) {
3432
- // Force 100% for live streams
3433
- progress = 100;
3873
+ // Check if live badge exists = it's a live stream
3874
+ const liveBadge = this.api.container.querySelector('.live-badge');
3875
+ if (liveBadge) {
3876
+ // Force 100% for live streams
3877
+ progress = 100;
3878
+ }
3434
3879
  }
3435
3880
 
3436
3881
  this.api.player.progressFilled.style.width = `${progress}%`;
@@ -3442,10 +3887,82 @@ startBufferMonitoring() {
3442
3887
  const currentTimeEl = this.api.container.querySelector('.current-time');
3443
3888
  const durationEl = this.api.container.querySelector('.duration');
3444
3889
 
3445
- if (currentTimeEl) currentTimeEl.textContent = this.formatTime(currentTime);
3446
- if (durationEl && duration) durationEl.textContent = this.formatTime(duration);
3890
+ if (currentTimeEl) {
3891
+ currentTimeEl.textContent = this.formatTime(currentTime);
3892
+ }
3893
+
3894
+ if (durationEl && duration) {
3895
+ durationEl.textContent = this.formatTime(duration);
3896
+ }
3447
3897
  }
3448
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
+ }
3449
3966
  }
3450
3967
 
3451
3968
  bindVolumeSlider() {
@@ -3576,6 +4093,18 @@ startBufferMonitoring() {
3576
4093
  dispose() {
3577
4094
  if (this.api.player.options.debug) console.log('[YT Plugin] Disposing');
3578
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
+
3579
4108
  // Cleanup timeout
3580
4109
  if (this.playAttemptTimeout) {
3581
4110
  clearTimeout(this.playAttemptTimeout);