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