myetv-player 1.0.0 → 1.0.8
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/.github/workflows/codeql.yml +100 -0
- package/README.md +49 -58
- package/SECURITY.md +50 -0
- package/css/myetv-player.css +424 -219
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +1759 -1502
- package/dist/myetv-player.min.js +1705 -1469
- package/package.json +7 -1
- package/plugins/README.md +1016 -0
- package/plugins/cloudflare/README.md +1068 -0
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
- package/plugins/facebook/README.md +1024 -0
- package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
- package/plugins/gamepad-remote-controller/README.md +816 -0
- package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
- package/plugins/google-adsense-ads/README.md +1 -0
- package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
- package/plugins/google-ima-ads/README.md +1 -0
- package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
- package/plugins/twitch/README.md +1185 -0
- package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
- package/plugins/vast-vpaid-ads/README.md +1 -0
- package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
- package/plugins/vimeo/README.md +1416 -0
- package/plugins/vimeo/myetv-player-vimeo.js +640 -0
- package/plugins/youtube/README.md +851 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
- package/scss/README.md +160 -0
- package/scss/_controls.scss +184 -30
- package/scss/_menus.scss +840 -672
- package/scss/_responsive.scss +67 -105
- package/scss/_volume.scss +67 -105
- package/src/README.md +559 -0
- package/src/controls.js +17 -5
- package/src/core.js +1237 -1060
- package/src/i18n.js +27 -1
- package/src/quality.js +478 -436
- package/src/subtitles.js +2 -2
package/src/core.js
CHANGED
|
@@ -2,433 +2,436 @@
|
|
|
2
2
|
// Conservative modularization - original code preserved exactly
|
|
3
3
|
// Created by https://www.myetv.tv https://oskarcosimo.com
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
constructor(videoElement, options = {}) {
|
|
6
|
+
this.video = typeof videoElement === 'string'
|
|
7
|
+
? document.getElementById(videoElement)
|
|
8
|
+
: videoElement;
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
if (!this.video) {
|
|
11
|
+
throw new Error('Video element not found: ' + videoElement);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.options = {
|
|
15
|
+
showQualitySelector: true,
|
|
16
|
+
showSpeedControl: true,
|
|
17
|
+
showFullscreen: true,
|
|
18
|
+
showPictureInPicture: true,
|
|
19
|
+
showSubtitles: true,
|
|
20
|
+
subtitlesEnabled: false,
|
|
21
|
+
autoHide: true,
|
|
22
|
+
autoHideDelay: 3000,
|
|
23
|
+
poster: null, // URL of poster image
|
|
24
|
+
showPosterOnEnd: false, // Show poster again when video ends
|
|
25
|
+
keyboardControls: true,
|
|
26
|
+
showSeekTooltip: true,
|
|
27
|
+
showTitleOverlay: false,
|
|
28
|
+
videoTitle: '',
|
|
29
|
+
persistentTitle: false,
|
|
30
|
+
debug: false, // Enable/disable debug logging
|
|
31
|
+
autoplay: false, // if video should autoplay at start
|
|
32
|
+
defaultQuality: 'auto', // 'auto', '1080p', '720p', '480p', etc.
|
|
33
|
+
language: null, // language of the player (default english)
|
|
34
|
+
pauseClick: true, // the player should be paused when click over the video area
|
|
35
|
+
doubleTapPause: true, // first tap (or click) show the controlbar, second tap (or click) pause
|
|
36
|
+
brandLogoEnabled: false, // Enable/disable brand logo
|
|
37
|
+
brandLogoUrl: '', // URL for brand logo image
|
|
38
|
+
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
39
|
+
playlistEnabled: true, // Enable/disable playlist detection
|
|
40
|
+
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
41
|
+
playlistLoop: false, // Loop playlist when reaching the end
|
|
42
|
+
loop: false, // Loop video when it ends (restart from beginning)
|
|
43
|
+
volumeSlider: 'show', // Mobile volume slider: 'show' (horizontal popup) or 'hide' (no slider on mobile)
|
|
44
|
+
// WATERMARK OVERLAY
|
|
45
|
+
watermarkUrl: '', // URL of watermark image
|
|
46
|
+
watermarkLink: '', // Optional URL to open when clicking watermark
|
|
47
|
+
watermarkPosition: 'bottomright', // Position: topleft, topright, bottomleft, bottomright
|
|
48
|
+
watermarkTitle: '', // Optional tooltip title
|
|
49
|
+
hideWatermark: true, // Hide watermark with controls (default: true)
|
|
50
|
+
// ADAPTIVE STREAMING SUPPORT
|
|
51
|
+
adaptiveStreaming: false, // Enable DASH/HLS adaptive streaming
|
|
52
|
+
dashLibUrl: 'https://cdn.dashjs.org/latest/dash.all.min.js', // Dash.js library URL
|
|
53
|
+
hlsLibUrl: 'https://cdn.jsdelivr.net/npm/hls.js@latest', // HLS.js library URL
|
|
54
|
+
adaptiveQualityControl: true, // Show quality control for adaptive streams
|
|
55
|
+
//seek shape
|
|
56
|
+
seekHandleShape: 'circle', // Available shape: none, circle, square, diamond, arrow, triangle, heart, star
|
|
57
|
+
// AUDIO PLAYER
|
|
58
|
+
audiofile: false,
|
|
59
|
+
audiowave: false,
|
|
60
|
+
// RESOLUTION CONTROL
|
|
61
|
+
resolution: "normal", // "normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"
|
|
62
|
+
...options
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.isUserSeeking = false;
|
|
66
|
+
this.controlsTimeout = null;
|
|
67
|
+
this.titleTimeout = null;
|
|
68
|
+
this.currentQualityIndex = 0;
|
|
69
|
+
this.qualities = [];
|
|
70
|
+
this.originalSources = [];
|
|
71
|
+
this.setupMenuToggles(); // Initialize menu toggle system
|
|
72
|
+
this.isPiPSupported = this.checkPiPSupport();
|
|
73
|
+
this.seekTooltip = null;
|
|
74
|
+
this.titleOverlay = null;
|
|
75
|
+
this.isPlayerReady = false;
|
|
76
|
+
|
|
77
|
+
// Subtitle management
|
|
78
|
+
this.textTracks = [];
|
|
79
|
+
this.currentSubtitleTrack = null;
|
|
80
|
+
this.subtitlesEnabled = false;
|
|
81
|
+
this.customSubtitleRenderer = null;
|
|
13
82
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
showPictureInPicture: true,
|
|
19
|
-
showSubtitles: true,
|
|
20
|
-
subtitlesEnabled: false,
|
|
21
|
-
autoHide: true,
|
|
22
|
-
autoHideDelay: 3000,
|
|
23
|
-
poster: null, // URL of poster image
|
|
24
|
-
showPosterOnEnd: false, // Show poster again when video ends
|
|
25
|
-
keyboardControls: true,
|
|
26
|
-
showSeekTooltip: true,
|
|
27
|
-
showTitleOverlay: false,
|
|
28
|
-
videoTitle: '',
|
|
29
|
-
persistentTitle: false,
|
|
30
|
-
debug: false, // Enable/disable debug logging
|
|
31
|
-
autoplay: false, // if video should autoplay at start
|
|
32
|
-
defaultQuality: 'auto', // 'auto', '1080p', '720p', '480p', etc.
|
|
33
|
-
language: null, // language of the player (default english)
|
|
34
|
-
pauseClick: true, // the player should be paused when click over the video area
|
|
35
|
-
doubleTapPause: true, // first tap (or click) show the controlbar, second tap (or click) pause
|
|
36
|
-
brandLogoEnabled: false, // Enable/disable brand logo
|
|
37
|
-
brandLogoUrl: '', // URL for brand logo image
|
|
38
|
-
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
39
|
-
playlistEnabled: true, // Enable/disable playlist detection
|
|
40
|
-
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
41
|
-
playlistLoop: false, // Loop playlist when reaching the end
|
|
42
|
-
loop: false, // Loop video when it ends (restart from beginning)
|
|
43
|
-
volumeSlider: 'horizontal', //volume slider type: 'horizontal' or 'vertical'
|
|
44
|
-
// WATERMARK OVERLAY
|
|
45
|
-
watermarkUrl: '', // URL of watermark image
|
|
46
|
-
watermarkLink: '', // Optional URL to open when clicking watermark
|
|
47
|
-
watermarkPosition: 'bottomright', // Position: topleft, topright, bottomleft, bottomright
|
|
48
|
-
watermarkTitle: '', // Optional tooltip title
|
|
49
|
-
hideWatermark: true, // Hide watermark with controls (default: true)
|
|
50
|
-
// ADAPTIVE STREAMING SUPPORT
|
|
51
|
-
adaptiveStreaming: false, // Enable DASH/HLS adaptive streaming
|
|
52
|
-
dashLibUrl: 'https://cdn.dashjs.org/latest/dash.all.min.js', // Dash.js library URL
|
|
53
|
-
hlsLibUrl: 'https://cdn.jsdelivr.net/npm/hls.js@latest', // HLS.js library URL
|
|
54
|
-
adaptiveQualityControl: true, // Show quality control for adaptive streams
|
|
55
|
-
// AUDIO PLAYER
|
|
56
|
-
audiofile: false,
|
|
57
|
-
audiowave: false,
|
|
58
|
-
// RESOLUTION CONTROL
|
|
59
|
-
resolution: "normal", // "normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"
|
|
60
|
-
...options
|
|
61
|
-
};
|
|
83
|
+
// Chapter management
|
|
84
|
+
this.chapters = [];
|
|
85
|
+
this.chapterMarkersContainer = null;
|
|
86
|
+
this.chapterTooltip = null;
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.qualities = [];
|
|
68
|
-
this.originalSources = [];
|
|
69
|
-
this.isPiPSupported = this.checkPiPSupport();
|
|
70
|
-
this.seekTooltip = null;
|
|
71
|
-
this.titleOverlay = null;
|
|
72
|
-
this.isPlayerReady = false;
|
|
73
|
-
|
|
74
|
-
// Subtitle management
|
|
75
|
-
this.textTracks = [];
|
|
76
|
-
this.currentSubtitleTrack = null;
|
|
77
|
-
this.subtitlesEnabled = false;
|
|
78
|
-
this.customSubtitleRenderer = null;
|
|
79
|
-
|
|
80
|
-
// Chapter management
|
|
81
|
-
this.chapters = [];
|
|
82
|
-
this.chapterMarkersContainer = null;
|
|
83
|
-
this.chapterTooltip = null;
|
|
84
|
-
|
|
85
|
-
// Dual quality indicator management
|
|
86
|
-
this.selectedQuality = this.options.defaultQuality || 'auto';
|
|
87
|
-
this.currentPlayingQuality = null;
|
|
88
|
-
this.qualityMonitorInterval = null;
|
|
88
|
+
// Dual quality indicator management
|
|
89
|
+
this.selectedQuality = this.options.defaultQuality || 'auto';
|
|
90
|
+
this.currentPlayingQuality = null;
|
|
91
|
+
this.qualityMonitorInterval = null;
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
// Quality change management
|
|
94
|
+
this.qualityChangeTimeout = null;
|
|
95
|
+
this.isChangingQuality = false;
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
// Quality debug
|
|
98
|
+
this.debugQuality = false;
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// Auto-hide system
|
|
101
|
+
this.autoHideTimer = null;
|
|
102
|
+
this.mouseOverControls = false;
|
|
103
|
+
this.autoHideDebug = false;
|
|
104
|
+
this.autoHideInitialized = false;
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
// Poster management
|
|
107
|
+
this.posterOverlay = null;
|
|
105
108
|
|
|
106
109
|
// Watermark overlay
|
|
107
110
|
this.watermarkElement = null;
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
// Custom event system
|
|
113
|
+
this.eventCallbacks = {
|
|
114
|
+
'played': [],
|
|
115
|
+
'paused': [],
|
|
116
|
+
'subtitlechange': [],
|
|
117
|
+
'chapterchange': [],
|
|
118
|
+
'pipchange': [],
|
|
119
|
+
'fullscreenchange': [],
|
|
120
|
+
'speedchange': [],
|
|
121
|
+
'timeupdate': [],
|
|
122
|
+
'volumechange': [],
|
|
123
|
+
'qualitychange': [],
|
|
124
|
+
'playlistchange': [],
|
|
125
|
+
'ended': []
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Playlist management
|
|
129
|
+
this.playlist = [];
|
|
130
|
+
this.currentPlaylistIndex = -1;
|
|
131
|
+
this.playlistId = null;
|
|
132
|
+
this.isPlaylistActive = false;
|
|
133
|
+
|
|
134
|
+
// Adaptive streaming management
|
|
135
|
+
this.dashPlayer = null;
|
|
136
|
+
this.hlsPlayer = null;
|
|
137
|
+
this.adaptiveStreamingType = null; // 'dash', 'hls', or null
|
|
138
|
+
this.isAdaptiveStream = false;
|
|
139
|
+
this.adaptiveQualities = [];
|
|
140
|
+
this.librariesLoaded = {
|
|
141
|
+
dash: false,
|
|
142
|
+
hls: false
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
this.lastTimeUpdate = 0; // For throttling timeupdate events
|
|
146
|
+
|
|
147
|
+
if (this.options.language && this.isI18nAvailable()) {
|
|
148
|
+
VideoPlayerTranslations.setLanguage(this.options.language);
|
|
149
|
+
}
|
|
150
|
+
// Apply autoplay if enabled
|
|
151
|
+
if (options.autoplay) {
|
|
152
|
+
this.video.autoplay = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
this.interceptAutoLoading();
|
|
157
|
+
this.createPlayerStructure();
|
|
158
|
+
this.initializeElements();
|
|
159
|
+
// audio player adaptation
|
|
160
|
+
this.adaptToAudioFile = function () {
|
|
161
|
+
if (this.options.audiofile) {
|
|
162
|
+
// Nascondere video
|
|
163
|
+
if (this.video) {
|
|
164
|
+
this.video.style.display = 'none';
|
|
165
|
+
}
|
|
166
|
+
if (this.container) {
|
|
167
|
+
this.container.classList.add('audio-player');
|
|
168
|
+
}
|
|
169
|
+
if (this.options.audiowave) {
|
|
170
|
+
this.initAudioWave();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
123
173
|
};
|
|
174
|
+
// Audio wave with Web Audio API
|
|
175
|
+
this.initAudioWave = function () {
|
|
176
|
+
if (!this.video) return;
|
|
177
|
+
|
|
178
|
+
this.audioWaveCanvas = document.createElement('canvas');
|
|
179
|
+
this.audioWaveCanvas.className = 'audio-wave-canvas';
|
|
180
|
+
this.container.appendChild(this.audioWaveCanvas);
|
|
181
|
+
|
|
182
|
+
const canvasCtx = this.audioWaveCanvas.getContext('2d');
|
|
183
|
+
const WIDTH = this.audioWaveCanvas.width = this.container.clientWidth;
|
|
184
|
+
const HEIGHT = this.audioWaveCanvas.height = 60; // altezza onda audio
|
|
185
|
+
|
|
186
|
+
// Setup Web Audio API
|
|
187
|
+
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
188
|
+
this.audioCtx = new AudioContext();
|
|
189
|
+
this.analyser = this.audioCtx.createAnalyser();
|
|
190
|
+
this.source = this.audioCtx.createMediaElementSource(this.video);
|
|
191
|
+
this.source.connect(this.analyser);
|
|
192
|
+
this.analyser.connect(this.audioCtx.destination);
|
|
193
|
+
|
|
194
|
+
this.analyser.fftSize = 2048;
|
|
195
|
+
const bufferLength = this.analyser.fftSize;
|
|
196
|
+
const dataArray = new Uint8Array(bufferLength);
|
|
197
|
+
|
|
198
|
+
// canvas
|
|
199
|
+
const draw = () => {
|
|
200
|
+
requestAnimationFrame(draw);
|
|
201
|
+
this.analyser.getByteTimeDomainData(dataArray);
|
|
202
|
+
|
|
203
|
+
canvasCtx.fillStyle = '#222';
|
|
204
|
+
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
|
205
|
+
|
|
206
|
+
canvasCtx.lineWidth = 2;
|
|
207
|
+
canvasCtx.strokeStyle = '#33ccff';
|
|
208
|
+
canvasCtx.beginPath();
|
|
209
|
+
|
|
210
|
+
const sliceWidth = WIDTH / bufferLength;
|
|
211
|
+
let x = 0;
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < bufferLength; i++) {
|
|
214
|
+
const v = dataArray[i] / 128.0;
|
|
215
|
+
const y = v * HEIGHT / 2;
|
|
216
|
+
|
|
217
|
+
if (i === 0) {
|
|
218
|
+
canvasCtx.moveTo(x, y);
|
|
219
|
+
} else {
|
|
220
|
+
canvasCtx.lineTo(x, y);
|
|
221
|
+
}
|
|
124
222
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// Adaptive streaming management
|
|
132
|
-
this.dashPlayer = null;
|
|
133
|
-
this.hlsPlayer = null;
|
|
134
|
-
this.adaptiveStreamingType = null; // 'dash', 'hls', or null
|
|
135
|
-
this.isAdaptiveStream = false;
|
|
136
|
-
this.adaptiveQualities = [];
|
|
137
|
-
this.librariesLoaded = {
|
|
138
|
-
dash: false,
|
|
139
|
-
hls: false
|
|
140
|
-
};
|
|
223
|
+
x += sliceWidth;
|
|
224
|
+
}
|
|
225
|
+
canvasCtx.lineTo(WIDTH, HEIGHT / 2);
|
|
226
|
+
canvasCtx.stroke();
|
|
227
|
+
};
|
|
141
228
|
|
|
142
|
-
|
|
229
|
+
draw();
|
|
230
|
+
};
|
|
231
|
+
this.adaptToAudioFile();
|
|
232
|
+
this.bindEvents();
|
|
143
233
|
|
|
144
|
-
if (this.options.
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
// Apply autoplay if enabled
|
|
148
|
-
if (options.autoplay) {
|
|
149
|
-
this.video.autoplay = true;
|
|
234
|
+
if (this.options.keyboardControls) {
|
|
235
|
+
this.setupKeyboardControls();
|
|
150
236
|
}
|
|
151
237
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (this.options.audiofile) {
|
|
159
|
-
// Nascondere video
|
|
160
|
-
if (this.video) {
|
|
161
|
-
this.video.style.display = 'none';
|
|
162
|
-
}
|
|
163
|
-
if (this.container) {
|
|
164
|
-
this.container.classList.add('audio-player');
|
|
165
|
-
}
|
|
166
|
-
if (this.options.audiowave) {
|
|
167
|
-
this.initAudioWave();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
// Audio wave with Web Audio API
|
|
172
|
-
this.initAudioWave = function () {
|
|
173
|
-
if (!this.video) return;
|
|
174
|
-
|
|
175
|
-
this.audioWaveCanvas = document.createElement('canvas');
|
|
176
|
-
this.audioWaveCanvas.className = 'audio-wave-canvas';
|
|
177
|
-
this.container.appendChild(this.audioWaveCanvas);
|
|
178
|
-
|
|
179
|
-
const canvasCtx = this.audioWaveCanvas.getContext('2d');
|
|
180
|
-
const WIDTH = this.audioWaveCanvas.width = this.container.clientWidth;
|
|
181
|
-
const HEIGHT = this.audioWaveCanvas.height = 60; // altezza onda audio
|
|
182
|
-
|
|
183
|
-
// Setup Web Audio API
|
|
184
|
-
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
185
|
-
this.audioCtx = new AudioContext();
|
|
186
|
-
this.analyser = this.audioCtx.createAnalyser();
|
|
187
|
-
this.source = this.audioCtx.createMediaElementSource(this.video);
|
|
188
|
-
this.source.connect(this.analyser);
|
|
189
|
-
this.analyser.connect(this.audioCtx.destination);
|
|
190
|
-
|
|
191
|
-
this.analyser.fftSize = 2048;
|
|
192
|
-
const bufferLength = this.analyser.fftSize;
|
|
193
|
-
const dataArray = new Uint8Array(bufferLength);
|
|
194
|
-
|
|
195
|
-
// canvas
|
|
196
|
-
const draw = () => {
|
|
197
|
-
requestAnimationFrame(draw);
|
|
198
|
-
this.analyser.getByteTimeDomainData(dataArray);
|
|
199
|
-
|
|
200
|
-
canvasCtx.fillStyle = '#222';
|
|
201
|
-
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
|
202
|
-
|
|
203
|
-
canvasCtx.lineWidth = 2;
|
|
204
|
-
canvasCtx.strokeStyle = '#33ccff';
|
|
205
|
-
canvasCtx.beginPath();
|
|
206
|
-
|
|
207
|
-
const sliceWidth = WIDTH / bufferLength;
|
|
208
|
-
let x = 0;
|
|
209
|
-
|
|
210
|
-
for (let i = 0; i < bufferLength; i++) {
|
|
211
|
-
const v = dataArray[i] / 128.0;
|
|
212
|
-
const y = v * HEIGHT / 2;
|
|
213
|
-
|
|
214
|
-
if (i === 0) {
|
|
215
|
-
canvasCtx.moveTo(x, y);
|
|
216
|
-
} else {
|
|
217
|
-
canvasCtx.lineTo(x, y);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
x += sliceWidth;
|
|
221
|
-
}
|
|
222
|
-
canvasCtx.lineTo(WIDTH, HEIGHT / 2);
|
|
223
|
-
canvasCtx.stroke();
|
|
224
|
-
};
|
|
238
|
+
this.updateVolumeSliderVisual();
|
|
239
|
+
this.initVolumeTooltip();
|
|
240
|
+
this.updateTooltips();
|
|
241
|
+
this.markPlayerReady();
|
|
242
|
+
this.initializePluginSystem();
|
|
243
|
+
this.restoreSourcesAsync();
|
|
225
244
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.adaptToAudioFile();
|
|
229
|
-
this.bindEvents();
|
|
245
|
+
this.initializeSubtitles();
|
|
246
|
+
this.initializeQualityMonitoring();
|
|
230
247
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
this.initializeResolution();
|
|
249
|
+
this.initializeChapters();
|
|
250
|
+
this.initializePoster();
|
|
251
|
+
this.initializeWatermark();
|
|
234
252
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.initializePluginSystem();
|
|
240
|
-
this.restoreSourcesAsync();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (this.options.debug) console.error('Video player initialization error:', error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
241
257
|
|
|
242
|
-
|
|
243
|
-
|
|
258
|
+
getPlayerState() {
|
|
259
|
+
return {
|
|
260
|
+
isPlaying: !this.isPaused(),
|
|
261
|
+
isPaused: this.isPaused(),
|
|
262
|
+
currentTime: this.getCurrentTime(),
|
|
263
|
+
duration: this.getDuration(),
|
|
264
|
+
volume: this.getVolume(),
|
|
265
|
+
isMuted: this.isMuted(),
|
|
266
|
+
playbackRate: this.getPlaybackRate(),
|
|
267
|
+
isFullscreen: this.isFullscreenActive(),
|
|
268
|
+
isPictureInPicture: this.isPictureInPictureActive(),
|
|
269
|
+
subtitlesEnabled: this.isSubtitlesEnabled(),
|
|
270
|
+
currentSubtitle: this.getCurrentSubtitleTrack(),
|
|
271
|
+
selectedQuality: this.getSelectedQuality(),
|
|
272
|
+
currentQuality: this.getCurrentPlayingQuality(),
|
|
273
|
+
isAutoQuality: this.isAutoQualityActive()
|
|
274
|
+
};
|
|
275
|
+
}
|
|
244
276
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
277
|
+
isI18nAvailable() {
|
|
278
|
+
return typeof VideoPlayerTranslations !== 'undefined' &&
|
|
279
|
+
VideoPlayerTranslations !== null &&
|
|
280
|
+
typeof VideoPlayerTranslations.t === 'function';
|
|
281
|
+
}
|
|
249
282
|
|
|
283
|
+
t(key) {
|
|
284
|
+
if (this.isI18nAvailable()) {
|
|
285
|
+
try {
|
|
286
|
+
return VideoPlayerTranslations.t(key);
|
|
250
287
|
} catch (error) {
|
|
251
|
-
if (this.options.debug) console.
|
|
288
|
+
if (this.options.debug) console.warn('Translation error:', error);
|
|
252
289
|
}
|
|
253
290
|
}
|
|
254
291
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
typeof VideoPlayerTranslations.t === 'function';
|
|
278
|
-
}
|
|
292
|
+
const fallback = {
|
|
293
|
+
'play_pause': 'Play/Pause (Space)',
|
|
294
|
+
'mute_unmute': 'Mute/Unmute (M)',
|
|
295
|
+
'volume': 'Volume',
|
|
296
|
+
'playback_speed': 'Playback speed',
|
|
297
|
+
'video_quality': 'Video quality',
|
|
298
|
+
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
299
|
+
'fullscreen': 'Fullscreen (F)',
|
|
300
|
+
'subtitles': 'Subtitles (S)',
|
|
301
|
+
'subtitles_enable': 'Enable subtitles',
|
|
302
|
+
'subtitles_disable': 'Disable subtitles',
|
|
303
|
+
'subtitles_off': 'Off',
|
|
304
|
+
'auto': 'Auto',
|
|
305
|
+
'brand_logo': 'Brand logo',
|
|
306
|
+
'next_video': 'Next video (N)',
|
|
307
|
+
'prev_video': 'Previous video (P)',
|
|
308
|
+
'playlist_next': 'Next',
|
|
309
|
+
'playlist_prev': 'Previous'
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
return fallback[key] || key;
|
|
313
|
+
}
|
|
279
314
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return VideoPlayerTranslations.t(key);
|
|
284
|
-
} catch (error) {
|
|
285
|
-
if (this.options.debug) console.warn('Translation error:', error);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
315
|
+
interceptAutoLoading() {
|
|
316
|
+
this.saveOriginalSources();
|
|
317
|
+
this.disableSources();
|
|
288
318
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
'volume': 'Volume',
|
|
293
|
-
'playback_speed': 'Playback speed',
|
|
294
|
-
'video_quality': 'Video quality',
|
|
295
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
296
|
-
'fullscreen': 'Fullscreen (F)',
|
|
297
|
-
'subtitles': 'Subtitles (S)',
|
|
298
|
-
'subtitles_enable': 'Enable subtitles',
|
|
299
|
-
'subtitles_disable': 'Disable subtitles',
|
|
300
|
-
'subtitles_off': 'Off',
|
|
301
|
-
'auto': 'Auto',
|
|
302
|
-
'brand_logo': 'Brand logo',
|
|
303
|
-
'next_video': 'Next video (N)',
|
|
304
|
-
'prev_video': 'Previous video (P)',
|
|
305
|
-
'playlist_next': 'Next',
|
|
306
|
-
'playlist_prev': 'Previous'
|
|
307
|
-
};
|
|
319
|
+
this.video.preload = 'none';
|
|
320
|
+
this.video.controls = false;
|
|
321
|
+
this.video.autoplay = false;
|
|
308
322
|
|
|
309
|
-
|
|
323
|
+
if (this.video.src && this.video.src !== window.location.href) {
|
|
324
|
+
this.originalSrc = this.video.src;
|
|
325
|
+
this.video.removeAttribute('src');
|
|
326
|
+
this.video.src = '';
|
|
310
327
|
}
|
|
311
328
|
|
|
312
|
-
|
|
313
|
-
this.saveOriginalSources();
|
|
314
|
-
this.disableSources();
|
|
329
|
+
this.hideNativePlayer();
|
|
315
330
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
this.video.autoplay = false;
|
|
331
|
+
if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
|
|
332
|
+
}
|
|
319
333
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
334
|
+
saveOriginalSources() {
|
|
335
|
+
const sources = this.video.querySelectorAll('source');
|
|
336
|
+
this.originalSources = [];
|
|
337
|
+
|
|
338
|
+
sources.forEach((source, index) => {
|
|
339
|
+
if (source.src) {
|
|
340
|
+
this.originalSources.push({
|
|
341
|
+
element: source,
|
|
342
|
+
src: source.src,
|
|
343
|
+
type: source.type || 'video/mp4',
|
|
344
|
+
quality: source.getAttribute('data-quality') || `quality-${index}`,
|
|
345
|
+
index: index
|
|
346
|
+
});
|
|
324
347
|
}
|
|
348
|
+
});
|
|
325
349
|
|
|
326
|
-
|
|
350
|
+
if (this.options.debug) console.log(`📁 Saved ${this.originalSources.length} sources originali:`, this.originalSources);
|
|
351
|
+
}
|
|
327
352
|
|
|
328
|
-
|
|
329
|
-
|
|
353
|
+
disableSources() {
|
|
354
|
+
const sources = this.video.querySelectorAll('source');
|
|
355
|
+
sources.forEach(source => {
|
|
356
|
+
if (source.src) {
|
|
357
|
+
source.removeAttribute('src');
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
330
361
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.
|
|
362
|
+
restoreSourcesAsync() {
|
|
363
|
+
setTimeout(() => {
|
|
364
|
+
this.restoreSources();
|
|
365
|
+
}, 200);
|
|
366
|
+
}
|
|
334
367
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
368
|
+
async restoreSources() {
|
|
369
|
+
try {
|
|
370
|
+
// Check for adaptive streaming first
|
|
371
|
+
let adaptiveSource = null;
|
|
372
|
+
|
|
373
|
+
if (this.originalSrc) {
|
|
374
|
+
adaptiveSource = this.originalSrc;
|
|
375
|
+
} else if (this.originalSources.length > 0) {
|
|
376
|
+
// Check if any source is adaptive
|
|
377
|
+
const firstSource = this.originalSources[0];
|
|
378
|
+
if (firstSource.src && this.detectStreamType(firstSource.src)) {
|
|
379
|
+
adaptiveSource = firstSource.src;
|
|
344
380
|
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (this.options.debug) console.log(`📁 Saved ${this.originalSources.length} sources originali:`, this.originalSources);
|
|
348
|
-
}
|
|
381
|
+
}
|
|
349
382
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
383
|
+
// Initialize adaptive streaming if detected
|
|
384
|
+
if (adaptiveSource && this.options.adaptiveStreaming) {
|
|
385
|
+
const adaptiveInitialized = await this.initializeAdaptiveStreaming(adaptiveSource);
|
|
386
|
+
if (adaptiveInitialized) {
|
|
387
|
+
if (this.options.debug) console.log('📡 Adaptive streaming initialized');
|
|
388
|
+
return;
|
|
355
389
|
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
restoreSourcesAsync() {
|
|
360
|
-
setTimeout(() => {
|
|
361
|
-
this.restoreSources();
|
|
362
|
-
}, 200);
|
|
363
|
-
}
|
|
390
|
+
}
|
|
364
391
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (this.originalSrc) {
|
|
371
|
-
adaptiveSource = this.originalSrc;
|
|
372
|
-
} else if (this.originalSources.length > 0) {
|
|
373
|
-
// Check if any source is adaptive
|
|
374
|
-
const firstSource = this.originalSources[0];
|
|
375
|
-
if (firstSource.src && this.detectStreamType(firstSource.src)) {
|
|
376
|
-
adaptiveSource = firstSource.src;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
392
|
+
// Fallback to traditional sources
|
|
393
|
+
if (this.originalSrc) {
|
|
394
|
+
this.video.src = this.originalSrc;
|
|
395
|
+
}
|
|
379
396
|
|
|
380
|
-
|
|
381
|
-
if (
|
|
382
|
-
|
|
383
|
-
if (adaptiveInitialized) {
|
|
384
|
-
if (this.options.debug) console.log('📡 Adaptive streaming initialized');
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
397
|
+
this.originalSources.forEach(sourceData => {
|
|
398
|
+
if (sourceData.element && sourceData.src) {
|
|
399
|
+
sourceData.element.src = sourceData.src;
|
|
387
400
|
}
|
|
401
|
+
});
|
|
388
402
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
403
|
+
this.qualities = this.originalSources.map(s => ({
|
|
404
|
+
src: s.src,
|
|
405
|
+
quality: s.quality,
|
|
406
|
+
type: s.type
|
|
407
|
+
}));
|
|
393
408
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
409
|
+
if (this.originalSrc && this.qualities.length === 0) {
|
|
410
|
+
this.qualities.push({
|
|
411
|
+
src: this.originalSrc,
|
|
412
|
+
quality: 'default',
|
|
413
|
+
type: 'video/mp4'
|
|
398
414
|
});
|
|
415
|
+
}
|
|
399
416
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
quality: s.quality,
|
|
403
|
-
type: s.type
|
|
404
|
-
}));
|
|
405
|
-
|
|
406
|
-
if (this.originalSrc && this.qualities.length === 0) {
|
|
407
|
-
this.qualities.push({
|
|
408
|
-
src: this.originalSrc,
|
|
409
|
-
quality: 'default',
|
|
410
|
-
type: 'video/mp4'
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (this.qualities.length > 0) {
|
|
415
|
-
this.video.load();
|
|
417
|
+
if (this.qualities.length > 0) {
|
|
418
|
+
this.video.load();
|
|
416
419
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
420
|
+
// CRITICAL: Re-initialize subtitles AFTER video.load() completes
|
|
421
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
422
|
+
setTimeout(() => {
|
|
423
|
+
this.reinitializeSubtitles();
|
|
424
|
+
if (this.options.debug) console.log('🔄 Subtitles re-initialized after video load');
|
|
425
|
+
}, 300);
|
|
426
|
+
}, { once: true });
|
|
427
|
+
}
|
|
425
428
|
|
|
426
|
-
|
|
429
|
+
if (this.options.debug) console.log('✅ Sources ripristinate:', this.qualities);
|
|
427
430
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
+
} catch (error) {
|
|
432
|
+
if (this.options.debug) console.error('❌ Errore ripristino sources:', error);
|
|
431
433
|
}
|
|
434
|
+
}
|
|
432
435
|
|
|
433
436
|
reinitializeSubtitles() {
|
|
434
437
|
if (this.options.debug) console.log('🔄 Re-initializing subtitles...');
|
|
@@ -474,630 +477,706 @@ getDefaultSubtitleTrack() {
|
|
|
474
477
|
return -1;
|
|
475
478
|
}
|
|
476
479
|
|
|
477
|
-
|
|
480
|
+
markPlayerReady() {
|
|
481
|
+
setTimeout(() => {
|
|
482
|
+
this.isPlayerReady = true;
|
|
483
|
+
if (this.container) {
|
|
484
|
+
this.container.classList.add('player-initialized');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (this.video) {
|
|
488
|
+
this.video.style.visibility = '';
|
|
489
|
+
this.video.style.opacity = '';
|
|
490
|
+
this.video.style.pointerEvents = '';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// INITIALIZE AUTO-HIDE AFTER EVERYTHING IS READY
|
|
478
494
|
setTimeout(() => {
|
|
479
|
-
this.
|
|
480
|
-
|
|
481
|
-
this.container.classList.add('player-initialized');
|
|
495
|
+
if (this.options.autoHide && !this.autoHideInitialized) {
|
|
496
|
+
this.initAutoHide();
|
|
482
497
|
}
|
|
483
498
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.
|
|
487
|
-
|
|
499
|
+
// Fix: Apply default quality (auto or specific)
|
|
500
|
+
if (this.selectedQuality && this.qualities && this.qualities.length > 0) {
|
|
501
|
+
if (this.options.debug) console.log(`🎯 Applying defaultQuality: "${this.selectedQuality}"`);
|
|
502
|
+
|
|
503
|
+
if (this.selectedQuality === 'auto') {
|
|
504
|
+
this.enableAutoQuality();
|
|
505
|
+
} else {
|
|
506
|
+
// Check if requested quality is available
|
|
507
|
+
const requestedQuality = this.qualities.find(q => q.quality === this.selectedQuality);
|
|
508
|
+
if (requestedQuality) {
|
|
509
|
+
if (this.options.debug) console.log(`✅ Quality "${this.selectedQuality}" available`);
|
|
510
|
+
this.setQuality(this.selectedQuality);
|
|
511
|
+
} else {
|
|
512
|
+
if (this.options.debug) console.warn(`⚠️ Quality "${this.selectedQuality}" not available - fallback to auto`);
|
|
513
|
+
if (this.options.debug) console.log('📋 Available qualities:', this.qualities.map(q => q.quality));
|
|
514
|
+
this.enableAutoQuality();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
488
517
|
}
|
|
489
518
|
|
|
490
|
-
//
|
|
491
|
-
|
|
492
|
-
if (this.options.
|
|
493
|
-
|
|
494
|
-
|
|
519
|
+
// Autoplay
|
|
520
|
+
if (this.options.autoplay) {
|
|
521
|
+
if (this.options.debug) console.log('🎬 Autoplay enabled');
|
|
522
|
+
setTimeout(() => {
|
|
523
|
+
this.video.play().catch(error => {
|
|
524
|
+
if (this.options.debug) console.warn('⚠️ Autoplay blocked:', error);
|
|
525
|
+
});
|
|
526
|
+
}, 100);
|
|
527
|
+
}
|
|
528
|
+
}, 200);
|
|
495
529
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (this.options.debug) console.log(`🎯 Applying defaultQuality: "${this.selectedQuality}"`);
|
|
530
|
+
}, 100);
|
|
531
|
+
}
|
|
499
532
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
} else {
|
|
509
|
-
if (this.options.debug) console.warn(`⚠️ Quality "${this.selectedQuality}" not available - fallback to auto`);
|
|
510
|
-
if (this.options.debug) console.log('📋 Available qualities:', this.qualities.map(q => q.quality));
|
|
511
|
-
this.enableAutoQuality();
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
533
|
+
createPlayerStructure() {
|
|
534
|
+
let wrapper = this.video.closest('.video-wrapper');
|
|
535
|
+
if (!wrapper) {
|
|
536
|
+
wrapper = document.createElement('div');
|
|
537
|
+
wrapper.className = 'video-wrapper';
|
|
538
|
+
this.video.parentNode.insertBefore(wrapper, this.video);
|
|
539
|
+
wrapper.appendChild(this.video);
|
|
540
|
+
}
|
|
515
541
|
|
|
516
|
-
|
|
517
|
-
if (this.options.autoplay) {
|
|
518
|
-
if (this.options.debug) console.log('🎬 Autoplay enabled');
|
|
519
|
-
setTimeout(() => {
|
|
520
|
-
this.video.play().catch(error => {
|
|
521
|
-
if (this.options.debug) console.warn('⚠️ Autoplay blocked:', error);
|
|
522
|
-
});
|
|
523
|
-
}, 100);
|
|
524
|
-
}
|
|
525
|
-
}, 200);
|
|
542
|
+
this.container = wrapper;
|
|
526
543
|
|
|
527
|
-
|
|
544
|
+
this.createInitialLoading();
|
|
545
|
+
this.createLoadingOverlay();
|
|
546
|
+
this.collectVideoQualities();
|
|
547
|
+
this.createControls();
|
|
548
|
+
this.createBrandLogo();
|
|
549
|
+
this.detectPlaylist();
|
|
550
|
+
|
|
551
|
+
if (this.options.showTitleOverlay) {
|
|
552
|
+
this.createTitleOverlay();
|
|
528
553
|
}
|
|
554
|
+
}
|
|
529
555
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
556
|
+
createInitialLoading() {
|
|
557
|
+
const initialLoader = document.createElement('div');
|
|
558
|
+
initialLoader.className = 'initial-loading';
|
|
559
|
+
initialLoader.innerHTML = '<div class="loading-spinner"></div>';
|
|
560
|
+
this.container.appendChild(initialLoader);
|
|
561
|
+
this.initialLoading = initialLoader;
|
|
562
|
+
}
|
|
538
563
|
|
|
539
|
-
|
|
564
|
+
collectVideoQualities() {
|
|
565
|
+
if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
|
|
566
|
+
}
|
|
540
567
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
568
|
+
createLoadingOverlay() {
|
|
569
|
+
const overlay = document.createElement('div');
|
|
570
|
+
overlay.className = 'loading-overlay';
|
|
571
|
+
overlay.id = 'loadingOverlay-' + this.getUniqueId();
|
|
572
|
+
overlay.innerHTML = '<div class="loading-spinner"></div>';
|
|
573
|
+
this.container.appendChild(overlay);
|
|
574
|
+
this.loadingOverlay = overlay;
|
|
575
|
+
}
|
|
547
576
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
577
|
+
createTitleOverlay() {
|
|
578
|
+
const overlay = document.createElement('div');
|
|
579
|
+
overlay.className = 'title-overlay';
|
|
580
|
+
overlay.id = 'titleOverlay-' + this.getUniqueId();
|
|
552
581
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
initialLoader.innerHTML = '<div class="loading-spinner"></div>';
|
|
557
|
-
this.container.appendChild(initialLoader);
|
|
558
|
-
this.initialLoading = initialLoader;
|
|
559
|
-
}
|
|
582
|
+
const titleText = document.createElement('h2');
|
|
583
|
+
titleText.className = 'title-text';
|
|
584
|
+
titleText.textContent = this.options.videoTitle || '';
|
|
560
585
|
|
|
561
|
-
|
|
562
|
-
if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
|
|
563
|
-
}
|
|
586
|
+
overlay.appendChild(titleText);
|
|
564
587
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
overlay.id = 'loadingOverlay-' + this.getUniqueId();
|
|
569
|
-
overlay.innerHTML = '<div class="loading-spinner"></div>';
|
|
588
|
+
if (this.controls) {
|
|
589
|
+
this.container.insertBefore(overlay, this.controls);
|
|
590
|
+
} else {
|
|
570
591
|
this.container.appendChild(overlay);
|
|
571
|
-
this.loadingOverlay = overlay;
|
|
572
592
|
}
|
|
573
593
|
|
|
574
|
-
|
|
575
|
-
const overlay = document.createElement('div');
|
|
576
|
-
overlay.className = 'title-overlay';
|
|
577
|
-
overlay.id = 'titleOverlay-' + this.getUniqueId();
|
|
594
|
+
this.titleOverlay = overlay;
|
|
578
595
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
596
|
+
if (this.options.persistentTitle && this.options.videoTitle) {
|
|
597
|
+
this.showTitleOverlay();
|
|
598
|
+
}
|
|
599
|
+
}
|
|
582
600
|
|
|
583
|
-
|
|
601
|
+
updateTooltips() {
|
|
602
|
+
if (!this.controls) return;
|
|
584
603
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
this.
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
this.titleOverlay = overlay;
|
|
604
|
+
try {
|
|
605
|
+
this.controls.querySelectorAll('[data-tooltip]').forEach(element => {
|
|
606
|
+
const key = element.getAttribute('data-tooltip');
|
|
607
|
+
element.title = this.t(key);
|
|
608
|
+
});
|
|
592
609
|
|
|
593
|
-
|
|
594
|
-
|
|
610
|
+
const autoOption = this.controls.querySelector('.quality-option[data-quality="auto"]');
|
|
611
|
+
if (autoOption) {
|
|
612
|
+
autoOption.textContent = this.t('auto');
|
|
595
613
|
}
|
|
614
|
+
} catch (error) {
|
|
615
|
+
if (this.options.debug) console.warn('Errore aggiornamento tooltip:', error);
|
|
596
616
|
}
|
|
617
|
+
}
|
|
597
618
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
619
|
+
setLanguage(lang) {
|
|
620
|
+
if (this.isI18nAvailable()) {
|
|
601
621
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
const autoOption = this.controls.querySelector('.quality-option[data-quality="auto"]');
|
|
608
|
-
if (autoOption) {
|
|
609
|
-
autoOption.textContent = this.t('auto');
|
|
622
|
+
if (VideoPlayerTranslations.setLanguage(lang)) {
|
|
623
|
+
this.updateTooltips();
|
|
624
|
+
return true;
|
|
610
625
|
}
|
|
611
626
|
} catch (error) {
|
|
612
|
-
if (this.options.debug) console.warn('Errore
|
|
627
|
+
if (this.options.debug) console.warn('Errore cambio lingua:', error);
|
|
613
628
|
}
|
|
614
629
|
}
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
615
632
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
633
|
+
setVideoTitle(title) {
|
|
634
|
+
this.options.videoTitle = title || '';
|
|
635
|
+
|
|
636
|
+
if (this.titleOverlay) {
|
|
637
|
+
const titleElement = this.titleOverlay.querySelector('.title-text');
|
|
638
|
+
if (titleElement) {
|
|
639
|
+
titleElement.textContent = this.options.videoTitle;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (title) {
|
|
643
|
+
this.showTitleOverlay();
|
|
644
|
+
|
|
645
|
+
if (!this.options.persistentTitle) {
|
|
646
|
+
this.clearTitleTimeout();
|
|
647
|
+
this.titleTimeout = setTimeout(() => {
|
|
648
|
+
this.hideTitleOverlay();
|
|
649
|
+
}, 3000);
|
|
625
650
|
}
|
|
626
651
|
}
|
|
627
|
-
return false;
|
|
628
652
|
}
|
|
629
653
|
|
|
630
|
-
|
|
631
|
-
|
|
654
|
+
return this;
|
|
655
|
+
}
|
|
632
656
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
titleElement.textContent = this.options.videoTitle;
|
|
637
|
-
}
|
|
657
|
+
getVideoTitle() {
|
|
658
|
+
return this.options.videoTitle;
|
|
659
|
+
}
|
|
638
660
|
|
|
639
|
-
|
|
640
|
-
|
|
661
|
+
setPersistentTitle(persistent) {
|
|
662
|
+
this.options.persistentTitle = persistent;
|
|
641
663
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
664
|
+
if (this.titleOverlay && this.options.videoTitle) {
|
|
665
|
+
if (persistent) {
|
|
666
|
+
this.showTitleOverlay();
|
|
667
|
+
this.clearTitleTimeout();
|
|
668
|
+
} else {
|
|
669
|
+
this.titleOverlay.classList.remove('persistent');
|
|
670
|
+
if (this.titleOverlay.classList.contains('show')) {
|
|
671
|
+
this.clearTitleTimeout();
|
|
672
|
+
this.titleTimeout = setTimeout(() => {
|
|
673
|
+
this.hideTitleOverlay();
|
|
674
|
+
}, 3000);
|
|
648
675
|
}
|
|
649
676
|
}
|
|
677
|
+
}
|
|
650
678
|
|
|
651
|
-
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
enableTitleOverlay() {
|
|
683
|
+
if (!this.titleOverlay && !this.options.showTitleOverlay) {
|
|
684
|
+
this.options.showTitleOverlay = true;
|
|
685
|
+
this.createTitleOverlay();
|
|
652
686
|
}
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
653
689
|
|
|
654
|
-
|
|
655
|
-
|
|
690
|
+
disableTitleOverlay() {
|
|
691
|
+
if (this.titleOverlay) {
|
|
692
|
+
this.titleOverlay.remove();
|
|
693
|
+
this.titleOverlay = null;
|
|
656
694
|
}
|
|
695
|
+
this.options.showTitleOverlay = false;
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
657
698
|
|
|
658
|
-
|
|
659
|
-
|
|
699
|
+
getUniqueId() {
|
|
700
|
+
return Math.random().toString(36).substr(2, 9);
|
|
701
|
+
}
|
|
660
702
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
703
|
+
initializeElements() {
|
|
704
|
+
this.progressContainer = this.controls?.querySelector('.progress-container');
|
|
705
|
+
this.progressFilled = this.controls?.querySelector('.progress-filled');
|
|
706
|
+
this.progressBuffer = this.controls?.querySelector('.progress-buffer');
|
|
707
|
+
this.progressHandle = this.controls?.querySelector('.progress-handle');
|
|
708
|
+
this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
|
|
709
|
+
|
|
710
|
+
this.playPauseBtn = this.controls?.querySelector('.play-pause-btn');
|
|
711
|
+
this.muteBtn = this.controls?.querySelector('.mute-btn');
|
|
712
|
+
this.fullscreenBtn = this.controls?.querySelector('.fullscreen-btn');
|
|
713
|
+
this.speedBtn = this.controls?.querySelector('.speed-btn');
|
|
714
|
+
this.qualityBtn = this.controls?.querySelector('.quality-btn');
|
|
715
|
+
this.pipBtn = this.controls?.querySelector('.pip-btn');
|
|
716
|
+
this.subtitlesBtn = this.controls?.querySelector('.subtitles-btn');
|
|
717
|
+
this.playlistPrevBtn = this.controls?.querySelector('.playlist-prev-btn');
|
|
718
|
+
this.playlistNextBtn = this.controls?.querySelector('.playlist-next-btn');
|
|
719
|
+
|
|
720
|
+
this.playIcon = this.controls?.querySelector('.play-icon');
|
|
721
|
+
this.pauseIcon = this.controls?.querySelector('.pause-icon');
|
|
722
|
+
this.volumeIcon = this.controls?.querySelector('.volume-icon');
|
|
723
|
+
this.muteIcon = this.controls?.querySelector('.mute-icon');
|
|
724
|
+
this.fullscreenIcon = this.controls?.querySelector('.fullscreen-icon');
|
|
725
|
+
this.exitFullscreenIcon = this.controls?.querySelector('.exit-fullscreen-icon');
|
|
726
|
+
this.pipIcon = this.controls?.querySelector('.pip-icon');
|
|
727
|
+
this.pipExitIcon = this.controls?.querySelector('.pip-exit-icon');
|
|
728
|
+
|
|
729
|
+
this.volumeSlider = this.controls?.querySelector('.volume-slider');
|
|
730
|
+
this.currentTimeEl = this.controls?.querySelector('.current-time');
|
|
731
|
+
this.durationEl = this.controls?.querySelector('.duration');
|
|
732
|
+
this.speedMenu = this.controls?.querySelector('.speed-menu');
|
|
733
|
+
this.qualityMenu = this.controls?.querySelector('.quality-menu');
|
|
734
|
+
this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
|
|
735
|
+
}
|
|
675
736
|
|
|
676
|
-
|
|
677
|
-
|
|
737
|
+
// Generic method to close all active menus (works with plugins too)
|
|
738
|
+
closeAllMenus() {
|
|
739
|
+
// Find all elements with class ending in '-menu' that have 'active' class
|
|
740
|
+
const allMenus = this.controls?.querySelectorAll('[class*="-menu"].active');
|
|
741
|
+
allMenus?.forEach(menu => {
|
|
742
|
+
menu.classList.remove('active');
|
|
743
|
+
});
|
|
678
744
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
745
|
+
// Remove active state from all control buttons
|
|
746
|
+
const allButtons = this.controls?.querySelectorAll('.control-btn.active');
|
|
747
|
+
allButtons?.forEach(btn => {
|
|
748
|
+
btn.classList.remove('active');
|
|
749
|
+
});
|
|
750
|
+
}
|
|
686
751
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
752
|
+
// Generic menu toggle setup (works with core menus and plugin menus)
|
|
753
|
+
setupMenuToggles() {
|
|
754
|
+
// Delegate click events to control bar for any button with associated menu
|
|
755
|
+
if (this.controls) {
|
|
756
|
+
this.controls.addEventListener('click', (e) => {
|
|
757
|
+
// Find if clicked element is a control button or inside one
|
|
758
|
+
const button = e.target.closest('.control-btn');
|
|
759
|
+
|
|
760
|
+
if (!button) return;
|
|
761
|
+
|
|
762
|
+
// Get button classes to find associated menu
|
|
763
|
+
const buttonClasses = button.className.split(' ');
|
|
764
|
+
let menuClass = null;
|
|
765
|
+
|
|
766
|
+
// Find if this button has an associated menu (e.g., speed-btn -> speed-menu)
|
|
767
|
+
for (const cls of buttonClasses) {
|
|
768
|
+
if (cls.endsWith('-btn')) {
|
|
769
|
+
const menuName = cls.replace('-btn', '-menu');
|
|
770
|
+
const menu = this.controls.querySelector('.' + menuName);
|
|
771
|
+
if (menu) {
|
|
772
|
+
menuClass = menuName;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
695
777
|
|
|
696
|
-
|
|
697
|
-
return Math.random().toString(36).substr(2, 9);
|
|
698
|
-
}
|
|
778
|
+
if (!menuClass) return;
|
|
699
779
|
|
|
700
|
-
|
|
701
|
-
this.progressContainer = this.controls?.querySelector('.progress-container');
|
|
702
|
-
this.progressFilled = this.controls?.querySelector('.progress-filled');
|
|
703
|
-
this.progressBuffer = this.controls?.querySelector('.progress-buffer');
|
|
704
|
-
this.progressHandle = this.controls?.querySelector('.progress-handle');
|
|
705
|
-
this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
|
|
780
|
+
e.stopPropagation();
|
|
706
781
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
this.speedBtn = this.controls?.querySelector('.speed-btn');
|
|
711
|
-
this.qualityBtn = this.controls?.querySelector('.quality-btn');
|
|
712
|
-
this.pipBtn = this.controls?.querySelector('.pip-btn');
|
|
713
|
-
this.subtitlesBtn = this.controls?.querySelector('.subtitles-btn');
|
|
714
|
-
this.playlistPrevBtn = this.controls?.querySelector('.playlist-prev-btn');
|
|
715
|
-
this.playlistNextBtn = this.controls?.querySelector('.playlist-next-btn');
|
|
782
|
+
// Get the menu element
|
|
783
|
+
const menu = this.controls.querySelector('.' + menuClass);
|
|
784
|
+
const isOpen = menu.classList.contains('active');
|
|
716
785
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
this.volumeIcon = this.controls?.querySelector('.volume-icon');
|
|
720
|
-
this.muteIcon = this.controls?.querySelector('.mute-icon');
|
|
721
|
-
this.fullscreenIcon = this.controls?.querySelector('.fullscreen-icon');
|
|
722
|
-
this.exitFullscreenIcon = this.controls?.querySelector('.exit-fullscreen-icon');
|
|
723
|
-
this.pipIcon = this.controls?.querySelector('.pip-icon');
|
|
724
|
-
this.pipExitIcon = this.controls?.querySelector('.pip-exit-icon');
|
|
786
|
+
// Close all menus first
|
|
787
|
+
this.closeAllMenus();
|
|
725
788
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
789
|
+
// If menu was closed, open it
|
|
790
|
+
if (!isOpen) {
|
|
791
|
+
menu.classList.add('active');
|
|
792
|
+
button.classList.add('active');
|
|
793
|
+
}
|
|
794
|
+
});
|
|
732
795
|
}
|
|
733
796
|
|
|
734
|
-
|
|
735
|
-
|
|
797
|
+
// Close menus when clicking outside controls
|
|
798
|
+
document.addEventListener('click', (e) => {
|
|
799
|
+
if (!this.controls?.contains(e.target)) {
|
|
800
|
+
this.closeAllMenus();
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
}
|
|
736
804
|
|
|
737
|
-
|
|
738
|
-
|
|
805
|
+
updateVolumeSliderVisual() {
|
|
806
|
+
if (!this.video || !this.container) return;
|
|
739
807
|
|
|
740
|
-
|
|
808
|
+
const volume = this.video.muted ? 0 : this.video.volume;
|
|
809
|
+
const percentage = Math.round(volume * 100);
|
|
741
810
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
811
|
+
this.container.style.setProperty('--player-volume-fill', percentage + '%');
|
|
812
|
+
|
|
813
|
+
if (this.volumeSlider) {
|
|
814
|
+
this.volumeSlider.value = percentage;
|
|
745
815
|
}
|
|
816
|
+
}
|
|
746
817
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
818
|
+
createVolumeTooltip() {
|
|
819
|
+
const volumeContainer = this.controls?.querySelector('.volume-container');
|
|
820
|
+
if (!volumeContainer || volumeContainer.querySelector('.volume-tooltip')) {
|
|
821
|
+
return; // Tooltip already present
|
|
822
|
+
}
|
|
752
823
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
824
|
+
const tooltip = document.createElement('div');
|
|
825
|
+
tooltip.className = 'volume-tooltip';
|
|
826
|
+
tooltip.textContent = '50%';
|
|
827
|
+
volumeContainer.appendChild(tooltip);
|
|
757
828
|
|
|
758
|
-
|
|
829
|
+
this.volumeTooltip = tooltip;
|
|
759
830
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
831
|
+
if (this.options.debug) {
|
|
832
|
+
console.log('Dynamic volume tooltip created');
|
|
763
833
|
}
|
|
834
|
+
}
|
|
764
835
|
|
|
765
|
-
|
|
766
|
-
|
|
836
|
+
updateVolumeTooltip() {
|
|
837
|
+
if (!this.volumeTooltip || !this.video) return;
|
|
767
838
|
|
|
768
|
-
|
|
769
|
-
|
|
839
|
+
const volume = Math.round(this.video.volume * 100);
|
|
840
|
+
this.volumeTooltip.textContent = volume + '%';
|
|
770
841
|
|
|
771
|
-
|
|
772
|
-
|
|
842
|
+
// Aggiorna la posizione del tooltip
|
|
843
|
+
this.updateVolumeTooltipPosition(this.video.volume);
|
|
773
844
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
845
|
+
if (this.options.debug) {
|
|
846
|
+
console.log('Volume tooltip updated:', volume + '%');
|
|
777
847
|
}
|
|
848
|
+
}
|
|
778
849
|
|
|
779
|
-
|
|
780
|
-
|
|
850
|
+
updateVolumeTooltipPosition(volumeValue = null) {
|
|
851
|
+
if (!this.volumeTooltip || !this.video) return;
|
|
781
852
|
|
|
782
|
-
|
|
783
|
-
|
|
853
|
+
const volumeSlider = this.controls?.querySelector('.volume-slider');
|
|
854
|
+
if (!volumeSlider) return;
|
|
784
855
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
856
|
+
// If no volume provided, use current volume
|
|
857
|
+
if (volumeValue === null) {
|
|
858
|
+
volumeValue = this.video.volume;
|
|
859
|
+
}
|
|
789
860
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
861
|
+
// Calcola la posizione esatta del thumb
|
|
862
|
+
const sliderRect = volumeSlider.getBoundingClientRect();
|
|
863
|
+
const sliderWidth = sliderRect.width;
|
|
793
864
|
|
|
794
|
-
|
|
795
|
-
|
|
865
|
+
// Thumb size is typically 14px (as defined in CSS)
|
|
866
|
+
const thumbSize = 14; // var(--player-volume-handle-size)
|
|
796
867
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
868
|
+
// Calcola la posizione del centro del thumb
|
|
869
|
+
// Il thumb si muove da thumbSize/2 a (sliderWidth - thumbSize/2)
|
|
870
|
+
const availableWidth = sliderWidth - thumbSize;
|
|
871
|
+
const thumbCenterPosition = (thumbSize / 2) + (availableWidth * volumeValue);
|
|
801
872
|
|
|
802
|
-
|
|
803
|
-
|
|
873
|
+
// Converti in percentuale relativa al container dello slider
|
|
874
|
+
const percentage = (thumbCenterPosition / sliderWidth) * 100;
|
|
804
875
|
|
|
805
|
-
|
|
806
|
-
|
|
876
|
+
// Posiziona il tooltip
|
|
877
|
+
this.volumeTooltip.style.left = percentage + '%';
|
|
807
878
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
}
|
|
879
|
+
if (this.options.debug) {
|
|
880
|
+
console.log('Volume tooltip position updated:', {
|
|
881
|
+
volumeValue: volumeValue,
|
|
882
|
+
percentage: percentage + '%',
|
|
883
|
+
thumbCenter: thumbCenterPosition,
|
|
884
|
+
sliderWidth: sliderWidth
|
|
885
|
+
});
|
|
816
886
|
}
|
|
887
|
+
}
|
|
817
888
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
this.setupVolumeTooltipEvents();
|
|
889
|
+
initVolumeTooltip() {
|
|
890
|
+
this.createVolumeTooltip();
|
|
821
891
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
892
|
+
// Set initial position immediately
|
|
893
|
+
setTimeout(() => {
|
|
894
|
+
if (this.volumeTooltip && this.video) {
|
|
895
|
+
this.updateVolumeTooltipPosition(this.video.volume);
|
|
896
|
+
this.updateVolumeTooltip();
|
|
897
|
+
}
|
|
898
|
+
}, 50); // Shorter delay for faster initialization
|
|
899
|
+
}
|
|
830
900
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
901
|
+
updateVolumeSliderVisualWithTooltip() {
|
|
902
|
+
const volumeSlider = this.controls?.querySelector('.volume-slider');
|
|
903
|
+
if (!volumeSlider || !this.video) return;
|
|
834
904
|
|
|
835
|
-
|
|
836
|
-
|
|
905
|
+
const volume = this.video.volume || 0;
|
|
906
|
+
const percentage = Math.round(volume * 100);
|
|
837
907
|
|
|
838
|
-
|
|
908
|
+
volumeSlider.value = volume;
|
|
839
909
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
910
|
+
// Update CSS custom property per il riempimento visuale
|
|
911
|
+
const volumeFillPercentage = percentage + '%';
|
|
912
|
+
volumeSlider.style.setProperty('--player-volume-fill', volumeFillPercentage);
|
|
843
913
|
|
|
844
|
-
|
|
845
|
-
|
|
914
|
+
// Aggiorna anche il tooltip se presente (testo e posizione)
|
|
915
|
+
this.updateVolumeTooltip();
|
|
846
916
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
917
|
+
if (this.options.debug) {
|
|
918
|
+
console.log('Volume slider aggiornato:', {
|
|
919
|
+
volume: volume,
|
|
920
|
+
percentage: percentage,
|
|
921
|
+
fillPercentage: volumeFillPercentage
|
|
922
|
+
});
|
|
854
923
|
}
|
|
924
|
+
}
|
|
855
925
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
926
|
+
/**
|
|
927
|
+
* Set mobile volume slider visibility
|
|
928
|
+
* @param {String} mode - 'show' (horizontal popup) or 'hide' (no slider on mobile)
|
|
929
|
+
* @returns {Object} this
|
|
930
|
+
*/
|
|
931
|
+
setMobileVolumeSlider(mode) {
|
|
932
|
+
if (!['show', 'hide'].includes(mode)) {
|
|
933
|
+
if (this.options.debug) console.warn('Invalid mobile volume slider mode:', mode);
|
|
860
934
|
return this;
|
|
861
935
|
}
|
|
862
936
|
|
|
863
|
-
this.options.
|
|
937
|
+
this.options.mobileVolumeSlider = mode;
|
|
864
938
|
const volumeContainer = this.controls?.querySelector('.volume-container');
|
|
865
939
|
if (volumeContainer) {
|
|
866
|
-
|
|
940
|
+
// Set data attribute for CSS to use
|
|
941
|
+
volumeContainer.setAttribute('data-mobile-slider', mode);
|
|
942
|
+
if (this.options.debug) console.log('Mobile volume slider set to:', mode);
|
|
867
943
|
}
|
|
868
|
-
|
|
869
|
-
if (this.options.debug) console.log('Volume slider orientation set to:', orientation);
|
|
870
944
|
return this;
|
|
871
945
|
}
|
|
872
946
|
|
|
873
|
-
|
|
874
|
-
|
|
947
|
+
/**
|
|
948
|
+
* Get mobile volume slider mode
|
|
949
|
+
* @returns {String} Current mobile volume slider mode
|
|
950
|
+
*/
|
|
951
|
+
getMobileVolumeSlider() {
|
|
952
|
+
return this.options.mobileVolumeSlider;
|
|
875
953
|
}
|
|
876
954
|
|
|
955
|
+
initVolumeTooltip() {
|
|
877
956
|
|
|
878
|
-
|
|
957
|
+
this.createVolumeTooltip();
|
|
879
958
|
|
|
880
|
-
|
|
959
|
+
setTimeout(() => {
|
|
960
|
+
this.updateVolumeTooltip();
|
|
961
|
+
}, 200);
|
|
881
962
|
|
|
882
|
-
|
|
883
|
-
|
|
963
|
+
if (this.options.debug) {
|
|
964
|
+
console.log('Dynamic volume tooltip inizializzation');
|
|
965
|
+
}
|
|
966
|
+
}
|
|
884
967
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
}, 200);
|
|
968
|
+
setupSeekTooltip() {
|
|
969
|
+
if (!this.options.showSeekTooltip || !this.progressContainer || !this.seekTooltip) return;
|
|
888
970
|
|
|
889
|
-
|
|
890
|
-
|
|
971
|
+
this.progressContainer.addEventListener('mouseenter', () => {
|
|
972
|
+
if (this.seekTooltip) {
|
|
973
|
+
this.seekTooltip.classList.add('visible');
|
|
891
974
|
}
|
|
892
|
-
}
|
|
975
|
+
});
|
|
893
976
|
|
|
894
|
-
|
|
895
|
-
if (
|
|
977
|
+
this.progressContainer.addEventListener('mouseleave', () => {
|
|
978
|
+
if (this.seekTooltip) {
|
|
979
|
+
this.seekTooltip.classList.remove('visible');
|
|
980
|
+
}
|
|
981
|
+
});
|
|
896
982
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
});
|
|
983
|
+
this.progressContainer.addEventListener('mousemove', (e) => {
|
|
984
|
+
this.updateSeekTooltip(e);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
902
987
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
this.seekTooltip.classList.remove('visible');
|
|
906
|
-
}
|
|
907
|
-
});
|
|
988
|
+
updateSeekTooltip(e) {
|
|
989
|
+
if (!this.seekTooltip || !this.progressContainer || !this.video || !this.video.duration) return;
|
|
908
990
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
991
|
+
const rect = this.progressContainer.getBoundingClientRect();
|
|
992
|
+
const clickX = e.clientX - rect.left;
|
|
993
|
+
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
994
|
+
const targetTime = percentage * this.video.duration;
|
|
913
995
|
|
|
914
|
-
|
|
915
|
-
if (!this.seekTooltip || !this.progressContainer || !this.video || !this.video.duration) return;
|
|
996
|
+
this.seekTooltip.textContent = this.formatTime(targetTime);
|
|
916
997
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
920
|
-
const targetTime = percentage * this.video.duration;
|
|
998
|
+
const tooltipRect = this.seekTooltip.getBoundingClientRect();
|
|
999
|
+
let leftPosition = clickX;
|
|
921
1000
|
|
|
922
|
-
|
|
1001
|
+
const tooltipWidth = tooltipRect.width || 50;
|
|
1002
|
+
const containerWidth = rect.width;
|
|
923
1003
|
|
|
924
|
-
|
|
925
|
-
let leftPosition = clickX;
|
|
1004
|
+
leftPosition = Math.max(tooltipWidth / 2, Math.min(containerWidth - tooltipWidth / 2, clickX));
|
|
926
1005
|
|
|
927
|
-
|
|
928
|
-
|
|
1006
|
+
this.seekTooltip.style.left = leftPosition + 'px';
|
|
1007
|
+
}
|
|
929
1008
|
|
|
930
|
-
|
|
1009
|
+
play() {
|
|
1010
|
+
if (!this.video || this.isChangingQuality) return;
|
|
931
1011
|
|
|
932
|
-
|
|
933
|
-
|
|
1012
|
+
this.video.play().catch(err => {
|
|
1013
|
+
if (this.options.debug) console.log('Play failed:', err);
|
|
1014
|
+
});
|
|
934
1015
|
|
|
935
|
-
|
|
936
|
-
|
|
1016
|
+
if (this.playIcon) this.playIcon.classList.add('hidden');
|
|
1017
|
+
if (this.pauseIcon) this.pauseIcon.classList.remove('hidden');
|
|
937
1018
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1019
|
+
// Trigger event played
|
|
1020
|
+
this.triggerEvent('played', {
|
|
1021
|
+
currentTime: this.getCurrentTime(),
|
|
1022
|
+
duration: this.getDuration()
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
941
1025
|
|
|
942
|
-
|
|
943
|
-
|
|
1026
|
+
pause() {
|
|
1027
|
+
if (!this.video) return;
|
|
944
1028
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
duration: this.getDuration()
|
|
949
|
-
});
|
|
950
|
-
}
|
|
1029
|
+
this.video.pause();
|
|
1030
|
+
if (this.playIcon) this.playIcon.classList.remove('hidden');
|
|
1031
|
+
if (this.pauseIcon) this.pauseIcon.classList.add('hidden');
|
|
951
1032
|
|
|
952
|
-
|
|
953
|
-
|
|
1033
|
+
// Trigger paused event
|
|
1034
|
+
this.triggerEvent('paused', {
|
|
1035
|
+
currentTime: this.getCurrentTime(),
|
|
1036
|
+
duration: this.getDuration()
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
954
1039
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (this.pauseIcon) this.pauseIcon.classList.add('hidden');
|
|
1040
|
+
updateVolume(value) {
|
|
1041
|
+
if (!this.video) return;
|
|
958
1042
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
currentTime: this.getCurrentTime(),
|
|
962
|
-
duration: this.getDuration()
|
|
963
|
-
});
|
|
964
|
-
}
|
|
1043
|
+
const previousVolume = this.video.volume;
|
|
1044
|
+
const previousMuted = this.video.muted;
|
|
965
1045
|
|
|
966
|
-
|
|
967
|
-
if (!this.video) return;
|
|
1046
|
+
this.video.volume = Math.max(0, Math.min(1, value / 100));
|
|
968
1047
|
|
|
969
|
-
|
|
970
|
-
|
|
1048
|
+
if (this.video.volume > 0 && this.video.muted) {
|
|
1049
|
+
this.video.muted = false;
|
|
1050
|
+
}
|
|
971
1051
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
this.initVolumeTooltip();
|
|
1052
|
+
if (this.volumeSlider) this.volumeSlider.value = value;
|
|
1053
|
+
this.updateMuteButton();
|
|
1054
|
+
this.updateVolumeSliderVisual();
|
|
1055
|
+
this.initVolumeTooltip();
|
|
977
1056
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
}
|
|
1057
|
+
// Triggers volumechange event if there is a significant change
|
|
1058
|
+
if (Math.abs(previousVolume - this.video.volume) > 0.01 || previousMuted !== this.video.muted) {
|
|
1059
|
+
this.triggerEvent('volumechange', {
|
|
1060
|
+
volume: this.getVolume(),
|
|
1061
|
+
muted: this.isMuted(),
|
|
1062
|
+
previousVolume: previousVolume,
|
|
1063
|
+
previousMuted: previousMuted
|
|
1064
|
+
});
|
|
987
1065
|
}
|
|
1066
|
+
}
|
|
988
1067
|
|
|
989
|
-
|
|
990
|
-
|
|
1068
|
+
changeVolume(delta) {
|
|
1069
|
+
if (!this.video) return;
|
|
991
1070
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1071
|
+
const newVolume = Math.max(0, Math.min(1, this.video.volume + delta));
|
|
1072
|
+
this.updateVolume(newVolume * 100);
|
|
1073
|
+
this.updateVolumeSliderVisual();
|
|
1074
|
+
this.initVolumeTooltip();
|
|
1075
|
+
}
|
|
997
1076
|
|
|
998
|
-
|
|
999
|
-
|
|
1077
|
+
updateProgress() {
|
|
1078
|
+
if (!this.video || !this.progressFilled || !this.progressHandle || this.isUserSeeking) return;
|
|
1000
1079
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1080
|
+
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
1081
|
+
const progress = (this.video.currentTime / this.video.duration) * 100;
|
|
1082
|
+
this.progressFilled.style.width = progress + '%';
|
|
1083
|
+
this.progressHandle.style.left = progress + '%';
|
|
1084
|
+
}
|
|
1006
1085
|
|
|
1007
|
-
|
|
1086
|
+
this.updateTimeDisplay();
|
|
1008
1087
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1088
|
+
// Trigger timeupdate event (with throttling to avoid too many events)
|
|
1089
|
+
if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 250) {
|
|
1090
|
+
this.triggerEvent('timeupdate', {
|
|
1091
|
+
currentTime: this.getCurrentTime(),
|
|
1092
|
+
duration: this.getDuration(),
|
|
1093
|
+
progress: (this.getCurrentTime() / this.getDuration()) * 100 || 0
|
|
1094
|
+
});
|
|
1095
|
+
this.lastTimeUpdate = Date.now();
|
|
1018
1096
|
}
|
|
1097
|
+
}
|
|
1019
1098
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1099
|
+
updateBuffer() {
|
|
1100
|
+
if (!this.video || !this.progressBuffer) return;
|
|
1022
1101
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1028
|
-
} catch (error) {
|
|
1029
|
-
if (this.options.debug) console.log('Buffer update error (non-critical):', error);
|
|
1102
|
+
try {
|
|
1103
|
+
if (this.video.buffered && this.video.buffered.length > 0 && this.video.duration) {
|
|
1104
|
+
const buffered = (this.video.buffered.end(0) / this.video.duration) * 100;
|
|
1105
|
+
this.progressBuffer.style.width = buffered + '%';
|
|
1030
1106
|
}
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
if (this.options.debug) console.log('Buffer update error (non-critical):', error);
|
|
1031
1109
|
}
|
|
1110
|
+
}
|
|
1032
1111
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1112
|
+
startSeeking(e) {
|
|
1113
|
+
if (this.isChangingQuality) return;
|
|
1035
1114
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1115
|
+
this.isUserSeeking = true;
|
|
1116
|
+
this.seek(e);
|
|
1117
|
+
e.preventDefault();
|
|
1039
1118
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
}
|
|
1119
|
+
// Show controls during seeking
|
|
1120
|
+
if (this.options.autoHide && this.autoHideInitialized) {
|
|
1121
|
+
this.showControlsNow();
|
|
1122
|
+
this.resetAutoHideTimer();
|
|
1045
1123
|
}
|
|
1124
|
+
}
|
|
1046
1125
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1126
|
+
continueSeeking(e) {
|
|
1127
|
+
if (this.isUserSeeking && !this.isChangingQuality) {
|
|
1128
|
+
this.seek(e);
|
|
1051
1129
|
}
|
|
1130
|
+
}
|
|
1052
1131
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1132
|
+
endSeeking() {
|
|
1133
|
+
this.isUserSeeking = false;
|
|
1134
|
+
}
|
|
1056
1135
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1136
|
+
seek(e) {
|
|
1137
|
+
if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
|
|
1059
1138
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1139
|
+
const rect = this.progressContainer.getBoundingClientRect();
|
|
1140
|
+
const clickX = e.clientX - rect.left;
|
|
1141
|
+
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
1063
1142
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
}
|
|
1143
|
+
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
1144
|
+
this.video.currentTime = percentage * this.video.duration;
|
|
1145
|
+
const progress = percentage * 100;
|
|
1146
|
+
this.progressFilled.style.width = progress + '%';
|
|
1147
|
+
this.progressHandle.style.left = progress + '%';
|
|
1070
1148
|
}
|
|
1149
|
+
}
|
|
1071
1150
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1151
|
+
updateDuration() {
|
|
1152
|
+
if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
|
|
1153
|
+
this.durationEl.textContent = this.formatTime(this.video.duration);
|
|
1076
1154
|
}
|
|
1155
|
+
}
|
|
1077
1156
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
const speed = parseFloat(e.target.getAttribute('data-speed'));
|
|
1082
|
-
if (speed && speed > 0) {
|
|
1083
|
-
this.video.playbackRate = speed;
|
|
1084
|
-
if (this.speedBtn) this.speedBtn.textContent = speed + 'x';
|
|
1157
|
+
changeSpeed(e) {
|
|
1158
|
+
if (!this.video || !e.target.classList.contains('speed-option') || this.isChangingQuality) return;
|
|
1085
1159
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
e.target.classList.add('active');
|
|
1091
|
-
}
|
|
1160
|
+
const speed = parseFloat(e.target.getAttribute('data-speed'));
|
|
1161
|
+
if (speed && speed > 0) {
|
|
1162
|
+
this.video.playbackRate = speed;
|
|
1163
|
+
if (this.speedBtn) this.speedBtn.textContent = speed + 'x';
|
|
1092
1164
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
speed: speed,
|
|
1097
|
-
previousSpeed: previousSpeed
|
|
1165
|
+
if (this.speedMenu) {
|
|
1166
|
+
this.speedMenu.querySelectorAll('.speed-option').forEach(option => {
|
|
1167
|
+
option.classList.remove('active');
|
|
1098
1168
|
});
|
|
1169
|
+
e.target.classList.add('active');
|
|
1099
1170
|
}
|
|
1171
|
+
|
|
1172
|
+
// Trigger speedchange event
|
|
1173
|
+
const previousSpeed = this.video.playbackRate;
|
|
1174
|
+
this.triggerEvent('speedchange', {
|
|
1175
|
+
speed: speed,
|
|
1176
|
+
previousSpeed: previousSpeed
|
|
1177
|
+
});
|
|
1100
1178
|
}
|
|
1179
|
+
}
|
|
1101
1180
|
|
|
1102
1181
|
onVideoEnded() {
|
|
1103
1182
|
if (this.playIcon) this.playIcon.classList.remove('hidden');
|
|
@@ -1123,217 +1202,270 @@ onVideoEnded() {
|
|
|
1123
1202
|
});
|
|
1124
1203
|
}
|
|
1125
1204
|
|
|
1126
|
-
|
|
1205
|
+
/**
|
|
1206
|
+
* Handle video loading errors (404, 503, network errors, etc.)
|
|
1207
|
+
* Triggers 'ended' event to allow proper cleanup and playlist continuation
|
|
1208
|
+
*/
|
|
1209
|
+
onVideoError(error) {
|
|
1210
|
+
if (this.options.debug) {
|
|
1211
|
+
console.error('Video loading error detected:', {
|
|
1212
|
+
error: error,
|
|
1213
|
+
code: this.video?.error?.code,
|
|
1214
|
+
message: this.video?.error?.message,
|
|
1215
|
+
src: this.video?.currentSrc || this.video?.src
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// Hide loading overlay
|
|
1220
|
+
this.hideLoading();
|
|
1221
|
+
if (this.initialLoading) {
|
|
1222
|
+
this.initialLoading.style.display = 'none';
|
|
1223
|
+
}
|
|
1127
1224
|
|
|
1128
|
-
|
|
1225
|
+
// Remove quality-changing class if present
|
|
1226
|
+
if (this.video?.classList) {
|
|
1227
|
+
this.video.classList.remove('quality-changing');
|
|
1228
|
+
}
|
|
1129
1229
|
|
|
1130
|
-
|
|
1230
|
+
// Reset changing quality flag
|
|
1231
|
+
this.isChangingQuality = false;
|
|
1131
1232
|
|
|
1132
|
-
|
|
1233
|
+
// Show controls to allow user interaction
|
|
1234
|
+
this.showControlsNow();
|
|
1133
1235
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
}
|
|
1236
|
+
// Optional: Show poster if available
|
|
1237
|
+
if (this.options.showPosterOnEnd && this.posterOverlay) {
|
|
1238
|
+
this.showPoster();
|
|
1138
1239
|
}
|
|
1139
1240
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1241
|
+
// Trigger 'ended' event to allow proper cleanup
|
|
1242
|
+
// This allows playlist to continue or other error handling
|
|
1243
|
+
this.triggerEvent('ended', {
|
|
1244
|
+
currentTime: this.getCurrentTime(),
|
|
1245
|
+
duration: this.getDuration(),
|
|
1246
|
+
error: true,
|
|
1247
|
+
errorCode: this.video?.error?.code,
|
|
1248
|
+
errorMessage: this.video?.error?.message,
|
|
1249
|
+
playlistInfo: this.getPlaylistInfo()
|
|
1250
|
+
});
|
|
1143
1251
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
this.video.muted = muted;
|
|
1147
|
-
this.updateMuteButton();
|
|
1148
|
-
this.updateVolumeSliderVisual();
|
|
1149
|
-
this.initVolumeTooltip();
|
|
1150
|
-
}
|
|
1252
|
+
if (this.options.debug) {
|
|
1253
|
+
console.log('Video error handled - triggered ended event');
|
|
1151
1254
|
}
|
|
1255
|
+
}
|
|
1152
1256
|
|
|
1153
|
-
getPlaybackRate() { return this.video ? this.video.playbackRate || 1 : 1; }
|
|
1154
1257
|
|
|
1155
|
-
|
|
1258
|
+
getCurrentTime() { return this.video ? this.video.currentTime || 0 : 0; }
|
|
1156
1259
|
|
|
1157
|
-
|
|
1260
|
+
setCurrentTime(time) { if (this.video && typeof time === 'number' && time >= 0 && !this.isChangingQuality) { this.video.currentTime = time; } }
|
|
1158
1261
|
|
|
1159
|
-
|
|
1160
|
-
return this.isI18nAvailable() ?
|
|
1161
|
-
VideoPlayerTranslations.getCurrentLanguage() : 'en';
|
|
1162
|
-
}
|
|
1262
|
+
getDuration() { return this.video && this.video.duration ? this.video.duration : 0; }
|
|
1163
1263
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1264
|
+
getVolume() { return this.video ? this.video.volume || 0 : 0; }
|
|
1265
|
+
|
|
1266
|
+
setVolume(volume) {
|
|
1267
|
+
if (typeof volume === 'number' && volume >= 0 && volume <= 1) {
|
|
1268
|
+
this.updateVolume(volume * 100);
|
|
1167
1269
|
}
|
|
1270
|
+
}
|
|
1168
1271
|
|
|
1169
|
-
|
|
1170
|
-
if (!this.options.brandLogoEnabled || !this.options.brandLogoUrl) return;
|
|
1272
|
+
isPaused() { return this.video ? this.video.paused : true; }
|
|
1171
1273
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1274
|
+
isMuted() { return this.video ? this.video.muted : false; }
|
|
1275
|
+
|
|
1276
|
+
setMuted(muted) {
|
|
1277
|
+
if (this.video && typeof muted === 'boolean') {
|
|
1278
|
+
this.video.muted = muted;
|
|
1279
|
+
this.updateMuteButton();
|
|
1280
|
+
this.updateVolumeSliderVisual();
|
|
1281
|
+
this.initVolumeTooltip();
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1174
1284
|
|
|
1175
|
-
|
|
1176
|
-
const logo = document.createElement('img');
|
|
1177
|
-
logo.className = 'brand-logo';
|
|
1178
|
-
logo.src = this.options.brandLogoUrl;
|
|
1179
|
-
logo.alt = this.t('brand_logo');
|
|
1285
|
+
getPlaybackRate() { return this.video ? this.video.playbackRate || 1 : 1; }
|
|
1180
1286
|
|
|
1181
|
-
|
|
1182
|
-
logo.onerror = () => {
|
|
1183
|
-
if (this.options.debug) console.warn('Brand logo failed to load:', this.options.brandLogoUrl);
|
|
1184
|
-
logo.style.display = 'none';
|
|
1185
|
-
};
|
|
1287
|
+
setPlaybackRate(rate) { if (this.video && typeof rate === 'number' && rate > 0 && !this.isChangingQuality) { this.video.playbackRate = rate; if (this.speedBtn) this.speedBtn.textContent = rate + 'x'; } }
|
|
1186
1288
|
|
|
1187
|
-
|
|
1188
|
-
if (this.options.debug) console.log('Brand logo loaded successfully');
|
|
1189
|
-
};
|
|
1289
|
+
isPictureInPictureActive() { return document.pictureInPictureElement === this.video; }
|
|
1190
1290
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
e.stopPropagation(); // Prevent video controls interference
|
|
1196
|
-
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1197
|
-
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1198
|
-
});
|
|
1199
|
-
} else {
|
|
1200
|
-
logo.style.cursor = 'default';
|
|
1201
|
-
}
|
|
1291
|
+
getCurrentLanguage() {
|
|
1292
|
+
return this.isI18nAvailable() ?
|
|
1293
|
+
VideoPlayerTranslations.getCurrentLanguage() : 'en';
|
|
1294
|
+
}
|
|
1202
1295
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1296
|
+
getSupportedLanguages() {
|
|
1297
|
+
return this.isI18nAvailable() ?
|
|
1298
|
+
VideoPlayerTranslations.getSupportedLanguages() : ['en'];
|
|
1299
|
+
}
|
|
1205
1300
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1301
|
+
createBrandLogo() {
|
|
1302
|
+
if (!this.options.brandLogoEnabled || !this.options.brandLogoUrl) return;
|
|
1303
|
+
|
|
1304
|
+
const controlsRight = this.controls?.querySelector('.controls-right');
|
|
1305
|
+
if (!controlsRight) return;
|
|
1306
|
+
|
|
1307
|
+
// Create brand logo image
|
|
1308
|
+
const logo = document.createElement('img');
|
|
1309
|
+
logo.className = 'brand-logo';
|
|
1310
|
+
logo.src = this.options.brandLogoUrl;
|
|
1311
|
+
logo.alt = this.t('brand_logo');
|
|
1312
|
+
|
|
1313
|
+
// Handle loading error
|
|
1314
|
+
logo.onerror = () => {
|
|
1315
|
+
if (this.options.debug) console.warn('Brand logo failed to load:', this.options.brandLogoUrl);
|
|
1316
|
+
logo.style.display = 'none';
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
logo.onload = () => {
|
|
1320
|
+
if (this.options.debug) console.log('Brand logo loaded successfully');
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
// Add click functionality if link URL is provided
|
|
1324
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1325
|
+
logo.style.cursor = 'pointer';
|
|
1326
|
+
logo.addEventListener('click', (e) => {
|
|
1327
|
+
e.stopPropagation(); // Prevent video controls interference
|
|
1328
|
+
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1329
|
+
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1330
|
+
});
|
|
1331
|
+
} else {
|
|
1332
|
+
logo.style.cursor = 'default';
|
|
1213
1333
|
}
|
|
1214
1334
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if (url) {
|
|
1218
|
-
this.options.brandLogoUrl = url;
|
|
1219
|
-
}
|
|
1220
|
-
if (linkUrl !== '') {
|
|
1221
|
-
this.options.brandLogoLinkUrl = linkUrl;
|
|
1222
|
-
}
|
|
1335
|
+
// Position the brand logo at the right of the controlbar (at the left of the buttons)
|
|
1336
|
+
controlsRight.insertBefore(logo, controlsRight.firstChild);
|
|
1223
1337
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1338
|
+
if (this.options.debug) {
|
|
1339
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1340
|
+
console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
|
|
1341
|
+
} else {
|
|
1342
|
+
console.log('Brand logo created (no link)');
|
|
1228
1343
|
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1229
1346
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1347
|
+
setBrandLogo(enabled, url = '', linkUrl = '') {
|
|
1348
|
+
this.options.brandLogoEnabled = enabled;
|
|
1349
|
+
if (url) {
|
|
1350
|
+
this.options.brandLogoUrl = url;
|
|
1351
|
+
}
|
|
1352
|
+
if (linkUrl !== '') {
|
|
1353
|
+
this.options.brandLogoLinkUrl = linkUrl;
|
|
1354
|
+
}
|
|
1234
1355
|
|
|
1235
|
-
|
|
1356
|
+
// Remove existing brand logo
|
|
1357
|
+
const existingLogo = this.controls?.querySelector('.brand-logo');
|
|
1358
|
+
if (existingLogo) {
|
|
1359
|
+
existingLogo.remove();
|
|
1236
1360
|
}
|
|
1237
1361
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
url: this.options.brandLogoUrl,
|
|
1242
|
-
linkUrl: this.options.brandLogoLinkUrl
|
|
1243
|
-
};
|
|
1362
|
+
// Recreate the logo if enabled
|
|
1363
|
+
if (enabled && this.options.brandLogoUrl) {
|
|
1364
|
+
this.createBrandLogo();
|
|
1244
1365
|
}
|
|
1245
1366
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (this.options.debug) console.error('🎵 New video element not found');
|
|
1249
|
-
return false;
|
|
1250
|
-
}
|
|
1367
|
+
return this;
|
|
1368
|
+
}
|
|
1251
1369
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1370
|
+
getBrandLogoSettings() {
|
|
1371
|
+
return {
|
|
1372
|
+
enabled: this.options.brandLogoEnabled,
|
|
1373
|
+
url: this.options.brandLogoUrl,
|
|
1374
|
+
linkUrl: this.options.brandLogoLinkUrl
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1254
1377
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}));
|
|
1378
|
+
switchToVideo(newVideoElement, shouldPlay = false) {
|
|
1379
|
+
if (!newVideoElement) {
|
|
1380
|
+
if (this.options.debug) console.error('🎵 New video element not found');
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1261
1383
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1384
|
+
// Pause current video
|
|
1385
|
+
this.video.pause();
|
|
1386
|
+
|
|
1387
|
+
// Get new video sources and qualities
|
|
1388
|
+
const newSources = Array.from(newVideoElement.querySelectorAll('source')).map(source => ({
|
|
1389
|
+
src: source.src,
|
|
1390
|
+
quality: source.getAttribute('data-quality') || 'auto',
|
|
1391
|
+
type: source.type || 'video/mp4'
|
|
1392
|
+
}));
|
|
1266
1393
|
|
|
1267
|
-
|
|
1268
|
-
if (this.options.
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1394
|
+
if (newSources.length === 0) {
|
|
1395
|
+
if (this.options.debug) console.error('🎵 New video has no sources');
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Check if new video is adaptive stream
|
|
1400
|
+
if (this.options.adaptiveStreaming && newSources.length > 0) {
|
|
1401
|
+
const firstSource = newSources[0];
|
|
1402
|
+
if (this.detectStreamType(firstSource.src)) {
|
|
1403
|
+
// Initialize adaptive streaming for new video
|
|
1404
|
+
this.initializeAdaptiveStreaming(firstSource.src).then((initialized) => {
|
|
1405
|
+
if (initialized && shouldPlay) {
|
|
1406
|
+
const playPromise = this.video.play();
|
|
1407
|
+
if (playPromise) {
|
|
1408
|
+
playPromise.catch(error => {
|
|
1409
|
+
if (this.options.debug) console.log('Autoplay prevented:', error);
|
|
1410
|
+
});
|
|
1280
1411
|
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
return true;
|
|
1284
1415
|
}
|
|
1416
|
+
}
|
|
1285
1417
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1418
|
+
// Update traditional video sources
|
|
1419
|
+
this.video.innerHTML = '';
|
|
1420
|
+
newSources.forEach(source => {
|
|
1421
|
+
const sourceEl = document.createElement('source');
|
|
1422
|
+
sourceEl.src = source.src;
|
|
1423
|
+
sourceEl.type = source.type;
|
|
1424
|
+
sourceEl.setAttribute('data-quality', source.quality);
|
|
1425
|
+
this.video.appendChild(sourceEl);
|
|
1426
|
+
});
|
|
1295
1427
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1428
|
+
// Update subtitles if present
|
|
1429
|
+
const newTracks = Array.from(newVideoElement.querySelectorAll('track'));
|
|
1430
|
+
newTracks.forEach(track => {
|
|
1431
|
+
const trackEl = document.createElement('track');
|
|
1432
|
+
trackEl.kind = track.kind;
|
|
1433
|
+
trackEl.src = track.src;
|
|
1434
|
+
trackEl.srclang = track.srclang;
|
|
1435
|
+
trackEl.label = track.label;
|
|
1436
|
+
if (track.default) trackEl.default = true;
|
|
1437
|
+
this.video.appendChild(trackEl);
|
|
1438
|
+
});
|
|
1307
1439
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1440
|
+
// Update video title
|
|
1441
|
+
const newTitle = newVideoElement.getAttribute('data-video-title');
|
|
1442
|
+
if (newTitle && this.options.showTitleOverlay) {
|
|
1443
|
+
this.options.videoTitle = newTitle;
|
|
1444
|
+
if (this.titleText) {
|
|
1445
|
+
this.titleText.textContent = newTitle;
|
|
1315
1446
|
}
|
|
1447
|
+
}
|
|
1316
1448
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1449
|
+
// Reload video
|
|
1450
|
+
this.video.load();
|
|
1319
1451
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1452
|
+
// Update qualities and quality selector
|
|
1453
|
+
this.collectVideoQualities();
|
|
1454
|
+
this.updateQualityMenu();
|
|
1323
1455
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
}
|
|
1456
|
+
// Play if needed
|
|
1457
|
+
if (shouldPlay) {
|
|
1458
|
+
const playPromise = this.video.play();
|
|
1459
|
+
if (playPromise) {
|
|
1460
|
+
playPromise.catch(error => {
|
|
1461
|
+
if (this.options.debug) console.log('🎵 Autoplay prevented:', error);
|
|
1462
|
+
});
|
|
1332
1463
|
}
|
|
1333
|
-
|
|
1334
|
-
return true;
|
|
1335
1464
|
}
|
|
1336
1465
|
|
|
1466
|
+
return true;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1337
1469
|
/**
|
|
1338
1470
|
* POSTER IMAGE MANAGEMENT
|
|
1339
1471
|
* Initialize and manage video poster image
|
|
@@ -1550,59 +1682,104 @@ isPosterVisible() {
|
|
|
1550
1682
|
}
|
|
1551
1683
|
|
|
1552
1684
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1685
|
+
loadScript(src) {
|
|
1686
|
+
return new Promise((resolve, reject) => {
|
|
1687
|
+
if (document.querySelector(`script[src="${src}"]`)) {
|
|
1688
|
+
resolve();
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
const script = document.createElement('script');
|
|
1693
|
+
script.src = src;
|
|
1694
|
+
script.onload = resolve;
|
|
1695
|
+
script.onerror = reject;
|
|
1696
|
+
document.head.appendChild(script);
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Set seek handle shape dynamically
|
|
1702
|
+
* @param {string} shape - Shape type: none, circle, square, diamond, arrow, triangle, heart, star
|
|
1703
|
+
* @returns {Object} this
|
|
1704
|
+
*/
|
|
1705
|
+
setSeekHandleShape(shape) {
|
|
1706
|
+
const validShapes = ['none', 'circle', 'square', 'diamond', 'arrow', 'triangle', 'heart', 'star'];
|
|
1559
1707
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1708
|
+
if (!validShapes.includes(shape)) {
|
|
1709
|
+
if (this.options.debug) console.warn('Invalid seek handle shape:', shape);
|
|
1710
|
+
return this;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
this.options.seekHandleShape = shape;
|
|
1714
|
+
|
|
1715
|
+
// Update handle class
|
|
1716
|
+
if (this.progressHandle) {
|
|
1717
|
+
// Remove all shape classes
|
|
1718
|
+
validShapes.forEach(s => {
|
|
1719
|
+
this.progressHandle.classList.remove(`progress-handle-${s}`);
|
|
1565
1720
|
});
|
|
1721
|
+
// Add new shape class
|
|
1722
|
+
this.progressHandle.classList.add(`progress-handle-${shape}`);
|
|
1566
1723
|
}
|
|
1567
1724
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
this.qualityMonitorInterval = null;
|
|
1572
|
-
}
|
|
1725
|
+
if (this.options.debug) console.log('Seek handle shape changed to:', shape);
|
|
1726
|
+
return this;
|
|
1727
|
+
}
|
|
1573
1728
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1729
|
+
/**
|
|
1730
|
+
* Get current seek handle shape
|
|
1731
|
+
* @returns {string} Current shape
|
|
1732
|
+
*/
|
|
1733
|
+
getSeekHandleShape() {
|
|
1734
|
+
return this.options.seekHandleShape;
|
|
1735
|
+
}
|
|
1578
1736
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1737
|
+
/**
|
|
1738
|
+
* Get available seek handle shapes
|
|
1739
|
+
* @returns {Array} Array of available shapes
|
|
1740
|
+
*/
|
|
1741
|
+
getAvailableSeekHandleShapes() {
|
|
1742
|
+
return ['none', 'circle', 'square', 'diamond', 'arrow', 'triangle', 'heart', 'star'];
|
|
1743
|
+
}
|
|
1582
1744
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1745
|
+
dispose() {
|
|
1746
|
+
if (this.qualityMonitorInterval) {
|
|
1747
|
+
clearInterval(this.qualityMonitorInterval);
|
|
1748
|
+
this.qualityMonitorInterval = null;
|
|
1749
|
+
}
|
|
1585
1750
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
this.loadingOverlay.remove();
|
|
1591
|
-
}
|
|
1592
|
-
if (this.titleOverlay) {
|
|
1593
|
-
this.titleOverlay.remove();
|
|
1594
|
-
}
|
|
1595
|
-
if (this.initialLoading) {
|
|
1596
|
-
this.initialLoading.remove();
|
|
1597
|
-
}
|
|
1751
|
+
if (this.autoHideTimer) {
|
|
1752
|
+
clearTimeout(this.autoHideTimer);
|
|
1753
|
+
this.autoHideTimer = null;
|
|
1754
|
+
}
|
|
1598
1755
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1756
|
+
this.cleanupQualityChange();
|
|
1757
|
+
this.clearControlsTimeout();
|
|
1758
|
+
this.clearTitleTimeout();
|
|
1759
|
+
|
|
1760
|
+
// Destroy adaptive streaming players
|
|
1761
|
+
this.destroyAdaptivePlayer();
|
|
1762
|
+
|
|
1763
|
+
if (this.controls) {
|
|
1764
|
+
this.controls.remove();
|
|
1765
|
+
}
|
|
1766
|
+
if (this.loadingOverlay) {
|
|
1767
|
+
this.loadingOverlay.remove();
|
|
1768
|
+
}
|
|
1769
|
+
if (this.titleOverlay) {
|
|
1770
|
+
this.titleOverlay.remove();
|
|
1771
|
+
}
|
|
1772
|
+
if (this.initialLoading) {
|
|
1773
|
+
this.initialLoading.remove();
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (this.video) {
|
|
1777
|
+
this.video.classList.remove('video-player');
|
|
1778
|
+
this.video.controls = true;
|
|
1779
|
+
this.video.style.visibility = '';
|
|
1780
|
+
this.video.style.opacity = '';
|
|
1781
|
+
this.video.style.pointerEvents = '';
|
|
1782
|
+
}
|
|
1606
1783
|
if (this.chapterMarkersContainer) {
|
|
1607
1784
|
this.chapterMarkersContainer.remove();
|
|
1608
1785
|
}
|
|
@@ -1614,37 +1791,37 @@ isPosterVisible() {
|
|
|
1614
1791
|
}
|
|
1615
1792
|
this.disposeAllPlugins();
|
|
1616
1793
|
|
|
1617
|
-
|
|
1794
|
+
}
|
|
1618
1795
|
|
|
1619
|
-
|
|
1796
|
+
/**
|
|
1620
1797
|
|
|
1621
|
-
|
|
1798
|
+
* Apply specified resolution mode to video
|
|
1622
1799
|
|
|
1623
|
-
|
|
1800
|
+
* @param {string} resolution - The resolution mode to apply
|
|
1624
1801
|
|
|
1625
|
-
|
|
1802
|
+
*/
|
|
1626
1803
|
|
|
1627
|
-
|
|
1804
|
+
/**
|
|
1628
1805
|
|
|
1629
|
-
|
|
1806
|
+
* Get currently set resolution
|
|
1630
1807
|
|
|
1631
|
-
|
|
1808
|
+
* @returns {string} Current resolution
|
|
1632
1809
|
|
|
1633
|
-
|
|
1810
|
+
*/
|
|
1634
1811
|
|
|
1635
|
-
|
|
1812
|
+
/**
|
|
1636
1813
|
|
|
1637
|
-
|
|
1814
|
+
* Initialize resolution from options value
|
|
1638
1815
|
|
|
1639
|
-
|
|
1816
|
+
*/
|
|
1640
1817
|
|
|
1641
|
-
|
|
1818
|
+
/**
|
|
1642
1819
|
|
|
1643
|
-
|
|
1820
|
+
* Restore resolution after quality change - internal method
|
|
1644
1821
|
|
|
1645
|
-
|
|
1822
|
+
* @private
|
|
1646
1823
|
|
|
1647
|
-
|
|
1824
|
+
*/
|
|
1648
1825
|
|
|
1649
1826
|
// Core methods for main class
|
|
1650
|
-
// All original functionality preserved exactly
|
|
1827
|
+
// All original functionality preserved exactly
|