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.
Files changed (38) hide show
  1. package/.github/workflows/codeql.yml +100 -0
  2. package/README.md +49 -58
  3. package/SECURITY.md +50 -0
  4. package/css/myetv-player.css +424 -219
  5. package/css/myetv-player.min.css +1 -1
  6. package/dist/myetv-player.js +1759 -1502
  7. package/dist/myetv-player.min.js +1705 -1469
  8. package/package.json +7 -1
  9. package/plugins/README.md +1016 -0
  10. package/plugins/cloudflare/README.md +1068 -0
  11. package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
  12. package/plugins/facebook/README.md +1024 -0
  13. package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
  14. package/plugins/gamepad-remote-controller/README.md +816 -0
  15. package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
  16. package/plugins/google-adsense-ads/README.md +1 -0
  17. package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
  18. package/plugins/google-ima-ads/README.md +1 -0
  19. package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
  20. package/plugins/twitch/README.md +1185 -0
  21. package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
  22. package/plugins/vast-vpaid-ads/README.md +1 -0
  23. package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
  24. package/plugins/vimeo/README.md +1416 -0
  25. package/plugins/vimeo/myetv-player-vimeo.js +640 -0
  26. package/plugins/youtube/README.md +851 -0
  27. package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
  28. package/scss/README.md +160 -0
  29. package/scss/_controls.scss +184 -30
  30. package/scss/_menus.scss +840 -672
  31. package/scss/_responsive.scss +67 -105
  32. package/scss/_volume.scss +67 -105
  33. package/src/README.md +559 -0
  34. package/src/controls.js +17 -5
  35. package/src/core.js +1237 -1060
  36. package/src/i18n.js +27 -1
  37. package/src/quality.js +478 -436
  38. package/src/subtitles.js +2 -2
package/src/quality.js CHANGED
@@ -2,141 +2,161 @@
2
2
  // Conservative modularization - original code preserved exactly
3
3
  // Created by https://www.myetv.tv https://oskarcosimo.com
4
4
 
5
- initializeQualityMonitoring() {
6
- this.qualityMonitorInterval = setInterval(() => {
5
+ initializeQualityMonitoring() {
6
+ this.qualityMonitorInterval = setInterval(() => {
7
+ if (!this.isChangingQuality) {
8
+ this.updateCurrentPlayingQuality();
9
+ }
10
+ }, 3000);
11
+
12
+ if (this.video) {
13
+ this.video.addEventListener('loadedmetadata', () => {
14
+ setTimeout(() => {
15
+ if (!this.isChangingQuality) {
16
+ this.updateCurrentPlayingQuality();
17
+ }
18
+ }, 100);
19
+ });
20
+
21
+ this.video.addEventListener('resize', () => {
7
22
  if (!this.isChangingQuality) {
8
23
  this.updateCurrentPlayingQuality();
9
24
  }
10
- }, 3000);
11
-
12
- if (this.video) {
13
- this.video.addEventListener('loadedmetadata', () => {
14
- setTimeout(() => {
15
- if (!this.isChangingQuality) {
16
- this.updateCurrentPlayingQuality();
17
- }
18
- }, 100);
19
- });
25
+ });
20
26
 
21
- this.video.addEventListener('resize', () => {
27
+ this.video.addEventListener('loadeddata', () => {
28
+ setTimeout(() => {
22
29
  if (!this.isChangingQuality) {
23
30
  this.updateCurrentPlayingQuality();
24
31
  }
25
- });
26
-
27
- this.video.addEventListener('loadeddata', () => {
28
- setTimeout(() => {
29
- if (!this.isChangingQuality) {
30
- this.updateCurrentPlayingQuality();
31
- }
32
- }, 1000);
33
- });
34
- }
32
+ }, 1000);
33
+ });
35
34
  }
35
+ }
36
36
 
37
- getCurrentPlayingQuality() {
38
- if (!this.video) return null;
39
-
40
- if (this.video.currentSrc && this.qualities && this.qualities.length > 0) {
41
- const currentSource = this.qualities.find(q => {
42
- const currentUrl = this.video.currentSrc.toLowerCase();
43
- const qualityUrl = q.src.toLowerCase();
44
-
45
- if (this.debugQuality) {
46
- if (this.options.debug) console.log('Quality comparison:', {
47
- current: currentUrl,
48
- quality: qualityUrl,
49
- qualityName: q.quality,
50
- match: currentUrl === qualityUrl || currentUrl.includes(qualityUrl) || qualityUrl.includes(currentUrl)
51
- });
52
- }
37
+ getCurrentPlayingQuality() {
38
+ if (!this.video) return null;
53
39
 
54
- return currentUrl === qualityUrl ||
55
- currentUrl.includes(qualityUrl) ||
56
- qualityUrl.includes(currentUrl);
57
- });
40
+ if (this.video.currentSrc && this.qualities && this.qualities.length > 0) {
41
+ const currentSource = this.qualities.find(q => {
42
+ const currentUrl = this.video.currentSrc.toLowerCase();
43
+ const qualityUrl = q.src.toLowerCase();
58
44
 
59
- if (currentSource) {
60
- if (this.debugQuality) {
61
- if (this.options.debug) console.log('Quality found from source:', currentSource.quality);
62
- }
63
- return currentSource.quality;
45
+ if (this.debugQuality) {
46
+ if (this.options.debug) console.log('Quality comparison:', {
47
+ current: currentUrl,
48
+ quality: qualityUrl,
49
+ qualityName: q.quality,
50
+ match: currentUrl === qualityUrl || currentUrl.includes(qualityUrl) || qualityUrl.includes(currentUrl)
51
+ });
64
52
  }
65
- }
66
53
 
67
- if (this.video.videoHeight && this.video.videoWidth) {
68
- const height = this.video.videoHeight;
69
- const width = this.video.videoWidth;
54
+ return currentUrl === qualityUrl ||
55
+ currentUrl.includes(qualityUrl) ||
56
+ qualityUrl.includes(currentUrl);
57
+ });
70
58
 
59
+ if (currentSource) {
71
60
  if (this.debugQuality) {
72
- if (this.options.debug) console.log('Risoluzione video:', { height, width });
61
+ if (this.options.debug) console.log('Quality found from source:', currentSource.quality);
73
62
  }
74
-
75
- if (height >= 2160) return '4K';
76
- if (height >= 1440) return '1440p';
77
- if (height >= 1080) return '1080p';
78
- if (height >= 720) return '720p';
79
- if (height >= 480) return '480p';
80
- if (height >= 360) return '360p';
81
- if (height >= 240) return '240p';
82
-
83
- return `${height}p`;
63
+ return currentSource.quality;
84
64
  }
65
+ }
66
+
67
+ if (this.video.videoHeight && this.video.videoWidth) {
68
+ const height = this.video.videoHeight;
69
+ const width = this.video.videoWidth;
85
70
 
86
71
  if (this.debugQuality) {
87
- if (this.options.debug) console.log('No quality detected:', {
88
- currentSrc: this.video.currentSrc,
89
- videoHeight: this.video.videoHeight,
90
- videoWidth: this.video.videoWidth,
91
- qualities: this.qualities
92
- });
72
+ if (this.options.debug) console.log('Risoluzione video:', { height, width });
93
73
  }
94
74
 
95
- return null;
96
- }
75
+ if (height >= 2160) return '4K';
76
+ if (height >= 1440) return '1440p';
77
+ if (height >= 1080) return '1080p';
78
+ if (height >= 720) return '720p';
79
+ if (height >= 480) return '480p';
80
+ if (height >= 360) return '360p';
81
+ if (height >= 240) return '240p';
97
82
 
98
- updateCurrentPlayingQuality() {
99
- const newPlayingQuality = this.getCurrentPlayingQuality();
83
+ return `${height}p`;
84
+ }
100
85
 
101
- if (newPlayingQuality && newPlayingQuality !== this.currentPlayingQuality) {
102
- if (this.options.debug) console.log(`Quality changed: ${this.currentPlayingQuality} → ${newPlayingQuality}`);
103
- this.currentPlayingQuality = newPlayingQuality;
104
- this.updateQualityDisplay();
105
- }
86
+ if (this.debugQuality) {
87
+ if (this.options.debug) console.log('No quality detected:', {
88
+ currentSrc: this.video.currentSrc,
89
+ videoHeight: this.video.videoHeight,
90
+ videoWidth: this.video.videoWidth,
91
+ qualities: this.qualities
92
+ });
106
93
  }
107
94
 
108
- updateQualityDisplay() {
109
- this.updateQualityButton();
110
- this.updateQualityMenu();
95
+ return null;
96
+ }
97
+
98
+ updateCurrentPlayingQuality() {
99
+ const newPlayingQuality = this.getCurrentPlayingQuality();
100
+
101
+ if (newPlayingQuality && newPlayingQuality !== this.currentPlayingQuality) {
102
+ if (this.options.debug) console.log(`Quality changed: ${this.currentPlayingQuality} → ${newPlayingQuality}`);
103
+ this.currentPlayingQuality = newPlayingQuality;
104
+ this.updateQualityDisplay();
111
105
  }
106
+ }
112
107
 
113
- updateQualityButton() {
114
- const qualityBtn = this.controls?.querySelector('.quality-btn');
115
- if (!qualityBtn) return;
108
+ updateQualityDisplay() {
109
+ this.updateQualityButton();
110
+ this.updateQualityMenu();
111
+ }
116
112
 
117
- let btnText = qualityBtn.querySelector('.quality-btn-text');
118
- if (!btnText) {
119
- qualityBtn.innerHTML = `
120
- <span class="icon">⚙</span>
121
- <div class="quality-btn-text">
122
- <div class="selected-quality">${this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality}</div>
123
- <div class="current-quality">${this.currentPlayingQuality || ''}</div>
124
- </div>
125
- `;
126
- } else {
127
- const selectedEl = btnText.querySelector('.selected-quality');
128
- const currentEl = btnText.querySelector('.current-quality');
113
+ updateQualityButton() {
114
+ const qualityBtn = this.controls?.querySelector('.quality-btn');
115
+ if (!qualityBtn) return;
129
116
 
130
- if (selectedEl) {
131
- selectedEl.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
132
- }
117
+ let btnText = qualityBtn.querySelector('.quality-btn-text');
118
+ if (!btnText) {
119
+ // SECURITY: Use DOM methods instead of innerHTML to prevent XSS
120
+ qualityBtn.textContent = ''; // Clear existing content
121
+
122
+ // Create icon element
123
+ const iconSpan = document.createElement('span');
124
+ iconSpan.className = 'icon';
125
+ iconSpan.textContent = '⚙';
126
+ qualityBtn.appendChild(iconSpan);
127
+
128
+ // Create text container
129
+ btnText = document.createElement('div');
130
+ btnText.className = 'quality-btn-text';
131
+
132
+ // Create selected quality element
133
+ const selectedQualityDiv = document.createElement('div');
134
+ selectedQualityDiv.className = 'selected-quality';
135
+ selectedQualityDiv.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
136
+ btnText.appendChild(selectedQualityDiv);
137
+
138
+ // Create current quality element
139
+ const currentQualityDiv = document.createElement('div');
140
+ currentQualityDiv.className = 'current-quality';
141
+ currentQualityDiv.textContent = this.currentPlayingQuality || '';
142
+ btnText.appendChild(currentQualityDiv);
143
+
144
+ // Append to button
145
+ qualityBtn.appendChild(btnText);
146
+ } else {
147
+ // SECURITY: Update existing elements using textContent (not innerHTML)
148
+ const selectedEl = btnText.querySelector('.selected-quality');
149
+ const currentEl = btnText.querySelector('.current-quality');
133
150
 
134
- if (currentEl) {
135
- currentEl.textContent = this.currentPlayingQuality || '';
136
- currentEl.style.display = this.currentPlayingQuality ? 'block' : 'none';
137
- }
151
+ if (selectedEl) {
152
+ selectedEl.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
153
+ }
154
+
155
+ if (currentEl) {
156
+ currentEl.textContent = this.currentPlayingQuality || '';
138
157
  }
139
158
  }
159
+ }
140
160
 
141
161
  updateQualityMenu() {
142
162
  const qualityMenu = this.controls?.querySelector('.quality-menu');
@@ -190,213 +210,217 @@ updateQualityMenu() {
190
210
  qualityMenu.innerHTML = menuHTML;
191
211
  }
192
212
 
193
- getQualityStatus() {
194
- return {
195
- selected: this.selectedQuality,
196
- playing: this.currentPlayingQuality,
197
- isAuto: this.selectedQuality === 'auto',
198
- isChanging: this.isChangingQuality
199
- };
200
- }
213
+ getQualityStatus() {
214
+ return {
215
+ selected: this.selectedQuality,
216
+ playing: this.currentPlayingQuality,
217
+ isAuto: this.selectedQuality === 'auto',
218
+ isChanging: this.isChangingQuality
219
+ };
220
+ }
201
221
 
202
- getSelectedQuality() {
203
- return this.selectedQuality;
204
- }
222
+ getSelectedQuality() {
223
+ return this.selectedQuality;
224
+ }
205
225
 
206
- isAutoQualityActive() {
207
- return this.selectedQuality === 'auto';
208
- }
226
+ isAutoQualityActive() {
227
+ return this.selectedQuality === 'auto';
228
+ }
209
229
 
210
- enableQualityDebug() {
211
- this.debugQuality = true;
212
- this.enableAutoHideDebug(); // Abilita anche debug auto-hide
213
- if (this.options.debug) console.log('Quality AND auto-hide debug enabled');
214
- this.updateCurrentPlayingQuality();
215
- }
230
+ enableQualityDebug() {
231
+ this.debugQuality = true;
232
+ this.enableAutoHideDebug(); // Abilita anche debug auto-hide
233
+ if (this.options.debug) console.log('Quality AND auto-hide debug enabled');
234
+ this.updateCurrentPlayingQuality();
235
+ }
216
236
 
217
- disableQualityDebug() {
218
- this.debugQuality = false;
219
- this.disableAutoHideDebug();
220
- if (this.options.debug) console.log('Quality AND auto-hide debug disabled');
237
+ disableQualityDebug() {
238
+ this.debugQuality = false;
239
+ this.disableAutoHideDebug();
240
+ if (this.options.debug) console.log('Quality AND auto-hide debug disabled');
241
+ }
242
+
243
+ changeQuality(e) {
244
+ if (!e.target.classList.contains('quality-option')) return;
245
+ if (this.isChangingQuality) return;
246
+
247
+ // Handle adaptive streaming quality change
248
+ const adaptiveQuality = e.target.getAttribute('data-adaptive-quality');
249
+ if (adaptiveQuality !== null && this.isAdaptiveStream) {
250
+ const qualityIndex = adaptiveQuality === 'auto' ? -1 : parseInt(adaptiveQuality);
251
+ this.setAdaptiveQuality(qualityIndex);
252
+ this.updateAdaptiveQualityMenu();
253
+ return;
221
254
  }
222
255
 
223
- changeQuality(e) {
224
- if (!e.target.classList.contains('quality-option')) return;
225
- if (this.isChangingQuality) return;
256
+ const quality = e.target.getAttribute('data-quality');
257
+ if (!quality || quality === this.selectedQuality) return;
226
258
 
227
- // Handle adaptive streaming quality change
228
- const adaptiveQuality = e.target.getAttribute('data-adaptive-quality');
229
- if (adaptiveQuality !== null && this.isAdaptiveStream) {
230
- const qualityIndex = adaptiveQuality === 'auto' ? -1 : parseInt(adaptiveQuality);
231
- this.setAdaptiveQuality(qualityIndex);
232
- this.updateAdaptiveQualityMenu();
233
- return;
234
- }
259
+ if (this.options.debug) console.log(`Quality change requested: ${this.selectedQuality} → ${quality}`);
235
260
 
236
- const quality = e.target.getAttribute('data-quality');
237
- if (!quality || quality === this.selectedQuality) return;
261
+ this.selectedQuality = quality;
238
262
 
239
- if (this.options.debug) console.log(`Quality change requested: ${this.selectedQuality} → ${quality}`);
263
+ if (quality === 'auto') {
264
+ this.enableAutoQuality();
265
+ } else {
266
+ this.setQuality(quality);
267
+ }
240
268
 
241
- this.selectedQuality = quality;
269
+ this.updateQualityDisplay();
270
+ }
242
271
 
243
- if (quality === 'auto') {
244
- this.enableAutoQuality();
245
- } else {
246
- this.setQuality(quality);
247
- }
272
+ setQuality(targetQuality) {
273
+ if (this.options.debug) console.log(`setQuality("${targetQuality}") called`);
248
274
 
249
- this.updateQualityDisplay();
275
+ if (!targetQuality) {
276
+ if (this.options.debug) console.error('targetQuality is empty!');
277
+ return;
250
278
  }
251
279
 
252
- setQuality(targetQuality) {
253
- if (this.options.debug) console.log(`setQuality("${targetQuality}") called`);
280
+ if (!this.video || !this.qualities || this.qualities.length === 0) return;
281
+ if (this.isChangingQuality) return;
254
282
 
255
- if (!targetQuality) {
256
- if (this.options.debug) console.error('targetQuality is empty!');
257
- return;
258
- }
283
+ const newSource = this.qualities.find(q => q.quality === targetQuality);
284
+ if (!newSource || !newSource.src) {
285
+ if (this.options.debug) console.error(`Quality "${targetQuality}" not found`);
286
+ return;
287
+ }
259
288
 
260
- if (!this.video || !this.qualities || this.qualities.length === 0) return;
261
- if (this.isChangingQuality) return;
289
+ const currentTime = this.video.currentTime || 0;
290
+ const wasPlaying = !this.video.paused;
262
291
 
263
- const newSource = this.qualities.find(q => q.quality === targetQuality);
264
- if (!newSource || !newSource.src) {
265
- if (this.options.debug) console.error(`Quality "${targetQuality}" not found`);
266
- return;
267
- }
292
+ this.isChangingQuality = true;
293
+ this.selectedQuality = targetQuality;
294
+ this.video.pause();
268
295
 
269
- const currentTime = this.video.currentTime || 0;
270
- const wasPlaying = !this.video.paused;
296
+ // Show loading state during quality change
297
+ this.showLoading();
298
+ if (this.video.classList) {
299
+ this.video.classList.add('quality-changing');
300
+ }
271
301
 
272
- this.isChangingQuality = true;
273
- this.selectedQuality = targetQuality;
274
- this.video.pause();
302
+ const onLoadedData = () => {
303
+ if (this.options.debug) console.log(`Quality ${targetQuality} applied!`);
304
+ this.video.currentTime = currentTime;
275
305
 
276
- // Show loading state during quality change
277
- this.showLoading();
278
- if (this.video.classList) {
279
- this.video.classList.add('quality-changing');
306
+ if (wasPlaying) {
307
+ this.video.play().catch(e => {
308
+ if (this.options.debug) console.log('Play error:', e);
309
+ });
280
310
  }
281
311
 
282
- const onLoadedData = () => {
283
- if (this.options.debug) console.log(`Quality ${targetQuality} applied!`);
284
- this.video.currentTime = currentTime;
285
-
286
- if (wasPlaying) {
287
- this.video.play().catch(e => {
288
- if (this.options.debug) console.log('Play error:', e);
289
- });
290
- }
291
-
292
- this.currentPlayingQuality = targetQuality;
293
- this.updateQualityDisplay();
294
- this.isChangingQuality = false;
312
+ this.currentPlayingQuality = targetQuality;
313
+ this.updateQualityDisplay();
314
+ this.isChangingQuality = false;
295
315
 
296
- // Restore resolution settings after quality change
297
- this.restoreResolutionAfterQualityChange();
298
- cleanup();
299
- };
316
+ // Restore resolution settings after quality change
317
+ this.restoreResolutionAfterQualityChange();
318
+ cleanup();
319
+ };
300
320
 
301
- const onError = (error) => {
302
- if (this.options.debug) console.error(`Loading error ${targetQuality}:`, error);
303
- this.isChangingQuality = false;
304
- cleanup();
305
- };
321
+ const onError = (error) => {
322
+ if (this.options.debug) console.error(`Loading error ${targetQuality}:`, error);
323
+ this.isChangingQuality = false;
306
324
 
307
- const cleanup = () => {
308
- this.video.removeEventListener('loadeddata', onLoadedData);
309
- this.video.removeEventListener('error', onError);
310
- };
325
+ // Trigger ended event for error handling
326
+ this.onVideoError(error);
311
327
 
312
- this.video.addEventListener('loadeddata', onLoadedData, { once: true });
313
- this.video.addEventListener('error', onError, { once: true });
328
+ cleanup();
329
+ };
314
330
 
315
- this.video.src = newSource.src;
316
- this.video.load();
317
- }
331
+ const cleanup = () => {
332
+ this.video.removeEventListener('loadeddata', onLoadedData);
333
+ this.video.removeEventListener('error', onError);
334
+ };
318
335
 
319
- finishQualityChange(success, wasPlaying, currentTime, currentVolume, wasMuted, targetQuality) {
320
- if (this.options.debug) console.log(`Quality change completion: success=${success}, target=${targetQuality}`);
336
+ this.video.addEventListener('loadeddata', onLoadedData, { once: true });
337
+ this.video.addEventListener('error', onError, { once: true });
321
338
 
322
- if (this.qualityChangeTimeout) {
323
- clearTimeout(this.qualityChangeTimeout);
324
- this.qualityChangeTimeout = null;
325
- }
339
+ this.video.src = newSource.src;
340
+ this.video.load();
341
+ }
326
342
 
327
- if (this.video) {
328
- try {
329
- if (success && currentTime > 0 && this.video.duration) {
330
- this.video.currentTime = Math.min(currentTime, this.video.duration);
331
- }
343
+ finishQualityChange(success, wasPlaying, currentTime, currentVolume, wasMuted, targetQuality) {
344
+ if (this.options.debug) console.log(`Quality change completion: success=${success}, target=${targetQuality}`);
332
345
 
333
- this.video.volume = currentVolume;
334
- this.video.muted = wasMuted;
346
+ if (this.qualityChangeTimeout) {
347
+ clearTimeout(this.qualityChangeTimeout);
348
+ this.qualityChangeTimeout = null;
349
+ }
335
350
 
336
- if (success && wasPlaying) {
337
- this.video.play().catch(err => {
338
- if (this.options.debug) console.warn('Play after quality change failed:', err);
339
- });
340
- }
341
- } catch (error) {
342
- if (this.options.debug) console.error('Errore ripristino stato:', error);
351
+ if (this.video) {
352
+ try {
353
+ if (success && currentTime > 0 && this.video.duration) {
354
+ this.video.currentTime = Math.min(currentTime, this.video.duration);
343
355
  }
344
356
 
345
- if (this.video.classList) {
346
- this.video.classList.remove('quality-changing');
357
+ this.video.volume = currentVolume;
358
+ this.video.muted = wasMuted;
359
+
360
+ if (success && wasPlaying) {
361
+ this.video.play().catch(err => {
362
+ if (this.options.debug) console.warn('Play after quality change failed:', err);
363
+ });
347
364
  }
365
+ } catch (error) {
366
+ if (this.options.debug) console.error('Errore ripristino stato:', error);
348
367
  }
349
368
 
350
- this.hideLoading();
351
- this.isChangingQuality = false;
352
-
353
- if (success) {
354
- if (this.options.debug) console.log('Quality change completed successfully');
355
- setTimeout(() => {
356
- this.currentPlayingQuality = targetQuality;
357
- this.updateQualityDisplay();
358
- if (this.options.debug) console.log(`🎯 Quality confirmed active: ${targetQuality}`);
359
- }, 100);
360
- } else {
361
- if (this.options.debug) console.warn('Quality change failed or timeout');
369
+ if (this.video.classList) {
370
+ this.video.classList.remove('quality-changing');
362
371
  }
372
+ }
363
373
 
374
+ this.hideLoading();
375
+ this.isChangingQuality = false;
376
+
377
+ if (success) {
378
+ if (this.options.debug) console.log('Quality change completed successfully');
364
379
  setTimeout(() => {
365
- this.updateCurrentPlayingQuality();
366
- }, 2000);
380
+ this.currentPlayingQuality = targetQuality;
381
+ this.updateQualityDisplay();
382
+ if (this.options.debug) console.log(`🎯 Quality confirmed active: ${targetQuality}`);
383
+ }, 100);
384
+ } else {
385
+ if (this.options.debug) console.warn('Quality change failed or timeout');
367
386
  }
368
387
 
369
- cleanupQualityChange() {
370
- if (this.qualityChangeTimeout) {
371
- clearTimeout(this.qualityChangeTimeout);
372
- this.qualityChangeTimeout = null;
373
- }
374
- }
388
+ setTimeout(() => {
389
+ this.updateCurrentPlayingQuality();
390
+ }, 2000);
391
+ }
375
392
 
376
- enableAutoQuality() {
377
- if (this.options.debug) console.log('🔄 enableAutoQuality - keeping selectedQuality as "auto"');
393
+ cleanupQualityChange() {
394
+ if (this.qualityChangeTimeout) {
395
+ clearTimeout(this.qualityChangeTimeout);
396
+ this.qualityChangeTimeout = null;
397
+ }
398
+ }
378
399
 
379
- // IMPORTANT: Keep selectedQuality as 'auto' for proper UI display
380
- this.selectedQuality = 'auto';
400
+ enableAutoQuality() {
401
+ if (this.options.debug) console.log('🔄 enableAutoQuality - keeping selectedQuality as "auto"');
381
402
 
382
- if (!this.qualities || this.qualities.length === 0) {
383
- if (this.options.debug) console.warn('⚠️ No qualities available for auto selection');
384
- this.updateQualityDisplay();
385
- return;
386
- }
403
+ // IMPORTANT: Keep selectedQuality as 'auto' for proper UI display
404
+ this.selectedQuality = 'auto';
387
405
 
388
- // Smart connection-based quality selection
389
- let autoSelectedQuality = this.getAutoQualityBasedOnConnection();
406
+ if (!this.qualities || this.qualities.length === 0) {
407
+ if (this.options.debug) console.warn('⚠️ No qualities available for auto selection');
408
+ this.updateQualityDisplay();
409
+ return;
410
+ }
390
411
 
391
- if (this.options.debug) {
392
- console.log('🎯 Auto quality selected:', autoSelectedQuality);
393
- console.log('📊 selectedQuality remains: "auto" (for UI)');
394
- }
412
+ // Smart connection-based quality selection
413
+ let autoSelectedQuality = this.getAutoQualityBasedOnConnection();
395
414
 
396
- // Apply the auto-selected quality but keep UI showing "auto"
397
- this.applyAutoQuality(autoSelectedQuality);
415
+ if (this.options.debug) {
416
+ console.log('🎯 Auto quality selected:', autoSelectedQuality);
417
+ console.log('📊 selectedQuality remains: "auto" (for UI)');
398
418
  }
399
419
 
420
+ // Apply the auto-selected quality but keep UI showing "auto"
421
+ this.applyAutoQuality(autoSelectedQuality);
422
+ }
423
+
400
424
  // ENHANCED CONNECTION DETECTION - Uses RTT + downlink heuristics
401
425
  // Handles both Ethernet and real mobile 4G intelligently
402
426
 
@@ -663,102 +687,120 @@ getAutoQualityBasedOnConnection() {
663
687
  return maxQuality.quality;
664
688
  }
665
689
 
666
- applyAutoQuality(targetQuality) {
667
- if (!targetQuality || !this.video || !this.qualities || this.qualities.length === 0) {
668
- return;
669
- }
690
+ applyAutoQuality(targetQuality) {
691
+ if (!targetQuality || !this.video || !this.qualities || this.qualities.length === 0) {
692
+ return;
693
+ }
670
694
 
671
- if (this.isChangingQuality) return;
695
+ if (this.isChangingQuality) return;
672
696
 
673
- const newSource = this.qualities.find(q => q.quality === targetQuality);
674
- if (!newSource || !newSource.src) {
675
- if (this.options.debug) console.error('Auto quality', targetQuality, 'not found');
676
- return;
677
- }
697
+ const newSource = this.qualities.find(q => q.quality === targetQuality);
698
+ if (!newSource || !newSource.src) {
699
+ if (this.options.debug) console.error('Auto quality', targetQuality, 'not found');
700
+ return;
701
+ }
678
702
 
679
- // Store current resolution to restore after quality change
680
- const currentResolution = this.getCurrentResolution();
703
+ // Store current resolution to restore after quality change
704
+ const currentResolution = this.getCurrentResolution();
681
705
 
682
- const currentTime = this.video.currentTime || 0;
683
- const wasPlaying = !this.video.paused;
706
+ const currentTime = this.video.currentTime || 0;
707
+ const wasPlaying = !this.video.paused;
684
708
 
685
- this.isChangingQuality = true;
686
- this.video.pause();
709
+ this.isChangingQuality = true;
710
+ this.video.pause();
687
711
 
688
- const onLoadedData = () => {
689
- if (this.options.debug) console.log('Auto quality', targetQuality, 'applied');
690
- this.video.currentTime = currentTime;
691
- if (wasPlaying) {
692
- this.video.play().catch(e => {
693
- if (this.options.debug) console.log('Autoplay prevented:', e);
694
- });
695
- }
696
- this.currentPlayingQuality = targetQuality;
697
- // Keep selectedQuality as 'auto' for UI display
698
- this.updateQualityDisplay();
699
- this.isChangingQuality = false;
700
- cleanup();
701
- };
712
+ // Show loading overlay
713
+ this.showLoading();
714
+ if (this.video.classList) {
715
+ this.video.classList.add('quality-changing');
716
+ }
702
717
 
703
- const onError = (error) => {
704
- if (this.options.debug) console.error('Auto quality loading error:', error);
705
- this.isChangingQuality = false;
706
- cleanup();
707
- };
708
718
 
709
- const cleanup = () => {
710
- this.video.removeEventListener('loadeddata', onLoadedData);
711
- this.video.removeEventListener('error', onError);
712
- };
719
+ const onLoadedData = () => {
720
+ if (this.options.debug) console.log('Auto quality', targetQuality, 'applied');
721
+ this.video.currentTime = currentTime;
722
+ if (wasPlaying) {
723
+ this.video.play().catch(e => {
724
+ if (this.options.debug) console.log('Autoplay prevented:', e);
725
+ });
726
+ }
727
+ this.currentPlayingQuality = targetQuality;
728
+ // Keep selectedQuality as 'auto' for UI display
729
+ this.updateQualityDisplay();
713
730
 
714
- this.video.addEventListener('loadeddata', onLoadedData, { once: true });
715
- this.video.addEventListener('error', onError, { once: true });
716
- this.video.src = newSource.src;
717
- this.video.load();
718
- }
731
+ // Hide loading overlay
732
+ this.hideLoading();
733
+ if (this.video.classList) {
734
+ this.video.classList.remove('quality-changing');
735
+ }
719
736
 
720
- setDefaultQuality(quality) {
721
- if (this.options.debug) console.log(`🔧 Setting defaultQuality: "${quality}"`);
722
- this.options.defaultQuality = quality;
723
- this.selectedQuality = quality;
737
+ this.isChangingQuality = false;
738
+ cleanup();
739
+ };
724
740
 
725
- if (quality === 'auto') {
726
- this.enableAutoQuality();
727
- } else {
728
- this.setQuality(quality);
729
- }
741
+ const onError = (error) => {
742
+ if (this.options.debug) console.error('Auto quality loading error:', error);
743
+ this.isChangingQuality = false;
730
744
 
731
- return this;
732
- }
745
+ // Trigger ended event for error handling
746
+ this.onVideoError(error);
733
747
 
734
- getDefaultQuality() {
735
- return this.options.defaultQuality;
736
- }
748
+ cleanup();
749
+ };
737
750
 
738
- getQualityLabel(height, width) {
739
- if (height >= 2160) return '4K';
740
- if (height >= 1440) return '1440p';
741
- if (height >= 1080) return '1080p';
742
- if (height >= 720) return '720p';
743
- if (height >= 480) return '480p';
744
- if (height >= 360) return '360p';
745
- if (height >= 240) return '240p';
746
- return `${height}p`;
751
+ const cleanup = () => {
752
+ this.video.removeEventListener('loadeddata', onLoadedData);
753
+ this.video.removeEventListener('error', onError);
754
+ };
755
+
756
+ this.video.addEventListener('loadeddata', onLoadedData, { once: true });
757
+ this.video.addEventListener('error', onError, { once: true });
758
+ this.video.src = newSource.src;
759
+ this.video.load();
760
+ }
761
+
762
+ setDefaultQuality(quality) {
763
+ if (this.options.debug) console.log(`🔧 Setting defaultQuality: "${quality}"`);
764
+ this.options.defaultQuality = quality;
765
+ this.selectedQuality = quality;
766
+
767
+ if (quality === 'auto') {
768
+ this.enableAutoQuality();
769
+ } else {
770
+ this.setQuality(quality);
747
771
  }
748
772
 
749
- updateAdaptiveQualityMenu() {
750
- const qualityMenu = this.controls?.querySelector('.quality-menu');
751
- if (!qualityMenu || !this.isAdaptiveStream) return;
773
+ return this;
774
+ }
775
+
776
+ getDefaultQuality() {
777
+ return this.options.defaultQuality;
778
+ }
752
779
 
753
- let menuHTML = `<div class="quality-option ${this.isAutoQuality() ? 'active' : ''}" data-adaptive-quality="auto">Auto</div>`;
780
+ getQualityLabel(height, width) {
781
+ if (height >= 2160) return '4K';
782
+ if (height >= 1440) return '1440p';
783
+ if (height >= 1080) return '1080p';
784
+ if (height >= 720) return '720p';
785
+ if (height >= 480) return '480p';
786
+ if (height >= 360) return '360p';
787
+ if (height >= 240) return '240p';
788
+ return `${height}p`;
789
+ }
754
790
 
755
- this.adaptiveQualities.forEach(quality => {
756
- const isActive = this.getCurrentAdaptiveQuality() === quality.index;
757
- menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-adaptive-quality="${quality.index}">${quality.label}</div>`;
758
- });
791
+ updateAdaptiveQualityMenu() {
792
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
793
+ if (!qualityMenu || !this.isAdaptiveStream) return;
759
794
 
760
- qualityMenu.innerHTML = menuHTML;
761
- }
795
+ let menuHTML = `<div class="quality-option ${this.isAutoQuality() ? 'active' : ''}" data-adaptive-quality="auto">Auto</div>`;
796
+
797
+ this.adaptiveQualities.forEach(quality => {
798
+ const isActive = this.getCurrentAdaptiveQuality() === quality.index;
799
+ menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-adaptive-quality="${quality.index}">${quality.label}</div>`;
800
+ });
801
+
802
+ qualityMenu.innerHTML = menuHTML;
803
+ }
762
804
 
763
805
  updateAdaptiveQualityDisplay() {
764
806
  if (!this.isAdaptiveStream) return;
@@ -784,61 +826,61 @@ updateAdaptiveQualityDisplay() {
784
826
  }
785
827
  }
786
828
 
787
- setAdaptiveQuality(qualityIndex) {
788
- if (!this.isAdaptiveStream) return;
829
+ setAdaptiveQuality(qualityIndex) {
830
+ if (!this.isAdaptiveStream) return;
789
831
 
790
- try {
791
- if (qualityIndex === 'auto' || qualityIndex === -1) {
792
- // Enable auto quality
793
- if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
794
- this.dashPlayer.updateSettings({
795
- streaming: {
796
- abr: { autoSwitchBitrate: { video: true } }
797
- }
798
- });
799
- } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
800
- this.hlsPlayer.currentLevel = -1; // Auto level selection
801
- }
802
- this.selectedQuality = 'auto';
803
- } else {
804
- // Set specific quality
805
- if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
806
- this.dashPlayer.updateSettings({
807
- streaming: {
808
- abr: { autoSwitchBitrate: { video: false } }
809
- }
810
- });
811
- this.dashPlayer.setQualityFor('video', qualityIndex);
812
- } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
813
- this.hlsPlayer.currentLevel = qualityIndex;
814
- }
815
- this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
832
+ try {
833
+ if (qualityIndex === 'auto' || qualityIndex === -1) {
834
+ // Enable auto quality
835
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
836
+ this.dashPlayer.updateSettings({
837
+ streaming: {
838
+ abr: { autoSwitchBitrate: { video: true } }
839
+ }
840
+ });
841
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
842
+ this.hlsPlayer.currentLevel = -1; // Auto level selection
843
+ }
844
+ this.selectedQuality = 'auto';
845
+ } else {
846
+ // Set specific quality
847
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
848
+ this.dashPlayer.updateSettings({
849
+ streaming: {
850
+ abr: { autoSwitchBitrate: { video: false } }
851
+ }
852
+ });
853
+ this.dashPlayer.setQualityFor('video', qualityIndex);
854
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
855
+ this.hlsPlayer.currentLevel = qualityIndex;
816
856
  }
857
+ this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
858
+ }
817
859
 
818
- this.updateAdaptiveQualityDisplay();
819
- if (this.options.debug) console.log('📡 Adaptive quality set to:', qualityIndex);
860
+ this.updateAdaptiveQualityDisplay();
861
+ if (this.options.debug) console.log('📡 Adaptive quality set to:', qualityIndex);
820
862
 
821
- } catch (error) {
822
- if (this.options.debug) console.error('📡 Error setting adaptive quality:', error);
823
- }
863
+ } catch (error) {
864
+ if (this.options.debug) console.error('📡 Error setting adaptive quality:', error);
824
865
  }
866
+ }
825
867
 
826
- getCurrentAdaptiveQuality() {
827
- if (!this.isAdaptiveStream) return null;
868
+ getCurrentAdaptiveQuality() {
869
+ if (!this.isAdaptiveStream) return null;
828
870
 
829
- try {
830
- if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
831
- return this.dashPlayer.getQualityFor('video');
832
- } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
833
- return this.hlsPlayer.currentLevel;
834
- }
835
- } catch (error) {
836
- if (this.options.debug) console.error('📡 Error getting current quality:', error);
871
+ try {
872
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
873
+ return this.dashPlayer.getQualityFor('video');
874
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
875
+ return this.hlsPlayer.currentLevel;
837
876
  }
838
-
839
- return null;
877
+ } catch (error) {
878
+ if (this.options.debug) console.error('📡 Error getting current quality:', error);
840
879
  }
841
880
 
881
+ return null;
882
+ }
883
+
842
884
  getCurrentAdaptiveQualityLabel() {
843
885
  const currentIndex = this.getCurrentAdaptiveQuality();
844
886
  if (currentIndex === null || currentIndex === -1) {
@@ -847,75 +889,75 @@ getCurrentAdaptiveQualityLabel() {
847
889
  return this.adaptiveQualities[currentIndex]?.label || this.tauto;
848
890
  }
849
891
 
850
- isAutoQuality() {
851
- if (this.isAdaptiveStream) {
852
- const currentQuality = this.getCurrentAdaptiveQuality();
853
- return currentQuality === null || currentQuality === -1 || this.selectedQuality === 'auto';
854
- }
855
- return this.selectedQuality === 'auto';
892
+ isAutoQuality() {
893
+ if (this.isAdaptiveStream) {
894
+ const currentQuality = this.getCurrentAdaptiveQuality();
895
+ return currentQuality === null || currentQuality === -1 || this.selectedQuality === 'auto';
856
896
  }
897
+ return this.selectedQuality === 'auto';
898
+ }
857
899
 
858
- setResolution(resolution) {
859
- if (!this.video || !this.container) {
860
- if (this.options.debug) console.warn("Video or container not available for setResolution");
861
- return;
862
- }
900
+ setResolution(resolution) {
901
+ if (!this.video || !this.container) {
902
+ if (this.options.debug) console.warn("Video or container not available for setResolution");
903
+ return;
904
+ }
863
905
 
864
- // Supported values including new scale-to-fit mode
865
- const supportedResolutions = ["normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"];
906
+ // Supported values including new scale-to-fit mode
907
+ const supportedResolutions = ["normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"];
866
908
 
867
- if (!supportedResolutions.includes(resolution)) {
868
- if (this.options.debug) console.warn(`Resolution "${resolution}" not supported. Supported values: ${supportedResolutions.join(", ")}`);
869
- return;
870
- }
909
+ if (!supportedResolutions.includes(resolution)) {
910
+ if (this.options.debug) console.warn(`Resolution "${resolution}" not supported. Supported values: ${supportedResolutions.join(", ")}`);
911
+ return;
912
+ }
871
913
 
872
- // Remove all previous resolution classes
873
- const allResolutionClasses = [
874
- "resolution-normal", "resolution-4-3", "resolution-16-9",
875
- "resolution-stretched", "resolution-fit-to-screen", "resolution-scale-to-fit"
876
- ];
914
+ // Remove all previous resolution classes
915
+ const allResolutionClasses = [
916
+ "resolution-normal", "resolution-4-3", "resolution-16-9",
917
+ "resolution-stretched", "resolution-fit-to-screen", "resolution-scale-to-fit"
918
+ ];
877
919
 
878
- this.video.classList.remove(...allResolutionClasses);
879
- if (this.container) {
880
- this.container.classList.remove(...allResolutionClasses);
881
- }
920
+ this.video.classList.remove(...allResolutionClasses);
921
+ if (this.container) {
922
+ this.container.classList.remove(...allResolutionClasses);
923
+ }
882
924
 
883
- // Apply new resolution class
884
- const cssClass = `resolution-${resolution.replace(":", "-")}`;
885
- this.video.classList.add(cssClass);
886
- if (this.container) {
887
- this.container.classList.add(cssClass);
888
- }
925
+ // Apply new resolution class
926
+ const cssClass = `resolution-${resolution.replace(":", "-")}`;
927
+ this.video.classList.add(cssClass);
928
+ if (this.container) {
929
+ this.container.classList.add(cssClass);
930
+ }
889
931
 
890
- // Update option
891
- this.options.resolution = resolution;
932
+ // Update option
933
+ this.options.resolution = resolution;
892
934
 
893
- if (this.options.debug) {
894
- console.log(`Resolution applied: ${resolution} (CSS class: ${cssClass})`);
895
- }
935
+ if (this.options.debug) {
936
+ console.log(`Resolution applied: ${resolution} (CSS class: ${cssClass})`);
896
937
  }
938
+ }
897
939
 
898
- getCurrentResolution() {
899
- return this.options.resolution || "normal";
900
- }
940
+ getCurrentResolution() {
941
+ return this.options.resolution || "normal";
942
+ }
901
943
 
902
- initializeResolution() {
903
- if (this.options.resolution && this.options.resolution !== "normal") {
904
- this.setResolution(this.options.resolution);
905
- }
944
+ initializeResolution() {
945
+ if (this.options.resolution && this.options.resolution !== "normal") {
946
+ this.setResolution(this.options.resolution);
906
947
  }
948
+ }
907
949
 
908
- restoreResolutionAfterQualityChange() {
909
- if (this.options.resolution && this.options.resolution !== "normal") {
910
- if (this.options.debug) {
911
- console.log(`Restoring resolution "${this.options.resolution}" after quality change`);
912
- }
913
- // Small delay to ensure video element is ready
914
- setTimeout(() => {
915
- this.setResolution(this.options.resolution);
916
- }, 150);
950
+ restoreResolutionAfterQualityChange() {
951
+ if (this.options.resolution && this.options.resolution !== "normal") {
952
+ if (this.options.debug) {
953
+ console.log(`Restoring resolution "${this.options.resolution}" after quality change`);
917
954
  }
955
+ // Small delay to ensure video element is ready
956
+ setTimeout(() => {
957
+ this.setResolution(this.options.resolution);
958
+ }, 150);
918
959
  }
960
+ }
919
961
 
920
962
  // Quality methods for main class
921
- // All original functionality preserved exactly
963
+ // All original functionality preserved exactly