myetv-player 1.4.0 → 1.6.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.
@@ -23,7 +23,9 @@ class VideoPlayerI18n {
23
23
  'prev_video': 'Video precedente (P)',
24
24
  'playlist_next': 'Avanti',
25
25
  'playlist_prev': 'Indietro',
26
- 'settings_menu': 'Impostazioni'
26
+ 'settings_menu': 'Impostazioni',
27
+ 'loading': 'Caricamento...',
28
+ 'encoding_in_progress': 'Encoding in corso'
27
29
  },
28
30
 
29
31
  'en': {
@@ -44,7 +46,9 @@ class VideoPlayerI18n {
44
46
  'prev_video': 'Previous video (P)',
45
47
  'playlist_next': 'Next',
46
48
  'playlist_prev': 'Previous',
47
- 'settings_menu': 'Settings'
49
+ 'settings_menu': 'Settings',
50
+ 'loading': 'Loading...',
51
+ 'encoding_in_progress': 'Encoding in progress'
48
52
  },
49
53
 
50
54
  'es': {
@@ -65,7 +69,9 @@ class VideoPlayerI18n {
65
69
  'prev_video': 'Vídeo anterior (P)',
66
70
  'playlist_next': 'Siguiente',
67
71
  'playlist_prev': 'Anterior',
68
- 'settings_menu': 'Configuración'
72
+ 'settings_menu': 'Configuración',
73
+ 'loading': 'Cargando...',
74
+ 'encoding_in_progress': 'Codificación en curso'
69
75
  },
70
76
 
71
77
  'fr': {
@@ -78,7 +84,7 @@ class VideoPlayerI18n {
78
84
  'volume': 'Volume',
79
85
  'playback_speed': 'Vitesse de lecture',
80
86
  'video_quality': 'Qualité vidéo',
81
- 'picture_in_picture': 'Image dans limage (P)',
87
+ 'picture_in_picture': 'Image dans l\'image(P)',
82
88
  'fullscreen': 'Plein écran (F)',
83
89
  'auto': 'Auto',
84
90
  'brand_logo': 'Logo de marque',
@@ -86,7 +92,9 @@ class VideoPlayerI18n {
86
92
  'prev_video': 'Vidéo précédente (P)',
87
93
  'playlist_next': 'Suivant',
88
94
  'playlist_prev': 'Précédent',
89
- 'settings_menu': 'Paramètres'
95
+ 'settings_menu': 'Paramètres',
96
+ 'loading': 'Chargement...',
97
+ 'encoding_in_progress': 'Encodage en cours'
90
98
  },
91
99
 
92
100
  'de': {
@@ -107,7 +115,9 @@ class VideoPlayerI18n {
107
115
  'prev_video': 'Vorheriges Video (P)',
108
116
  'playlist_next': 'Weiter',
109
117
  'playlist_prev': 'Zurück',
110
- 'settings_menu': 'Einstellungen'
118
+ 'settings_menu': 'Einstellungen',
119
+ 'loading': 'Laden...',
120
+ 'encoding_in_progress': 'Kodierung läuft'
111
121
  },
112
122
 
113
123
  'pt': {
@@ -128,7 +138,9 @@ class VideoPlayerI18n {
128
138
  'prev_video': 'Vídeo anterior (P)',
129
139
  'playlist_next': 'Próximo',
130
140
  'playlist_prev': 'Anterior',
131
- 'settings_menu': 'Configurações'
141
+ 'settings_menu': 'Configurações',
142
+ 'loading': 'Carregando...',
143
+ 'encoding_in_progress': 'Codificação em andamento'
132
144
  },
133
145
 
134
146
  'zh': {
@@ -149,7 +161,9 @@ class VideoPlayerI18n {
149
161
  'prev_video': '上一个视频 (P)',
150
162
  'playlist_next': '下一个',
151
163
  'playlist_prev': '上一个',
152
- 'settings_menu': '设置'
164
+ 'settings_menu': '设置',
165
+ 'loading': '加载中...',
166
+ 'encoding_in_progress': '编码中'
153
167
  },
154
168
 
155
169
  'ja': {
@@ -170,7 +184,9 @@ class VideoPlayerI18n {
170
184
  'prev_video': '前の動画 (P)',
171
185
  'playlist_next': '次へ',
172
186
  'playlist_prev': '前へ',
173
- 'settings_menu': '設定'
187
+ 'settings_menu': '設定',
188
+ 'loading': '読み込み中...',
189
+ 'encoding_in_progress': 'エンコード中'
174
190
  },
175
191
 
176
192
  'ru': {
@@ -191,7 +207,9 @@ class VideoPlayerI18n {
191
207
  'prev_video': 'Предыдущее видео (P)',
192
208
  'playlist_next': 'Далее',
193
209
  'playlist_prev': 'Назад',
194
- 'settings_menu': 'Настройки'
210
+ 'settings_menu': 'Настройки',
211
+ 'loading': 'Загрузка...',
212
+ 'encoding_in_progress': 'Кодирование'
195
213
  },
196
214
 
197
215
  'ar': {
@@ -212,7 +230,9 @@ class VideoPlayerI18n {
212
230
  'prev_video': 'الفيديو السابق (P)',
213
231
  'playlist_next': 'التالي',
214
232
  'playlist_prev': 'السابق',
215
- 'settings_menu': 'الإعدادات'
233
+ 'settings_menu': 'الإعدادات',
234
+ 'loading': 'جاري التحميل...',
235
+ 'encoding_in_progress': 'الترميز جارٍ'
216
236
  },
217
237
 
218
238
  'ko': {
@@ -233,7 +253,9 @@ class VideoPlayerI18n {
233
253
  'prev_video': '이전 비디오 (P)',
234
254
  'playlist_next': '다음',
235
255
  'playlist_prev': '이전',
236
- 'settings_menu': '설정'
256
+ 'settings_menu': '설정',
257
+ 'loading': '로딩 중...',
258
+ 'encoding_in_progress': '인코딩 진행 중'
237
259
  },
238
260
 
239
261
  'pl': {
@@ -254,7 +276,9 @@ class VideoPlayerI18n {
254
276
  'prev_video': 'Poprzednie wideo (P)',
255
277
  'playlist_next': 'Dalej',
256
278
  'playlist_prev': 'Wstecz',
257
- 'settings_menu': 'Ustawienia'
279
+ 'settings_menu': 'Ustawienia',
280
+ 'loading': 'Ładowanie...',
281
+ 'encoding_in_progress': 'Kodowanie w toku'
258
282
  },
259
283
 
260
284
  'hu': {
@@ -275,7 +299,9 @@ class VideoPlayerI18n {
275
299
  'prev_video': 'Előző videó (P)',
276
300
  'playlist_next': 'Következő',
277
301
  'playlist_prev': 'Előző',
278
- 'settings_menu': 'Beállítások'
302
+ 'settings_menu': 'Beállítások',
303
+ 'loading': 'Betöltés...',
304
+ 'encoding_in_progress': 'Kódolás folyamatban'
279
305
  },
280
306
 
281
307
  'tr': {
@@ -296,7 +322,9 @@ class VideoPlayerI18n {
296
322
  'prev_video': 'Önceki video (P)',
297
323
  'playlist_next': 'Sonraki',
298
324
  'playlist_prev': 'Önceki',
299
- 'settings_menu': 'Ayarlar'
325
+ 'settings_menu': 'Ayarlar',
326
+ 'loading': 'Yükleniyor...',
327
+ 'encoding_in_progress': 'Kodlama devam ediyor'
300
328
  },
301
329
 
302
330
  'nl': {
@@ -317,7 +345,9 @@ class VideoPlayerI18n {
317
345
  'prev_video': 'Vorige video (P)',
318
346
  'playlist_next': 'Volgende',
319
347
  'playlist_prev': 'Vorige',
320
- 'settings_menu': 'Instellingen'
348
+ 'settings_menu': 'Instellingen',
349
+ 'loading': 'Laden...',
350
+ 'encoding_in_progress': 'Codering bezig'
321
351
  },
322
352
 
323
353
  'hi': {
@@ -338,7 +368,9 @@ class VideoPlayerI18n {
338
368
  'prev_video': 'पिछला वीडियो (P)',
339
369
  'playlist_next': 'अगला',
340
370
  'playlist_prev': 'पिछला',
341
- 'settings_menu': 'सेटिंग्स'
371
+ 'settings_menu': 'सेटिंग्स',
372
+ 'loading': 'लोड हो रहा है...',
373
+ 'encoding_in_progress': 'एन्कोडिंग प्रगति में'
342
374
  },
343
375
 
344
376
  'sv': {
@@ -359,7 +391,9 @@ class VideoPlayerI18n {
359
391
  'prev_video': 'Föregående video (P)',
360
392
  'playlist_next': 'Nästa',
361
393
  'playlist_prev': 'Föregående',
362
- 'settings_menu': 'Inställningar'
394
+ 'settings_menu': 'Inställningar',
395
+ 'loading': 'Laddar...',
396
+ 'encoding_in_progress': 'Kodning pågår'
363
397
  },
364
398
 
365
399
  'id': {
@@ -380,7 +414,9 @@ class VideoPlayerI18n {
380
414
  'prev_video': 'Video sebelumnya (P)',
381
415
  'playlist_next': 'Berikutnya',
382
416
  'playlist_prev': 'Sebelumnya',
383
- 'settings_menu': 'Pengaturan'
417
+ 'settings_menu': 'Pengaturan',
418
+ 'loading': 'Memuat...',
419
+ 'encoding_in_progress': 'Encoding sedang berlangsung'
384
420
  }
385
421
  };
386
422
 
@@ -493,7 +529,9 @@ try {
493
529
  'fullscreen': 'Fullscreen (F)',
494
530
  'auto': 'Auto',
495
531
  'brand_logo': 'Brand logo',
496
- 'settings_menu': 'Settings'
532
+ 'settings_menu': 'Settings',
533
+ 'loading': 'Loading...',
534
+ 'encoding_in_progress': 'Encoding in progress'
497
535
  };
498
536
  return fallback[key] || key;
499
537
  },
@@ -802,6 +840,49 @@ constructor(videoElement, options = {}) {
802
840
  }
803
841
  }
804
842
 
843
+ decodeHTMLEntities(text) {
844
+ if (!text) return '';
845
+ const textarea = document.createElement('textarea');
846
+ textarea.innerHTML = text;
847
+ return textarea.value;
848
+ }
849
+
850
+ isFireTV() {
851
+ const ua = navigator.userAgent.toLowerCase();
852
+ return ua.includes('aftm') ||
853
+ ua.includes('aftb') ||
854
+ ua.includes('afts') ||
855
+ ua.includes('aftmm') ||
856
+ ua.includes('aftt');
857
+ }
858
+
859
+ optimizeVideoForFireTV() {
860
+ if (!this.isFireTV() || !this.video) return;
861
+
862
+ if (this.options.debug) {
863
+ console.log('Fire TV detected - applying optimizations');
864
+ }
865
+
866
+ this.video.setAttribute('playsinline', '');
867
+ this.video.setAttribute('webkit-playsinline', '');
868
+
869
+ this.video.style.transform = 'translateZ(0)';
870
+ this.video.style.webkitTransform = 'translateZ(0)';
871
+ this.video.style.backfaceVisibility = 'hidden';
872
+ this.video.style.webkitBackfaceVisibility = 'hidden';
873
+ this.video.style.willChange = 'transform';
874
+
875
+ this.video.addEventListener('loadeddata', () => {
876
+ if (this.options.debug) {
877
+ console.log('Fire TV: Video loaded, forcing repaint');
878
+ }
879
+ this.video.style.display = 'none';
880
+ setTimeout(() => {
881
+ this.video.style.display = 'block';
882
+ }, 10);
883
+ }, { once: true });
884
+ }
885
+
805
886
  getPlayerState() {
806
887
  return {
807
888
  isPlaying: !this.isPaused(),
@@ -1091,6 +1172,7 @@ createPlayerStructure() {
1091
1172
 
1092
1173
  this.container = wrapper;
1093
1174
 
1175
+ this.optimizeVideoForFireTV();
1094
1176
  this.createInitialLoading();
1095
1177
  this.createLoadingOverlay();
1096
1178
  this.collectVideoQualities();
@@ -1131,13 +1213,13 @@ createTitleOverlay() {
1131
1213
 
1132
1214
  const titleText = document.createElement('h2');
1133
1215
  titleText.className = 'title-text';
1134
- titleText.textContent = this.options.videoTitle || '';
1216
+ titleText.textContent = this.decodeHTMLEntities(this.options.videoTitle) || '';
1135
1217
  overlay.appendChild(titleText);
1136
1218
 
1137
1219
  if (this.options.videoSubtitle) {
1138
1220
  const subtitleText = document.createElement('p');
1139
1221
  subtitleText.className = 'subtitle-text';
1140
- subtitleText.textContent = this.options.videoSubtitle;
1222
+ subtitleText.textContent = this.decodeHTMLEntities(this.options.videoSubtitle);
1141
1223
  overlay.appendChild(subtitleText);
1142
1224
  }
1143
1225
 
@@ -1192,7 +1274,7 @@ setVideoTitle(title) {
1192
1274
  if (this.titleOverlay) {
1193
1275
  const titleElement = this.titleOverlay.querySelector('.title-text');
1194
1276
  if (titleElement) {
1195
- titleElement.textContent = this.options.videoTitle;
1277
+ titleElement.textContent = this.decodeHTMLEntities(this.options.videoTitle);
1196
1278
  }
1197
1279
 
1198
1280
  if (title) {
@@ -1727,6 +1809,8 @@ updateDuration() {
1727
1809
  if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
1728
1810
  this.durationEl.textContent = this.formatTime(this.video.duration);
1729
1811
  }
1812
+
1813
+ this.updateTimeDisplay();
1730
1814
  }
1731
1815
 
1732
1816
  changeSpeed(e) {
@@ -1992,7 +2076,7 @@ switchToVideo(newVideoElement, shouldPlay = false) {
1992
2076
  if (newTitle && this.options.showTitleOverlay) {
1993
2077
  this.options.videoTitle = newTitle;
1994
2078
  if (this.titleText) {
1995
- this.titleText.textContent = newTitle;
2079
+ this.titleText.textContent = this.decodeHTMLEntities(newTitle);
1996
2080
  }
1997
2081
  }
1998
2082
 
@@ -2482,12 +2566,16 @@ addEventListener(eventType, callback) {
2482
2566
  this.showLoading();
2483
2567
  }
2484
2568
 
2569
+ this.updateTimeDisplay();
2570
+
2485
2571
  this.triggerEvent('loadstart');
2486
2572
  });
2487
2573
 
2488
2574
  this.video.addEventListener('loadedmetadata', () => {
2489
2575
  this.updateDuration();
2490
2576
 
2577
+ this.updateTimeDisplay();
2578
+
2491
2579
  this.triggerEvent('loadedmetadata', {
2492
2580
  duration: this.getDuration(),
2493
2581
  videoWidth: this.video.videoWidth,
@@ -2504,6 +2592,8 @@ addEventListener(eventType, callback) {
2504
2592
  this.hideLoading();
2505
2593
  }
2506
2594
 
2595
+ this.updateTimeDisplay();
2596
+
2507
2597
  this.triggerEvent('loadeddata', {
2508
2598
  currentTime: this.getCurrentTime()
2509
2599
  });
@@ -2514,12 +2604,26 @@ addEventListener(eventType, callback) {
2514
2604
  this.hideLoading();
2515
2605
  }
2516
2606
 
2607
+ this.updateTimeDisplay();
2608
+
2517
2609
  this.triggerEvent('canplay', {
2518
2610
  currentTime: this.getCurrentTime(),
2519
2611
  duration: this.getDuration()
2520
2612
  });
2521
2613
  });
2522
2614
 
2615
+ this.video.addEventListener('waiting', () => {
2616
+ if (!this.isChangingQuality) {
2617
+ this.showLoading();
2618
+
2619
+ this.updateTimeDisplay();
2620
+
2621
+ this.triggerEvent('waiting', {
2622
+ currentTime: this.getCurrentTime()
2623
+ });
2624
+ }
2625
+ });
2626
+
2523
2627
  this.video.addEventListener('progress', () => {
2524
2628
  this.updateBuffer();
2525
2629
 
@@ -3219,7 +3323,7 @@ createControls() {
3219
3323
  <div class="settings-menu"></div>
3220
3324
  </div>
3221
3325
 
3222
- ${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
3326
+ ${(this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1) || this.options.adaptiveQualityControl ? `
3223
3327
  <div class="quality-control">
3224
3328
  <button class="control-btn quality-btn" data-tooltip="video_quality">
3225
3329
  <div class="quality-btn-text">
@@ -4011,6 +4115,11 @@ updateQualityDisplay() {
4011
4115
  }
4012
4116
 
4013
4117
  updateQualityButton() {
4118
+ if (this.isAdaptiveStream) {
4119
+ if (this.options.debug) console.log('🔒 Adaptive streaming active - quality button managed by streaming.js');
4120
+ return;
4121
+ }
4122
+
4014
4123
  const qualityBtn = this.controls?.querySelector('.quality-btn');
4015
4124
  if (!qualityBtn) return;
4016
4125
 
@@ -4057,6 +4166,11 @@ updateQualityMenu() {
4057
4166
  const qualityMenu = this.controls?.querySelector('.quality-menu');
4058
4167
  if (!qualityMenu) return;
4059
4168
 
4169
+ if (this.isAdaptiveStream) {
4170
+ if (this.options.debug) console.log('🔒 Adaptive streaming active - quality menu managed by streaming.js');
4171
+ return;
4172
+ }
4173
+
4060
4174
  let menuHTML = '';
4061
4175
 
4062
4176
  if (this.isAdaptiveStream && this.adaptiveQualities && this.adaptiveQualities.length > 0) {
@@ -6391,6 +6505,13 @@ async loadAdaptiveLibraries() {
6391
6505
 
6392
6506
  try {
6393
6507
 
6508
+ this.selectedQuality = 'auto';
6509
+ this.qualityEventsInitialized = false;
6510
+
6511
+ if (this.options.debug) {
6512
+ console.log('🔍 initializeDash - FORCED selectedQuality to:', this.selectedQuality);
6513
+ }
6514
+
6394
6515
  if (this.dashPlayer) {
6395
6516
  this.dashPlayer.destroy();
6396
6517
  }
@@ -6542,17 +6663,67 @@ disableDashTextTracks() {
6542
6663
  }
6543
6664
  }
6544
6665
 
6545
- updateAdaptiveQualities() {
6666
+ updateAdaptiveQualities() {
6546
6667
  this.adaptiveQualities = [];
6547
6668
 
6548
6669
  if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
6549
- const bitrates = this.dashPlayer.getBitrateInfoListFor('video');
6550
- this.adaptiveQualities = bitrates.map((bitrate, index) => ({
6551
- index: index,
6552
- label: this.getQualityLabel(bitrate.height, bitrate.width),
6670
+ try {
6671
+
6672
+ const videoTracks = this.dashPlayer.getTracksFor('video');
6673
+
6674
+ if (this.options.debug) {
6675
+ console.log('✅ DASH getTracksFor result:', videoTracks);
6676
+ }
6677
+
6678
+ if (videoTracks && videoTracks.length > 0) {
6679
+
6680
+ const allQualities = [];
6681
+
6682
+ videoTracks.forEach((track, trackIndex) => {
6683
+ const bitrateList = track.bitrateList || [];
6684
+
6685
+ if (this.options.debug) {
6686
+ console.log(`✅ Track ${trackIndex} (${track.codec}):`, bitrateList);
6687
+ }
6688
+
6689
+ bitrateList.forEach((bitrate, index) => {
6690
+ allQualities.push({
6691
+ trackIndex: trackIndex,
6692
+ bitrateIndex: index,
6693
+ label: `${bitrate.height}p`,
6553
6694
  height: bitrate.height,
6554
- bandwidth: bitrate.bandwidth
6555
- }));
6695
+ width: bitrate.width,
6696
+ bandwidth: bitrate.bandwidth,
6697
+ codec: track.codec
6698
+ });
6699
+ });
6700
+ });
6701
+
6702
+ const uniqueHeights = [...new Set(allQualities.map(q => q.height))];
6703
+ uniqueHeights.sort((a, b) => b - a);
6704
+
6705
+ this.adaptiveQualities = uniqueHeights.map((height, index) => {
6706
+ const quality = allQualities.find(q => q.height === height);
6707
+ return {
6708
+ index: index,
6709
+ label: `${height}p`,
6710
+ height: height,
6711
+ trackIndex: quality.trackIndex,
6712
+ bitrateIndex: quality.bitrateIndex,
6713
+ bandwidth: quality.bandwidth,
6714
+ codec: quality.codec
6715
+ };
6716
+ });
6717
+
6718
+ if (this.options.debug) {
6719
+ console.log('✅ All DASH qualities merged:', this.adaptiveQualities);
6720
+ }
6721
+ }
6722
+ } catch (error) {
6723
+ if (this.options.debug) {
6724
+ console.error('❌ Error getting DASH qualities:', error);
6725
+ }
6726
+ }
6556
6727
  } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
6557
6728
  const levels = this.hlsPlayer.levels;
6558
6729
  this.adaptiveQualities = levels.map((level, index) => ({
@@ -6569,8 +6740,360 @@ disableDashTextTracks() {
6569
6740
 
6570
6741
  if (this.options.debug) {
6571
6742
  console.log('📡 Adaptive qualities available:', this.adaptiveQualities);
6743
+ console.log('📡 Selected quality mode:', this.selectedQuality);
6744
+ }
6745
+ }
6746
+
6747
+ updateAdaptiveQualityMenu() {
6748
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
6749
+ if (!qualityMenu) {
6750
+ if (this.options.debug) console.log('❌ Quality menu not found in DOM');
6751
+ return;
6752
+ }
6753
+
6754
+ if (this.adaptiveQualities.length === 0) {
6755
+ if (this.options.debug) console.log('❌ No adaptive qualities to display');
6756
+ return;
6757
+ }
6758
+
6759
+ const isAutoActive = this.selectedQuality === 'auto';
6760
+ let menuHTML = `<div class="quality-option ${isAutoActive ? 'active' : ''}" data-quality="auto">Auto</div>`;
6761
+
6762
+ this.adaptiveQualities.forEach((quality) => {
6763
+ const isActive = this.selectedQuality === quality.height;
6764
+
6765
+ if (this.options.debug) {
6766
+ console.log('🔍 Quality item:', quality.label, 'height:', quality.height, 'active:', isActive);
6767
+ }
6768
+
6769
+ menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-quality="${quality.height}">
6770
+ ${quality.label}
6771
+ <span class="quality-playing" style="display: none; color: #4CAF50; margin-left: 8px; font-size: 0.85em;">● Playing</span>
6772
+ </div>`;
6773
+ });
6774
+
6775
+ qualityMenu.innerHTML = menuHTML;
6776
+
6777
+ if (this.options.debug) {
6778
+ console.log('✅ Quality menu populated with', this.adaptiveQualities.length, 'options');
6779
+ }
6780
+
6781
+ if (!this.qualityEventsInitialized) {
6782
+ this.bindAdaptiveQualityEvents();
6783
+ this.qualityEventsInitialized = true;
6784
+ }
6785
+
6786
+ this.updateAdaptiveQualityDisplay();
6787
+ }
6788
+
6789
+ updateAdaptiveQualityDisplay() {
6790
+ if (!this.dashPlayer && !this.hlsPlayer) return;
6791
+
6792
+ let currentHeight = null;
6793
+
6794
+ try {
6795
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
6796
+
6797
+ if (this.video && this.video.videoHeight) {
6798
+ currentHeight = this.video.videoHeight;
6799
+ }
6800
+
6801
+ if (this.options.debug) {
6802
+ console.log('📊 Current video height:', currentHeight, 'Selected mode:', this.selectedQuality);
6803
+ }
6804
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
6805
+ const currentLevel = this.hlsPlayer.currentLevel;
6806
+ if (currentLevel >= 0 && this.hlsPlayer.levels[currentLevel]) {
6807
+ currentHeight = this.hlsPlayer.levels[currentLevel].height;
6808
+ }
6809
+ }
6810
+
6811
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
6812
+ if (qualityBtnText) {
6813
+ if (this.selectedQuality === 'auto') {
6814
+ qualityBtnText.textContent = 'Auto';
6815
+ } else {
6816
+ qualityBtnText.textContent = `${this.selectedQuality}p`;
6817
+ }
6818
+ }
6819
+
6820
+ const currentQualityText = this.controls?.querySelector('.quality-btn .current-quality');
6821
+ if (currentQualityText) {
6822
+ if (this.selectedQuality === 'auto' && currentHeight) {
6823
+ currentQualityText.textContent = `${currentHeight}p`;
6824
+ currentQualityText.style.display = 'block';
6825
+ } else {
6826
+ currentQualityText.textContent = '';
6827
+ currentQualityText.style.display = 'none';
6828
+ }
6829
+ }
6830
+
6831
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
6832
+ if (qualityMenu) {
6833
+
6834
+ qualityMenu.querySelectorAll('.quality-option').forEach(opt => {
6835
+ opt.classList.remove('active');
6836
+ });
6837
+
6838
+ if (this.selectedQuality === 'auto') {
6839
+ const autoOption = qualityMenu.querySelector('[data-quality="auto"]');
6840
+ if (autoOption) autoOption.classList.add('active');
6841
+ } else {
6842
+ const selectedOption = qualityMenu.querySelector(`[data-quality="${this.selectedQuality}"]`);
6843
+ if (selectedOption) selectedOption.classList.add('active');
6844
+ }
6845
+
6846
+ qualityMenu.querySelectorAll('.quality-playing').forEach(el => {
6847
+ el.style.display = 'none';
6848
+ });
6849
+
6850
+ if (this.selectedQuality === 'auto' && currentHeight) {
6851
+ const playingOption = qualityMenu.querySelector(`[data-quality="${currentHeight}"] .quality-playing`);
6852
+ if (playingOption) {
6853
+ playingOption.style.display = 'inline';
6854
+ }
6855
+ }
6856
+ }
6857
+
6858
+ } catch (error) {
6859
+ if (this.options.debug) console.error('❌ Error updating quality display:', error);
6860
+ }
6861
+ }
6862
+
6863
+ updateQualityButtonText() {
6864
+ const qualityBtn = this.controls?.querySelector('.quality-btn .selected-quality');
6865
+ if (!qualityBtn) return;
6866
+
6867
+ if (this.selectedQuality === 'auto' || !this.selectedQuality) {
6868
+ qualityBtn.textContent = this.t('auto');
6869
+ } else {
6870
+ const quality = this.adaptiveQualities.find(q => q.index === parseInt(this.selectedQuality));
6871
+ qualityBtn.textContent = quality ? quality.label : 'Auto';
6872
+ }
6873
+ }
6874
+
6875
+ bindAdaptiveQualityEvents() {
6876
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
6877
+ const qualityBtn = this.controls?.querySelector('.quality-btn');
6878
+
6879
+ if (!qualityMenu || !qualityBtn) return;
6880
+
6881
+ qualityBtn.addEventListener('click', (e) => {
6882
+ e.stopPropagation();
6883
+ qualityMenu.classList.toggle('active');
6884
+
6885
+ if (qualityMenu.classList.contains('active')) {
6886
+ this.updateAdaptiveQualityDisplay();
6887
+ }
6888
+ });
6889
+
6890
+ const closeMenuHandler = (e) => {
6891
+ if (!qualityBtn.contains(e.target) && !qualityMenu.contains(e.target)) {
6892
+ qualityMenu.classList.remove('active');
6893
+ }
6894
+ };
6895
+ document.addEventListener('click', closeMenuHandler);
6896
+
6897
+ qualityMenu.addEventListener('click', (e) => {
6898
+ const option = e.target.closest('.quality-option');
6899
+ if (!option) return;
6900
+
6901
+ e.stopPropagation();
6902
+
6903
+ const qualityData = option.getAttribute('data-quality');
6904
+
6905
+ if (this.options.debug) {
6906
+ console.log('🎬 Quality clicked - raw data:', qualityData, 'type:', typeof qualityData);
6907
+ }
6908
+
6909
+ if (qualityData === 'auto') {
6910
+
6911
+ this.selectedQuality = 'auto';
6912
+
6913
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
6914
+ this.dashPlayer.updateSettings({
6915
+ streaming: {
6916
+ abr: {
6917
+ autoSwitchBitrate: { video: true }
6918
+ }
6919
+ }
6920
+ });
6921
+ if (this.options.debug) console.log('✅ Auto quality enabled');
6922
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
6923
+ this.hlsPlayer.currentLevel = -1;
6924
+ }
6925
+
6926
+ } else {
6927
+
6928
+ const selectedHeight = parseInt(qualityData, 10);
6929
+
6930
+ if (isNaN(selectedHeight)) {
6931
+ if (this.options.debug) console.error('❌ Invalid quality data:', qualityData);
6932
+ return;
6933
+ }
6934
+
6935
+ if (this.options.debug) {
6936
+ console.log('🎬 Setting manual quality to height:', selectedHeight);
6937
+ }
6938
+
6939
+ this.selectedQuality = selectedHeight;
6940
+
6941
+ if (this.adaptiveStreamingType === 'dash') {
6942
+ this.setDashQualityByHeight(selectedHeight);
6943
+ } else if (this.adaptiveStreamingType === 'hls') {
6944
+ const levelIndex = this.hlsPlayer.levels.findIndex(l => l.height === selectedHeight);
6945
+ if (levelIndex >= 0) {
6946
+ this.hlsPlayer.currentLevel = levelIndex;
6947
+ }
6948
+ }
6949
+ }
6950
+
6951
+ this.updateAdaptiveQualityDisplay();
6952
+
6953
+ qualityMenu.classList.remove('active');
6954
+ });
6955
+
6956
+ if (this.options.debug) {
6957
+ console.log('✅ Quality events bound');
6958
+ }
6959
+ }
6960
+
6961
+ setDashQualityByHeight(targetHeight) {
6962
+ if (!this.dashPlayer) return;
6963
+
6964
+ try {
6965
+ const targetQuality = this.adaptiveQualities.find(q => q.height === targetHeight);
6966
+ if (!targetQuality) {
6967
+ if (this.options.debug) console.error('❌ Quality not found for height:', targetHeight);
6968
+ return;
6969
+ }
6970
+
6971
+ if (this.options.debug) {
6972
+ console.log('🎬 Setting quality:', targetQuality);
6973
+ }
6974
+
6975
+ this.dashPlayer.updateSettings({
6976
+ streaming: {
6977
+ abr: {
6978
+ autoSwitchBitrate: { video: false }
6979
+ }
6980
+ }
6981
+ });
6982
+
6983
+ const currentTrack = this.dashPlayer.getCurrentTrackFor('video');
6984
+
6985
+ if (!currentTrack) {
6986
+ if (this.options.debug) console.error('❌ No current video track');
6987
+ return;
6988
+ }
6989
+
6990
+ const allTracks = this.dashPlayer.getTracksFor('video');
6991
+ let targetTrack = null;
6992
+
6993
+ for (const track of allTracks) {
6994
+ if (track.bitrateList && track.bitrateList[targetQuality.bitrateIndex]) {
6995
+ const bitrate = track.bitrateList[targetQuality.bitrateIndex];
6996
+ if (bitrate.height === targetHeight) {
6997
+ targetTrack = track;
6998
+ break;
6999
+ }
7000
+ }
7001
+ }
7002
+
7003
+ if (!targetTrack) {
7004
+ if (this.options.debug) console.error('❌ Target track not found');
7005
+ return;
7006
+ }
7007
+
7008
+ if (currentTrack.index !== targetTrack.index) {
7009
+ this.dashPlayer.setCurrentTrack(targetTrack);
7010
+ if (this.options.debug) {
7011
+ console.log('✅ Switched to track:', targetTrack.index);
7012
+ }
7013
+ }
7014
+
7015
+ setTimeout(() => {
7016
+ try {
7017
+
7018
+ this.dashPlayer.updateSettings({
7019
+ streaming: {
7020
+ abr: {
7021
+ initialBitrate: { video: targetQuality.bandwidth / 1000 },
7022
+ maxBitrate: { video: targetQuality.bandwidth / 1000 },
7023
+ minBitrate: { video: targetQuality.bandwidth / 1000 }
7024
+ }
7025
+ }
7026
+ });
7027
+
7028
+ if (this.options.debug) {
7029
+ console.log('✅ Quality locked to:', targetHeight + 'p', 'bandwidth:', targetQuality.bandwidth);
7030
+ }
7031
+
7032
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7033
+ if (qualityBtnText) {
7034
+ qualityBtnText.textContent = `${targetHeight}p`;
7035
+ }
7036
+
7037
+ const currentTime = this.video.currentTime;
7038
+ this.dashPlayer.seek(currentTime + 0.1);
7039
+ setTimeout(() => {
7040
+ this.dashPlayer.seek(currentTime);
7041
+ }, 100);
7042
+
7043
+ } catch (innerError) {
7044
+ if (this.options.debug) console.error('❌ Error setting quality:', innerError);
7045
+ }
7046
+ }, 100);
7047
+
7048
+ } catch (error) {
7049
+ if (this.options.debug) console.error('❌ Error in setDashQualityByHeight:', error);
7050
+ }
7051
+ }
7052
+
7053
+ setDashQuality(qualityIndex) {
7054
+ if (!this.dashPlayer) return;
7055
+
7056
+ try {
7057
+ const selectedQuality = this.adaptiveQualities[qualityIndex];
7058
+ if (!selectedQuality) {
7059
+ if (this.options.debug) console.error('❌ Quality not found at index:', qualityIndex);
7060
+ return;
7061
+ }
7062
+
7063
+ if (this.options.debug) {
7064
+ console.log('🎬 Setting DASH quality:', selectedQuality);
7065
+ }
7066
+
7067
+ this.dashPlayer.updateSettings({
7068
+ streaming: {
7069
+ abr: {
7070
+ autoSwitchBitrate: { video: false }
7071
+ }
7072
+ }
7073
+ });
7074
+
7075
+ setTimeout(() => {
7076
+ try {
7077
+ this.dashPlayer.setQualityFor('video', selectedQuality.bitrateIndex);
7078
+
7079
+ if (this.options.debug) {
7080
+ console.log('✅ DASH quality set to bitrateIndex:', selectedQuality.bitrateIndex, 'height:', selectedQuality.height);
7081
+ }
7082
+
7083
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7084
+ if (qualityBtnText) {
7085
+ qualityBtnText.textContent = selectedQuality.label;
7086
+ }
7087
+
7088
+ } catch (innerError) {
7089
+ if (this.options.debug) console.error('❌ Error setting quality:', innerError);
6572
7090
  }
7091
+ }, 100);
7092
+
7093
+ } catch (error) {
7094
+ if (this.options.debug) console.error('❌ Error in setDashQuality:', error);
6573
7095
  }
7096
+ }
6574
7097
 
6575
7098
  handleAdaptiveError(data) {
6576
7099
  if (this.options.debug) console.error('📡 Fatal adaptive streaming error:', data);
@@ -6901,20 +7424,20 @@ getBufferedTime() {
6901
7424
  } catch (error) {
6902
7425
  return 0;
6903
7426
  }
6904
- }
7427
+ }
6905
7428
 
6906
- clearTitleTimeout() {
7429
+ clearTitleTimeout() {
6907
7430
  if (this.titleTimeout) {
6908
7431
  clearTimeout(this.titleTimeout);
6909
7432
  this.titleTimeout = null;
6910
7433
  }
6911
- }
7434
+ }
6912
7435
 
6913
- skipTime(seconds) {
7436
+ skipTime(seconds) {
6914
7437
  if (!this.video || !this.video.duration || this.isChangingQuality) return;
6915
7438
 
6916
7439
  this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
6917
- }
7440
+ }
6918
7441
 
6919
7442
  updateTimeDisplay() {
6920
7443
 
@@ -6924,20 +7447,34 @@ updateTimeDisplay() {
6924
7447
 
6925
7448
  if (this.durationEl && this.video) {
6926
7449
  const duration = this.video.duration;
7450
+ const readyState = this.video.readyState;
7451
+ const currentTime = this.video.currentTime;
7452
+ const networkState = this.video.networkState;
7453
+
7454
+ const isInitialBuffering = (readyState < 2 && currentTime === 0) ||
7455
+ (currentTime === 0 && (!duration || duration === 0) && networkState === 2);
7456
+
7457
+ const isDurationInvalid = !duration || isNaN(duration) || !isFinite(duration);
6927
7458
 
6928
- if (!duration || isNaN(duration) || !isFinite(duration)) {
7459
+ if (isInitialBuffering) {
6929
7460
 
6930
- this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
7461
+ this.durationEl.textContent = t('loading');
7462
+ this.durationEl.classList.remove('encoding-state');
7463
+ this.durationEl.classList.add('loading-state');
7464
+ } else if (isDurationInvalid) {
7465
+
7466
+ this.durationEl.textContent = t('encoding_in_progress');
7467
+ this.durationEl.classList.remove('loading-state');
6931
7468
  this.durationEl.classList.add('encoding-state');
6932
7469
  } else {
6933
7470
 
6934
7471
  this.durationEl.textContent = this.formatTime(duration);
6935
- this.durationEl.classList.remove('encoding-state');
7472
+ this.durationEl.classList.remove('encoding-state', 'loading-state');
6936
7473
  }
6937
7474
  }
6938
7475
  }
6939
7476
 
6940
- formatTime(seconds) {
7477
+ formatTime(seconds) {
6941
7478
  if (isNaN(seconds) || seconds < 0) return '0:00';
6942
7479
 
6943
7480
  const hours = Math.floor(seconds / 3600);
@@ -6948,7 +7485,7 @@ updateTimeDisplay() {
6948
7485
  return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
6949
7486
  }
6950
7487
  return `${minutes}:${secs.toString().padStart(2, '0')}`;
6951
- }
7488
+ }
6952
7489
 
6953
7490
  }
6954
7491