myetv-player 1.2.0 → 1.3.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.
- package/css/myetv-player.css +131 -0
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +547 -102
- package/dist/myetv-player.min.js +486 -93
- package/package.json +35 -17
- package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
- package/plugins/vimeo/myetv-player-vimeo.js +80 -49
- package/plugins/youtube/README.md +5 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
- package/.github/workflows/codeql.yml +0 -100
- package/.github/workflows/npm-publish.yml +0 -30
- package/SECURITY.md +0 -50
- package/build.js +0 -195
- package/scss/README.md +0 -161
- package/scss/_audio-player.scss +0 -21
- package/scss/_base.scss +0 -116
- package/scss/_controls.scss +0 -204
- package/scss/_loading.scss +0 -111
- package/scss/_menus.scss +0 -432
- package/scss/_mixins.scss +0 -112
- package/scss/_poster.scss +0 -8
- package/scss/_progress-bar.scss +0 -319
- package/scss/_resolution.scss +0 -68
- package/scss/_responsive.scss +0 -1368
- package/scss/_themes.scss +0 -30
- package/scss/_title-overlay.scss +0 -60
- package/scss/_tooltips.scss +0 -7
- package/scss/_variables.scss +0 -49
- package/scss/_video.scss +0 -221
- package/scss/_volume.scss +0 -122
- package/scss/_watermark.scss +0 -128
- package/scss/myetv-player.scss +0 -51
- package/scss/package.json +0 -16
- package/src/README.md +0 -560
- package/src/chapters.js +0 -521
- package/src/controls.js +0 -1242
- package/src/core.js +0 -1922
- package/src/events.js +0 -537
- package/src/fullscreen.js +0 -82
- package/src/i18n.js +0 -374
- package/src/playlist.js +0 -177
- package/src/plugins.js +0 -384
- package/src/quality.js +0 -963
- package/src/streaming.js +0 -346
- package/src/subtitles.js +0 -524
- package/src/utils.js +0 -65
- 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
|