myetv-player 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/css/myetv-player.css +242 -168
  2. package/css/myetv-player.min.css +1 -1
  3. package/dist/myetv-player.js +638 -203
  4. package/dist/myetv-player.min.js +548 -170
  5. package/package.json +35 -16
  6. package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
  7. package/plugins/vimeo/myetv-player-vimeo.js +80 -49
  8. package/plugins/youtube/README.md +5 -2
  9. package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
  10. package/.github/workflows/codeql.yml +0 -100
  11. package/.github/workflows/npm-publish.yml +0 -30
  12. package/SECURITY.md +0 -50
  13. package/build.js +0 -195
  14. package/scss/README.md +0 -161
  15. package/scss/_audio-player.scss +0 -21
  16. package/scss/_base.scss +0 -116
  17. package/scss/_controls.scss +0 -204
  18. package/scss/_loading.scss +0 -111
  19. package/scss/_menus.scss +0 -432
  20. package/scss/_mixins.scss +0 -112
  21. package/scss/_poster.scss +0 -8
  22. package/scss/_progress-bar.scss +0 -319
  23. package/scss/_resolution.scss +0 -68
  24. package/scss/_responsive.scss +0 -1368
  25. package/scss/_themes.scss +0 -30
  26. package/scss/_title-overlay.scss +0 -60
  27. package/scss/_tooltips.scss +0 -7
  28. package/scss/_variables.scss +0 -49
  29. package/scss/_video.scss +0 -221
  30. package/scss/_volume.scss +0 -122
  31. package/scss/_watermark.scss +0 -128
  32. package/scss/myetv-player.scss +0 -51
  33. package/scss/package.json +0 -16
  34. package/src/README.md +0 -560
  35. package/src/chapters.js +0 -521
  36. package/src/controls.js +0 -1242
  37. package/src/core.js +0 -1922
  38. package/src/events.js +0 -537
  39. package/src/fullscreen.js +0 -82
  40. package/src/i18n.js +0 -374
  41. package/src/playlist.js +0 -177
  42. package/src/plugins.js +0 -384
  43. package/src/quality.js +0 -963
  44. package/src/streaming.js +0 -346
  45. package/src/subtitles.js +0 -524
  46. package/src/utils.js +0 -65
  47. package/src/watermark.js +0 -246
package/src/quality.js DELETED
@@ -1,963 +0,0 @@
1
- // Quality Module for MYETV Video Player
2
- // Conservative modularization - original code preserved exactly
3
- // Created by https://www.myetv.tv https://oskarcosimo.com
4
-
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', () => {
22
- if (!this.isChangingQuality) {
23
- this.updateCurrentPlayingQuality();
24
- }
25
- });
26
-
27
- this.video.addEventListener('loadeddata', () => {
28
- setTimeout(() => {
29
- if (!this.isChangingQuality) {
30
- this.updateCurrentPlayingQuality();
31
- }
32
- }, 1000);
33
- });
34
- }
35
- }
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
- }
53
-
54
- return currentUrl === qualityUrl ||
55
- currentUrl.includes(qualityUrl) ||
56
- qualityUrl.includes(currentUrl);
57
- });
58
-
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;
64
- }
65
- }
66
-
67
- if (this.video.videoHeight && this.video.videoWidth) {
68
- const height = this.video.videoHeight;
69
- const width = this.video.videoWidth;
70
-
71
- if (this.debugQuality) {
72
- if (this.options.debug) console.log('Risoluzione video:', { height, width });
73
- }
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`;
84
- }
85
-
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
- });
93
- }
94
-
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();
105
- }
106
- }
107
-
108
- updateQualityDisplay() {
109
- this.updateQualityButton();
110
- this.updateQualityMenu();
111
- }
112
-
113
- updateQualityButton() {
114
- const qualityBtn = this.controls?.querySelector('.quality-btn');
115
- if (!qualityBtn) return;
116
-
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');
150
-
151
- if (selectedEl) {
152
- selectedEl.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
153
- }
154
-
155
- if (currentEl) {
156
- currentEl.textContent = this.currentPlayingQuality || '';
157
- }
158
- }
159
- }
160
-
161
- updateQualityMenu() {
162
- const qualityMenu = this.controls?.querySelector('.quality-menu');
163
- if (!qualityMenu) return;
164
-
165
- let menuHTML = '';
166
-
167
- // Check if adaptive streaming is active (HLS/DASH)
168
- if (this.isAdaptiveStream && this.adaptiveQualities && this.adaptiveQualities.length > 0) {
169
- // Show adaptive streaming qualities
170
- const currentIndex = this.getCurrentAdaptiveQuality();
171
- const autoSelected = currentIndex === -1 || currentIndex === null || this.selectedQuality === 'auto';
172
- const autoClass = autoSelected ? 'selected' : '';
173
-
174
- menuHTML += `<div class="quality-option ${autoClass}" data-adaptive-quality="auto">${this.t('auto')}</div>`;
175
-
176
- this.adaptiveQualities.forEach(quality => {
177
- const isSelected = currentIndex === quality.index && !autoSelected;
178
- const className = isSelected ? 'selected' : '';
179
- const label = quality.label || `${quality.height}p` || 'Unknown';
180
- menuHTML += `<div class="quality-option ${className}" data-adaptive-quality="${quality.index}">${label}</div>`;
181
- });
182
- } else {
183
- // Show standard qualities for regular videos
184
- const autoSelected = this.selectedQuality === 'auto';
185
- const autoPlaying = this.selectedQuality === 'auto' && this.currentPlayingQuality;
186
- let autoClass = '';
187
- if (autoSelected && autoPlaying) {
188
- autoClass = 'selected playing';
189
- } else if (autoSelected) {
190
- autoClass = 'selected';
191
- }
192
-
193
- menuHTML += `<div class="quality-option ${autoClass}" data-quality="auto">${this.t('auto')}</div>`;
194
-
195
- this.qualities.forEach(quality => {
196
- const isSelected = this.selectedQuality === quality.quality;
197
- const isPlaying = this.currentPlayingQuality === quality.quality;
198
- let className = 'quality-option';
199
- if (isSelected && isPlaying) {
200
- className += ' selected playing';
201
- } else if (isSelected) {
202
- className += ' selected';
203
- } else if (isPlaying) {
204
- className += ' playing';
205
- }
206
- menuHTML += `<div class="${className}" data-quality="${quality.quality}">${quality.quality}</div>`;
207
- });
208
- }
209
-
210
- qualityMenu.innerHTML = menuHTML;
211
- }
212
-
213
- getQualityStatus() {
214
- return {
215
- selected: this.selectedQuality,
216
- playing: this.currentPlayingQuality,
217
- isAuto: this.selectedQuality === 'auto',
218
- isChanging: this.isChangingQuality
219
- };
220
- }
221
-
222
- getSelectedQuality() {
223
- return this.selectedQuality;
224
- }
225
-
226
- isAutoQualityActive() {
227
- return this.selectedQuality === 'auto';
228
- }
229
-
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
- }
236
-
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;
254
- }
255
-
256
- const quality = e.target.getAttribute('data-quality');
257
- if (!quality || quality === this.selectedQuality) return;
258
-
259
- if (this.options.debug) console.log(`Quality change requested: ${this.selectedQuality} → ${quality}`);
260
-
261
- this.selectedQuality = quality;
262
-
263
- if (quality === 'auto') {
264
- this.enableAutoQuality();
265
- } else {
266
- this.setQuality(quality);
267
- }
268
-
269
- this.updateQualityDisplay();
270
- }
271
-
272
- setQuality(targetQuality) {
273
- if (this.options.debug) console.log(`setQuality("${targetQuality}") called`);
274
-
275
- if (!targetQuality) {
276
- if (this.options.debug) console.error('targetQuality is empty!');
277
- return;
278
- }
279
-
280
- if (!this.video || !this.qualities || this.qualities.length === 0) return;
281
- if (this.isChangingQuality) return;
282
-
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
- }
288
-
289
- const currentTime = this.video.currentTime || 0;
290
- const wasPlaying = !this.video.paused;
291
-
292
- this.isChangingQuality = true;
293
- this.selectedQuality = targetQuality;
294
- this.video.pause();
295
-
296
- // Show loading state during quality change
297
- this.showLoading();
298
- if (this.video.classList) {
299
- this.video.classList.add('quality-changing');
300
- }
301
-
302
- const onLoadedData = () => {
303
- if (this.options.debug) console.log(`Quality ${targetQuality} applied!`);
304
- this.video.currentTime = currentTime;
305
-
306
- if (wasPlaying) {
307
- this.video.play().catch(e => {
308
- if (this.options.debug) console.log('Play error:', e);
309
- });
310
- }
311
-
312
- this.currentPlayingQuality = targetQuality;
313
- this.updateQualityDisplay();
314
- this.isChangingQuality = false;
315
-
316
- // Restore resolution settings after quality change
317
- this.restoreResolutionAfterQualityChange();
318
- cleanup();
319
- };
320
-
321
- const onError = (error) => {
322
- if (this.options.debug) console.error(`Loading error ${targetQuality}:`, error);
323
- this.isChangingQuality = false;
324
-
325
- // Trigger ended event for error handling
326
- this.onVideoError(error);
327
-
328
- cleanup();
329
- };
330
-
331
- const cleanup = () => {
332
- this.video.removeEventListener('loadeddata', onLoadedData);
333
- this.video.removeEventListener('error', onError);
334
- };
335
-
336
- this.video.addEventListener('loadeddata', onLoadedData, { once: true });
337
- this.video.addEventListener('error', onError, { once: true });
338
-
339
- this.video.src = newSource.src;
340
- this.video.load();
341
- }
342
-
343
- finishQualityChange(success, wasPlaying, currentTime, currentVolume, wasMuted, targetQuality) {
344
- if (this.options.debug) console.log(`Quality change completion: success=${success}, target=${targetQuality}`);
345
-
346
- if (this.qualityChangeTimeout) {
347
- clearTimeout(this.qualityChangeTimeout);
348
- this.qualityChangeTimeout = null;
349
- }
350
-
351
- if (this.video) {
352
- try {
353
- if (success && currentTime > 0 && this.video.duration) {
354
- this.video.currentTime = Math.min(currentTime, this.video.duration);
355
- }
356
-
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
- });
364
- }
365
- } catch (error) {
366
- if (this.options.debug) console.error('Errore ripristino stato:', error);
367
- }
368
-
369
- if (this.video.classList) {
370
- this.video.classList.remove('quality-changing');
371
- }
372
- }
373
-
374
- this.hideLoading();
375
- this.isChangingQuality = false;
376
-
377
- if (success) {
378
- if (this.options.debug) console.log('Quality change completed successfully');
379
- setTimeout(() => {
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');
386
- }
387
-
388
- setTimeout(() => {
389
- this.updateCurrentPlayingQuality();
390
- }, 2000);
391
- }
392
-
393
- cleanupQualityChange() {
394
- if (this.qualityChangeTimeout) {
395
- clearTimeout(this.qualityChangeTimeout);
396
- this.qualityChangeTimeout = null;
397
- }
398
- }
399
-
400
- enableAutoQuality() {
401
- if (this.options.debug) console.log('🔄 enableAutoQuality - keeping selectedQuality as "auto"');
402
-
403
- // IMPORTANT: Keep selectedQuality as 'auto' for proper UI display
404
- this.selectedQuality = 'auto';
405
-
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
- }
411
-
412
- // Smart connection-based quality selection
413
- let autoSelectedQuality = this.getAutoQualityBasedOnConnection();
414
-
415
- if (this.options.debug) {
416
- console.log('🎯 Auto quality selected:', autoSelectedQuality);
417
- console.log('📊 selectedQuality remains: "auto" (for UI)');
418
- }
419
-
420
- // Apply the auto-selected quality but keep UI showing "auto"
421
- this.applyAutoQuality(autoSelectedQuality);
422
- }
423
-
424
- // ENHANCED CONNECTION DETECTION - Uses RTT + downlink heuristics
425
- // Handles both Ethernet and real mobile 4G intelligently
426
-
427
- getAutoQualityBasedOnConnection() {
428
- // Get available qualities
429
- const maxQualityIndex = this.qualities.length - 1;
430
- const maxQuality = this.qualities[maxQualityIndex];
431
- let selectedQuality = maxQuality.quality;
432
-
433
- // =====================================================
434
- // MOBILE DETECTION
435
- // =====================================================
436
- const isDefinitelyMobile = () => {
437
- const ua = navigator.userAgent.toLowerCase();
438
- const checks = [
439
- ua.includes('android'),
440
- ua.includes('mobile'),
441
- ua.includes('iphone'),
442
- ua.includes('ipad'),
443
- window.innerWidth < 1024,
444
- window.innerHeight < 768,
445
- 'ontouchstart' in window,
446
- navigator.maxTouchPoints > 0,
447
- 'orientation' in window,
448
- window.devicePixelRatio > 1.5
449
- ];
450
-
451
- // Count positive checks - mobile if 4+ indicators (more aggressive)
452
- const mobileScore = checks.filter(Boolean).length;
453
-
454
- if (this.options.debug) {
455
- console.log('🔍 Mobile Detection Score:', {
456
- score: mobileScore + '/10',
457
- android: ua.includes('android'),
458
- mobile: ua.includes('mobile'),
459
- width: window.innerWidth,
460
- touch: 'ontouchstart' in window,
461
- maxTouch: navigator.maxTouchPoints
462
- });
463
- }
464
-
465
- return mobileScore >= 4; // Threshold: 4 out of 10 checks
466
- };
467
-
468
- // FORCE MOBILE BEHAVIOR FIRST - Override everything else
469
- if (isDefinitelyMobile()) {
470
- // Helper function for mobile
471
- const findMobileQuality = (maxHeight) => {
472
- const mobileQualities = this.qualities
473
- .filter(q => q.height && q.height <= maxHeight)
474
- .sort((a, b) => b.height - a.height);
475
- return mobileQualities[0] || maxQuality;
476
- };
477
-
478
- // Conservative quality for mobile devices - MAX 1080p
479
- const mobileQuality = findMobileQuality(1080);
480
-
481
- if (this.options.debug) console.log('🚨 MOBILE FORCE OVERRIDE: ' + mobileQuality.quality + ' (max 1080p)');
482
- return mobileQuality.quality;
483
- }
484
-
485
- // =====================================================
486
- // DESKTOP CONNECTION ANALYSIS
487
- // =====================================================
488
- const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
489
-
490
- if (connection) {
491
- const physicalType = connection.type; // Usually undefined
492
- const downlinkSpeed = connection.downlink || 0;
493
- const rtt = connection.rtt; // Round Trip Time in milliseconds
494
-
495
- if (this.options.debug) {
496
- console.log('🌐 Enhanced Connection Detection:', {
497
- physicalType: physicalType || 'undefined',
498
- downlink: downlinkSpeed + ' Mbps',
499
- rtt: rtt + ' ms',
500
- userAgent: navigator.userAgent.includes('Mobile') ? 'Mobile' : 'Desktop'
501
- });
502
- }
503
-
504
- // Helper function to detect mobile device via User-Agent (backup)
505
- const isMobileDevice = () => {
506
- return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
507
- };
508
-
509
- // Helper function to find quality by minimum height
510
- const findQualityByMinHeight = (minHeight) => {
511
- // Sort qualities by height (descending) and find first match >= minHeight
512
- const sortedQualities = this.qualities
513
- .filter(q => q.height && q.height >= minHeight)
514
- .sort((a, b) => b.height - a.height);
515
-
516
- return sortedQualities[0] || maxQuality;
517
- };
518
-
519
- // Helper function to find highest available quality
520
- const findHighestQuality = () => {
521
- const sortedQualities = this.qualities
522
- .filter(q => q.height)
523
- .sort((a, b) => b.height - a.height);
524
-
525
- return sortedQualities[0] || maxQuality;
526
- };
527
-
528
- // PRIORITY 1: Physical type detection (when available - rare)
529
- if (physicalType === 'ethernet') {
530
- const quality = findHighestQuality(); // Maximum available quality
531
- if (this.options.debug) console.log('🔥 Ethernet Detected: ' + quality.quality);
532
- return quality.quality;
533
- }
534
-
535
- if (physicalType === 'wifi') {
536
- const quality = findQualityByMinHeight(1440) || findHighestQuality(); // 2K preferred
537
- if (this.options.debug) console.log('📶 WiFi Detected: ' + quality.quality);
538
- return quality.quality;
539
- }
540
-
541
- if (physicalType === 'cellular') {
542
- // Conservative approach for confirmed mobile connection
543
- if (downlinkSpeed >= 20 && rtt < 40) {
544
- const quality = findQualityByMinHeight(1080); // Max 1080p for excellent mobile
545
- if (this.options.debug) console.log('📱 Excellent Cellular: ' + quality.quality);
546
- return quality.quality;
547
- } else if (downlinkSpeed >= 10) {
548
- const quality = findQualityByMinHeight(720); // 720p for good mobile
549
- if (this.options.debug) console.log('📱 Good Cellular: ' + quality.quality);
550
- return quality.quality;
551
- } else {
552
- const quality = findQualityByMinHeight(480); // 480p for standard mobile
553
- if (this.options.debug) console.log('📱 Standard Cellular: ' + quality.quality);
554
- return quality.quality;
555
- }
556
- }
557
-
558
- // PRIORITY 2: RTT + Downlink + User-Agent heuristics (most common case)
559
- if (this.options.debug) {
560
- console.log('🌐 Physical type undefined - using enhanced RTT + UA heuristics');
561
- }
562
-
563
- // SPECIAL CASE: RTT = 0 (Ultra-fast connection with mobile detection)
564
- if (rtt === 0) {
565
- if (isMobileDevice()) {
566
- // Mobile device with RTT=0 = excellent 4G/5G, but be conservative for data usage
567
- const quality = findQualityByMinHeight(1080); // Max 1080p for mobile
568
- if (this.options.debug) console.log('📱 Mobile Device (UA) with RTT=0: ' + quality.quality);
569
- return quality.quality;
570
- } else {
571
- // Desktop with RTT=0 = true ultra-fast fixed connection (Ethernet/Fiber)
572
- const quality = findHighestQuality();
573
- if (this.options.debug) console.log('🚀 Desktop Ultra-Fast (RTT=0): ' + quality.quality);
574
- return quality.quality;
575
- }
576
- }
577
-
578
- // Very low RTT + high speed with mobile detection
579
- if (rtt < 20 && downlinkSpeed >= 10) {
580
- if (isMobileDevice()) {
581
- if (rtt < 10 && downlinkSpeed >= 15) {
582
- // Excellent 5G with very low RTT - allow higher quality but still conservative
583
- const quality = findQualityByMinHeight(1080); // Max 1080p for excellent mobile
584
- if (this.options.debug) console.log('📱 Mobile 5G Ultra-Fast (RTT<10): ' + quality.quality);
585
- return quality.quality;
586
- } else {
587
- // Good mobile connection but conservative
588
- const quality = findQualityByMinHeight(720); // 720p for mobile with good RTT
589
- if (this.options.debug) console.log('📱 Mobile Good Connection (RTT<20): ' + quality.quality);
590
- return quality.quality;
591
- }
592
- } else {
593
- // Desktop with low RTT = fast fixed connection
594
- const quality = findQualityByMinHeight(1440) || findHighestQuality(); // 2K or best available
595
- if (this.options.debug) console.log('🔥 Desktop High-Speed Fixed (RTT<20): ' + quality.quality);
596
- return quality.quality;
597
- }
598
- }
599
-
600
- // Low-medium RTT with speed analysis
601
- if (rtt < 40 && downlinkSpeed >= 8) {
602
- if (isMobileDevice()) {
603
- // Mobile with decent connection
604
- const quality = findQualityByMinHeight(720); // 720p for mobile
605
- if (this.options.debug) console.log('📱 Mobile Decent Connection (RTT<40): ' + quality.quality);
606
- return quality.quality;
607
- } else {
608
- // Desktop with medium RTT = good fixed connection (WiFi/ADSL)
609
- const quality = findQualityByMinHeight(1080); // 1080p for desktop
610
- if (this.options.debug) console.log('⚡ Desktop Good Connection (RTT<40): ' + quality.quality);
611
- return quality.quality;
612
- }
613
- }
614
-
615
- // Higher RTT = likely mobile or congested connection
616
- if (rtt >= 40) {
617
- if (downlinkSpeed >= 15 && !isMobileDevice()) {
618
- // High speed but high RTT on desktop = congested but fast connection
619
- const quality = findQualityByMinHeight(1080); // 1080p
620
- if (this.options.debug) console.log('🌐 Desktop Congested Fast Connection: ' + quality.quality);
621
- return quality.quality;
622
- } else if (downlinkSpeed >= 10) {
623
- // High RTT with good speed = mobile or congested WiFi
624
- const quality = findQualityByMinHeight(720); // 720p
625
- if (this.options.debug) console.log('📱 Mobile/Congested Connection (RTT≥40): ' + quality.quality);
626
- return quality.quality;
627
- } else {
628
- // High RTT with lower speed = definitely mobile or slow connection
629
- const quality = findQualityByMinHeight(480); // 480p
630
- if (this.options.debug) console.log('📱 Slow Mobile Connection: ' + quality.quality);
631
- return quality.quality;
632
- }
633
- }
634
-
635
- // Medium speed cases without clear RTT data
636
- if (downlinkSpeed >= 8) {
637
- if (isMobileDevice()) {
638
- const quality = findQualityByMinHeight(720); // Conservative for mobile
639
- if (this.options.debug) console.log('📱 Mobile Standard Speed: ' + quality.quality);
640
- return quality.quality;
641
- } else {
642
- const quality = findQualityByMinHeight(1080); // Good for desktop
643
- if (this.options.debug) console.log('🌐 Desktop Standard Speed: ' + quality.quality);
644
- return quality.quality;
645
- }
646
- } else if (downlinkSpeed >= 5) {
647
- // Lower speed - conservative approach
648
- const quality = findQualityByMinHeight(720);
649
- if (this.options.debug) console.log('🌐 Lower Speed Connection: ' + quality.quality);
650
- return quality.quality;
651
- } else {
652
- // Very low speed
653
- const quality = findQualityByMinHeight(480);
654
- if (this.options.debug) console.log('🌐 Very Low Speed Connection: ' + quality.quality);
655
- return quality.quality;
656
- }
657
-
658
- } else {
659
- // No connection information available
660
- const isMobileDevice = () => {
661
- return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
662
- };
663
-
664
- // Helper function for fallback
665
- const findQualityByMinHeight = (minHeight) => {
666
- const sortedQualities = this.qualities
667
- .filter(q => q.height && q.height >= minHeight)
668
- .sort((a, b) => b.height - a.height);
669
- return sortedQualities[0] || maxQuality;
670
- };
671
-
672
- if (isMobileDevice()) {
673
- // Mobile device without connection info - be conservative
674
- const quality = findQualityByMinHeight(720);
675
- if (this.options.debug) console.log('📱 Mobile - No Connection Info: ' + quality.quality);
676
- return quality.quality;
677
- } else {
678
- // Desktop without connection info - assume good connection
679
- const quality = findQualityByMinHeight(1080) || maxQuality;
680
- if (this.options.debug) console.log('🌐 Desktop - No Connection Info: ' + quality.quality);
681
- return quality.quality;
682
- }
683
- }
684
-
685
- // Final fallback (should rarely reach here)
686
- if (this.options.debug) console.log('🌐 Fallback to max quality: ' + maxQuality.quality);
687
- return maxQuality.quality;
688
- }
689
-
690
- applyAutoQuality(targetQuality) {
691
- if (!targetQuality || !this.video || !this.qualities || this.qualities.length === 0) {
692
- return;
693
- }
694
-
695
- if (this.isChangingQuality) return;
696
-
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
- }
702
-
703
- // Store current resolution to restore after quality change
704
- const currentResolution = this.getCurrentResolution();
705
-
706
- const currentTime = this.video.currentTime || 0;
707
- const wasPlaying = !this.video.paused;
708
-
709
- this.isChangingQuality = true;
710
- this.video.pause();
711
-
712
- // Show loading overlay
713
- this.showLoading();
714
- if (this.video.classList) {
715
- this.video.classList.add('quality-changing');
716
- }
717
-
718
-
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();
730
-
731
- // Hide loading overlay
732
- this.hideLoading();
733
- if (this.video.classList) {
734
- this.video.classList.remove('quality-changing');
735
- }
736
-
737
- this.isChangingQuality = false;
738
- cleanup();
739
- };
740
-
741
- const onError = (error) => {
742
- if (this.options.debug) console.error('Auto quality loading error:', error);
743
- this.isChangingQuality = false;
744
-
745
- // Trigger ended event for error handling
746
- this.onVideoError(error);
747
-
748
- cleanup();
749
- };
750
-
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);
771
- }
772
-
773
- return this;
774
- }
775
-
776
- getDefaultQuality() {
777
- return this.options.defaultQuality;
778
- }
779
-
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
- }
790
-
791
- updateAdaptiveQualityMenu() {
792
- const qualityMenu = this.controls?.querySelector('.quality-menu');
793
- if (!qualityMenu || !this.isAdaptiveStream) return;
794
-
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
- }
804
-
805
- updateAdaptiveQualityDisplay() {
806
- if (!this.isAdaptiveStream) return;
807
-
808
- const qualityBtn = this.controls?.querySelector('.quality-btn');
809
- if (!qualityBtn) return;
810
-
811
- // Determine if auto quality is active
812
- const isAuto = this.selectedQuality === 'auto' || this.getCurrentAdaptiveQuality() === -1;
813
- const currentQuality = isAuto ? this.tauto : this.getCurrentAdaptiveQualityLabel();
814
-
815
- const btnText = qualityBtn.querySelector('.quality-btn-text');
816
- if (btnText) {
817
- const selectedEl = btnText.querySelector('.selected-quality');
818
- const currentEl = btnText.querySelector('.current-quality');
819
-
820
- if (selectedEl) {
821
- selectedEl.textContent = isAuto ? this.tauto : currentQuality;
822
- }
823
- if (currentEl) {
824
- currentEl.textContent = currentQuality;
825
- }
826
- }
827
- }
828
-
829
- setAdaptiveQuality(qualityIndex) {
830
- if (!this.isAdaptiveStream) return;
831
-
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;
856
- }
857
- this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
858
- }
859
-
860
- this.updateAdaptiveQualityDisplay();
861
- if (this.options.debug) console.log('📡 Adaptive quality set to:', qualityIndex);
862
-
863
- } catch (error) {
864
- if (this.options.debug) console.error('📡 Error setting adaptive quality:', error);
865
- }
866
- }
867
-
868
- getCurrentAdaptiveQuality() {
869
- if (!this.isAdaptiveStream) return null;
870
-
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;
876
- }
877
- } catch (error) {
878
- if (this.options.debug) console.error('📡 Error getting current quality:', error);
879
- }
880
-
881
- return null;
882
- }
883
-
884
- getCurrentAdaptiveQualityLabel() {
885
- const currentIndex = this.getCurrentAdaptiveQuality();
886
- if (currentIndex === null || currentIndex === -1) {
887
- return this.tauto; // Return "Auto" instead of "Unknown"
888
- }
889
- return this.adaptiveQualities[currentIndex]?.label || this.tauto;
890
- }
891
-
892
- isAutoQuality() {
893
- if (this.isAdaptiveStream) {
894
- const currentQuality = this.getCurrentAdaptiveQuality();
895
- return currentQuality === null || currentQuality === -1 || this.selectedQuality === 'auto';
896
- }
897
- return this.selectedQuality === 'auto';
898
- }
899
-
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
- }
905
-
906
- // Supported values including new scale-to-fit mode
907
- const supportedResolutions = ["normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"];
908
-
909
- if (!supportedResolutions.includes(resolution)) {
910
- if (this.options.debug) console.warn(`Resolution "${resolution}" not supported. Supported values: ${supportedResolutions.join(", ")}`);
911
- return;
912
- }
913
-
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
- ];
919
-
920
- this.video.classList.remove(...allResolutionClasses);
921
- if (this.container) {
922
- this.container.classList.remove(...allResolutionClasses);
923
- }
924
-
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
- }
931
-
932
- // Update option
933
- this.options.resolution = resolution;
934
-
935
- if (this.options.debug) {
936
- console.log(`Resolution applied: ${resolution} (CSS class: ${cssClass})`);
937
- }
938
- }
939
-
940
- getCurrentResolution() {
941
- return this.options.resolution || "normal";
942
- }
943
-
944
- initializeResolution() {
945
- if (this.options.resolution && this.options.resolution !== "normal") {
946
- this.setResolution(this.options.resolution);
947
- }
948
- }
949
-
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`);
954
- }
955
- // Small delay to ensure video element is ready
956
- setTimeout(() => {
957
- this.setResolution(this.options.resolution);
958
- }, 150);
959
- }
960
- }
961
-
962
- // Quality methods for main class
963
- // All original functionality preserved exactly