myetv-player 1.0.10 → 1.1.1
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 +145 -2
- package/css/myetv-player.css +69 -0
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +227 -52
- package/dist/myetv-player.min.js +225 -50
- package/package.json +3 -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 +572 -116
- package/scss/_controls.scss +53 -0
- package/scss/_title-overlay.scss +27 -0
- package/src/core.js +89 -21
- package/src/events.js +118 -25
- package/src/utils.js +20 -6
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
|
|
16
16
|
autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
|
|
17
17
|
quality: options.quality || 'default',
|
|
18
|
+
|
|
18
19
|
enableQualityControl: options.enableQualityControl !== undefined ? options.enableQualityControl : true,
|
|
19
20
|
enableCaptions: options.enableCaptions !== undefined ? options.enableCaptions : true,
|
|
20
21
|
|
|
@@ -24,10 +25,20 @@
|
|
|
24
25
|
// Auto caption language option
|
|
25
26
|
autoCaptionLanguage: options.autoCaptionLanguage || null, // e.g., 'it', 'en', 'es', 'de', 'fr'
|
|
26
27
|
|
|
28
|
+
// Enable or disable click over youtube player
|
|
29
|
+
mouseClick: options.mouseClick !== undefined ? options.mouseClick : false,
|
|
30
|
+
|
|
27
31
|
debug: true,
|
|
28
32
|
...options
|
|
29
33
|
};
|
|
30
34
|
|
|
35
|
+
// Normalize 'auto' to 'default' for YouTube API compatibility
|
|
36
|
+
if (this.options.quality === 'auto') {
|
|
37
|
+
this.options.quality = 'default';
|
|
38
|
+
}
|
|
39
|
+
// Track original user choice for UI display
|
|
40
|
+
this.userQualityChoice = options.quality || 'default';
|
|
41
|
+
|
|
31
42
|
this.ytPlayer = null;
|
|
32
43
|
this.isYouTubeReady = false;
|
|
33
44
|
this.videoId = this.options.videoId;
|
|
@@ -154,6 +165,14 @@
|
|
|
154
165
|
* Update main player watermark options with channel data
|
|
155
166
|
*/
|
|
156
167
|
async updatePlayerWatermark() {
|
|
168
|
+
// Don't create watermark when YouTube native UI is active
|
|
169
|
+
if (this.options.showYouTubeUI) {
|
|
170
|
+
if (this.api.player.options.debug) {
|
|
171
|
+
console.log('[YT Plugin] Skipping watermark - YouTube UI active');
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
157
176
|
if (!this.options.enableChannelWatermark || !this.videoId) {
|
|
158
177
|
return;
|
|
159
178
|
}
|
|
@@ -673,6 +692,7 @@ width: fit-content;
|
|
|
673
692
|
cc_lang_pref: this.options.autoCaptionLanguage || 'en',
|
|
674
693
|
hl: this.options.autoCaptionLanguage || 'en',
|
|
675
694
|
iv_load_policy: 3,
|
|
695
|
+
showinfo: 0,
|
|
676
696
|
...options.playerVars
|
|
677
697
|
};
|
|
678
698
|
|
|
@@ -704,65 +724,334 @@ width: fit-content;
|
|
|
704
724
|
this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
|
|
705
725
|
}
|
|
706
726
|
|
|
727
|
+
setAdaptiveQuality() {
|
|
728
|
+
if (!this.ytPlayer) return;
|
|
729
|
+
|
|
730
|
+
try {
|
|
731
|
+
// Check network connection speed if available
|
|
732
|
+
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
733
|
+
let suggestedQuality = 'default';
|
|
734
|
+
|
|
735
|
+
if (connection) {
|
|
736
|
+
const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
|
|
737
|
+
const downlink = connection.downlink; // Mbps
|
|
738
|
+
|
|
739
|
+
if (this.api.player.options.debug) {
|
|
740
|
+
console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Set quality based on connection speed
|
|
744
|
+
if (effectiveType === 'slow-2g' || downlink < 0.5) {
|
|
745
|
+
suggestedQuality = 'small'; // 240p
|
|
746
|
+
} else if (effectiveType === '2g' || downlink < 1) {
|
|
747
|
+
suggestedQuality = 'medium'; // 360p
|
|
748
|
+
} else if (effectiveType === '3g' || downlink < 2.5) {
|
|
749
|
+
suggestedQuality = 'large'; // 480p
|
|
750
|
+
} else if (downlink < 5) {
|
|
751
|
+
suggestedQuality = 'hd720'; // 720p
|
|
752
|
+
} else if (downlink < 10) {
|
|
753
|
+
suggestedQuality = 'hd1080'; // 1080p
|
|
754
|
+
} else if (downlink < 20) {
|
|
755
|
+
suggestedQuality = 'hd1440'; // 1440p (2K)
|
|
756
|
+
} else if (downlink < 35) {
|
|
757
|
+
suggestedQuality = 'hd2160'; // 2160p (4K)
|
|
758
|
+
} else {
|
|
759
|
+
suggestedQuality = 'highres'; // 8K o migliore disponibile
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (this.api.player.options.debug) {
|
|
763
|
+
console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
this.ytPlayer.setPlaybackQuality(suggestedQuality);
|
|
767
|
+
} else {
|
|
768
|
+
// Fallback: start with medium quality on unknown devices
|
|
769
|
+
if (this.api.player.options.debug) {
|
|
770
|
+
console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
|
|
771
|
+
}
|
|
772
|
+
this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
|
|
773
|
+
}
|
|
774
|
+
} catch (error) {
|
|
775
|
+
if (this.api.player.options.debug) {
|
|
776
|
+
console.error('[YT Plugin] Error setting adaptive quality:', error);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
startBufferMonitoring() {
|
|
782
|
+
if (this.bufferMonitorInterval) {
|
|
783
|
+
clearInterval(this.bufferMonitorInterval);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
let bufferingCount = 0;
|
|
787
|
+
let lastState = null;
|
|
788
|
+
|
|
789
|
+
this.bufferMonitorInterval = setInterval(() => {
|
|
790
|
+
if (!this.ytPlayer) return;
|
|
791
|
+
|
|
792
|
+
try {
|
|
793
|
+
const state = this.ytPlayer.getPlayerState();
|
|
794
|
+
|
|
795
|
+
// Detect buffering (state 3)
|
|
796
|
+
if (state === YT.PlayerState.BUFFERING) {
|
|
797
|
+
bufferingCount++;
|
|
798
|
+
|
|
799
|
+
if (this.api.player.options.debug) {
|
|
800
|
+
console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// If buffering happens too often, reduce quality
|
|
804
|
+
if (bufferingCount >= 3) {
|
|
805
|
+
const currentQuality = this.ytPlayer.getPlaybackQuality();
|
|
806
|
+
const availableQualities = this.ytPlayer.getAvailableQualityLevels();
|
|
807
|
+
|
|
808
|
+
if (this.api.player.options.debug) {
|
|
809
|
+
console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
|
|
810
|
+
console.log('[YT Plugin] Available qualities:', availableQualities);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Quality hierarchy (highest to lowest)
|
|
814
|
+
const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
|
|
815
|
+
const currentIndex = qualityLevels.indexOf(currentQuality);
|
|
816
|
+
|
|
817
|
+
// Try to go to next lower quality
|
|
818
|
+
if (currentIndex < qualityLevels.length - 1) {
|
|
819
|
+
const lowerQuality = qualityLevels[currentIndex + 1];
|
|
820
|
+
|
|
821
|
+
// Check if lower quality is available
|
|
822
|
+
if (availableQualities.includes(lowerQuality)) {
|
|
823
|
+
if (this.api.player.options.debug) {
|
|
824
|
+
console.log('[YT Plugin] Reducing quality to:', lowerQuality);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
this.ytPlayer.setPlaybackQuality(lowerQuality);
|
|
828
|
+
bufferingCount = 0; // Reset counter
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
} else if (state === YT.PlayerState.PLAYING) {
|
|
833
|
+
// Reset buffering count when playing smoothly
|
|
834
|
+
if (lastState === YT.PlayerState.BUFFERING) {
|
|
835
|
+
setTimeout(() => {
|
|
836
|
+
if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
|
|
837
|
+
bufferingCount = Math.max(0, bufferingCount - 1);
|
|
838
|
+
}
|
|
839
|
+
}, 5000); // Wait 5 seconds of smooth playback
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
lastState = state;
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (this.api.player.options.debug) {
|
|
846
|
+
console.error('[YT Plugin] Error in buffer monitoring:', error);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}, 1000); // Check every second
|
|
850
|
+
}
|
|
851
|
+
|
|
707
852
|
createMouseMoveOverlay() {
|
|
708
853
|
if (this.mouseMoveOverlay) return;
|
|
709
854
|
|
|
855
|
+
// Do NOT create overlay if YouTube native UI is enabled (ToS compliant)
|
|
856
|
+
if (this.options.showYouTubeUI) {
|
|
857
|
+
if (this.api.player.options.debug) {
|
|
858
|
+
console.log('[YT Plugin] Skipping overlay - YouTube native UI enabled (ToS compliant)');
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Enable clicks on YouTube player
|
|
862
|
+
if (this.options.mouseClick !== false) {
|
|
863
|
+
this.enableYouTubeClicks();
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Setup mouse detection for custom controls visibility
|
|
867
|
+
this.setupMouseMoveDetection();
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
710
871
|
this.mouseMoveOverlay = document.createElement('div');
|
|
711
872
|
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
873
|
|
|
716
|
-
//
|
|
717
|
-
this.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
874
|
+
// Apply pointer-events based on mouseClick option
|
|
875
|
+
const pointerEvents = this.options.mouseClick ? 'none' : 'auto';
|
|
876
|
+
|
|
877
|
+
this.mouseMoveOverlay.style.cssText = `
|
|
878
|
+
position: absolute;
|
|
879
|
+
top: 0;
|
|
880
|
+
left: 0;
|
|
881
|
+
width: 100%;
|
|
882
|
+
height: 100%;
|
|
883
|
+
z-index: 2;
|
|
884
|
+
background: transparent;
|
|
885
|
+
pointer-events: ${pointerEvents};
|
|
886
|
+
cursor: default;
|
|
887
|
+
`;
|
|
723
888
|
|
|
724
|
-
this.
|
|
725
|
-
const doubleTap = this.api.player.options.doubleTapPause;
|
|
726
|
-
const pauseClick = this.api.player.options.pauseClick;
|
|
889
|
+
this.api.container.insertBefore(this.mouseMoveOverlay, this.api.controls);
|
|
727
890
|
|
|
728
|
-
|
|
729
|
-
|
|
891
|
+
// Setup mouse detection
|
|
892
|
+
this.setupMouseMoveDetection();
|
|
730
893
|
|
|
731
|
-
|
|
732
|
-
|
|
894
|
+
// Only add event listeners if mouseClick is disabled
|
|
895
|
+
if (!this.options.mouseClick) {
|
|
896
|
+
this.mouseMoveOverlay.addEventListener('mousemove', (e) => {
|
|
897
|
+
if (this.api.player.onMouseMove) {
|
|
898
|
+
this.api.player.onMouseMove(e);
|
|
733
899
|
}
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
this.mouseMoveOverlay.addEventListener('click', (e) => {
|
|
903
|
+
const doubleTap = this.api.player.options.doubleTapPause;
|
|
904
|
+
const pauseClick = this.api.player.options.pauseClick;
|
|
734
905
|
|
|
735
|
-
if (
|
|
736
|
-
|
|
737
|
-
if (controls) {
|
|
738
|
-
controlsHidden = controls.classList.contains('hide');
|
|
906
|
+
if (doubleTap) {
|
|
907
|
+
let controlsHidden = false;
|
|
908
|
+
if (this.api.controls) {
|
|
909
|
+
controlsHidden = this.api.controls.classList.contains('hide');
|
|
739
910
|
}
|
|
740
|
-
}
|
|
741
911
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
912
|
+
if (!controlsHidden) {
|
|
913
|
+
const controls = this.player.container.querySelector('.controls');
|
|
914
|
+
if (controls) {
|
|
915
|
+
controlsHidden = controls.classList.contains('hide');
|
|
916
|
+
}
|
|
917
|
+
}
|
|
746
918
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
919
|
+
if (!controlsHidden && this.api.controls) {
|
|
920
|
+
const style = window.getComputedStyle(this.api.controls);
|
|
921
|
+
controlsHidden = style.opacity === '0' || style.visibility === 'hidden';
|
|
750
922
|
}
|
|
751
|
-
|
|
752
|
-
|
|
923
|
+
|
|
924
|
+
if (controlsHidden) {
|
|
925
|
+
if (this.api.player.showControlsNow) {
|
|
926
|
+
this.api.player.showControlsNow();
|
|
927
|
+
}
|
|
928
|
+
if (this.api.player.resetAutoHideTimer) {
|
|
929
|
+
this.api.player.resetAutoHideTimer();
|
|
930
|
+
}
|
|
931
|
+
return;
|
|
753
932
|
}
|
|
754
|
-
|
|
933
|
+
|
|
934
|
+
this.togglePlayPauseYT();
|
|
935
|
+
} else if (pauseClick) {
|
|
936
|
+
this.togglePlayPauseYT();
|
|
755
937
|
}
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
}
|
|
756
941
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
942
|
+
// monitor mouse movement over container
|
|
943
|
+
setupMouseMoveDetection() {
|
|
944
|
+
// track last mouse position
|
|
945
|
+
this.lastMouseX = null;
|
|
946
|
+
this.lastMouseY = null;
|
|
947
|
+
this.mouseCheckInterval = null;
|
|
948
|
+
|
|
949
|
+
// Listener on container
|
|
950
|
+
this.api.container.addEventListener('mouseenter', () => {
|
|
951
|
+
if (this.api.player.options.debug) {
|
|
952
|
+
console.log('[YT Plugin] Mouse entered player container');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// show controls immediately
|
|
956
|
+
if (this.api.player.showControlsNow) {
|
|
957
|
+
this.api.player.showControlsNow();
|
|
958
|
+
}
|
|
959
|
+
if (this.api.player.resetAutoHideTimer) {
|
|
960
|
+
this.api.player.resetAutoHideTimer();
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// start monitoring
|
|
964
|
+
this.startMousePositionTracking();
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
this.api.container.addEventListener('mouseleave', () => {
|
|
968
|
+
if (this.api.player.options.debug) {
|
|
969
|
+
console.log('[YT Plugin] Mouse left player container');
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// stop monitoring
|
|
973
|
+
this.stopMousePositionTracking();
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
// capture mouse move on container
|
|
977
|
+
this.api.container.addEventListener('mousemove', (e) => {
|
|
978
|
+
this.lastMouseX = e.clientX;
|
|
979
|
+
this.lastMouseY = e.clientY;
|
|
980
|
+
|
|
981
|
+
if (this.api.player.onMouseMove) {
|
|
982
|
+
this.api.player.onMouseMove(e);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (this.api.player.resetAutoHideTimer) {
|
|
986
|
+
this.api.player.resetAutoHideTimer();
|
|
762
987
|
}
|
|
763
988
|
});
|
|
764
989
|
}
|
|
765
990
|
|
|
991
|
+
// check mouse position on iframe
|
|
992
|
+
startMousePositionTracking() {
|
|
993
|
+
if (this.mouseCheckInterval) return;
|
|
994
|
+
|
|
995
|
+
this.mouseCheckInterval = setInterval(() => {
|
|
996
|
+
// Listener to capture mouse position on iframe
|
|
997
|
+
const handleGlobalMove = (e) => {
|
|
998
|
+
const newX = e.clientX;
|
|
999
|
+
const newY = e.clientY;
|
|
1000
|
+
|
|
1001
|
+
// if mouse is moving
|
|
1002
|
+
if (this.lastMouseX !== newX || this.lastMouseY !== newY) {
|
|
1003
|
+
this.lastMouseX = newX;
|
|
1004
|
+
this.lastMouseY = newY;
|
|
1005
|
+
|
|
1006
|
+
// verify if mouse is enter the container
|
|
1007
|
+
const rect = this.api.container.getBoundingClientRect();
|
|
1008
|
+
const isInside = (
|
|
1009
|
+
newX >= rect.left &&
|
|
1010
|
+
newX <= rect.right &&
|
|
1011
|
+
newY >= rect.top &&
|
|
1012
|
+
newY <= rect.bottom
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
if (isInside) {
|
|
1016
|
+
if (this.api.player.showControlsNow) {
|
|
1017
|
+
this.api.player.showControlsNow();
|
|
1018
|
+
}
|
|
1019
|
+
if (this.api.player.resetAutoHideTimer) {
|
|
1020
|
+
this.api.player.resetAutoHideTimer();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
// Listener temp
|
|
1027
|
+
document.addEventListener('mousemove', handleGlobalMove, { once: true, passive: true });
|
|
1028
|
+
}, 100); // Check ogni 100ms
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
stopMousePositionTracking() {
|
|
1032
|
+
if (this.mouseCheckInterval) {
|
|
1033
|
+
clearInterval(this.mouseCheckInterval);
|
|
1034
|
+
this.mouseCheckInterval = null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
// enable or disable clicks over youtube player
|
|
1040
|
+
enableYouTubeClicks() {
|
|
1041
|
+
if (this.ytPlayerContainer) {
|
|
1042
|
+
this.ytPlayerContainer.style.pointerEvents = 'auto';
|
|
1043
|
+
|
|
1044
|
+
const iframe = this.ytPlayerContainer.querySelector('iframe');
|
|
1045
|
+
if (iframe) {
|
|
1046
|
+
iframe.style.pointerEvents = 'auto';
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (this.api.player.options.debug) {
|
|
1050
|
+
console.log('[YT Plugin] YouTube clicks enabled - overlay transparent');
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
766
1055
|
togglePlayPauseYT() {
|
|
767
1056
|
if (!this.ytPlayer) return;
|
|
768
1057
|
|
|
@@ -788,6 +1077,9 @@ width: fit-content;
|
|
|
788
1077
|
this.mouseMoveOverlay.remove();
|
|
789
1078
|
this.mouseMoveOverlay = null;
|
|
790
1079
|
}
|
|
1080
|
+
|
|
1081
|
+
// stop tracking mouse
|
|
1082
|
+
this.stopMousePositionTracking();
|
|
791
1083
|
}
|
|
792
1084
|
|
|
793
1085
|
hidePosterOverlay() {
|
|
@@ -842,11 +1134,47 @@ width: fit-content;
|
|
|
842
1134
|
document.head.appendChild(forceVisibilityCSS);
|
|
843
1135
|
if (this.api.player.options.debug) console.log('[YT Plugin] 🎨 CSS force visibility injected');
|
|
844
1136
|
|
|
1137
|
+
// Enable YouTube clicks if option is set
|
|
1138
|
+
if (this.options.mouseClick) {
|
|
1139
|
+
this.enableYouTubeClicks();
|
|
1140
|
+
}
|
|
845
1141
|
this.hideLoadingOverlay();
|
|
846
1142
|
this.hideInitialLoading();
|
|
847
1143
|
this.injectYouTubeCSSOverride();
|
|
848
1144
|
|
|
849
1145
|
this.syncControls();
|
|
1146
|
+
// Start buffer monitoring for auto quality adjustment
|
|
1147
|
+
this.startBufferMonitoring();
|
|
1148
|
+
|
|
1149
|
+
// Hide custom controls when YouTube native UI is enabled
|
|
1150
|
+
if (this.options.showYouTubeUI) {
|
|
1151
|
+
// Hide controls
|
|
1152
|
+
if (this.api.controls) {
|
|
1153
|
+
this.api.controls.style.display = 'none';
|
|
1154
|
+
this.api.controls.style.opacity = '0';
|
|
1155
|
+
this.api.controls.style.visibility = 'hidden';
|
|
1156
|
+
this.api.controls.style.pointerEvents = 'none';
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Hide overlay title
|
|
1160
|
+
const overlayTitle = this.api.container.querySelector('.title-overlay');
|
|
1161
|
+
if (overlayTitle) {
|
|
1162
|
+
overlayTitle.style.display = 'none';
|
|
1163
|
+
overlayTitle.style.opacity = '0';
|
|
1164
|
+
overlayTitle.style.visibility = 'hidden';
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Hide watermark
|
|
1168
|
+
const watermark = this.api.container.querySelector('.watermark');
|
|
1169
|
+
if (watermark) {
|
|
1170
|
+
watermark.style.display = 'none';
|
|
1171
|
+
watermark.style.opacity = '0';
|
|
1172
|
+
watermark.style.visibility = 'hidden';
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Force hide via CSS
|
|
1176
|
+
this.forceHideCustomControls();
|
|
1177
|
+
}
|
|
850
1178
|
|
|
851
1179
|
// Handle responsive layout for PiP and subtitles buttons
|
|
852
1180
|
this.handleResponsiveLayout();
|
|
@@ -858,6 +1186,8 @@ width: fit-content;
|
|
|
858
1186
|
// Check if this is a live stream
|
|
859
1187
|
setTimeout(() => this.checkIfLiveStream(), 2000);
|
|
860
1188
|
setTimeout(() => this.checkIfLiveStream(), 5000);
|
|
1189
|
+
// Set adaptive quality based on connection
|
|
1190
|
+
setTimeout(() => this.setAdaptiveQuality(), 1000);
|
|
861
1191
|
|
|
862
1192
|
|
|
863
1193
|
// Listen for window resize
|
|
@@ -895,14 +1225,41 @@ width: fit-content;
|
|
|
895
1225
|
|
|
896
1226
|
}
|
|
897
1227
|
|
|
1228
|
+
forceHideCustomControls() {
|
|
1229
|
+
const existingStyle = document.getElementById('yt-force-hide-controls');
|
|
1230
|
+
if (existingStyle) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const style = document.createElement('style');
|
|
1235
|
+
style.id = 'yt-force-hide-controls';
|
|
1236
|
+
style.textContent = `
|
|
1237
|
+
.video-wrapper.youtube-native-ui .controls,
|
|
1238
|
+
.video-wrapper.youtube-native-ui .title-overlay,
|
|
1239
|
+
.video-wrapper.youtube-native-ui .watermark {
|
|
1240
|
+
display: none !important;
|
|
1241
|
+
opacity: 0 !important;
|
|
1242
|
+
visibility: hidden !important;
|
|
1243
|
+
pointer-events: none !important;
|
|
1244
|
+
}
|
|
1245
|
+
`;
|
|
1246
|
+
document.head.appendChild(style);
|
|
1247
|
+
|
|
1248
|
+
this.api.container.classList.add('youtube-native-ui');
|
|
1249
|
+
|
|
1250
|
+
if (this.api.player.options.debug) {
|
|
1251
|
+
console.log('[YT Plugin] CSS injected - custom elements hidden (simple method)');
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
898
1255
|
checkIfLiveStream() {
|
|
899
1256
|
if (this.api.player.options.debug) {
|
|
900
|
-
console.log('[YT Plugin]
|
|
1257
|
+
console.log('[YT Plugin] Starting live stream check...');
|
|
901
1258
|
}
|
|
902
1259
|
|
|
903
1260
|
if (!this.ytPlayer) {
|
|
904
1261
|
if (this.api.player.options.debug) {
|
|
905
|
-
console.log('[YT Plugin]
|
|
1262
|
+
console.log('[YT Plugin] ytPlayer not available');
|
|
906
1263
|
}
|
|
907
1264
|
return false;
|
|
908
1265
|
}
|
|
@@ -911,14 +1268,15 @@ width: fit-content;
|
|
|
911
1268
|
// Method 1: Check video data for isLive property
|
|
912
1269
|
if (this.ytPlayer.getVideoData) {
|
|
913
1270
|
const videoData = this.ytPlayer.getVideoData();
|
|
1271
|
+
|
|
914
1272
|
if (this.api.player.options.debug) {
|
|
915
|
-
console.log('[YT Plugin]
|
|
1273
|
+
console.log('[YT Plugin] Video Data:', videoData);
|
|
916
1274
|
}
|
|
917
1275
|
|
|
918
1276
|
// Check if video data indicates it's live
|
|
919
1277
|
if (videoData.isLive || videoData.isLiveBroadcast) {
|
|
920
1278
|
if (this.api.player.options.debug) {
|
|
921
|
-
console.log('[YT Plugin]
|
|
1279
|
+
console.log('[YT Plugin] LIVE detected via videoData.isLive');
|
|
922
1280
|
}
|
|
923
1281
|
this.isLiveStream = true;
|
|
924
1282
|
this.handleLiveStreamUI();
|
|
@@ -926,19 +1284,18 @@ width: fit-content;
|
|
|
926
1284
|
}
|
|
927
1285
|
}
|
|
928
1286
|
|
|
929
|
-
// Method 2: Check duration
|
|
1287
|
+
// Method 2: Check duration - live streams have special duration values
|
|
930
1288
|
if (this.ytPlayer.getDuration) {
|
|
931
1289
|
const duration = this.ytPlayer.getDuration();
|
|
1290
|
+
|
|
932
1291
|
if (this.api.player.options.debug) {
|
|
933
|
-
console.log('[YT Plugin]
|
|
1292
|
+
console.log('[YT Plugin] Initial duration:', duration);
|
|
934
1293
|
}
|
|
935
1294
|
|
|
936
|
-
// For live streams, duration changes over time
|
|
937
|
-
// Wait 3 seconds and check again
|
|
938
1295
|
setTimeout(() => {
|
|
939
1296
|
if (!this.ytPlayer || !this.ytPlayer.getDuration) {
|
|
940
1297
|
if (this.api.player.options.debug) {
|
|
941
|
-
console.log('[YT Plugin]
|
|
1298
|
+
console.log('[YT Plugin] ytPlayer lost during duration check');
|
|
942
1299
|
}
|
|
943
1300
|
return;
|
|
944
1301
|
}
|
|
@@ -947,37 +1304,36 @@ width: fit-content;
|
|
|
947
1304
|
const difference = Math.abs(newDuration - duration);
|
|
948
1305
|
|
|
949
1306
|
if (this.api.player.options.debug) {
|
|
950
|
-
console.log('[YT Plugin]
|
|
951
|
-
console.log('[YT Plugin]
|
|
1307
|
+
console.log('[YT Plugin] Duration after 5s:', newDuration);
|
|
1308
|
+
console.log('[YT Plugin] Duration difference:', difference);
|
|
952
1309
|
}
|
|
953
1310
|
|
|
954
|
-
|
|
955
|
-
if (difference > 0.5) {
|
|
1311
|
+
if (difference > 10) {
|
|
956
1312
|
if (this.api.player.options.debug) {
|
|
957
|
-
console.log('[YT Plugin]
|
|
1313
|
+
console.log('[YT Plugin] LIVE STREAM DETECTED - duration changing significantly');
|
|
958
1314
|
}
|
|
959
1315
|
this.isLiveStream = true;
|
|
960
1316
|
this.handleLiveStreamUI();
|
|
961
1317
|
} else {
|
|
962
1318
|
if (this.api.player.options.debug) {
|
|
963
|
-
console.log('[YT Plugin]
|
|
1319
|
+
console.log('[YT Plugin] Regular video - duration stable');
|
|
964
1320
|
}
|
|
965
1321
|
this.isLiveStream = false;
|
|
966
1322
|
}
|
|
967
|
-
},
|
|
1323
|
+
}, 5000);
|
|
968
1324
|
}
|
|
969
1325
|
|
|
970
1326
|
// Method 3: Check player state
|
|
971
1327
|
if (this.ytPlayer.getPlayerState) {
|
|
972
1328
|
const state = this.ytPlayer.getPlayerState();
|
|
973
1329
|
if (this.api.player.options.debug) {
|
|
974
|
-
console.log('[YT Plugin]
|
|
1330
|
+
console.log('[YT Plugin] Player state:', state);
|
|
975
1331
|
}
|
|
976
1332
|
}
|
|
977
1333
|
|
|
978
1334
|
} catch (error) {
|
|
979
1335
|
if (this.api.player.options.debug) {
|
|
980
|
-
console.error('[YT Plugin]
|
|
1336
|
+
console.error('[YT Plugin] Error checking live stream:', error);
|
|
981
1337
|
}
|
|
982
1338
|
}
|
|
983
1339
|
|
|
@@ -1008,20 +1364,36 @@ width: fit-content;
|
|
|
1008
1364
|
|
|
1009
1365
|
this.startLiveMonitoring();
|
|
1010
1366
|
|
|
1367
|
+
// Force progress bar to 100% for live streams
|
|
1368
|
+
this.liveProgressInterval = setInterval(() => {
|
|
1369
|
+
if (this.isLiveStream && this.api.player.progressFilled) {
|
|
1370
|
+
this.api.player.progressFilled.style.width = '100%';
|
|
1371
|
+
|
|
1372
|
+
if (this.api.player.progressHandle) {
|
|
1373
|
+
this.api.player.progressHandle.style.left = '100%';
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}, 100); // Every 100ms to override any other updates
|
|
1377
|
+
|
|
1011
1378
|
if (this.api.player.options.debug) {
|
|
1012
1379
|
console.log('[YT Plugin] ✅ Live UI setup complete');
|
|
1013
1380
|
}
|
|
1014
1381
|
}
|
|
1015
1382
|
|
|
1016
1383
|
checkDVRAvailability() {
|
|
1017
|
-
// Disable progress bar immediately while testing
|
|
1018
1384
|
const progressContainer = this.api.container.querySelector('.progress-container');
|
|
1385
|
+
const progressFill = this.api.container.querySelector('.progress-fill');
|
|
1386
|
+
|
|
1019
1387
|
if (progressContainer) {
|
|
1020
1388
|
progressContainer.style.opacity = '0.3';
|
|
1021
1389
|
progressContainer.style.pointerEvents = 'none';
|
|
1022
1390
|
}
|
|
1023
1391
|
|
|
1024
|
-
//
|
|
1392
|
+
// Set darkgoldenrod during test
|
|
1393
|
+
if (progressFill) {
|
|
1394
|
+
progressFill.style.backgroundColor = 'darkgoldenrod';
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1025
1397
|
setTimeout(() => {
|
|
1026
1398
|
if (!this.ytPlayer) return;
|
|
1027
1399
|
|
|
@@ -1043,25 +1415,31 @@ width: fit-content;
|
|
|
1043
1415
|
const seekDifference = Math.abs(newCurrentTime - testSeekPosition);
|
|
1044
1416
|
|
|
1045
1417
|
const progressContainer = this.api.container.querySelector('.progress-container');
|
|
1418
|
+
const progressFill = this.api.container.querySelector('.progress-fill');
|
|
1046
1419
|
|
|
1047
1420
|
if (seekDifference < 2) {
|
|
1048
|
-
// DVR enabled - restore
|
|
1421
|
+
// DVR enabled - restore with theme color
|
|
1049
1422
|
if (progressContainer) {
|
|
1050
1423
|
progressContainer.style.opacity = '';
|
|
1051
1424
|
progressContainer.style.pointerEvents = '';
|
|
1052
1425
|
}
|
|
1053
1426
|
|
|
1427
|
+
// Remove inline style to use theme color
|
|
1428
|
+
if (progressFill) {
|
|
1429
|
+
progressFill.style.backgroundColor = ''; // Let theme CSS handle color
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1054
1432
|
if (this.api.player.options.debug) {
|
|
1055
|
-
console.log('[YT Plugin] ✅ DVR ENABLED - progress bar active');
|
|
1433
|
+
console.log('[YT Plugin] ✅ DVR ENABLED - progress bar active with theme color');
|
|
1056
1434
|
}
|
|
1057
1435
|
|
|
1058
1436
|
this.ytPlayer.seekTo(duration, true);
|
|
1059
1437
|
} else {
|
|
1060
|
-
// No DVR - keep
|
|
1438
|
+
// No DVR - keep darkgoldenrod
|
|
1061
1439
|
this.modifyProgressBarForLive();
|
|
1062
1440
|
|
|
1063
1441
|
if (this.api.player.options.debug) {
|
|
1064
|
-
console.log('[YT Plugin] ❌ DVR DISABLED - progress bar locked');
|
|
1442
|
+
console.log('[YT Plugin] ❌ DVR DISABLED - progress bar locked with darkgoldenrod');
|
|
1065
1443
|
}
|
|
1066
1444
|
}
|
|
1067
1445
|
}, 500);
|
|
@@ -1209,6 +1587,7 @@ width: fit-content;
|
|
|
1209
1587
|
modifyProgressBarForLive() {
|
|
1210
1588
|
const progressContainer = this.api.container.querySelector('.progress-container');
|
|
1211
1589
|
const progressHandle = this.api.container.querySelector('.progress-handle');
|
|
1590
|
+
const progressFill = this.api.container.querySelector('.progress-fill');
|
|
1212
1591
|
|
|
1213
1592
|
if (progressContainer) {
|
|
1214
1593
|
// Disable all pointer events on progress bar
|
|
@@ -1224,11 +1603,21 @@ width: fit-content;
|
|
|
1224
1603
|
if (progressHandle) {
|
|
1225
1604
|
progressHandle.style.display = 'none';
|
|
1226
1605
|
}
|
|
1606
|
+
|
|
1607
|
+
// Change color to darkgoldenrod when disabled
|
|
1608
|
+
if (progressFill) {
|
|
1609
|
+
progressFill.style.backgroundColor = 'darkgoldenrod';
|
|
1610
|
+
|
|
1611
|
+
if (this.api.player.options.debug) {
|
|
1612
|
+
console.log('[YT Plugin] Progress fill color changed to darkgoldenrod');
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1227
1615
|
}
|
|
1228
1616
|
|
|
1229
1617
|
restoreProgressBarNormal() {
|
|
1230
1618
|
const progressContainer = this.api.container.querySelector('.progress-container');
|
|
1231
1619
|
const progressHandle = this.api.container.querySelector('.progress-handle');
|
|
1620
|
+
const progressFill = this.api.container.querySelector('.progress-fill');
|
|
1232
1621
|
|
|
1233
1622
|
if (progressContainer) {
|
|
1234
1623
|
progressContainer.style.pointerEvents = '';
|
|
@@ -1239,6 +1628,15 @@ width: fit-content;
|
|
|
1239
1628
|
if (progressHandle) {
|
|
1240
1629
|
progressHandle.style.display = '';
|
|
1241
1630
|
}
|
|
1631
|
+
|
|
1632
|
+
// Remove inline backgroundColor to let CSS theme take over
|
|
1633
|
+
if (progressFill) {
|
|
1634
|
+
progressFill.style.backgroundColor = ''; // Reset to theme color
|
|
1635
|
+
|
|
1636
|
+
if (this.api.player.options.debug) {
|
|
1637
|
+
console.log('[YT Plugin] Progress fill color restored to theme default');
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1242
1640
|
}
|
|
1243
1641
|
|
|
1244
1642
|
startLiveMonitoring() {
|
|
@@ -1270,7 +1668,7 @@ width: fit-content;
|
|
|
1270
1668
|
} else if (playerState === YT.PlayerState.PLAYING) {
|
|
1271
1669
|
// Only update color if playing
|
|
1272
1670
|
// Check latency only if duration is reasonable
|
|
1273
|
-
if (latency > 60
|
|
1671
|
+
if (latency > 60) {
|
|
1274
1672
|
// DE-SYNCED - Black background
|
|
1275
1673
|
badge.style.background = '#1a1a1a';
|
|
1276
1674
|
badge.textContent = 'LIVE';
|
|
@@ -1348,9 +1746,9 @@ width: fit-content;
|
|
|
1348
1746
|
// Show time display again
|
|
1349
1747
|
this.showTimeDisplay();
|
|
1350
1748
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
this.
|
|
1749
|
+
if (this.liveProgressInterval) {
|
|
1750
|
+
clearInterval(this.liveProgressInterval);
|
|
1751
|
+
this.liveProgressInterval = null;
|
|
1354
1752
|
}
|
|
1355
1753
|
|
|
1356
1754
|
if (this.api.player.options.debug) {
|
|
@@ -1386,12 +1784,11 @@ width: fit-content;
|
|
|
1386
1784
|
overflow: hidden !important;
|
|
1387
1785
|
}
|
|
1388
1786
|
`;
|
|
1389
|
-
|
|
1390
1787
|
document.head.appendChild(style);
|
|
1391
1788
|
this.api.container.classList.add('youtube-active');
|
|
1392
1789
|
|
|
1393
1790
|
if (this.api.player.options.debug) {
|
|
1394
|
-
console.log('YT Plugin
|
|
1791
|
+
console.log('[YT Plugin] CSS override injected (ToS compliant)');
|
|
1395
1792
|
}
|
|
1396
1793
|
}
|
|
1397
1794
|
|
|
@@ -1440,7 +1837,7 @@ width: fit-content;
|
|
|
1440
1837
|
const actualQuality = this.ytPlayer.getPlaybackQuality();
|
|
1441
1838
|
|
|
1442
1839
|
// Only update UI if in AUTO mode, otherwise respect manual selection
|
|
1443
|
-
if (this.
|
|
1840
|
+
if (this.userQualityChoice === 'default' || this.userQualityChoice === 'auto') {
|
|
1444
1841
|
if (actualQuality !== this.currentPlayingQuality) {
|
|
1445
1842
|
this.currentPlayingQuality = actualQuality;
|
|
1446
1843
|
if (this.api.player.options.debug) {
|
|
@@ -1620,27 +2017,59 @@ width: fit-content;
|
|
|
1620
2017
|
if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
|
|
1621
2018
|
|
|
1622
2019
|
try {
|
|
1623
|
-
//
|
|
1624
|
-
this.
|
|
2020
|
+
// Track user's quality choice for display
|
|
2021
|
+
this.userQualityChoice = quality;
|
|
2022
|
+
if (this.api.player.options.debug) {
|
|
2023
|
+
console.log('[YT Plugin] Setting quality to:', quality);
|
|
2024
|
+
console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
|
|
2025
|
+
console.log('[YT Plugin] Available qualities:', this.ytPlayer.getAvailableQualityLevels());
|
|
2026
|
+
}
|
|
1625
2027
|
|
|
1626
|
-
//
|
|
1627
|
-
|
|
1628
|
-
|
|
2028
|
+
// Check if requested quality is actually available
|
|
2029
|
+
const availableLevels = this.ytPlayer.getAvailableQualityLevels();
|
|
2030
|
+
if (quality !== 'default' && quality !== 'auto' && !availableLevels.includes(quality)) {
|
|
2031
|
+
if (this.api.player.options.debug) {
|
|
2032
|
+
console.warn('[YT Plugin] Requested quality not available:', quality);
|
|
2033
|
+
}
|
|
1629
2034
|
}
|
|
1630
2035
|
|
|
1631
2036
|
// Update state
|
|
1632
2037
|
this.currentQuality = quality;
|
|
1633
|
-
|
|
2038
|
+
|
|
2039
|
+
// Set the quality
|
|
2040
|
+
this.ytPlayer.setPlaybackQuality(quality);
|
|
2041
|
+
|
|
2042
|
+
// Also try setPlaybackQualityRange for better enforcement
|
|
2043
|
+
if (this.ytPlayer.setPlaybackQualityRange) {
|
|
2044
|
+
this.ytPlayer.setPlaybackQualityRange(quality, quality);
|
|
2045
|
+
}
|
|
1634
2046
|
|
|
1635
2047
|
// Force UI update immediately
|
|
1636
2048
|
this.updateQualityMenuPlayingState(quality);
|
|
1637
|
-
|
|
1638
|
-
// Update button display
|
|
1639
2049
|
const qualityLabel = this.getQualityLabel(quality);
|
|
1640
|
-
|
|
2050
|
+
|
|
2051
|
+
// For manual quality selection, show only the selected quality
|
|
2052
|
+
if (quality !== 'default' && quality !== 'auto') {
|
|
2053
|
+
this.updateQualityButtonDisplay(qualityLabel, '');
|
|
2054
|
+
} else {
|
|
2055
|
+
// For auto mode, show "Auto" and let monitoring update the actual quality
|
|
2056
|
+
this.updateQualityButtonDisplay('Auto', '');
|
|
2057
|
+
}
|
|
1641
2058
|
|
|
1642
2059
|
if (this.api.player.options.debug) {
|
|
1643
|
-
|
|
2060
|
+
// Check actual quality after a moment
|
|
2061
|
+
setTimeout(() => {
|
|
2062
|
+
if (this.ytPlayer && this.ytPlayer.getPlaybackQuality) {
|
|
2063
|
+
const actualQuality = this.ytPlayer.getPlaybackQuality();
|
|
2064
|
+
console.log('[YT Plugin] Actual quality after 1s:', actualQuality);
|
|
2065
|
+
if (actualQuality !== quality && quality !== 'default' && quality !== 'auto') {
|
|
2066
|
+
console.warn('[YT Plugin] YouTube did not apply requested quality. This may mean:');
|
|
2067
|
+
console.warn(' - The quality is not available for this video');
|
|
2068
|
+
console.warn(' - Embedding restrictions apply');
|
|
2069
|
+
console.warn(' - Network/bandwidth limitations');
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
}, 1000); // Check every 1 second for faster updates
|
|
1644
2073
|
}
|
|
1645
2074
|
|
|
1646
2075
|
this.api.triggerEvent('youtubeplugin:qualitychanged', { quality });
|
|
@@ -1653,7 +2082,7 @@ width: fit-content;
|
|
|
1653
2082
|
}
|
|
1654
2083
|
}
|
|
1655
2084
|
|
|
1656
|
-
// =====
|
|
2085
|
+
// ===== SUBTITLE METHODS =====
|
|
1657
2086
|
|
|
1658
2087
|
/**
|
|
1659
2088
|
* Load available captions and create subtitle control
|
|
@@ -2311,31 +2740,6 @@ width: fit-content;
|
|
|
2311
2740
|
}
|
|
2312
2741
|
}
|
|
2313
2742
|
|
|
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
2743
|
switch (event.data) {
|
|
2340
2744
|
case YT.PlayerState.PLAYING:
|
|
2341
2745
|
this.api.triggerEvent('played', {});
|
|
@@ -2386,18 +2790,39 @@ width: fit-content;
|
|
|
2386
2790
|
}
|
|
2387
2791
|
|
|
2388
2792
|
onPlayerError(event) {
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2793
|
+
const errorCode = event.data;
|
|
2794
|
+
|
|
2795
|
+
if (this.api.player.options.debug) {
|
|
2796
|
+
console.error('[YT Plugin] Player error:', errorCode);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// Error codes che indicano video non disponibile
|
|
2800
|
+
const unavailableErrors = [
|
|
2801
|
+
2, // Invalid video ID
|
|
2802
|
+
5, // HTML5 player error
|
|
2803
|
+
100, // Video not found / removed
|
|
2804
|
+
101, // Video not allowed to be played in embedded players (private/restricted)
|
|
2805
|
+
150 // Same as 101
|
|
2806
|
+
];
|
|
2807
|
+
|
|
2808
|
+
if (unavailableErrors.includes(errorCode)) {
|
|
2809
|
+
if (this.api.player.options.debug) {
|
|
2810
|
+
console.log('[YT Plugin] Video unavailable, triggering ended event');
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
// Trigger the ended event from your player API
|
|
2814
|
+
this.api.triggerEvent('ended', {
|
|
2815
|
+
reason: 'video_unavailable',
|
|
2816
|
+
errorCode: errorCode
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
// Optional: show poster overlay again
|
|
2820
|
+
this.showPosterOverlay();
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2398
2823
|
this.api.triggerEvent('youtubeplugin:error', {
|
|
2399
|
-
errorCode:
|
|
2400
|
-
|
|
2824
|
+
errorCode: errorCode,
|
|
2825
|
+
videoId: this.videoId
|
|
2401
2826
|
});
|
|
2402
2827
|
}
|
|
2403
2828
|
|
|
@@ -2700,7 +3125,24 @@ width: fit-content;
|
|
|
2700
3125
|
const duration = this.ytPlayer.getDuration();
|
|
2701
3126
|
|
|
2702
3127
|
if (this.api.player.progressFilled && duration) {
|
|
2703
|
-
|
|
3128
|
+
let progress;
|
|
3129
|
+
|
|
3130
|
+
// For live streams, always show progress at 100%
|
|
3131
|
+
if (this.isLiveStream) {
|
|
3132
|
+
progress = 100;
|
|
3133
|
+
} else {
|
|
3134
|
+
// For regular videos, calculate normally
|
|
3135
|
+
progress = (currentTime / duration) * 100;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// Check if live badge exists = it's a live stream
|
|
3139
|
+
const liveBadge = this.api.container.querySelector('.live-badge');
|
|
3140
|
+
|
|
3141
|
+
if (liveBadge) {
|
|
3142
|
+
// Force 100% for live streams
|
|
3143
|
+
progress = 100;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
2704
3146
|
this.api.player.progressFilled.style.width = `${progress}%`;
|
|
2705
3147
|
if (this.api.player.progressHandle) {
|
|
2706
3148
|
this.api.player.progressHandle.style.left = `${progress}%`;
|
|
@@ -2869,6 +3311,16 @@ width: fit-content;
|
|
|
2869
3311
|
this.ytPlayerContainer = null;
|
|
2870
3312
|
}
|
|
2871
3313
|
|
|
3314
|
+
if (this.bufferMonitorInterval) {
|
|
3315
|
+
clearInterval(this.bufferMonitorInterval);
|
|
3316
|
+
this.bufferMonitorInterval = null;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
if (this.qualityMonitorInterval) {
|
|
3320
|
+
clearInterval(this.qualityMonitorInterval);
|
|
3321
|
+
this.qualityMonitorInterval = null;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
2872
3324
|
this.removeMouseMoveOverlay();
|
|
2873
3325
|
|
|
2874
3326
|
this.api.container.classList.remove('youtube-active');
|
|
@@ -2922,6 +3374,10 @@ width: fit-content;
|
|
|
2922
3374
|
this.isLiveStream = false;
|
|
2923
3375
|
this.isAtLiveEdge = false;
|
|
2924
3376
|
|
|
3377
|
+
if (this.liveProgressInterval) {
|
|
3378
|
+
clearInterval(this.liveProgressInterval);
|
|
3379
|
+
this.liveProgressInterval = null;
|
|
3380
|
+
}
|
|
2925
3381
|
}
|
|
2926
3382
|
}
|
|
2927
3383
|
|