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.
- package/README.md +2 -0
- package/dist/myetv-player.js +51 -1
- package/dist/myetv-player.min.js +49 -0
- package/package.json +2 -1
- package/plugins/facebook/README.md +3 -0
- package/plugins/facebook/myetv-player-facebook-plugin.js +283 -17
- package/plugins/vimeo/README.md +3 -0
- package/plugins/vimeo/myetv-player-vimeo.js +333 -39
- package/plugins/youtube/README.md +7 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +711 -174
- package/src/controls.js +46 -0
- package/src/core.js +5 -1
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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]
|
|
950
|
+
console.log('[YT Plugin] Video duration:', Math.floor(durationMinutes), 'minutes');
|
|
1008
951
|
}
|
|
1009
952
|
|
|
1010
|
-
//
|
|
1011
|
-
if (
|
|
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]
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
962
|
+
// Normal adaptive quality for shorter videos
|
|
963
|
+
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
964
|
+
let suggestedQuality = 'default';
|
|
1023
965
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
966
|
+
if (connection) {
|
|
967
|
+
const effectiveType = connection.effectiveType;
|
|
968
|
+
const downlink = connection.downlink;
|
|
1027
969
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
-
|
|
1035
|
-
|
|
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
|
-
}
|
|
1040
|
-
|
|
1041
|
-
|
|
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); //
|
|
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('
|
|
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"
|
|
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.
|
|
2155
|
-
|
|
2156
|
-
|
|
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
|
-
|
|
2540
|
+
console.log('[YT Plugin] 🎯 CSS Trick - Setting quality to:', quality);
|
|
2227
2541
|
|
|
2228
|
-
|
|
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
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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
|
-
|
|
2246
|
-
this.currentQuality = quality;
|
|
2563
|
+
const targetSize = qualityToSize[quality] || qualityToSize['hd720'];
|
|
2247
2564
|
|
|
2248
|
-
|
|
2249
|
-
|
|
2565
|
+
// Get actual container size
|
|
2566
|
+
const container = this.ytPlayerContainer;
|
|
2567
|
+
const containerWidth = container.offsetWidth;
|
|
2568
|
+
const containerHeight = container.offsetHeight;
|
|
2250
2569
|
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
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
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
-
|
|
2261
|
-
|
|
2262
|
-
this.updateQualityButtonDisplay(qualityLabel, '');
|
|
2611
|
+
if (actualQuality === quality || quality === 'default') {
|
|
2612
|
+
console.log('[YT Plugin] ✅ CSS TRICK WORKED!');
|
|
2263
2613
|
} else {
|
|
2264
|
-
|
|
2265
|
-
this.updateQualityButtonDisplay('Auto', '');
|
|
2614
|
+
console.log('[YT Plugin] ⚠️ Partially worked - got:', actualQuality);
|
|
2266
2615
|
}
|
|
2616
|
+
}, 2000);
|
|
2267
2617
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
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
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
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
|
-
|
|
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
|
|
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);
|