myetv-player 1.1.0 → 1.1.2
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 +69 -0
- package/css/myetv-player.css +726 -11937
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +254 -111
- package/dist/myetv-player.min.js +249 -106
- package/package.json +3 -1
- package/plugins/youtube/myetv-player-youtube-plugin.js +237 -19
- package/scss/_controls.scss +120 -317
- package/scss/_menus.scss +117 -4023
- package/scss/_progress-bar.scss +168 -2052
- package/scss/_title-overlay.scss +10 -2239
- package/scss/_video.scss +62 -2167
- package/scss/_volume.scss +25 -1846
- package/src/controls.js +7 -7
- package/src/core.js +121 -69
- package/src/events.js +123 -25
- package/src/subtitles.js +3 -10
|
@@ -14,7 +14,8 @@
|
|
|
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 || 'default',
|
|
18
|
+
|
|
18
19
|
enableQualityControl: options.enableQualityControl !== undefined ? options.enableQualityControl : true,
|
|
19
20
|
enableCaptions: options.enableCaptions !== undefined ? options.enableCaptions : true,
|
|
20
21
|
|
|
@@ -31,6 +32,13 @@
|
|
|
31
32
|
...options
|
|
32
33
|
};
|
|
33
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
|
+
|
|
34
42
|
this.ytPlayer = null;
|
|
35
43
|
this.isYouTubeReady = false;
|
|
36
44
|
this.videoId = this.options.videoId;
|
|
@@ -46,6 +54,7 @@
|
|
|
46
54
|
this.mouseMoveOverlay = null;
|
|
47
55
|
this.qualityCheckAttempts = 0;
|
|
48
56
|
this.captionCheckAttempts = 0;
|
|
57
|
+
this.liveStreamChecked = false;
|
|
49
58
|
this.captionStateCheckInterval = null;
|
|
50
59
|
this.qualityMonitorInterval = null;
|
|
51
60
|
this.resizeListenerAdded = false;
|
|
@@ -69,6 +78,7 @@
|
|
|
69
78
|
{ code: 'ar', name: 'Arabic' },
|
|
70
79
|
{ code: 'pt', name: 'Portuguese' },
|
|
71
80
|
{ code: 'ru', name: 'Russian' },
|
|
81
|
+
{ code: 'zh', name: 'Chinese' },
|
|
72
82
|
{ code: 'ja', name: 'Japanese' },
|
|
73
83
|
{ code: 'de', name: 'German' },
|
|
74
84
|
{ code: 'fr', name: 'French' },
|
|
@@ -716,6 +726,131 @@ width: fit-content;
|
|
|
716
726
|
this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
|
|
717
727
|
}
|
|
718
728
|
|
|
729
|
+
setAdaptiveQuality() {
|
|
730
|
+
if (!this.ytPlayer) return;
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
// Check network connection speed if available
|
|
734
|
+
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
735
|
+
let suggestedQuality = 'default';
|
|
736
|
+
|
|
737
|
+
if (connection) {
|
|
738
|
+
const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
|
|
739
|
+
const downlink = connection.downlink; // Mbps
|
|
740
|
+
|
|
741
|
+
if (this.api.player.options.debug) {
|
|
742
|
+
console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Set quality based on connection speed
|
|
746
|
+
if (effectiveType === 'slow-2g' || downlink < 0.5) {
|
|
747
|
+
suggestedQuality = 'small'; // 240p
|
|
748
|
+
} else if (effectiveType === '2g' || downlink < 1) {
|
|
749
|
+
suggestedQuality = 'medium'; // 360p
|
|
750
|
+
} else if (effectiveType === '3g' || downlink < 2.5) {
|
|
751
|
+
suggestedQuality = 'large'; // 480p
|
|
752
|
+
} else if (downlink < 5) {
|
|
753
|
+
suggestedQuality = 'hd720'; // 720p
|
|
754
|
+
} else if (downlink < 10) {
|
|
755
|
+
suggestedQuality = 'hd1080'; // 1080p
|
|
756
|
+
} else if (downlink < 20) {
|
|
757
|
+
suggestedQuality = 'hd1440'; // 1440p (2K)
|
|
758
|
+
} else if (downlink < 35) {
|
|
759
|
+
suggestedQuality = 'hd2160'; // 2160p (4K)
|
|
760
|
+
} else {
|
|
761
|
+
suggestedQuality = 'highres'; // 8K o migliore disponibile
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (this.api.player.options.debug) {
|
|
765
|
+
console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
this.ytPlayer.setPlaybackQuality(suggestedQuality);
|
|
769
|
+
} else {
|
|
770
|
+
// Fallback: start with medium quality on unknown devices
|
|
771
|
+
if (this.api.player.options.debug) {
|
|
772
|
+
console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
|
|
773
|
+
}
|
|
774
|
+
this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
|
|
775
|
+
}
|
|
776
|
+
} catch (error) {
|
|
777
|
+
if (this.api.player.options.debug) {
|
|
778
|
+
console.error('[YT Plugin] Error setting adaptive quality:', error);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
startBufferMonitoring() {
|
|
784
|
+
if (this.bufferMonitorInterval) {
|
|
785
|
+
clearInterval(this.bufferMonitorInterval);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
let bufferingCount = 0;
|
|
789
|
+
let lastState = null;
|
|
790
|
+
|
|
791
|
+
this.bufferMonitorInterval = setInterval(() => {
|
|
792
|
+
if (!this.ytPlayer) return;
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
const state = this.ytPlayer.getPlayerState();
|
|
796
|
+
|
|
797
|
+
// Detect buffering (state 3)
|
|
798
|
+
if (state === YT.PlayerState.BUFFERING) {
|
|
799
|
+
bufferingCount++;
|
|
800
|
+
|
|
801
|
+
if (this.api.player.options.debug) {
|
|
802
|
+
console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// If buffering happens too often, reduce quality
|
|
806
|
+
if (bufferingCount >= 3) {
|
|
807
|
+
const currentQuality = this.ytPlayer.getPlaybackQuality();
|
|
808
|
+
const availableQualities = this.ytPlayer.getAvailableQualityLevels();
|
|
809
|
+
|
|
810
|
+
if (this.api.player.options.debug) {
|
|
811
|
+
console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
|
|
812
|
+
console.log('[YT Plugin] Available qualities:', availableQualities);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Quality hierarchy (highest to lowest)
|
|
816
|
+
const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
|
|
817
|
+
const currentIndex = qualityLevels.indexOf(currentQuality);
|
|
818
|
+
|
|
819
|
+
// Try to go to next lower quality
|
|
820
|
+
if (currentIndex < qualityLevels.length - 1) {
|
|
821
|
+
const lowerQuality = qualityLevels[currentIndex + 1];
|
|
822
|
+
|
|
823
|
+
// Check if lower quality is available
|
|
824
|
+
if (availableQualities.includes(lowerQuality)) {
|
|
825
|
+
if (this.api.player.options.debug) {
|
|
826
|
+
console.log('[YT Plugin] Reducing quality to:', lowerQuality);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
this.ytPlayer.setPlaybackQuality(lowerQuality);
|
|
830
|
+
bufferingCount = 0; // Reset counter
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
} else if (state === YT.PlayerState.PLAYING) {
|
|
835
|
+
// Reset buffering count when playing smoothly
|
|
836
|
+
if (lastState === YT.PlayerState.BUFFERING) {
|
|
837
|
+
setTimeout(() => {
|
|
838
|
+
if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
|
|
839
|
+
bufferingCount = Math.max(0, bufferingCount - 1);
|
|
840
|
+
}
|
|
841
|
+
}, 5000); // Wait 5 seconds of smooth playback
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
lastState = state;
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (this.api.player.options.debug) {
|
|
848
|
+
console.error('[YT Plugin] Error in buffer monitoring:', error);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}, 1000); // Check every second
|
|
852
|
+
}
|
|
853
|
+
|
|
719
854
|
createMouseMoveOverlay() {
|
|
720
855
|
if (this.mouseMoveOverlay) return;
|
|
721
856
|
|
|
@@ -1010,6 +1145,8 @@ width: fit-content;
|
|
|
1010
1145
|
this.injectYouTubeCSSOverride();
|
|
1011
1146
|
|
|
1012
1147
|
this.syncControls();
|
|
1148
|
+
// Start buffer monitoring for auto quality adjustment
|
|
1149
|
+
this.startBufferMonitoring();
|
|
1013
1150
|
|
|
1014
1151
|
// Hide custom controls when YouTube native UI is enabled
|
|
1015
1152
|
if (this.options.showYouTubeUI) {
|
|
@@ -1044,13 +1181,16 @@ width: fit-content;
|
|
|
1044
1181
|
// Handle responsive layout for PiP and subtitles buttons
|
|
1045
1182
|
this.handleResponsiveLayout();
|
|
1046
1183
|
|
|
1184
|
+
this.playAttemptTimeout = null;
|
|
1185
|
+
|
|
1047
1186
|
// Hide PiP from settings menu (separate function, called after responsive layout)
|
|
1048
1187
|
setTimeout(() => this.hidePipFromSettingsMenuOnly(), 500);
|
|
1049
|
-
|
|
1050
|
-
setTimeout(() => this.hidePipFromSettingsMenuOnly(), 3000);
|
|
1188
|
+
|
|
1051
1189
|
// Check if this is a live stream
|
|
1052
1190
|
setTimeout(() => this.checkIfLiveStream(), 2000);
|
|
1053
1191
|
setTimeout(() => this.checkIfLiveStream(), 5000);
|
|
1192
|
+
// Set adaptive quality based on connection
|
|
1193
|
+
setTimeout(() => this.setAdaptiveQuality(), 1000);
|
|
1054
1194
|
|
|
1055
1195
|
|
|
1056
1196
|
// Listen for window resize
|
|
@@ -1062,13 +1202,9 @@ width: fit-content;
|
|
|
1062
1202
|
|
|
1063
1203
|
// Load qualities with multiple attempts
|
|
1064
1204
|
setTimeout(() => this.loadAvailableQualities(), 500);
|
|
1065
|
-
setTimeout(() => this.loadAvailableQualities(), 1000);
|
|
1066
|
-
setTimeout(() => this.loadAvailableQualities(), 2000);
|
|
1067
1205
|
|
|
1068
1206
|
// Load captions with multiple attempts
|
|
1069
1207
|
setTimeout(() => this.loadAvailableCaptions(), 500);
|
|
1070
|
-
setTimeout(() => this.loadAvailableCaptions(), 1000);
|
|
1071
|
-
setTimeout(() => this.loadAvailableCaptions(), 2000);
|
|
1072
1208
|
|
|
1073
1209
|
if (this.options.quality && this.options.quality !== 'default') {
|
|
1074
1210
|
setTimeout(() => this.setQuality(this.options.quality), 1000);
|
|
@@ -1700,7 +1836,7 @@ width: fit-content;
|
|
|
1700
1836
|
const actualQuality = this.ytPlayer.getPlaybackQuality();
|
|
1701
1837
|
|
|
1702
1838
|
// Only update UI if in AUTO mode, otherwise respect manual selection
|
|
1703
|
-
if (this.
|
|
1839
|
+
if (this.userQualityChoice === 'default' || this.userQualityChoice === 'auto') {
|
|
1704
1840
|
if (actualQuality !== this.currentPlayingQuality) {
|
|
1705
1841
|
this.currentPlayingQuality = actualQuality;
|
|
1706
1842
|
if (this.api.player.options.debug) {
|
|
@@ -1880,6 +2016,8 @@ width: fit-content;
|
|
|
1880
2016
|
if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
|
|
1881
2017
|
|
|
1882
2018
|
try {
|
|
2019
|
+
// Track user's quality choice for display
|
|
2020
|
+
this.userQualityChoice = quality;
|
|
1883
2021
|
if (this.api.player.options.debug) {
|
|
1884
2022
|
console.log('[YT Plugin] Setting quality to:', quality);
|
|
1885
2023
|
console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
|
|
@@ -2562,6 +2700,49 @@ width: fit-content;
|
|
|
2562
2700
|
};
|
|
2563
2701
|
if (this.api.player.options.debug) console.log('[YT Plugin] State:', states[event.data], event.data);
|
|
2564
2702
|
|
|
2703
|
+
// Know if video have some problems to start
|
|
2704
|
+
if (event.data === YT.PlayerState.UNSTARTED || event.data === -1) {
|
|
2705
|
+
// start timeout when video is unstarted
|
|
2706
|
+
if (this.playAttemptTimeout) {
|
|
2707
|
+
clearTimeout(this.playAttemptTimeout);
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
this.playAttemptTimeout = setTimeout(() => {
|
|
2711
|
+
if (!this.ytPlayer) return;
|
|
2712
|
+
|
|
2713
|
+
const currentState = this.ytPlayer.getPlayerState();
|
|
2714
|
+
|
|
2715
|
+
// If video is unstrated after timeout, consider it restricted
|
|
2716
|
+
if (currentState === YT.PlayerState.UNSTARTED || currentState === -1) {
|
|
2717
|
+
if (this.api.player.options.debug) {
|
|
2718
|
+
console.log('YT Plugin: Video stuck in UNSTARTED - possibly members-only or restricted');
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// Trigger ended event
|
|
2722
|
+
this.api.triggerEvent('ended', {
|
|
2723
|
+
reason: 'video_restricted_or_membership',
|
|
2724
|
+
state: currentState
|
|
2725
|
+
});
|
|
2726
|
+
|
|
2727
|
+
// Show poster overlay if present
|
|
2728
|
+
this.showPosterOverlay();
|
|
2729
|
+
|
|
2730
|
+
// Trigger custom event
|
|
2731
|
+
this.api.triggerEvent('youtubeplugin:membershiprestricted', {
|
|
2732
|
+
videoId: this.videoId
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
}, 15000); // 15 seconds of timeout
|
|
2736
|
+
|
|
2737
|
+
} else if (event.data === YT.PlayerState.PLAYING ||
|
|
2738
|
+
event.data === YT.PlayerState.BUFFERING) {
|
|
2739
|
+
// Clear the timeout if video starts correctly
|
|
2740
|
+
if (this.playAttemptTimeout) {
|
|
2741
|
+
clearTimeout(this.playAttemptTimeout);
|
|
2742
|
+
this.playAttemptTimeout = null;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2565
2746
|
// Get play/pause icons
|
|
2566
2747
|
const playIcon = this.api.container.querySelector('.play-icon');
|
|
2567
2748
|
const pauseIcon = this.api.container.querySelector('.pause-icon');
|
|
@@ -2651,18 +2832,39 @@ width: fit-content;
|
|
|
2651
2832
|
}
|
|
2652
2833
|
|
|
2653
2834
|
onPlayerError(event) {
|
|
2654
|
-
const
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2835
|
+
const errorCode = event.data;
|
|
2836
|
+
|
|
2837
|
+
if (this.api.player.options.debug) {
|
|
2838
|
+
console.error('[YT Plugin] Player error:', errorCode);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// Error codes che indicano video non disponibile
|
|
2842
|
+
const unavailableErrors = [
|
|
2843
|
+
2, // Invalid video ID
|
|
2844
|
+
5, // HTML5 player error
|
|
2845
|
+
100, // Video not found / removed
|
|
2846
|
+
101, // Video not allowed to be played in embedded players (private/restricted)
|
|
2847
|
+
150 // Same as 101
|
|
2848
|
+
];
|
|
2849
|
+
|
|
2850
|
+
if (unavailableErrors.includes(errorCode)) {
|
|
2851
|
+
if (this.api.player.options.debug) {
|
|
2852
|
+
console.log('[YT Plugin] Video unavailable, triggering ended event');
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// Trigger the ended event from your player API
|
|
2856
|
+
this.api.triggerEvent('ended', {
|
|
2857
|
+
reason: 'video_unavailable',
|
|
2858
|
+
errorCode: errorCode
|
|
2859
|
+
});
|
|
2860
|
+
|
|
2861
|
+
// Optional: show poster overlay again
|
|
2862
|
+
this.showPosterOverlay();
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2663
2865
|
this.api.triggerEvent('youtubeplugin:error', {
|
|
2664
|
-
errorCode:
|
|
2665
|
-
|
|
2866
|
+
errorCode: errorCode,
|
|
2867
|
+
videoId: this.videoId
|
|
2666
2868
|
});
|
|
2667
2869
|
}
|
|
2668
2870
|
|
|
@@ -3126,6 +3328,12 @@ width: fit-content;
|
|
|
3126
3328
|
dispose() {
|
|
3127
3329
|
if (this.api.player.options.debug) console.log('[YT Plugin] Disposing');
|
|
3128
3330
|
|
|
3331
|
+
// Cleanup timeout
|
|
3332
|
+
if (this.playAttemptTimeout) {
|
|
3333
|
+
clearTimeout(this.playAttemptTimeout);
|
|
3334
|
+
this.playAttemptTimeout = null;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3129
3337
|
if (this.timeUpdateInterval) {
|
|
3130
3338
|
clearInterval(this.timeUpdateInterval);
|
|
3131
3339
|
this.timeUpdateInterval = null;
|
|
@@ -3151,6 +3359,16 @@ width: fit-content;
|
|
|
3151
3359
|
this.ytPlayerContainer = null;
|
|
3152
3360
|
}
|
|
3153
3361
|
|
|
3362
|
+
if (this.bufferMonitorInterval) {
|
|
3363
|
+
clearInterval(this.bufferMonitorInterval);
|
|
3364
|
+
this.bufferMonitorInterval = null;
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
if (this.qualityMonitorInterval) {
|
|
3368
|
+
clearInterval(this.qualityMonitorInterval);
|
|
3369
|
+
this.qualityMonitorInterval = null;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3154
3372
|
this.removeMouseMoveOverlay();
|
|
3155
3373
|
|
|
3156
3374
|
this.api.container.classList.remove('youtube-active');
|